53. DCMI 您所在的位置:网站首页 stm32有源晶振电路原理图 53. DCMI

53. DCMI

2024-05-22 12:45| 来源: 网络整理| 查看: 265

53.5.2. 软件设计¶

为了使工程更加有条理,我们把摄像头控制相关的代码独立分开存储,方便以后移植。在“液晶显示”工程的基础上新建“bsp_ov2640.c”及“bsp_ov2640.h”文件, 这些文件也可根据您的喜好命名,它们不属于STM32标准库的内容,是由我们自己根据应用需要编写的。

53.5.2.1. 编程要点¶

初始化DCMI时钟,I2C时钟;

使用I2C接口向OV2640写入寄存器配置;

初始化DCMI工作模式;

初始化DMA,用于搬运DCMI的数据到显存空间进行显示;

编写测试程序,控制采集图像数据并显示到液晶屏。

53.5.2.2. 代码分析¶

摄像头硬件相关宏定义

我们把摄像头控制硬件相关的配置都以宏的形式定义到 “bsp_ov2640.h”文件中,其中包括I2C及DCMI接口的,见 代码清单:OV2640-2 。

代码清单:OV2640-2 摄像头硬件配置相关的宏(省略了部分数据线)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47/*摄像头接口 */ //IIC SCCB #define CAMERA_I2C I2C1 #define CAMERA_I2C_CLK RCC_APB1Periph_I2C1 #define CAMERA_I2C_SCL_PIN GPIO_Pin_8 #define CAMERA_I2C_SCL_GPIO_PORT GPIOB #define CAMERA_I2C_SCL_GPIO_CLK RCC_AHB1Periph_GPIOB #define CAMERA_I2C_SCL_SOURCE GPIO_PinSource8 #define CAMERA_I2C_SCL_AF GPIO_AF_I2C1 #define CAMERA_I2C_SDA_PIN GPIO_Pin_9 #define CAMERA_I2C_SDA_GPIO_PORT GPIOB #define CAMERA_I2C_SDA_GPIO_CLK RCC_AHB1Periph_GPIOB #define CAMERA_I2C_SDA_SOURCE GPIO_PinSource9 #define CAMERA_I2C_SDA_AF GPIO_AF_I2C1 //VSYNC #define DCMI_VSYNC_GPIO_PORT GPIOB #define DCMI_VSYNC_GPIO_CLK RCC_AHB1Periph_GPIOB #define DCMI_VSYNC_GPIO_PIN GPIO_Pin_7 #define DCMI_VSYNC_PINSOURCE GPIO_PinSource7 #define DCMI_VSYNC_AF GPIO_AF_DCMI // HSYNC #define DCMI_HSYNC_GPIO_PORT GPIOA #define DCMI_HSYNC_GPIO_CLK RCC_AHB1Periph_GPIOA #define DCMI_HSYNC_GPIO_PIN GPIO_Pin_4 #define DCMI_HSYNC_PINSOURCE GPIO_PinSource4 #define DCMI_HSYNC_AF GPIO_AF_DCMI //PIXCLK #define DCMI_PIXCLK_GPIO_PORT GPIOA #define DCMI_PIXCLK_GPIO_CLK RCC_AHB1Periph_GPIOA #define DCMI_PIXCLK_GPIO_PIN GPIO_Pin_6 #define DCMI_PIXCLK_PINSOURCE GPIO_PinSource6 #define DCMI_PIXCLK_AF GPIO_AF_DCMI //PWDN #define DCMI_PWDN_GPIO_PORT GPIOC #define DCMI_PWDN_GPIO_CLK RCC_AHB1Periph_GPIOC #define DCMI_PWDN_GPIO_PIN GPIO_Pin_0 //数据信号线 #define DCMI_D0_GPIO_PORT GPIOC #define DCMI_D0_GPIO_CLK RCC_AHB1Periph_GPIOC #define DCMI_D0_GPIO_PIN GPIO_Pin_6 #define DCMI_D0_PINSOURCE GPIO_PinSource6 #define DCMI_D0_AF GPIO_AF_DCMI /*....省略部分数据线*/

以上代码根据硬件的连接,把与DCMI、I2C接口与摄像头通讯使用的引脚号、引脚源以及复用功能映射都以宏封装起来。

初始化DCMI的 GPIO及I2C

利用上面的宏,初始化DCMI的GPIO引脚及I2C,见 代码清单:OV2640-3 。

代码清单:OV2640-3 初始化DCMI的GPIO及I2C(省略了部分数据线)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81/** * @brief 初始化控制摄像头使用的GPIO(I2C/DCMI) * @param None * @retval None */ void OV2640_HW_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; I2C_InitTypeDef I2C_InitStruct; /***DCMI引脚配置***/ /* 使能DCMI时钟 */ RCC_AHB1PeriphClockCmd(DCMI_PWDN_GPIO_CLK|DCMI_VSYNC_GPIO_CLK | DCMI_HSYNC_GPIO_CLK | DCMI_PIXCLK_GPIO_CLK| DCMI_D0_GPIO_CLK| DCMI_D1_GPIO_CLK|, ENABLE); /*控制/同步信号线*/ GPIO_InitStructure.GPIO_Pin = DCMI_VSYNC_GPIO_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; GPIO_Init(DCMI_VSYNC_GPIO_PORT, &GPIO_InitStructure); GPIO_PinAFConfig(DCMI_VSYNC_GPIO_PORT, DCMI_VSYNC_PINSOURCE, DCMI_VSYNC_AF); GPIO_InitStructure.GPIO_Pin = DCMI_HSYNC_GPIO_PIN ; GPIO_Init(DCMI_HSYNC_GPIO_PORT, &GPIO_InitStructure); GPIO_PinAFConfig(DCMI_HSYNC_GPIO_PORT, DCMI_HSYNC_PINSOURCE, DCMI_HSYNC_AF); GPIO_InitStructure.GPIO_Pin = DCMI_PIXCLK_GPIO_PIN ; GPIO_Init(DCMI_PIXCLK_GPIO_PORT, &GPIO_InitStructure); GPIO_PinAFConfig(DCMI_PIXCLK_GPIO_PORT, DCMI_PIXCLK_PINSOURCE, DCMI_PIXCLK_AF); GPIO_InitStructure.GPIO_Pin = DCMI_PWDN_GPIO_PIN ; GPIO_Init(DCMI_PWDN_GPIO_PORT, &GPIO_InitStructure); /*PWDN引脚,高电平关闭电源,低电平供电*/ GPIO_ResetBits(DCMI_PWDN_GPIO_PORT,DCMI_PWDN_GPIO_PIN); /*数据信号*/ GPIO_InitStructure.GPIO_Pin = DCMI_D0_GPIO_PIN ; GPIO_Init(DCMI_D0_GPIO_PORT, &GPIO_InitStructure); GPIO_PinAFConfig(DCMI_D0_GPIO_PORT, DCMI_D0_PINSOURCE, DCMI_D0_AF); /*...省略部分数据信号线*/ /****** 配置I2C,使用I2C与摄像头的SCCB接口通讯*****/ /* 使能I2C时钟 */ RCC_APB1PeriphClockCmd(CAMERA_I2C_CLK, ENABLE); /* 使能I2C使用的GPIO时钟 */ RCC_AHB1PeriphClockCmd(CAMERA_I2C_SCL_GPIO_CLK|CAMERA_I2C_SDA_GPIO_CLK, ENABLE); /* 配置引脚源 */ GPIO_PinAFConfig(CAMERA_I2C_SCL_GPIO_PORT, CAMERA_I2C_SCL_SOURCE, CAMERA_I2C_SCL_AF); GPIO_PinAFConfig(CAMERA_I2C_SDA_GPIO_PORT, CAMERA_I2C_SDA_SOURCE, CAMERA_I2C_SDA_AF); /* 初始化GPIO */ GPIO_InitStructure.GPIO_Pin = CAMERA_I2C_SCL_PIN ; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(CAMERA_I2C_SCL_GPIO_PORT, &GPIO_InitStructure); GPIO_PinAFConfig(CAMERA_I2C_SCL_GPIO_PORT, CAMERA_I2C_SCL_SOURCE, CAMERA_I2C_SCL_AF); GPIO_InitStructure.GPIO_Pin = CAMERA_I2C_SDA_PIN ; GPIO_Init(CAMERA_I2C_SDA_GPIO_PORT, &GPIO_InitStructure); /*初始化I2C模式 */ I2C_DeInit(CAMERA_I2C); I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStruct.I2C_OwnAddress1 = 0xFE; I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_InitStruct.I2C_ClockSpeed = 40000; /* 写入配置 */ I2C_Init(CAMERA_I2C, &I2C_InitStruct); /* 使能I2C */ I2C_Cmd(CAMERA_I2C, ENABLE); }

与所有使用到GPIO的外设一样,都要先把使用到的GPIO引脚模式初始化,以上代码把DCMI接口的信号线全都初始化为DCMI复用功能, 而PWDN则被初始化成普通的推挽输出模式,并且在初始化完毕后直接控制它为低电平,使能给摄像头供电。

函数中还包含了I2C的初始化配置,使用I2C与OV2640的SCCB接口通讯,这里的I2C模式配置与标准的I2C无异。

配置DCMI的模式

接下来需要配置DCMI的工作模式,我们通过编写OV2640_Init函数完成该功能,见 代码清单:OV2640-4 。

代码清单:OV2640-4 配置DCMI的模式(bsp_ov2640.c文件)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41#define FSMC_LCD_ADDRESS FSMC_Addr_ILI9806G_DATA /** * @brief 配置 DCMI/DMA 以捕获摄像头数据 * @param None * @retval None */ void OV2640_Init(void) { DCMI_InitTypeDef DCMI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; /*** 配置DCMI接口 ***/ /* 使能DCMI时钟 */ RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_DCMI, ENABLE); /* DCMI 配置*/ DCMI_InitStructure.DCMI_CaptureMode = DCMI_CaptureMode_Continuous; DCMI_InitStructure.DCMI_SynchroMode = DCMI_SynchroMode_Hardware; DCMI_InitStructure.DCMI_PCKPolarity = DCMI_PCKPolarity_Rising; DCMI_InitStructure.DCMI_VSPolarity = DCMI_VSPolarity_Low; DCMI_InitStructure.DCMI_HSPolarity = DCMI_HSPolarity_Low; DCMI_InitStructure.DCMI_CaptureRate = DCMI_CaptureRate_All_Frame; DCMI_InitStructure.DCMI_ExtendedDataMode = DCMI_ExtendedDataMode_8b; DCMI_Init(&DCMI_InitStructure); //配置DMA传输,直接配置循环传输即可 OV2640_DMA_Config(FSMC_LCD_ADDRESS,1); /* 配置中断 */ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); /* 配置帧中断,接收到帧同步信号就进入中断 */ NVIC_InitStructure.NVIC_IRQChannel = DCMI_IRQn ; //帧中断 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); DCMI_ITConfig (DCMI_IT_FRAME,ENABLE); }

该函数的执行流程如下:

(1) 使能DCMI外设的时钟, 它是挂载在AHB2总线上的;

(2) 根据摄像头的时序和硬件连接的要求,配置DCMI工作模式为:使用硬件同步, 连续采集所有帧数据,采集时使用8根数据线,PIXCLK被设置为上升沿有效,VSYNC和HSYNC都被设置成低电平有效;

(3) 调用OV2640_DMA_Config函数开始DMA数据传输, 它包含本次传输的目的首地址及传输的数据量,后面我们再详细解释 ;

(4) 配置DCMI的帧传输中断,可以用于统计帧率。另外, 为了防止有时DMA出现传输错误或传输速度跟不上导致数据错位、偏移等问题,每次DCMI接收到摄像头的一帧数据,得到新的帧同步信号后(VSYNC),就进入中断,重新设置液晶屏显示窗口,可使它重新开始一帧的数据传输(本工程没有实现这个功能)。

配置DMA数据传输

上面的DCMI配置函数中调用了OV2640_DMA_Config函数开始了DMA传输,该函数的定义见 代码清单:OV2640-5 。

代码清单:OV2640-5 配置DMA数据传输(bsp_ov2640.c文件)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40/** * @brief 配置 DCMI/DMA 以捕获摄像头数据 * @param DMA_Memory0BaseAddr:本次传输的目的首地址 * @param DMA_BufferSize:本次传输的数据量(单位为字,即4字节) */ void OV2640_DMA_Config(uint32_t DMA_Memory0BaseAddr,uint16_t DMA_BufferSize) { DMA_InitTypeDef DMA_InitStructure; /* 配置DMA从DCMI中获取数据*/ /* 使能DMA*/ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); DMA_Cmd(DMA2_Stream1,DISABLE); while (DMA_GetCmdStatus(DMA2_Stream1) != DISABLE) {} DMA_InitStructure.DMA_Channel = DMA_Channel_1; DMA_InitStructure.DMA_PeripheralBaseAddr = DCMI_DR_ADDRESS; //DCMI数据寄存器地址 //DMA传输的目的地址(传入的参数) DMA_InitStructure.DMA_Memory0BaseAddr = DMA_Memory0BaseAddr; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; DMA_InitStructure.DMA_BufferSize =DMA_BufferSize; //传输的数据大小(传入的参数) DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //液晶数据地址,不自增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //循环模式 DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_INC8; DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; /*DMA初始化 */ DMA_Init(DMA2_Stream1, &DMA_InitStructure); DMA_Cmd(DMA2_Stream1,ENABLE); while (DMA_GetCmdStatus(DMA2_Stream1) != ENABLE) {} }

该函数跟普通的DMA配置无异,它把DCMI接收到的数据从它的数据寄存器搬运到液晶显示器上。 它包含2个输入参数:DMA_Memory0BaseAddr和DMA_BufferSize,其中DMA_Memory0BaseAddr用于设置本次DMA传输的目的首地址, 该参数会被赋值到结构体成员DMA_InitStructure.DMA_Memory0BaseAddr中。

根据前面的函数OV2640_Init对它的调用,DMA_Memory0BaseAddr的值为ILI9806G液晶屏接收显存数据的地址,当STM32往该地址写入数据时, FSMC会自动产生时序把该数据写入到液晶屏,从而可以使用液晶屏显示摄像头采集得的图像。

另一个参数DMA_BufferSize则用于指示本次DMA传输的数据量,它会被赋值到结构体成员DMA_InitStructure.DMA_BufferSize中, 要注意它的单位是一个字,即4字节,是DCMI数据寄存器的单位大小。

根据前面的函数OV2640_Init对它的调用,DMA_BufferSize的值被直接赋为1,而DMA又被配置为循环模式,所以当开始传输时, STM32会把摄像头采集到DCMI数据寄存器里的4字节数据使用DMA分2次写入到液晶屏数据地址,然后不断循环该过程。

若液晶屏提前调用ILI9806G_OpenWindow设置好了数据的显示窗口,那么这些数据就会一个一个(16位的数据)地显示在对应的位置,从而实时显示摄像头采集得到的数据。

DCMI帧中断

OV2640_Init函数初始化了DCMI,使能了帧中断,当摄像头采集到一帧图像的,会进入该中断,见 代码清单:OV2640-6 中的DCMI_IRQHandler。

代码清单:OV2640-6 DMA传输完成中断与帧中断(stm32f4xx_it.c文件)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14extern uint8_t fps; //使用帧中断重置line_num,可防止有时掉数据的时候DMA传送行数出现偏移 void DCMI_IRQHandler(void) { if ( DCMI_GetITStatus (DCMI_IT_FRAME) == SET ) { /*传输完一帧,计数复位*/ fps++; //帧率计数 DCMI_ClearITPendingBit(DCMI_IT_FRAME); } }

在本工程中的帧中断服务函数中,仅仅是使用了一个变量用于统计帧数,以便配合定时器计算帧率。实际上, 为了避免有时STM32内部DMA传输受到阻塞而跟不上外部摄像头信号导致的数据错误(即液晶显示摄像头的数据错位), 可以在本断服务函数中重新调用液晶屏的ILI9806G_OpenWindow复位液晶显示的起始位置,达到重新同步的目的, 由于本工程实测并没有错位的问题,所以这里省略了,当您了解了整个工程的原理后,可以自行添加防错位的处理代码。

使能DCMI采集

以上是我们使用DCMI的传输配置,但它还没有使能DCMI采集,在实际使用中还需要调用下面两个库函数开始采集数据。

//使能DCMI采集数据 DCMI_Cmd(ENABLE); DCMI_CaptureCmd(ENABLE);

读取OV2640芯片ID

配置完了STM32的DCMI,还需要控制摄像头,它有很多寄存器用于配置工作模式。利用STM32的I2C接口,可向OV2640的寄存器写入控制参数, 我们先写个读取芯片ID的函数测试一下,见 代码清单:OV2640-7 。

代码清单:OV2640-7 读取OV2640的芯片ID(bsp_ov2640.c文件)¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30//存储摄像头ID的结构体 typedef struct { uint8_t Manufacturer_ID1; uint8_t Manufacturer_ID2; uint8_t PIDH; uint8_t PIDL; } OV2640_IDTypeDef; /*寄存器地址*/ #define OV2640_DSP_RA_DLMT 0xFF #define OV2640_SENSOR_MIDH 0x1C #define OV2640_SENSOR_MIDL 0x1D #define OV2640_SENSOR_PIDH 0x0A #define OV2640_SENSOR_PIDL 0x0B /** * @brief 读取摄像头的ID. * @param OV2640ID: 存储ID的结构体 * @retval None */ void OV2640_ReadID(OV2640_IDTypeDef *OV2640ID) { /*OV2640有两组寄存器,设置0xFF寄存器的值为0或为1时可选择使用不同组的寄存器*/ OV2640_WriteReg(OV2640_DSP_RA_DLMT, 0x01); /*读取寄存芯片ID*/ OV2640ID->Manufacturer_ID1 = OV2640_ReadReg(OV2640_SENSOR_MIDH); OV2640ID->Manufacturer_ID2 = OV2640_ReadReg(OV2640_SENSOR_MIDL); OV2640ID->PIDH = OV2640_ReadReg(OV2640_SENSOR_PIDH); OV2640ID->PIDL = OV2640_ReadReg(OV2640_SENSOR_PIDL); }

在OV2640的MIDH及MIDL寄存器中存储了它的厂商ID,默认值为0x7F和0xA2;而PIDH及PIDL寄存器存储了产品ID,PIDH的默认值为0x26, PIDL的默认值不定,可能的值为0x40、0x41及0x42。在代码中我们定义了一个结构体OV2640_IDTypeDef专门存储这些读取得的ID信息。

由于这些寄存器都是属于Sensor组的,所以在读取这些寄存器的内容前,需要先把OV2640中0xFF地址的DLMT寄存器值设置为1。

OV2640_ReadID函数中使用的OV2640_ReadReg及OV2640_WriteReg函数是使用STM32的I2C外设向某寄存器读写单个字节数据的底层函数, 它与我们前面章节中用到的I2C函数大同小异,就不展开分析了。

向OV2640写入寄存器配置

检测到OV2640的存在后,向它写入配置参数,见 代码清单:OV2640-8 。

代码清单:OV2640-8向OV2640写入寄存器配置¶ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35uint16_t img_width=852, img_height=480; /* 使用用 UXGA 模式,再根据所需的图像窗口裁剪 */ const unsigned char OV2640_UXGA[][2]= { 0xff, 0x00, 0x2c, 0xff, 0x2e, 0xdf, 0xff, 0x01, 0x3c, 0x32, 0x11, 0x00, 0x09, 0x02, 0x04, 0xD8, //水平翻转 0x13, 0xe5, /*....以下省略*/ } /** * @brief 配置OV2640为UXGA模式,并设置输出图像大小 * @param None * @retval None */ void OV2640_UXGAConfig(void) { uint32_t i; /* 写入寄存器配置 */ for (i=0; i


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有