stm32驱动ov7670摄像头,但是没有屏幕,怎么查看照片呢?
能否通过串口通信,把照片传输到电脑上呢?
通过百度搜索,发现了一款调试助手,这个调试助手支持摄像头调试,或许我先可以试试用一下这个调试助手,然后用python把实现它的摄像头调试功能。
通信协议如下:
根据它的通信协议,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芯片中
}
调试经验:
多次调试发现,上位机设成小端时,传输的照片发蓝发绿,没法看。
上面的串口传输代码,是最终的代码,是可以看到照片的,把上位机设成这样就可以看到照片了。
相应的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},
///
};
需要的库: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程序)。