完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
以下内容基于 STM32F103C8T6 Blue Pill 板子 + OV7670 摄像头(带 AL422B FIFO 模块)。
目前我用的 STM32 IO 口资源如下所示: PA9, PA10 用于 USART1; PB10, PB11 用于 SCCB(I2C) PA0~PA7 用于 D0-D7 数据传输; PB0 用于 PWDN 管脚配置(设置 Normal 或 Power Down Mode); PB1 用于 RESET 管脚配置; PB6 用于 VSYNC 管脚捕获(即捕获帧同步信号); PB7 用于 HREF 管脚捕获(即捕获行同步信号); PB8 用于 FIFO WR 管脚配置(即控制向 FIFO 的写使能); PB9 用于 FIFO WRST 管脚配置(即控制向 FIFO 的写复位); PC13 用于 FIFO RCK 管脚配置(即提供从 FIFO 的读时钟); PC14 用于 FIFO OE 管脚配置(即控制从 FIFO 的读使能); PC15 用于 FIFO RRST 管脚配置(即控制从 FIFO 的读复位); 注意在 STM32F103C8T6 系列的 IO 口中,PB3/PB4/PA13/PA14/PA15 都是默认为 JTAG 的功能,建议最好就不使用了,如果要使用的话需要重新映射后才可以正常拉低/拉高。可以参考以下博客的介绍:关于STM32的PB3/PB4/PA13/PA14/PA15的引脚不能控制输出的问题 我做这个驱动开发的流程是这样的:先验证与摄像头的 SCCB 通信是否正常 -> 通过 8-color bar 输出验证从 FIFO 写/读的操作是否正确 -> 验证摄像头图像传输功能。以下对代码的解释也将按照这个顺序来进行。 代码首先是各种外设的 RCC 配置,这块儿应该没有太多需要解释的,只要是需要用到的外设都需要开启对应的 RCC 时钟,需要注意的是不要忘了对 AFIO 的 RCC 时钟进行初始化。这个是当 IO口 需要作为某些外设的特定管脚时就需要用到的。 void RCC_Config(void){ RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); } 接下来是对 GPIO 的配置,每种外设的不同接口都需要对 GPIO 做不同的输入/输出配置,具体的可以参考 STM32 Reference Manual 9.1.11 章节,有很详细的配置说明。后续讲到每个模块时会再对 GPIO 口配置做进一步说明。 void GPIO_Config(void){ // for USART1; PA9-TX, PA10-RX GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_Init(GPIOA, &GPIO_InitStructure); // for I2C2; PB10-I2C_SCL, PB11-I2C_SDA GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); // for PWDN, RESET; PB0 - PWDN, PB5 - RESET GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = OV_PWDN; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = OV_RESET; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); // for HREF, VSYNC; PB6 - VSYNC, PB7 - HREF GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = OV_VSYNC; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource6); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = OV_HREF; GPIO_Init(GPIOB, &GPIO_InitStructure); // for WR, WRRST; PB8 - WR ENABLE, PB9 - WR RST GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = FIFO_WR; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = FIFO_WRST; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB,&GPIO_InitStructure); //for RCK, OE and RRST; PC13 - RCK, PC14 - OE, PC15 - RRST GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = FIFO_RCK; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = FIFO_OE; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin = FIFO_RRST; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC,&GPIO_InitStructure); // for Camera Read Port; PA0~7 - D0~D7 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_Init(GPIOA,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; GPIO_Init(GPIOA,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_Init(GPIOA,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_Init(GPIOA,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_Init(GPIOA,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_Init(GPIOA,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_Init(GPIOA,&GPIO_InitStructure); GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; GPIO_Init(GPIOA,&GPIO_InitStructure); } 把这些配置做好后,我们就可以来看如何使用 SCCB 与摄像头模块进行通信:最简单的尝试就是通过 SCCB 读取设备的 ID,再通过串口打印出返回的 ID 值就可以判断 SCCB 通信的读写操作是否正确了。 首先我们需要对 USART 串口进行初始化配置,相应的 GPIO 配置在 GPIO_Config 函数中已经完成了,注意的是 USART TX 需要配置成 Alternate function push-pull,而不是 push pull output;RX 配置成 Input Floating 即可。 对 USART 串口的配置如下所示,配置成 115200 波特率,stop Bits = 1。 void USART1_Config(void){ USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_Init(USART1,&USART_InitStructure); USART_Cmd(USART1,ENABLE); } 为了能方便的打印出串口信息,我们再对 printf 函数进行重写,这样就可以像 C 一样直接使用 printf 这个函数了,注意还需要在编译菜单 Target 中勾选 “Use Micro LIB” 选项。 另外注意的是需要通过检查 USART_FLAG_TXE 标志位来确保 USART_SendData 这一字节是发送完了,然后再发下一字节。否则打印出来的信息会不完整。 int fputc(int ch, FILE *f){ USART_SendData(USART1,(uint8_t) ch); while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET){}; return (ch); } 这样,我们串口打印这块儿就配置完了,可以通过 printf 任何一段文字来进行验证。 接下来就是对 SCCB 的通信初始化配置了,SCCB 通信协议和 I2C 基本是一样的,所以我们直接用 I2C 外设来做 SCCB 通信就可以了。相应的 GPIO 配置在 GPIO_Config 函数中已经完成了,注意在 GPIO_Config 中需要把 I2C 的两个 PIN (PB10 和 PB11)配置成 Alternate function open-drain,这样的话我们也就需要在 PB10 和 PB11 两个管脚增加上拉 3.3V;因为开漏模式本身是没法输出高电平的,上拉的话一般用 4.7kohm 或 2.2kohm 都可以。 对 SCCB (I2C)的初始化配置如下,I2C 时钟速率配置为 100khz,其余的也没有太多可说的,都是标准配置: void I2C2_Config(void){ I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_10bit; I2C_InitStructure.I2C_ClockSpeed = 100000; I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; I2C_InitStructure.I2C_OwnAddress1 = 0x01; I2C_Init(I2C2,&I2C_InitStructure); I2C_Cmd(I2C2, ENABLE); } 对 SCCB(I2C)配置完成后,我们来做 SCCB 的读写函数,这部分是相当重要的,建议参考 《OV Serial Camera Control Bus Functional Specification》文档来进行深入理解。 首先是 SCCB 的写函数,对于 SCCB 的写操作实际就是参考文档中的 3-Phase Write Transmission Cycle:先写 ID address,再写 Register Addresss,最后写 Data,每个 phase 都传输的 1字节。所以 SCCB 写函数是这样的: 第一步检查 SCCB Bus 是否空闲,通过检查 I2C_FLAG_BUSY 来实现,本质就是检测总线上的电平,因为空闲时总线电平是被上拉到 3.3V。第二步 SCCB 发送 START 标志位,实际就是选择 Master/Slave 模式,通过检查 MASTER_MODE_SELECT 事件来确认,如果超时(SCCB_TIME_OUT) 该事件还没有 SET,就会返回错误。第三步是发送 ID address,注意 SCCB 定义的读写状态下 ID address 是不一样的,通过最后1位来进行区分读写操作;通过检查 MASTER_TRANSMITTER_MODE_SELECT 事件来确认。第四步是发送寄存器地址,注意根据文档描述,只有在写状态的 ID address 下,发送的寄存器地址才会有效;通过检查 MASTER_BYTE_TRANSMITTED 事件来确认。第五步是发送需要写入该寄存器的数据,也是通过检查 MASTER_BYTE_TRANSMITTED 事件来确认。第六步就是发送 STOP 标志位,注意 SCCB 通信协议写操作只支持一次写入一个字节,因此在写完一个字节数据后需要发送停止位,如果还需要再写,就需要重复这个 3-Phase Write Transmission Cycle。这就是整个 SCCB 写函数了。 void SCCB_WRITE(uint8_t Reg_Addr, uint8_t Write_Data){ // Detect the bus is busy or not while(I2C_GetFlagStatus(I2C2,I2C_FLAG_BUSY)){}; // SCCB Generate START I2C_GenerateSTART(I2C2,ENABLE); SCCB_TIME_OUT = TIME_OUT; while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) == RESET){ if((SCCB_TIME_OUT--) == 0){ printf("n Start_Event Check Fail"); } } // SCCB Send 7-bits Write Address I2C_Send7bitAddress(I2C2,SCCB_WRITE_ADDR,I2C_Direction_Transmitter); SCCB_TIME_OUT = TIME_OUT; while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == RESET){ if((SCCB_TIME_OUT--) == 0){ printf("n Transmit Address set Fail"); } } // SCCB Send Reg Data. Can only be done at WRITE Phase !!! I2C_SendData(I2C2,Reg_Addr); SCCB_TIME_OUT = TIME_OUT; while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED) == RESET){ if((SCCB_TIME_OUT--) == 0){ printf("n Register Address set Fail"); } } // SCCB Send Write Data. I2C_SendData(I2C2,Write_Data); SCCB_TIME_OUT = TIME_OUT; while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED) == RESET){ if((SCCB_TIME_OUT--) == 0){ printf("n Write Data Fail"); } } // SCCB Generate STOP to end the 3-phase write I2C_GenerateSTOP(I2C2,ENABLE); } 接下来是 SCCB 读函数,根据参考文档描述,读操作就是 2-phase Write transmission cycle + 2-phase read transmission cycle:先写 ID address(写操作的),再写需要读的寄存器地址,再写 ID address(读操作的),最后读出数据。注意的是写需要读的寄存器地址操作一定是跟在 写操作的 ID address 后面。 从检查总线是否 busy 开始,到第一次发送 STOP 标志位的过程与前面的 SCCB写函数是一样的,就不做赘述了。在第一次 STOP 标志位后,第一步是发送 START 标志位。第二步发送读操作对应的 ID address,通过检查 MASTER_RECEIVER_MODE_SELECTED 事件来确认。第三步是我们停止 Ack 并发送 STOP标志位,这一点是十分重要的!!! 因为 SCCB 的读只返回一个字节,所以根据 STM32 Reference Manual page763 的描述,当只剩一个字节需要读取时,应该先 Clear ACK,再 STOP,再等待 RXNE flag,最后再进行读取操作。我实测过如果不按照这个顺序进行,SCCB 读会有失败。第四步即读取数据,当 RXNE flag 立起后就可以进行读操作了。第五步需要重新使能 Ack,为下一次读写做准备。这就是整个 SCCB 的读函数了,第三步是最重要的,一定不能忽视。 uint8_t SCCB_READ(uint8_t Reg_Addr){ uint8_t DATA_REC; // Detect the bus is busy or not while(I2C_GetFlagStatus(I2C2,I2C_FLAG_BUSY)){}; // SCCB Generate START I2C_GenerateSTART(I2C2,ENABLE); SCCB_TIME_OUT = TIME_OUT; while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) == RESET){ if((SCCB_TIME_OUT--) == 0){ printf("n Start_Event Check Fail"); } } // SCCB Send 7-bits Write Address I2C_Send7bitAddress(I2C2,SCCB_WRITE_ADDR,I2C_Direction_Transmitter); SCCB_TIME_OUT = TIME_OUT; while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) == RESET){ if((SCCB_TIME_OUT--) == 0){ printf("n Read T Address set Fail"); } } // SCCB Send Reg Data. Can only be done at WRITE Phase !!! I2C_SendData(I2C2,Reg_Addr); SCCB_TIME_OUT = TIME_OUT; while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_BYTE_TRANSMITTED) == RESET){ if((SCCB_TIME_OUT--) == 0){ printf("n Register Address set Fail"); } } // SCCB Generate STOP to end the 2-phase write I2C_GenerateSTOP(I2C2,ENABLE); // SCCB Generate START Again I2C_GenerateSTART(I2C2,ENABLE); SCCB_TIME_OUT = TIME_OUT; while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_MODE_SELECT) == RESET){ if((SCCB_TIME_OUT--) == 0){ printf("n Start_Event Check Fail"); } } // SCCB Send 7-bits Read Address I2C_Send7bitAddress(I2C2,SCCB_READ_ADDR,I2C_Direction_Receiver); SCCB_TIME_OUT = TIME_OUT; while(I2C_CheckEvent(I2C2,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED) == RESET){ if((SCCB_TIME_OUT--) == 0){ printf("n R Address set Fail"); } } // VERY IMPORTANT !!! We need to STOP before the last byte I2C_AcknowledgeConfig(I2C2,DISABLE); I2C_GenerateSTOP(I2C2,ENABLE); // SCCB RECEVIE DATA SCCB_TIME_OUT = TIME_OUT; while(I2C_GetFlagStatus(I2C2,I2C_FLAG_RXNE) == RESET){ if((SCCB_TIME_OUT--)==0){ printf("n Receive Fail"); } } DATA_REC = I2C_ReceiveData(I2C2); // SCCB enable ACK for next transmission I2C_AcknowledgeConfig(I2C2,ENABLE); return DATA_REC; } 现在,我们已经做好了 SCCB 的读写函数,我们就通过读摄像头的 ID 来验证 SCCB 通信是否正常。在此之前,有一个坑需要注意,即我们在对 STM32 和摄像头上电后,因为上电时序我们没有控制,所以一定要在 STM32 跑起来后对摄像头先进行一个复位操作,并且复位后等待至少 1s 再进行 SCCB 读写操作,这样才能保证 SCCB 的通信正常;如果不进行复位,很可能 STM32 发起读写操作时摄像头还没有 ready。 所以我们先通过 PB1 管脚的拉低再拉高进行复位,然后将 PB0(PWDN)置低使摄像头进入 Normal Mode。 // Reset Camera GPIO_ResetBits(GPIOB,OV_RESET); Delay_ms(50); GPIO_SetBits(GPIOB, OV_RESET); Delay_ms(5000); // Set Device into Normal Mode GPIO_ResetBits(GPIOB,OV_PWDN); 接下来读取 0A,0B,1C,1D 寄存器来获取摄像头的 Product ID 和 Manufacturer ID,并通过 Printf 函数打印出来。 // Read Device ID Information Pro_ID_MSB = SCCB_READ(SCCB_READ_PRO_ID_MSB); Pro_ID_LSB = SCCB_READ(SCCB_READ_PRO_ID_LSB); Manu_ID_MSB = SCCB_READ(SCCB_READ_MANU_ID_MSB); Manu_ID_LSB = SCCB_READ(SCCB_READ_MANU_ID_LSB); printf("n The Product ID is: %d %d ", Pro_ID_MSB, Pro_ID_LSB); printf("n The Manufactuer ID is: %d %d ", Manu_ID_MSB, Manu_ID_LSB); 因为打印出的是十进制格式,转为16进制格式就分别为: Product ID:76 73 Manufactuer ID:7F A2 与 OV7670 Datasheet 所描述的一致。证明我们与摄像头之间的 SCCB 通信是没问题的。 到此,我们完成了 SCCB 通信,下一步就可以通过 SCCB 写函数对摄像头进行配置,并获取摄像头返回的数据。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1792 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1626 浏览 1 评论
1094 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
732 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1682 浏览 2 评论
1943浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
740浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
577浏览 3评论
600浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
562浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-26 11:03 , Processed in 0.793361 second(s), Total 80, Slave 63 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号