• stm32摄像头调试 | 串口传输照片数据 | 用python来设计上位机通信软件


    问题

    stm32驱动ov7670摄像头,但是没有屏幕,怎么查看照片呢?

    思路

    能否通过串口通信,把照片传输到电脑上呢?

    通过百度搜索,发现了一款调试助手,这个调试助手支持摄像头调试,或许我先可以试试用一下这个调试助手,然后用python把实现它的摄像头调试功能。

    山外多功能调试助手

    在这里插入图片描述这个软件的特点如下:

    • 支持彩色摄像头、灰度摄像头、二值化摄像头。
    • 分辨率可调
    • 彩色摄像头格式有:RGB565 小端和 RGB565 大端

    通信协议如下:
    在这里插入图片描述
    根据它的通信协议,stm32串口传输代码可以这样写:

    extern u8 ov_sta;   //在ov7670.c里面定义,当ov7670拍摄了完整的一张照片时,ov_sta=1
    
    void send_pic_using_USART() // 使用串口发送数据
    {
    	u8 pixel1, pixel2;
    	u32 j=0;
    
    	GPIO_WriteOutBits(OV7670_RRST_GPIO, OV7670_RRST_PIN, RESET); // RRST=0 复位读指针开始
    	OV7670_RCK_L;
    	OV7670_RCK_H;
    	OV7670_RCK_L;
    	GPIO_WriteOutBits(OV7670_RRST_GPIO, OV7670_RRST_PIN, SET);   // RRST=1 复位读指针结束 
    	OV7670_RCK_H;
    
    	// 发送帧头
    	Usart_Sendbyte(COM_PORT, 0x01);
    	Usart_Sendbyte(COM_PORT, 0xfe);
    	for(j=0;j<76800;j++)   // 240*320=76800
    	{
    		OV7670_RCK_L;
    		pixel1 = OV7670_DATA;  // 读1个数据,得到高字节
    		OV7670_RCK_H;
    
    		OV7670_RCK_L;
    		pixel2 = OV7670_DATA;  // 读数据,得到低字节
    		OV7670_RCK_H;
    
    		// 发送数据,上位机要选择大端。
    		Usart_Sendbyte(COM_PORT, pixel1); // 发送高字节 R5 G3
    		Usart_Sendbyte(COM_PORT, pixel2); // 发送低字节 G3 B5
    	}
    
    	// 发送帧尾
    	Usart_Sendbyte(COM_PORT, 0xfe);
    	Usart_Sendbyte(COM_PORT, 0x01);
    
    	ov_sta=0;  // 把ov_sta设成0后,ov7670会把新的照片数据写入到FIFO芯片中
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    调试经验:

    多次调试发现,上位机设成小端时,传输的照片发蓝发绿,没法看。
    上面的串口传输代码,是最终的代码,是可以看到照片的,把上位机设成这样就可以看到照片了。
    在这里插入图片描述

    相应的ov7670寄存器配置如下:

    //初始化寄存器序列及其对应的值:{寄存器, 值}
    const u8 ov7670_init_reg_tbl[][2]= 
    {   
    	/*以下为OV7670 QVGA RGB565参数  */
    	{0x3a, 0x04},//当分辨率改变时,传感器不会自动设置窗口,后端处理器能立即调整窗口
    	{0x40, 0xd0},//输出范围:[00]到[FF],RGB565,在RGB444[1]为低时有效   
    	{0x12, 0x14},//SCCB Register Reset: 0(Not),输出格式:QVGA,RGB
    
    	/*输出窗口设置*/
    	//Horizontal Frame 
    	{0x32, 0x80},//HREF control,bit[2:0]:HREF start 3 LSB,bit[5:3]:HREF end 3 LSB,bit[7:6]:HREF edge offset to data output
    	{0x17, 0x16},//HSTART start high 8-bit MSB       
    	{0x18, 0x04},//HSTOP end high 8-bit MSB
      //Vertical Frame
    	{0x19, 0x02},//VSTRT start high 8-bit MSB
    	{0x1a, 0x7b},//VSTOP end high 8-bit MSB
    	{0x03, 0x06},//VREF control, bit[1:0]: VREF start 2 LSB,bit[3:2] VREF end 2 LSB,bit[7:6]:AGC[9:8]
    
    	{0x0c, 0x00},//省电模式期间输出时钟三态和数据三态,禁止缩放,禁止DCW使能
    	{0x15, 0x00},//PCLK连续输出,在PCLK的下降沿VSYNC改变
    	{0x3e, 0x00},//正常的PCLK,禁止缩放参数手动调节,PCLK分频(PCLK divider)为1(不分频)
    	
    	{0x70, 0x3a},//无测试图案输出,垂直缩放系数为0x3a
    	{0x71, 0x35},//水平缩放系数为0x35
    	
    	{0x72, 0x11},//竖直平均计算、水平平均计算、竖直亚抽样、水平亚抽样
    	{0x73, 0x00},//DSP缩放时钟分频
    
    	{0xa2, 0x02},//15
    	{0x11, 0x81},//时钟分频设置,0,不分频.
    	{0x7a, 0x20},
    	{0x7b, 0x1c},
    	{0x7c, 0x28},
    
    	{0x7d, 0x3c},//20
    	{0x7e, 0x55},
    	{0x7f, 0x68},
    	{0x80, 0x76},
    	{0x81, 0x80},
    
    	{0x82, 0x88},
    	{0x83, 0x8f},
    	{0x84, 0x96},
    	{0x85, 0xa3},
    	{0x86, 0xaf},
    
    	{0x87, 0xc4},//30
    	{0x88, 0xd7},
    	{0x89, 0xe8},
    	{0x13, 0xe0},
    	{0x00, 0x00},//AGC
    
    	{0x10, 0x00},
    	{0x0d, 0x00},//全窗口, 位[5:4]: 01 半窗口,10 1/4窗口,11 1/4窗口 
    	{0x14, 0x28},//0x38, limit the max gain
    	{0xa5, 0x05},
    	{0xab, 0x07},
    
    	{0x24, 0x75},//40
    	{0x25, 0x63},
    	{0x26, 0xA5},
    	{0x9f, 0x78},
    	{0xa0, 0x68},
    
    	{0xa1, 0x03},//0x0b,
    	{0xa6, 0xdf},//0xd8,
    	{0xa7, 0xdf},//0xd8,
    	{0xa8, 0xf0},
    	{0xa9, 0x90},
    
    	{0xaa, 0x94},//50
    	{0x13, 0xe5},
    	{0x0e, 0x61},
    	{0x0f, 0x4b},
    	{0x16, 0x02},
    
    	{0x1e, 0x07},//图像输出镜像控制//修改配置值将产生图像显示上下或左右颠倒
    	{0x21, 0x02},
    	{0x22, 0x91},
    	{0x29, 0x07},
    	{0x33, 0x0b},
    
    	{0x35, 0x0b},//60
    	{0x37, 0x1d},
    	{0x38, 0x71},
    	{0x39, 0x2a},
    	{0x3c, 0x78},
    
    	{0x4d, 0x40},
    	{0x4e, 0x20},
    	{0x69, 0x00},
    	{0x6b, 0x40},//PLL*4=48Mhz
    	{0x74, 0x19},
    	{0x8d, 0x4f},
    
    	{0x8e, 0x00},//70
    	{0x8f, 0x00},
    	{0x90, 0x00},
    	{0x91, 0x00},
    	{0x92, 0x00},//0x19,//0x66
    
    	{0x96, 0x00},
    	{0x9a, 0x80},
    	{0xb0, 0x84},
    	{0xb1, 0x0c},
    	{0xb2, 0x0e},
    
    	{0xb3, 0x82},//80
    	{0xb8, 0x0a},
    	{0x43, 0x14},
    	{0x44, 0xf0},
    	{0x45, 0x34},
    
    	{0x46, 0x58},
    	{0x47, 0x28},
    	{0x48, 0x3a},
    	{0x59, 0x88},
    	{0x5a, 0x88},
    
    	{0x5b, 0x44},//90
    	{0x5c, 0x67},
    	{0x5d, 0x49},
    	{0x5e, 0x0e},
    	{0x64, 0x04},
    	{0x65, 0x20},
    
    	{0x66, 0x05},
    	{0x94, 0x04},
    	{0x95, 0x08},
    	{0x6c, 0x0a},
    	{0x6d, 0x55},
    
    
    	{0x4f, 0x80},
    	{0x50, 0x80},
    	{0x51, 0x00},
    	{0x52, 0x22},
    	{0x53, 0x5e},
    	{0x54, 0x80},
    
    	//{0x54, 0x40},//110
    
    
    	{0x09, 0x03},//驱动能力最大
    
    	{0x6e, 0x11},//100
    	{0x6f, 0x9f},//0x9e for advance AWB
    	{0x55, 0x00},//亮度
    	{0x56, 0x40},//对比度 0x40
    	{0x57, 0x40},//0x40,  change according to Jim's request
    
    ///
    //以下部分代码由开源电子网网友:duanzhang512 提出
    //添加此部分代码将可以获得更好的成像效果,但是最下面一行会有蓝色的抖动.
    //如不想要,可以屏蔽此部分代码.然后将:OV7670_Window_Set(12,176,240,320);
    //改为:OV7670_Window_Set(12,174,240,320);,即可去掉最下一行的蓝色抖动
    	{0x6a, 0x40},
    	{0x01, 0x40},
    	{0x02, 0x40},
    	{0x13, 0xe7},
    	{0x15, 0x00},  
    	
    		
    	{0x58, 0x9e},
    	
    	{0x41, 0x08},
    	{0x3f, 0x00},
    	{0x75, 0x05},
    	{0x76, 0xe1},
    	{0x4c, 0x00},
    	{0x77, 0x01},
    	{0x3d, 0xc2},	
    	{0x4b, 0x09},
    	{0xc9, 0x60},
    	{0x41, 0x38},
    	
    	{0x34, 0x11},
    	{0x3b, 0x02}, 
    
    	{0xa4, 0x89},
    	{0x96, 0x00},
    	{0x97, 0x30},
    	{0x98, 0x20},
    	{0x99, 0x30},
    	{0x9a, 0x84},
    	{0x9b, 0x29},
    	{0x9c, 0x03},
    	{0x9d, 0x4c},
    	{0x9e, 0x3f},
    	{0x78, 0x04},
    	
    	{0x79, 0x01},
    	{0xc8, 0xf0},
    	{0x79, 0x0f},
    	{0xc8, 0x00},
    	{0x79, 0x10},
    	{0xc8, 0x7e},
    	{0x79, 0x0a},
    	{0xc8, 0x80},
    	{0x79, 0x0b},
    	{0xc8, 0x01},
    	{0x79, 0x0c},
    	{0xc8, 0x0f},
    	{0x79, 0x0d},
    	{0xc8, 0x20},
    	{0x79, 0x09},
    	{0xc8, 0x80},
    	{0x79, 0x02},
    	{0xc8, 0xc0},
    	{0x79, 0x03},
    	{0xc8, 0x40},
    	{0x79, 0x05},
    	{0xc8, 0x30},
    	{0x79, 0x26}, 
    	{0x09, 0x00},
    ///	
    }; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217

    用python实现这个通信协议,作一个上位机软件

    需要的库:serial、PIL、numpy

    import serial
    import serial.tools.list_ports
    import numpy as np
    from PIL import Image
    import time
    import os
    
    def rgb5652array(rgb565, normal_type=False):
        img_list = []
        for i in range(0, len(rgb565), 2):
            pixel = (rgb565[i] << 8) | rgb565[i+1]
            r = pixel >> 11
            g = (pixel >> 5) & 0x3f
            b = pixel & 0x1f
            if normal_type is True:  # 回到正常范围
                r = r * 255.0 / 31.0
                g = g * 255.0 / 63.0
                b = b * 255.0 / 31.0
            img_list.append([r, g, b])
        img_list = np.array(img_list).reshape((240, 320, 3))
        return img_list
    
    # %%
    # 获取串口列表
    ports_list = list(serial.tools.list_ports.comports())
    if len(ports_list) <= 0:
        print("无串口设备。")
    else:
        print("可用的串口设备如下:")
        for comport in ports_list:
            print(list(comport)[0], list(comport)[1])
    
    # %%
    ser = serial.Serial()
    ser.port = ports_list[0][0]
    ser.baudrate = 115200
    ser.bytesize = 8
    ser.stopbits = 1
    ser.parity = 'N'
    print("串口详情参数: ", ser)
    
    try:
        ser.open()  # 打开串口
    except Exception as e:
        print(e)
    
    # %%
    running_time = time.time()
    threshold = 120
    pic_count = 0  # 统计从下位机收到的照片数
    while ser.isOpen():
        try:
            size = ser.inWaiting()
            if time.time() - running_time > threshold:  # threshold秒后没接到信息就退出
                break
            if size >= 24:
                running_time = time.time()
                cmd = ser.read(24).decode('utf-8', 'replace')
                start_idx = cmd.find('car')  # 找到指令头car的起始位置
      
                if start_idx != -1:
                    print("接收到指令:", cmd)
                    # 形如 car0000001 17 00000000end
                    park_id = int(cmd[start_idx+3:start_idx+3+8])
                    opcode = int(cmd[start_idx+11:start_idx+11+2])
                    data = cmd[start_idx+13:start_idx+13+8]
    
                    if opcode == 17:  # 上传照片
                        print('正在上传照片')
                        flag_start = time.time()
                        flag_end = flag_start
                        data = []
                        count = 0
                        number = 1  # 获取1张照片
                        while ser.isOpen():
                            if count >= number:
                                break
                            try:
                                size = ser.inWaiting()
                                if size >= 2:
                                    two_byte = ser.read(2)
                                    if two_byte[0] == 1 and two_byte[1] == 254:
                                        flag_start = time.time()
                                        print('检测到帧头')
                                    elif two_byte[0] == 254 and two_byte[1] == 1:
                                        flag_end = time.time()
                                        print('检测到帧尾')
                                    if flag_start > flag_end:
                                        data.append(two_byte[0])
                                        data.append(two_byte[1])
                                    elif flag_start < flag_end or len(data) >= 200000:
                                        flag_start = time.time()
                                        flag_end = flag_start
                                        count = count + 1
                                        pic_count = pic_count + 1
    
                                        length = len(data) - 2
                                        print('照片数据长度:{}'.format(length))
                                        if length == 153600:  # 240×320=76800的图片数据
                                            data = data[0+2:]
                                        elif length > 153600:
                                            data = data[0+2:153600+2]
                                        else:
                                            data.extend(list([0 for i in range(153600 - (length))]))
                                            data = data[0+2:]
                                        print('处理后长度:{}'.format(len(data)))
                                        pic = rgb5652array(data, True)
                                        print('RGB565解码完成')
                                        pic = pic.astype('uint8')
                                        img = Image.fromarray(pic).convert('RGB')
                                        img_path = './inference/images/pic_{}.png'.format(pic_count)
                                        print('保存照片到: %s\n' % img_path)
                                        img.save(img_path)
                                        data = []
                            except Exception as e:
                                print(e)
    
                    elif opcode == 18:  # 识别车牌
                        pass
                    elif opcode == 15:  # 计费
                        pass
                    else:
                        pass
    
        except Exception as e:
            print(e)
    
    ser.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128

    简单的介绍一下吧,这是我做为车牌识别这个项目而写的上位机软件。
    单片机先发送24位的指令,如果这个指令的操作码是17,即这个指令为car0000001 17 00000000end,上位机进入接收照片模式,等待下位机发送1张照片数据,下位机发送照片的协议就是山外调试助手的照片通信协议,上位机软件(python程序)使用按照这个通信协议来解析照片。
    至于那个24位的指令,则是为了实现上位机多功能而设计的,你可以根据自己的需求来修改。

    指令头车位号操作码数据段指令尾
    car七位两位八位end
    car00000011700000000end

    这个指令是一个字符串,单片机通过串口发送字符串给上位机(python程序)。

  • 相关阅读:
    ACM. HJ24 合唱队 ●●
    什么是慢查询——Java全栈知识(26)
    附录1.图书管理案例
    逻辑卷LVM和交换分区
    LeetCode高频题79. 单词搜索,如果 word 存在于网格中,返回 true ;否则,返回 false
    探究Vue 的脚手架-详细版
    Excel大量表格选择,快速定位表格
    ubuntu20.04 ROS 环境下使用 Flir Blackfly S 工业相机
    使用 NIO 模式简单模拟一个 Tomcat 运行原理
    软件测试的发展趋势
  • 原文地址:https://blog.csdn.net/qq_50258800/article/details/126155626