stm32f407读取ov7670(无FIFO)图像灰度值
1. 前言、几经辗转还是让我读到了这个摄像头的灰度值。我使用的是正点原子的stm32f407zgt最小系统板ov7670摄像头如下图所示。整体流程是单片机的一些配置 → 单片机使用SCCB与OV7670通信进行配置OV7670寄存器 → USB串口显示图像所有像素的灰度值。2. ov7670配置废话不多说直接先从最关键的部分讲起。2.1. ov7670寄存器配置ov7670的寄存器配置主要有以下流程复位全部寄存器。一般复位后要延时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_CaptureModeDCMI_CaptureMode_Continuous;// 连续捕获DCMI_InitStruct.DCMI_SynchroModeDCMI_SynchroMode_Hardware;// 硬件同步(VSYNC/HREF)DCMI_InitStruct.DCMI_PCKPolarityDCMI_PCKPolarity_Rising;// 像素时钟上升沿有效DCMI_InitStruct.DCMI_VSPolarityDCMI_VSPolarity_High;// VSYNC高电平有效DCMI_InitStruct.DCMI_HSPolarityDCMI_HSPolarity_Low;// HREF低电平有效DCMI_InitStruct.DCMI_CaptureRateDCMI_CaptureRate_All_Frame;// 捕获所有帧DCMI_InitStruct.DCMI_ExtendedDataModeDCMI_ExtendedDataMode_8b;// 8位数据模式DCMI_Init(DCMI_InitStruct);这里提一下这里单片机配置和官方文档的COM10介绍不一致的原因可能是官方标错了毕竟能采集出图像才是王道。时钟链配置。这里推荐官方配置如图我用的是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(startxwidth*2)%784;// 水平: 需乘以2并对784取模u16 endystartyheight*2;// 垂直: 需乘以2不取模u8 x_reg,y_reg;u8 temp;// 读取并修改 HREF 寄存器 (0x32)x_regOV7670_ReadReg(0x32);x_reg0xC0;tempx_reg|((endx0x7)3)|(startx0x7);OV7670_WriteReg(0x32,temp);// 读取并修改 VREF 寄存器 (0x03)y_regOV7670_ReadReg(0x03);y_reg0xF0;tempy_reg|((endy0x3)2)|(starty0x3);OV7670_WriteReg(0x03,temp);// 写入高8位值到主寄存器OV7670_WriteReg(0x17,(startx3)0xFF);// HSTART (0x17)OV7670_WriteReg(0x18,(endx3)0xFF);// HSTOP (0x18)OV7670_WriteReg(0x19,(starty2)0xFF);// VSTART (0x19)OV7670_WriteReg(0x1A,(endy2)0xFF);// VSTOP (0x1A)}解释1官方QQVGA配置流程是这样的先对CLKRC寄存器配置0x01其目的是将XCLK的输入时钟信号进行分频分频公式如下图一般XCLK引脚我们都是提供占空比50%左右24Mhz的时钟官方要求如图如果你配置了DBLV[7:6]值为01(PLL进行4倍频)那么摄像头内部正常工作的时钟就是24Mhz × 4 /2×(11)24Mhz。然后COM7寄存器配置0x00是为了配置为VGA模式和YUYV模式输出。COM7寄存器如下图然后COM3配置0x04是为了使能DCW失能scaleDCW是下采样官方介绍如图然后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分频没有生效故而PCLK24Mhz×4/(2×(11)) 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是为了使640184 784(因为一行数据读完是需要计数器归零重新从头开始读取新一行的数据的) 防止行数据取到消隐区所以该配置OV7670_config_window(184, 10, 320, 240)照抄即可无需变动参数10对应是指竖直方向竖直方向上不用循环所以无需顾及要大于510无需考虑消隐区。AGC、AEC、AWB、GAM配置其实如果只需要读取灰度值的话那么AWB、GAM相关寄存器默认配置即可无需改动。与AWB有关的寄存器名有AWBC1-6、AWBCTR0-3与GAM有关的寄存器有SLOP、GAM1-15AGC和AEC负责灰度值的亮度(还有CONTRAS寄存器可以调整对比度来改变画面亮度)AGC负责增益AEC负责曝光COM8负责AGC、AEC、AWB的使能如下图我配置该寄存器为0xe5即AGC/AEC使能、使能快速AGC/AEC算法、AWB不使能 、AEC不受步长限制AEC受不受步长限制没什么大影响。如果没有使能AEC那么就会开启手动曝光调整官方解释如图与手动曝光有关的寄存器有COM1、AECHH、AECH我是使用了自动曝光没有试过手动曝光感兴趣的读者可以尝试一下。其他有必要调整的寄存器首先是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倍频。其他寄存器剩下没提到的寄存器则是一些没有必要设置的寄存器比如关于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个GNDSCL和SDA的SCCB通信引脚VS、HS引脚分别负责1帧的开始和结束、1行的开始和结束XCLK时钟输入引脚PCLK时钟输出引脚D0-D7是负责8位并行输出图像像素数据RST和PWDN分别负责摄像头复位和正常运行开关。3.3v和GND照常接线即可注意同地。SCCB通信网上应该很多我会后面简要介绍。VS、HS主要影响DCMI的配置正常按照单片机中的DCMI模块接线即可如下图、、我这里VS接PB7HS接PA4。XCLK需接10-48Mhz的时钟一般接24Mhz前文已提到。我这里配置TIM3_CH2为PWM模式提供24Mhz时钟。PCLK接PA6D0-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_tack1;//默认应答不成功uint8_ttime0;// gpio_sda_out(); //切换输出模式for(i0;i8;i){GPIO_ResetBits(SCCB_PORT,SCCB_SCL_PIN);//准备数据SCCB_DELAY();if(data0x80)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();data1;}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))ack1;// 读取应答, ack1则为无应答表示发送数据失败ack0则应答成功表示发送数据成功elseack0;//应答成功GPIO_ResetBits(SCCB_PORT,SCCB_SCL_PIN);//结束应答SCCB_DELAY();gpio_sda_out();//切换输出模式SCCB_DELAY();returnack;// 0表示ACK1表示NACK (SCCB中Dont care)}uint8_tSCCB_ReadByte(uint8_tack){uint8_ti,data0;GPIO_ResetBits(SCCB_PORT,SCCB_SCL_PIN);//准备读取数据SCCB_DELAY();gpio_sda_in();//切换输入模式SCCB_DELAY();for(i0;i8;i){data1;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();// 发送应答位 (如果ack1则发送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);valSCCB_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_ChannelDMA_Channel_1;DMA_InitStruct.DMA_PeripheralBaseAddr(uint32_t)DCMI-DR;//详细解释DMA_InitStruct.DMA_Memory0BaseAddr(uint32_t)frame_buffer;DMA_InitStruct.DMA_DIRDMA_DIR_PeripheralToMemory;DMA_InitStruct.DMA_BufferSizeIMG_WIDTH*IMG_HEIGHT/2;// 传输1字(32位)DMA_InitStruct.DMA_PeripheralIncDMA_PeripheralInc_Disable;DMA_InitStruct.DMA_MemoryIncDMA_MemoryInc_Enable;DMA_InitStruct.DMA_PeripheralDataSizeDMA_PeripheralDataSize_Word;//DCMI每次传输4字节DMA_InitStruct.DMA_MemoryDataSizeDMA_MemoryDataSize_Word;//DMA一次读取1字(4字节)即DMA读取DCMI的4次传输1字节之后再一起发送到DMA_InitStruct.DMA_Memory0BaseAddrDMA_InitStruct.DMA_ModeDMA_Mode_Circular;// 循环模式持续接收// DMA_InitStruct.DMA_Mode DMA_Mode_Normal; // 正常模式传输完成结束DMA_InitStruct.DMA_PriorityDMA_Priority_High;//高优先级DMA_InitStruct.DMA_FIFOModeDMA_FIFOMode_Enable;//外设与DMA每次传输数据位数不同则需使能FIFO若想要禁用FIFO使用DCMI和DMA那么DMA与DCMI每次传输都要为1字DMA_InitStruct.DMA_FIFOThresholdDMA_FIFOThreshold_Full;DMA_InitStruct.DMA_MemoryBurstDMA_MemoryBurst_INC4;DMA_InitStruct.DMA_PeripheralBurstDMA_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_BaudRate115200;// 波特率115200USART_InitStructure.USART_WordLengthUSART_WordLength_8b;// 8位数据位USART_InitStructure.USART_StopBitsUSART_StopBits_1;// 1位停止位USART_InitStructure.USART_ParityUSART_Parity_No;// 无校验USART_InitStructure.USART_HardwareFlowControlUSART_HardwareFlowControl_None;// 无流控USART_InitStructure.USART_ModeUSART_Mode_Tx|USART_Mode_Rx;// 收发模式USART_Init(USART6,USART_InitStructure);// 初始化USART6//使能USART1USART_Cmd(USART6,ENABLE);}5. 效果DSP彩条测试正常图像6. 最后关键点已经说完有疑问可以私信或者邮箱进一步交流本人水平有限欢迎指正