1.前言
之前在淘宝买了一个不带FIFO的OV7670,由于比赛和其他事一直搁置,现在有时间于是想玩一玩。我发现网上这个的教程多为标准库,有些甚至利用了DCMI(数字摄像头接口,目前已知F4系列是有这个外设的)。标准库以及使用硬件外设使得它的移植不是那么友好,加上今年来ST官方大力推广HAL库以及CUBEMX的使用,于是我决定做一个CUBEMX驱动这个的教程。
无FIFO的OV7670更加考验单片机性能,并且坑比较多,大家如果还没有买,不要和我一样贪便宜买不带FIFO的。
实物:
比较新的博客:
2.设备
(1)单片机
STM32G474RET6,准备使用IO直驱,因为它没有DCMI接口,模拟的方式更加好移植。
(2)无FIFO的OV7670引脚定义
SCL-----------------------------------时钟线(SCCB)
SDA-----------------------------------数据线(SCCB)
VS-------------------------------------帧数据线(图像输出),信号出现说明一帧图像传输完成
HS-------------------------------------行数据线(图像输出),信号出现说明一帧图像的一行传输完成
PCLK---------------------------------像素时钟(OV7670输出给STM32)
MCLK---------------------------------输入时钟(STM32输入给OV7670),使用MCO输入24MHz
D0到D7-------------------------------数据接口(图像信号输出给单片机)
(3)无FIFO的OV7670数据传输扫描方式
从左到右,从下到上
(4)使用的上位机
直接显示在LCD屏幕上跨度太大,中间会出很多问题,我们先将图像输出到电脑上观察。一步一步慢慢来,华军软件园一般流氓软件较少。
3.需要掌握的知识
(1)SSCB
这个是用来设置OV7670的,跟IIC很像,但不是同一个东西。SCCB有一个EN线,但是在OV7670中被省略了,因为没有必要。
参考文章
接口线
起始时序
结束时序
(2)部分问题踩坑解决
(3) 像素读取时序(RGB565,一个像素两个字节)
(4)VGA与QVGA的时序决定了垂直像素也需要*2
VGA像素:640*480
QVGA像素:320*240
4.开始驱动
由于这种传感器本身并不算简单,所以要做好付出大代价的准备。一步一步来,确保每一步的成功运行。
(1)读取OV7670的ID
参考文章
一.cubemx配置及接线
配置外部时钟
配置时钟
配置SW调试下载
配置SCCB_SCL和SCCB_SDA
配置串口
配置PWM输出(4MHz,50%占空比),用于输入MCLK,这个是非常重要的,输入这里最大为24MHz,输入的频率最好的4的整数倍。
PC2-------------SCCB_CSL
PC3-------------SCCB_SDA
PA6--------------MCLK(4MHz 50%占空比)
RST-------------3V3
PWNN----------GND
二、用下面这个函数检查ID是否正确读取
本人一般喜欢用在线调试打断点观察数据的变化,也可以使用串口打印的方式来检验这段代码是否正确运行。
当最后能成功返回0时,说明初始化没问题。
记得取消注释(我之所以注释是为了更快调试验证ID,因为初始化序列没拷贝过来),现在可以拷贝过来了,取消注释。
三、ov7670.c代码部分讲解
编程手册链接:想要理解为什么这么配置主要对照手册这个来看,仅仅是使用不需要看
白平衡设置(默认为自动白平衡调节,主函数中传入参数为0)
色度设置
亮度调节
对比度
特效设置
对图像输出窗口进行设置(分辨率)
(2)配置行同步(HS),帧同步(VS),像素时钟(PCLK)以及数据位
数据位:
D0---------D7配置为下拉输入,我的板子配置为PA0,PA1和PA7----PA12。这个最好配置为0到7或者8到15,要不然需要修改程序,我这里是没办法,有些引脚没有引出来。后续会分析为什么要修改程序,以及怎么修改。
行同步和帧同步:
HS和VS配置为上拉输入,我的板子配置为PC8和PC9,PC8对应HS,PC9对应VS
像素时钟PCLK配置:
PCLK配置为浮空输入,我的板子配置为PC6
(3)完成所有接线,令人心情愉悦
(4)移植image.c
这一步骤可能需要参考寄存器,寄存器参考
之前说过选择数据引脚时最好选择0到7或者8到15,因为这里移植的image.c中,读取数据位时,它直接操作了寄存器。
如果是低八位(0到7),那么就只需要像如图注释这样,直接
8位像素=GPIOX->IDR & 0XFF
如果是高八位(8到15),只需要
8位像素=(GPIO_IDR & 0XFF00)>>8
而我的由于不是这样,就只能编写一个函数来读取,这样可能会造成运行速度变慢,可能会出问题
/*读取D0到D7的值
D0-D1 PA0-PA1
D2-D7 PA7-PA12
IDR:
1111 1111 1111 1111
0001 1111 1000 0011
*/
uint8_t DATA_8bit_Read(void)
{
uint8_t data1;
uint8_t data2;
uint8_t data3;
uint8_t data4;
uint8_t pix;
data1= GPIOA->IDR & 0x0003; //3~0
data2=(GPIOA->IDR & 0x0080)>>5; //7~4
data3=(GPIOA->IDR & 0X0F00)>>5; //11~8
data4=(GPIOA->IDR & 0X1000)>>5; //15~12
pix=data1|data2|data3|data4;
return pix;
}
(5)
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
5.重新选择
主控:天空星STM32F407VET6(立创活动购得),这颗芯片含有DCMI(数字摄像头接口)。
摄像头模块:无FIFO的ov7670
成本约为:STM32F407VET6(7.5元)+无FIFO的ov7670(10元)=17.5元
(1)目前实现功能:
成功驱动无FIFO的ov7670将图像信息读取显示在ILI9341(SPI驱动)屏幕上。
(2)引脚对应
#F407VET6
摄像头引脚分布:
PC6---------D0
PC7---------D1
PC8---------D2
PC9---------D3
PE4---------D4
PB6---------D5
PE5---------D6
PE6---------D7PA6---------PCLK
PA4----------HS
PB7----------VS
PB5(TIME3_CH2)----------MCLKPE9---------SCL
PD11--------SDA
PD13--------RST
PD15--------PWDNILI9341引脚分布:
PE15--------SCK (此处改动)
PB15--------SDI
PD9---------RST
PD8---------DC
PB12--------CS
PD10--------LED
PB14--------SDOPA5---------T_CLK
PA2---------T_CS (此处改动)
PA3---------T_DIN
PA1---------T_DO
PA0---------T_IRQWIFI串口:
USART3
PB10---TX
PB11---RX按钮:
PD4、PD6
(3)电路图绘制
(4)基础工程下载(可以在这里下载驱动ILI9341的基础工程):
(5)实物图
(6)代码以及工程
(7)屏幕选择
屏幕并不需要必须选择ILI9341,将图像信息显示到屏幕上就使用了一个函数,下面这个函数是将图像信息显示到屏幕上的关键函数,可以看到它只使用了LCD_Fill函数
所以我们无论选择什么屏幕(彩色屏幕),只需要能够保证屏幕正常使用,更换此处的函数即可,这个函数为填充像素点。
(8)更新填充函数
2024年7月27日再次更新
上面的填充函数速度过慢,使用下面的函数可以大幅提高屏幕刷新速度
//在指定区域内填充指定颜色块
//(sx,sy),(ex,ey):填充矩形对角坐标,区域大小为:(ex-sx+1)*(ey-sy+1)
//color:要填充的颜色
void LCD_Color_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 *color)
{
u16 height,width;
u16 i,j;
width=ex-sx+1; //得到填充的宽度
height=ey-sy+1; //高度
LCD_SetWindows(sx, sy,ex, ey);
for(i=0;i<height;i++)
{
for(j=0;j<width;j++)
{
Lcd_WriteData_16Bit(color[i*width+j]);
}
}
}
6.注意
使用软件SPI刷新屏幕速度太慢,建议屏幕接口配置硬件SPI加DMA