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

问题

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

思路

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

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

山外多功能调试助手

stm32摄像头调试 | 串口传输照片数据 | 用python来设计上位机通信软件_第1张图片这个软件的特点如下:

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

通信协议如下:
stm32摄像头调试 | 串口传输照片数据 | 用python来设计上位机通信软件_第2张图片
根据它的通信协议,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芯片中
}

调试经验:

多次调试发现,上位机设成小端时,传输的照片发蓝发绿,没法看。
上面的串口传输代码,是最终的代码,是可以看到照片的,把上位机设成这样就可以看到照片了。
stm32摄像头调试 | 串口传输照片数据 | 用python来设计上位机通信软件_第3张图片

相应的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},
///	
}; 

用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()

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

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

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

你可能感兴趣的:(单片机,stm32,单片机,python)