当前位置: 首页 > news >正文

stm32f407读取ov7670(无FIFO)图像灰度值

1. 前言、

几经辗转,还是让我读到了这个摄像头的灰度值。我使用的是正点原子的stm32f407zgt最小系统板,ov7670摄像头,如下图所示。整体流程是单片机的一些配置 → 单片机使用SCCB与OV7670通信进行配置OV7670寄存器 → USB串口显示图像所有像素的灰度值。

2. ov7670配置

废话不多说,直接先从最关键的部分讲起。

2.1. ov7670寄存器配置

ov7670的寄存器配置主要有以下流程:

  1. 复位全部寄存器。一般复位后要延时5-10ms,我这里延时50ms 。
OV7670_WriteReg(0x12,0x80);delay_ms(50);

0x12寄存器如图:给COM7[7]值1即复位所有寄存器

2. 时序配置。实际上不用对其进行配置,默认即可。时序的配置由COM10寄存器配置,如图:,默认值为0x00,实测单片机的DCMI配置为VS有效电平为高电平,HS有效电平为低电平,PCLK为上升沿有效时,可读到数据,代码如下:

DCMI_InitTypeDef DCMI_InitStruct;RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_DCMI,ENABLE);DCMI_InitStruct.DCMI_CaptureMode=DCMI_CaptureMode_Continuous;// 连续捕获DCMI_InitStruct.DCMI_SynchroMode=DCMI_SynchroMode_Hardware;// 硬件同步(VSYNC/HREF)DCMI_InitStruct.DCMI_PCKPolarity=DCMI_PCKPolarity_Rising;// 像素时钟上升沿有效DCMI_InitStruct.DCMI_VSPolarity=DCMI_VSPolarity_High;// VSYNC高电平有效DCMI_InitStruct.DCMI_HSPolarity=DCMI_HSPolarity_Low;// HREF低电平有效DCMI_InitStruct.DCMI_CaptureRate=DCMI_CaptureRate_All_Frame;// 捕获所有帧DCMI_InitStruct.DCMI_ExtendedDataMode=DCMI_ExtendedDataMode_8b;// 8位数据模式DCMI_Init(&DCMI_InitStruct);

这里提一下,这里单片机配置和官方文档的COM10介绍不一致的原因可能是官方标错了,毕竟能采集出图像才是王道。

  1. 时钟链配置。这里推荐官方配置,如图:
    我用的是160x120分辨率,所以我按照QQVGA进行配置。另外还需要配置HSTART、HSTOP、HREF、VSTART、VSTOP、VREF进行窗口配置,如下配置即可:
OV7670_config_window(184,10,320,240);voidOV7670_config_window(u16 startx,u16 starty,u16 width,u16 height){u16 endx=(startx+width*2)%784;// 水平: 需乘以2,并对784取模u16 endy=starty+height*2;// 垂直: 需乘以2,不取模u8 x_reg,y_reg;u8 temp;// 读取并修改 HREF 寄存器 (0x32)x_reg=OV7670_ReadReg(0x32);x_reg&=0xC0;temp=x_reg|((endx&0x7)<<3)|(startx&0x7);OV7670_WriteReg(0x32,temp);// 读取并修改 VREF 寄存器 (0x03)y_reg=OV7670_ReadReg(0x03);y_reg&=0xF0;temp=y_reg|((endy&0x3)<<2)|(starty&0x3);OV7670_WriteReg(0x03,temp);// 写入高8位值到主寄存器OV7670_WriteReg(0x17,(startx>>3)&0xFF);// HSTART (0x17)OV7670_WriteReg(0x18,(endx>>3)&0xFF);// HSTOP (0x18)OV7670_WriteReg(0x19,(starty>>2)&0xFF);// VSTART (0x19)OV7670_WriteReg(0x1A,(endy>>2)&0xFF);// VSTOP (0x1A)}

解释:

(1)官方QQVGA配置流程是这样的:先对CLKRC寄存器配置0x01,其目的是将XCLK的输入时钟信号进行分频,分频公式如下图:一般XCLK引脚我们都是提供占空比50%左右24Mhz的时钟,官方要求如图:如果你配置了DBLV[7:6]值为01(PLL进行4倍频),那么摄像头内部正常工作的时钟就是24Mhz × 4 /(2×(1+1))=24Mhz。
然后COM7寄存器配置0x00是为了配置为VGA模式和YUYV模式输出。COM7寄存器如下图:
然后COM3配置0x04是为了使能DCW,失能scale,DCW是下采样,官方介绍如图:
然后COM14配置0x1a是将位3置1,使缩放参数可以手动调整,[2:0]设为010,使PCLK4分频,4分频的原因是30帧VGA的YUYV模式下的时钟是需为784×510×30×2 = 23,990,400 =24Mhz(详见https://www.docin.com/p-1022748380.html),那么30帧QQVGA是30帧VGA的1/4就需要4分频,是为了匹配数据传输速度,你也可以自己试试其他配置。但是,实际上我用示波器测XCLK和PCLK引脚后,发现这两个引脚的输出时钟都是24Mhz,想过之后应该是因为失能了scale,所以SCALING_PCLK_DIV和COM14的4分频没有生效,故而PCLK=24Mhz×4/(2×(1+1)) = 24Mhz,但24Mhz没有超过单片机DCMI接收的最大频率48Mhz,所以不影响数据采集,故而没有调整。COM14寄存器如下图:
然后SCALING_XSC和SCALING_YSC由于失能了SCALE,所以这里官方配置为了默认值。
然后SCALING_DCWCTR配置为0x22是使DCW下采样配置为4,即每4个时钟信号t p t_{p}tp才取一个像素(t p t_{p}tp匹配官方datasheet手册中图6 VGA Flame Timing如下图),通俗理解后就是下采样配置为4就会使VGA(640×480)格式转换为QQVGA(160×120)格式。
然后SCALING_PCLK_DIV配置为0xF2是使DSP scale时钟4分频。
然后SCALING_PCLK_DELAY配置为0x02,是默认值,没有大作用。

(2)介绍完时序链后,接下来解释HSTART、HSTOP、HREF、VSTART、VSTOP、VREF这几个寄存器,网上有个OV7670_config_window(184, 10, 320, 240)的配置,具体可以见https://www.docin.com/p-1022748380.html,我只提1个关键点,这里写入函数320参数值相当于配置行开始至停止的640个有效t p t_ptp(可以参考官方给出的时序图,即官方手册1中的图6),对应我们配置COM7为VGA格式,配合DCW下采样每4个t p t_ptp取1个t p t_ptp,也就是每隔3个t p t_ptp取1个t p t_ptp,即取一个像素,最终得到160×120分辨率的图像;240参数值同理;参数184是为了使640+184 > 784(因为一行数据读完是需要计数器归零重新从头开始读取新一行的数据的) ,防止行数据取到消隐区,所以该配置OV7670_config_window(184, 10, 320, 240)照抄即可,无需变动;参数10对应是指竖直方向,竖直方向上不用循环,所以无需顾及要大于510,无需考虑消隐区。

  1. AGC、AEC、AWB、GAM配置
    其实,如果只需要读取灰度值的话,那么AWB、GAM相关寄存器默认配置即可,无需改动。与AWB有关的寄存器名有:AWBC1-6、AWBCTR0-3;与GAM有关的寄存器有SLOP、GAM1-15;AGC和AEC负责灰度值的亮度(还有CONTRAS寄存器可以调整对比度来改变画面亮度),AGC负责增益,AEC负责曝光,COM8负责AGC、AEC、AWB的使能,如下图:我配置该寄存器为0xe5,即AGC/AEC使能、使能快速AGC/AEC算法、AWB不使能 、AEC不受步长限制(AEC受不受步长限制没什么大影响)。如果没有使能AEC,那么就会开启手动曝光调整,官方解释如图:,与手动曝光有关的寄存器有COM1、AECHH、AECH,我是使用了自动曝光,没有试过手动曝光,感兴趣的读者可以尝试一下。

  2. 其他有必要调整的寄存器
    首先是MANU、MANV寄存器,该寄存器功能是固定YUYV格式下U、V的值,可以在KEIL调试中检测自己寄存器配置的正确与否,也可以检验YUYV的顺序是否正确。要固定U、V的值,需要配置TSLB寄存器,如图,注意我配置TSLB为0x10和COM13为0x80才是输出YUYV的顺序,如果TSLB配置为0x14,那么就会输出YVYU顺序,与官方说的不一致,但是我的目的是读取Y值,所以影响不大。

其次是COM17寄存器,该寄存器有个DSP color bar模式,配置为0x08是开始DSP彩条模式,0x00是关闭 DSP彩条模式。

然后是BRIGHT和CONTRAS寄存器,一个是调整亮度,一个是调整对比度,BRIGHT和CONTRAS一般默认即可,如果需要对比度高一点,则调整CONTRAS为0x80或更大值。
然后是COM2寄存器,如果摄像头与单片机连接的线过长,比如20cm以上,则建议提高输出驱动能力为4x,即配置值为0x03。

然后是DBLV寄存器,该寄存器可以倍频PLL。我配置为0x40使PLL进行4倍频。

  1. 其他寄存器
    剩下没提到的寄存器则是一些没有必要设置的寄存器,比如关于LCC镜头的配置,MTX的配置。官方手册1中列举的reserved的寄存器军不需要配置,上面提到的一些寄存器中reserved的位在网上找不到参考或没必要的话直接置为0即可,其他没有提到的寄存器的reserved的位不用管,默认 即可。

2.2. ov7670官方手册

,如果ov7670的官方手册有两个,一个是主要介绍OV7670和其寄存器,叫Advanced Information Preliminary Datasheet,有英文版和中文版,本文称为官方手册1,推荐看英文版,中文版有两三处错误,不建议看;另一个是主要介绍ov7670的功能和其功能的配置(只是浅显介绍,要看其效果还是要自己多尝试),叫OV7670/OV7171 CMOS VGA (640x480) CameraChip Implementation Guide,本文称为官方手册2。
[[OV7670_DS_(1_4).pdf]]
[[OV7670app.pdf]]
网上应该有这些资料,我这里就不提供了

2.3. ov7670引脚介绍

ov7670摄像头有18个引脚,包括1个3.3v和1个GND;SCL和SDA的SCCB通信引脚;VS、HS引脚分别负责1帧的开始和结束、1行的开始和结束;XCLK时钟输入引脚,PCLK时钟输出引脚;D0-D7是负责8位并行输出图像像素数据;RST和PWDN分别负责摄像头复位和正常运行开关。
3.3v和GND照常接线即可,注意同地。
SCCB通信网上应该很多,我会后面简要介绍。
VS、HS主要影响DCMI的配置,正常按照单片机中的DCMI模块接线即可,如下图:我这里VS接PB7,HS接PA4。

XCLK需接10-48Mhz的时钟,一般接24Mhz,前文已提到。我这里配置TIM3_CH2为PWM模式,提供24Mhz时钟。
PCLK接PA6
D0-D7按照上面的表(单片机的引脚图)配置即可。
RST和PWDN按照官方要求配置,如图:
开机时建议开机复位,代码如下:

voidOV7670_PowerOn(void){GPIO_ResetBits(DCMI_PWDN_PORT,DCMI_PWDN_PIN);//开机delay_ms(10);GPIO_ResetBits(DCMI_RET_PORT,DCMI_RET_PIN);//复位delay_ms(10);GPIO_SetBits(DCMI_RET_PORT,DCMI_RET_PIN);//一般模式delay_ms(30);}

3. SCCB通信

SCCB与I2C类似,可以参考https://www.cnblogs.com/xianyuIC/p/11338204.html,注意写入寄存器数据时需要有停止信号。我的代码如下:

voidSCCB_Start(void)//初始状态SCL、SDA为高电平{// gpio_sda_out(); //切换输出模式// SDA 下降沿,SCL 高电平GPIO_SetBits(SCCB_PORT,SCCB_SDA_PIN);SCCB_DELAY();GPIO_SetBits(SCCB_PORT,SCCB_SCL_PIN);SCCB_DELAY();GPIO_ResetBits(SCCB_PORT,SCCB_SDA_PIN);SCCB_DELAY();GPIO_ResetBits(SCCB_PORT,SCCB_SCL_PIN);SCCB_DELAY();}voidSCCB_Stop(void)//stop之后SCL、SDA应处于空闲状态(高电平){// gpio_sda_out(); //切换输出模式// SDA 上升沿,SCL 高电平GPIO_ResetBits(SCCB_PORT,SCCB_SCL_PIN);SCCB_DELAY();GPIO_ResetBits(SCCB_PORT,SCCB_SDA_PIN);SCCB_DELAY();GPIO_SetBits(SCCB_PORT,SCCB_SCL_PIN);SCCB_DELAY();GPIO_SetBits(SCCB_PORT,SCCB_SDA_PIN);SCCB_DELAY();}uint8_tSCCB_WriteByte(uint8_tdata)//从机有可能不发出应答信号,因此主机可不用判断此处是否有应答,直接默认当前传输完成即可{uint8_ti;uint8_tack=1;//默认应答不成功uint8_ttime=0;// gpio_sda_out(); //切换输出模式for(i=0;i<8;i++){GPIO_ResetBits(SCCB_PORT,SCCB_SCL_PIN);//准备数据SCCB_DELAY();if(data&0x80)GPIO_SetBits(SCCB_PORT,SCCB_SDA_PIN);elseGPIO_ResetBits(SCCB_PORT,SCCB_SDA_PIN);SCCB_DELAY();GPIO_SetBits(SCCB_PORT,SCCB_SCL_PIN);SCCB_DELAY();data<<=1;}GPIO_ResetBits(SCCB_PORT,SCCB_SCL_PIN);//结束字节最后一位的传输SCCB_DELAY();gpio_sda_in();//切换输入模式SCCB_DELAY();GPIO_SetBits(SCCB_PORT,SCCB_SCL_PIN);//接收第九位SCCB_DELAY();if(GPIO_ReadInputDataBit(SCCB_PORT,SCCB_SDA_PIN))ack=1;// 读取应答, ack=1则为无应答,表示发送数据失败;ack=0则应答成功,表示发送数据成功elseack=0;//应答成功GPIO_ResetBits(SCCB_PORT,SCCB_SCL_PIN);//结束应答SCCB_DELAY();gpio_sda_out();//切换输出模式SCCB_DELAY();returnack;// 0表示ACK,1表示NACK (SCCB中Don't care)}uint8_tSCCB_ReadByte(uint8_tack){uint8_ti,data=0;GPIO_ResetBits(SCCB_PORT,SCCB_SCL_PIN);//准备读取数据SCCB_DELAY();gpio_sda_in();//切换输入模式SCCB_DELAY();for(i=0;i<8;i++){data<<=1;GPIO_SetBits(SCCB_PORT,SCCB_SCL_PIN);//开始读取sdaSCCB_DELAY();if(GPIO_ReadInputDataBit(SCCB_PORT,SCCB_SDA_PIN))data|=0x01;GPIO_ResetBits(SCCB_PORT,SCCB_SCL_PIN);SCCB_DELAY();}gpio_sda_out();//切换输出模式SCCB_DELAY();// 发送应答位 (如果ack=1,则发送NACK)if(ack)GPIO_SetBits(SCCB_PORT,SCCB_SDA_PIN);elseGPIO_ResetBits(SCCB_PORT,SCCB_SDA_PIN);SCCB_DELAY();GPIO_SetBits(SCCB_PORT,SCCB_SCL_PIN);//发送应答SCCB_DELAY();GPIO_ResetBits(SCCB_PORT,SCCB_SCL_PIN);//结束应答SCCB_DELAY();returndata;}uint8_tOV7670_WriteReg(uint8_treg,uint8_tval){SCCB_Start();if(SCCB_WriteByte(OV7670_WRITE_ADDR))printf("无应答 ");delay_us(100);if(SCCB_WriteByte(reg))printf("无应答 ");delay_us(100);if(SCCB_WriteByte(val))printf("无应答 ");delay_us(100);SCCB_Stop();return0;}uint8_tOV7670_ReadReg(uint8_treg){uint8_tval;SCCB_Start();if(SCCB_WriteByte(OV7670_WRITE_ADDR))printf("无应答 ");// 写操作delay_us(100);if(SCCB_WriteByte(reg))printf("无应答 ");delay_us(100);SCCB_Stop();delay_us(100);SCCB_Start();// 重复起始if(SCCB_WriteByte(OV7670_WRITE_ADDR|0x01))printf("无应答 ");// 读操作 (最低位为1)delay_us(100);val=SCCB_ReadByte(1);// 最后发NACKSCCB_Stop();returnval;}

通信时注意,在一个字节传输周期内,建议SDA无需不必要的改动,SCL保持低电平即可,在两个字节传输周期间,SDA和SCL要保持高电平。

4. DMA和串口配置

DMA_InitTypeDef DMA_InitStruct;// 使能DMA2时钟RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);// 配置DMA2 Stream1 (DCMI通常使用Stream1, Channel1)DMA_DeInit(DMA2_Stream1);DMA_InitStruct.DMA_Channel=DMA_Channel_1;DMA_InitStruct.DMA_PeripheralBaseAddr=(uint32_t)&DCMI->DR;//详细解释DMA_InitStruct.DMA_Memory0BaseAddr=(uint32_t)frame_buffer;DMA_InitStruct.DMA_DIR=DMA_DIR_PeripheralToMemory;DMA_InitStruct.DMA_BufferSize=IMG_WIDTH*IMG_HEIGHT/2;// 传输1字(32位)DMA_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Disable;DMA_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable;DMA_InitStruct.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Word;//DCMI每次传输4字节DMA_InitStruct.DMA_MemoryDataSize=DMA_MemoryDataSize_Word;//DMA一次读取1字(4字节),即DMA读取DCMI的4次传输1字节之后再一起发送到DMA_InitStruct.DMA_Memory0BaseAddrDMA_InitStruct.DMA_Mode=DMA_Mode_Circular;// 循环模式,持续接收// DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; // 正常模式,传输完成结束DMA_InitStruct.DMA_Priority=DMA_Priority_High;//高优先级DMA_InitStruct.DMA_FIFOMode=DMA_FIFOMode_Enable;//外设与DMA每次传输数据位数不同,则需使能FIFO;若想要禁用FIFO使用DCMI和DMA,那么DMA与DCMI每次传输都要为1字DMA_InitStruct.DMA_FIFOThreshold=DMA_FIFOThreshold_Full;DMA_InitStruct.DMA_MemoryBurst=DMA_MemoryBurst_INC4;DMA_InitStruct.DMA_PeripheralBurst=DMA_PeripheralBurst_Single;//37.与OV7670相关DMA 初始化的突发传输模式配置DMA_Init(DMA2_Stream1,&DMA_InitStruct);
voidusart_init(){USART_InitTypeDef USART_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART6,ENABLE);// USART6时钟//配置USART1参数USART_InitStructure.USART_BaudRate=115200;// 波特率115200USART_InitStructure.USART_WordLength=USART_WordLength_8b;// 8位数据位USART_InitStructure.USART_StopBits=USART_StopBits_1;// 1位停止位USART_InitStructure.USART_Parity=USART_Parity_No;// 无校验USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;// 无流控USART_InitStructure.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;// 收发模式USART_Init(USART6,&USART_InitStructure);// 初始化USART6//使能USART1USART_Cmd(USART6,ENABLE);}

5. 效果

DSP彩条测试

正常图像

6. 最后

关键点已经说完,有疑问可以私信或者邮箱进一步交流,本人水平有限,欢迎指正

http://www.rkmt.cn/news/1498827.html

相关文章:

  • 2026思维导图工具实测:7款主流工具横向对比,按场景选型不踩坑
  • 避开这些坑!DS1302与蓝桥杯单片机I/O冲突的排查与解决实录
  • 机器学习工程师必须掌握的PDF与CDF实战指南
  • NSK VH20AN高防尘直线导轨技术手册
  • 拆开一个烧坏的IGBT模块,手把手教你识别过压、过流、过温的“案发现场”
  • 2026南昌市民常去贵金属回收实体店实测整理 黄金铂金白银回收正规商家前五榜单 - 诚金汇钻回收公司
  • ARM7TDMI-S经典架构解析:LPC2377/78嵌入式系统设计与外设实战
  • 深耕甬城十载 赋能数字转型——宁波森迈商务信息咨询有限公司打造全域小程序综合服务标杆 - 资讯速览
  • PIR、PSI、OT…傻傻分不清?一文讲透隐私计算中几个易混淆的“查询”协议
  • CPS总线安全:GRACYBUS组密钥协议设计与实现
  • 一本书读懂微积分!
  • 不止于点灯:用STM32H7的复杂时钟树驱动高精度外设(CubeMx配置SPI/I2S实战)
  • 从‘玻璃丝’到‘信息高速公路’:用大白话图解光纤通信的核心原理(附公式推导避坑指南)
  • LPC2468低功耗与电气特性实战:从数据手册到稳定设计
  • 2026濮阳贵金属旧料回收优质门店排行 TOP5 黄金白银铂金金条回收正规老店实地走访整理 - 信誉隆金银铂奢回收
  • 从食堂打饭到银行排队:用C++优先队列(priority_queue)模拟‘接水问题’的通用思路
  • 手把手教你点亮480x480圆形屏:ST7701s双通道MIPI驱动代码逐行解析
  • 用ESP8266和巴法云,10分钟搞定Alexa智能灯泡(附继电器接线图)
  • 从登录到无感刷新:一个真实Vue+SpringBoot项目的Token管理实战复盘
  • 2026年数据安全管理平台推荐,满足等保与合规新要求 - 品牌2026
  • 2026 东莞瓷砖空鼓修复 TOP6|防水补漏修缮,本地权威榜单(独家数据 + 技术标准 + 避坑指南) - 鲁顺
  • 告别Raytracing!FreeCAD新宠Render工作台实战:对比POV-Ray与LuxCoreRender哪个更适合你
  • 2026淮南市民常去贵金属回收实体店实测整理 黄金铂金白银回收正规商家前五榜单 - 诚金汇钻回收公司
  • 智能音箱/会议设备背后的耳朵:四麦克风阵列TDOA定位实战与精度优化心得
  • 保姆级教程:WinCC 7.5经典版与S7-1200/1500 PLC的TCP/IP通讯配置(含TIA环境避坑指南)
  • 保姆级教程:手把手带你用C++搞定洛谷P2855‘河中跳房子’(含无序数据处理)
  • 衡水本地老牌黄金白银铂金回收门店权威排行 TOP5 2026 线下实体商家联系方式大全 - 中安检金银铂钻回收
  • Arma3任务编辑进阶:用SQF脚本让你的自定义任务“活”起来(从触发器到AI逻辑)
  • 2026铜仁餐饮实测封神!5款碧江铜仁古城中南门古城特色小吃餐厅门店包间地道风味口碑爆棚 - 十大品牌榜
  • 告别手动造数据!用SystemVerilog的$fscanf和$fwrite实现自动化测试数据生成与解析