第十九章 摄像头图像捕获实验
本章将介绍如何运用Kendryte K210通过数字视频接口(DVP)来驱动摄像头,以实现实时采集摄像头捕获的图像数据,并对这些数据进行后续处理。通过学习本章内容,读者将掌握利用SDK编程技术实现摄像头实时图像数据捕获的方法。 本章分为如下几个小节: 19.1 OV2640和DVP简介 19.2 硬件设计 19.3 程序设计 19.4 运行验证
19.1 OV2640和DVP简介 本节将分为两个部分,分别介绍OV2640简介和Kendryte K210 DVP接口简介。另外,所有OV2640的相关资料,都在光盘:A盘à硬件资料à摄像头资料àOV2640资料 文件夹里面。 19.1.1 OV2640简介OV2640是OV(OmniVision)公司生产的一颗1/4寸的CMOS UXGA(1632*1232)图像传感器。该传感器体积小、工作电压低,提供单片UXGA摄像头和影像处理器的所有功能。通过SCCB 总线控制,可以输出整帧、子采样、缩放和取窗口等方式的各种分辨率8/10位影像数据。该产品UXGA图像最高达到15帧/秒(SVGA可达30帧,CIF可达60帧)。用户可以完全控制图像质量、数据格式和传输方式。所有图像处理功能过程包括伽玛曲线、白平衡、对比度、色度等都可以通过SCCB接口编程。OmmiVision 图像传感器应用独有的传感器技术,通过减少或消除光学或电子缺陷如固定图案噪声、拖尾、浮散等,提高图像质量,得到清晰的稳定的彩色图像。 OV2640的特点有: l 高灵敏度、低电压适合嵌入式应用 l 标准的SCCB接口,兼容IIC接口 l 支持RawRGB、RGB(RGB565/RGB555)、GRB422、YUV(422/420)和YCbCr(422)输出格式 l 支持UXGA、SXGA、SVGA以及按比例缩小到从SXGA到40*30的任何尺寸 l 支持自动曝光控制、自动增益控制、自动白平衡、自动消除灯光条纹、自动黑电平校准等自动控制功能。同时支持色饱和度、色相、伽马、锐度等设置。 l 支持闪光灯 l 支持图像缩放、平移和窗口设置 l 支持图像压缩,即可输出JPEG图像数据 l 自带嵌入式微处理器 OV2640的功能框图如图19.1.1.1所示: 图19.1.1.1 OV2640功能框图 OV2640传感器包括如下一些功能模块。 1.感光整列(Image Array) OV2640总共有1632*1232个像素,最大输出尺寸为UXGA(1600*1200),即200W像素。 2.模拟信号处理(Analog Processing) 模拟信号处理所有模拟功能,并包括:模拟放大(AMP)、增益控制、通道平衡和平衡控制等。 3.10位A/D 转换(A/D) 原始的信号经过模拟放大后,分G和BR两路进入一个10 位的A/D 转换器,A/D 转换器工作频率高达20M,与像素频率完全同步(转换的频率和帧率有关)。除A/D转换器外,该模块还有黑电平校正(BLC)功能。 4.数字信号处理器(DSP) 这个部分控制由原始信号插值到RGB信号的过程,并控制一些图像质量: l 边缘锐化(二维高通滤波器) l 颜色空间转换(原始信号到RGB或者YUV/YCbYCr) l RGB色彩矩阵以消除串扰 l 色相和饱和度的控制 l 黑/白点补偿 l 降噪 l 镜头补偿 l 可编程的伽玛 l 十位到八位数据转换 5.输出格式模块(Output Formatter) 该模块按设定优先级控制图像的所有输出数据及其格式。 6.压缩引擎(Compression Engine) 压缩引擎框图如下图所示: 图19.1.1.2 压缩引擎框图 从图可以看出,压缩引擎主要包括三部分:DCT、QZ和entropy encoder(熵编码器),将原始的数据流,压缩成jpeg数据输出。 7.微处理器(Microcontroller) OV2640自带了一个8位微处理器,该处理器有512字节SRAM,4KB的ROM,它提供一个灵活的主机到控制系统的指令接口,同时也具有细调图像质量的功能。 8.SCCB接口(SCCB Interface) SCCB接口控制图像传感器芯片的运行,详细使用方法参照光盘的《OmniVision Technologies Seril Camera Control Bus(SCCB) Specification》这个文档 9.数字视频接口(Digital Video Port) OV2640拥有一个10位数字视频接口(支持8位接法),其MSB和LSB可以程序设置先后顺序,ALIENTEK OV2640模块采用默认的8位连接方式,如下图所示: 图19.1.1.3 OV2640默认8位连接方式 OV2640的寄存器通过SCCB时序访问并设置,SCCB时序和IIC时序十分类似,在本章我们不做介绍,请大家参考光盘《OmniVision Technologies Seril Camera Control Bus(SCCB) Specification》这个文档。 接下来,我们介绍一下OV2640的传感器窗口设置、图像尺寸设置、图像窗口设置和图像输出大小设置,这几个设置与我们的正常使用密切相关,有必要了解一下。其中,除了传感器窗口设置是直接针对传感器阵列的设置,其他都是DSP部分的设置了,接下来我们一个个介绍。 传感器窗口设置,该功能允许用户设置整个传感器区域(1632*1220)的感兴趣部分,也就是在传感器里面开窗,开窗范围从2*2~1632*1220都可以设置,不过要求这个窗口必须大于等于随后设置的图像尺寸。传感器窗口设置,通过:0X03/0X19/0X1A/0X07/0X17/0X18等寄存器设置,寄存器定义请看OV2640_DS(1.6).pdf这个文档(下同)。 图像尺寸设置,也就是DSP输出(最终输出到LCD的)图像的最大尺寸,该尺寸要小于等于前面我们传感器窗口设置所设定的窗口尺寸。图像尺寸通过:0XC0/0XC1/0X8C等寄存器设置。 图像窗口设置,这里起始和前面的传感器窗口设置类似,只是这个窗口是在我们前面设置的图像尺寸里面,再一次设置窗口大小,该窗口必须小于等于前面设置的图像尺寸。该窗口设置后的图像范围,将用于输出到外部。图像窗口设置通过:0X51/0X52/0X53/0X54/0X55/0X57等寄存器设置。 图像输出大小设置,这是最终输出到外部的图像尺寸。该设置将图像窗口设置所决定的窗口大小,通过内部DSP处理,缩放成我们输出到外部的图像大小。该设置将会对图像进行缩放处理,如果设置的图像输出大小不等于图像窗口设置图像大小,那么图像就会被缩放处理,只有这两者设置一样大的时候,输出比例才是1 : 1的。 因为OmniVision 公司公开的文档对这些设置实在是没有详细介绍。只能从他们提供的初始化代码(还得去linux源码里面移植过来)里面去分析规律,所以,这几个设置,都是作者根据OV2640的调试经验,以及相关文档总结出来的,不保证百分比正确,如有错误,还请大家指正。 以上几个设置,光看文字可能不太清楚,这里我们画一个简图有助于大家理解,如图19.1.1.4所示: 图19.1.1.4 OV2640图像窗口设置简图 上图,最终红色框所示的图像输出大小,才是OV2640输出给外部的图像尺寸,也就是显示在LCD上面的图像大小。当图像输出大小与图像窗口不等时,会进行缩放处理,在LCD上面看到的图像将会变形。 最后,我们介绍一下OV2640的图像数据输出格式。首先我们简单介绍一些定义: UXGA,即分辨率位1600*1200的输出格式,类似的还有:SXGA(1280*1024)、WXGA+(1440*900)、XVGA(1280*960)、WXGA(1280*800)、XGA(1024*768)、SVGA(800*600)、VGA(640*480)、CIF(352*288)、WQVGA(400*240)、QCIF(176*144)和QQVGA(160*120)等。 PCLK,即像素时钟,一个PCLK时钟,输出一个像素(或半个像素)。 VSYNC,即帧同步信号。 HREF /HSYNC,即行同步信号。 OV2640的图像数据输出(通过Y[9:0])就是在PCLK,VSYNC和HREF/ HSYNC的控制下进行的。首先看看行输出时序,如图19.1.1.5所示: 图19.1.1.5 OV2640行输出时序 从上图可以看出,图像数据在HREF为高的时候输出,当HREF变高后,每一个PCLK时钟,输出一个8位/10位数据。我们采用8位接口,所以每个PCLK输出1个字节,且在RGB/YUV输出格式下,每个tp=2个Tpclk,如果是Raw格式,则一个tp=1个Tpclk。比如我们采用UXGA时序,RGB565格式输出,每2个字节组成一个像素的颜色(高低字节顺序可通过0XDA寄存器设置),这样每行输出总共有1600*2个PCLK周期,输出1600*2个字节。 再来看看帧时序(UXGA模式),如图19.1.1.6所示: 图19.1.1.6 OV2640帧时序 上图清楚的表示了OV2640在UXGA模式下的数据输出。我们按照这个时序去读取OV2640的数据,就可以得到图像数据。 最后说一下OV2640的图像数据格式,我们一般用2种输出方式:RGB565和JPEG。当输出RGB565格式数据的时候,时序完全就是上面两幅图介绍的关系。以满足不同需要。而当输出数据是JPEG数据的时候,同样也是这种方式输出(所以数据读取方法一模一样),不过PCLK数目大大减少了,且不连续,输出的数据是压缩后的JPEG数据,输出的JPEG数据以:0XFF,0XD8开头,以0XFF,0XD9结尾,且在0XFF,0XD8之前,或者0XFF,0XD9之后,会有不定数量的其他数据存在(一般是0),这些数据我们直接忽略即可,将得到的0XFF,0XD8~0XFF,0XD9之间的数据,保存为.jpg/.jpeg文件,就可以直接在电脑上打开看到图像了。 OV2640自带的JPEG输出功能,大大减少了图像的数据量,使得其在网络摄像头、无线视频传输等方面具有很大的优势。OV2640我们就介绍到这,关于OV2640更详细的介绍,请参考:A盘à硬件资料à摄像头资料àOV2640资料à OV2640_DS(1.6).pdf。 19.1.2 Kendryte K210 DVP接口简介DVP是摄像头接⼝模块,支持把摄像头输入图像数据转发给AI模块或者内存。主要功能包括: 1. 支持DVP接⼝的摄像头 2. 支持SCCB协议配置摄像头寄存器 3. 最⼤支持640x480每帧的图像输入,每帧图像⼤⼩可配置 4. 支持YUV422和RGB565格式的图像输入 5. 支持图像同时输出到AI模块和显示屏 6. 输出到AI模块的格式可选RGB888,或YUV422输入时的Y分量 7. 输出到显示屏的格式为RGB565 8. 图像输出接⼝为AXI总线,支持4 beats burst和1 beat burst 9. 检测到一帧开始或一帧图像传输完成时可向CPU发送中断 Kendryte K210芯片内置的摄像头接口模块功能强大,能够同时输出AI模型所需的数据以及RGB565格式的图像信息,从而充分满足开发板在AI应用和图像显示方面的需求。通过学习本章内容,读者将掌握如何利用Kendryte K210的DVP接口驱动摄像头实时捕获图像数据,并将其显示出来。 Kendryte K210官方SDK提供了多个操作DVP模块的函数,这里我们只讲述部分用到的函数,这些函数介绍如下: 1, dvp_init函数 该函数主要用于DVP初始化,如下代码所示: void dvp_init(uint8_t reg_len) { g_sccb_reg_len = reg_len; sysctl_clock_enable(SYSCTL_CLOCK_DVP); sysctl_reset(SYSCTL_RESET_DVP); dvp->cmos_cfg &= (~DVP_CMOS_CLK_DIV_MASK); dvp->cmos_cfg |= DVP_CMOS_CLK_DIV(3) | DVP_CMOS_CLK_ENABLE; dvp_sccb_clk_init(); dvp_reset(); } 首先我们要使能DVP时钟,然后重新配置系统DVP,最后初始化SCCB和DVP配置,简单来说,这个函数用于完成DVP驱动的必要初始化配置。 2,dvp_set_xclk_rate函数 该函数用于设置DVP时钟频率,如下代码所示: uint32_t dvp_set_xclk_rate(uint32_t xclk_rate); 函数只有一个参数,用于设置DVP的时钟频率,设置成功后函数返回实际的DVP时钟频率值。 3,dvp_start_convert函数 该函数用于启动DVP转换,如下代码所示: void dvp_start_convert(void) { dvp->sts = DVP_STS_DVP_EN | DVP_STS_DVP_EN_WE; } 当图像缓存buf未满时,启动图像捕获。 4,dvp_set_image_size函数 该函数用于设置图像分辨率大小,如下代码所示: void dvp_set_image_size(uint32_t width, uint32_t height) { uint32_t tmp; tmp = dvp->dvp_cfg & (~(DVP_CFG_HREF_BURST_NUM_MASK | DVP_CFG_LINE_NUM_MASK)); tmp |= DVP_CFG_LINE_NUM(height); if(dvp->dvp_cfg & DVP_CFG_BURST_SIZE_4BEATS) tmp |= DVP_CFG_HREF_BURST_NUM(width / 8 / 4); else tmp |= DVP_CFG_HREF_BURST_NUM(width / 8 / 1); dvp->dvp_cfg = tmp; } 该函数设置捕获图片分辨率大小,有两个参数,第一个参数设置图像宽的大小,第二个参数设置图像高的大小。 5,dvp_set_ai_addr函数 该函数用于设置AI图像的缓存地址,用于AI模块的算法处理,如下代码所示: void dvp_set_ai_addr(uint32_t r_addr, uint32_t g_addr, uint32_t b_addr) { #if FIX_CACHE configASSERT(!is_memory_cache((uintptr_t)r_addr)); configASSERT(!is_memory_cache((uintptr_t)g_addr)); configASSERT(!is_memory_cache((uintptr_t)b_addr)); #endif dvp->r_addr = r_addr; dvp->g_addr = g_addr; dvp->b_addr = b_addr; } 该函数用于设置AI的RGB地址,AI图像常用RGB888格式,图像的R、G、B三个颜色分量数据分别存放在连续的内存中(不同于RGB565)。函数有三个参数,第一个参数为存放R分量的内存地址,第二个参数存放G分量的内存地址,第三个参数存放B分量的内存地址。 6,dvp_set_ai_addr函数 该函数用于设置显示图像的内存地址,用于LCD显示,如下代码所示: void dvp_set_display_addr(uint32_t addr) { #if FIX_CACHE configASSERT(!is_memory_cache((uintptr_t)addr)); #endif dvp->rgb_addr = addr; } 该函数用于设置LCD显示的RGB地址,图像使用RGB565格式,即图像的R、G、B三个颜色分量占用16bit,可用一个uint16_t类型数据表示一个颜色,故RGB565格式的图像是以16bit长度为单位连续存放在内存中。函数的参数是RGB565图像的首地址。 Kendryte K210的DVP接口提供了多达18个API函数,而除此之外的其他相关API函数介绍,则可以在官方提供的裸机SDK编程指南手册中查阅获取。 19.2 硬件设计 19.2.1 例程功能 1. 使用SDK的DVP接口驱动OV2640摄像头模块,并配置摄像头的输出帧大小以及输出格式等进行配置,最后获取摄像头采集到的图像数据,并在LCD进行显示。 19.2.2 硬件资源 1. 摄像头 SCCB_SDA - IO40 SCCB_SCL - IO41 DVP_RESET - IO42 DVP_VSYNC - IO43 DVP_PWDN - IO44 DVP_HREF - IO45 DVP_XCLK - IO46 DVP_PCLK - IO47 D0~D7 - DVP_D0~DVP_D7 2. LCD LCD_RD - IO34 LCD_BL - IO35 LCD_CS - IO36 LCD_RST - IO37 LCD_RS - IO38 LCD_WR - IO39 LCD_D0~LCD_D7 - SPI0_D0~SPI0_D7 19.2.3 原理图 本章实验内容,需要使用到板载的摄像头接口,在正点原子DNK210开发板上有两处摄像头接口分别为位于正点原子DNK210开发板底板上的ATK-MC2640摄像头模块接口,该接口用于连接正点原子的ATK-MC2640模块,另一个摄像头接口位于正点原子CNK210F核心板,该接口同于直接连接OV2640等摄像头模组,但需要特别注意的是,这两个摄像头接口不能同时使用,否则可能导致硬件损坏。 正点原子DNK210开发板上的ATK-MC2640摄像头模块接口的连接原理图,如下图所示: 图19.2.3.1 ATK-MC2640摄像头模块接口原理图 正点原子CNK210F核心板上的OV2640等摄像头模组接口的连接原理图,如下图所示: 图19.2.3.2 OV2640等摄像头模组接口原理图 19.3 程序设计 19.3.1 OV2640驱动代码 这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。OV2640驱动源码包括四个文件:ov2640.c、ov2640.h、camera.c和camera.h。这四个文件可以做个简单的区分,ov2640.c和ov2640.h是用于OV2640摄像头得底层驱动,而camera.c和camera.h文件用于摄像头得上层应用,对外提供函数接口方便读取摄像头数据和配置摄像头等。我们这里仅介绍部分camera.c和camera.h文件的函数。 下面,首先介绍camera.h头文件中摄像头接口的宏定义,其定义情况如下: #if !defined(CAMERA_SDA_PIN) #define CAMERA_SDA_PIN 40 #endif #if !defined(CAMERA_SCL_PIN) #define CAMERA_SCL_PIN 41 #endif #if !defined(CAMERA_RST_PIN) #define CAMERA_RST_PIN 42 #endif #if !defined(CAMERA_VSYNC_PIN) #define CAMERA_VSYNC_PIN 43 #endif #if !defined(CAMERA_PWDN_PIN) #define CAMERA_PWDN_PIN 44 #endif #if !defined(CAMERA_HSYNC_PIN) #define CAMERA_HSYNC_PIN 45 #endif #if !defined(CAMERA_XCLK_PIN) #define CAMERA_XCLK_PIN 46 #endif #if !defined(CAMERA_PCLK_PIN) #define CAMERA_PCLK_PIN 47 #endif 这里是SCCB的硬件引脚和一些控制引脚的宏。OV2640摄像头模块需要连接Kendryte K210 16个引脚,除了上述8个引脚外还有8个DVP专用引脚,专用引脚初始化后便可使用,无需绑定。 下面介绍camera.c文件的内容。 /** * @param xclk_rate :DVP时钟速率 * @retval 0,操作成功 */ int camera_init(uint32_t xclk_rate) { /* Reset framebuffer */ camera_fb_reset(); /* Automatically detect and initialize sensor */ if (camera_sensor_init(xclk_rate) != 0) { return 1; } /* Configure DVP and disable DVP capture */ camera_dvp_init(); camera_dvp_run(0); return 0; } 可以看到,函数首先对摄像头进行重置,然后初始化传感器,如果初始化出现问题则退出函数并返回1,传感器初始化完成后重新配置摄像头,最后关闭DVP中断暂停捕获图像数据。 /** * @brief 设置摄像头输出的图像格式 * @param format :像素格式结构体参数 * @retval 0,操作成功 */ int camera_set_pixformat(pixformat_t format) { if (format == PIXFORMAT_INVLAID) { return 1; } /* Reconfigure sensor's pixel format if that is changed */ if (fb.pixformat != format) { if (sensor->set_pixformat(format) != 0) { return 1; } /* Recode frame parameters */ fb.pixformat = format; switch (fb.pixformat) { case PIXFORMAT_RGB565: { fb.bpp = 2; break; } default: { break; } } } return 0; } 这个函数是设置输出到显示器的图像格式,我们设置为RGB565格式。 /** * @brief 设置图像分辨率大小 * @param width :宽度 * @param height :高度 * @retval 0,设置成功 1,设置失败 */ int camera_set_framesize(uint16_t width, uint16_t height) { uint8_t index; uint8_t rec_index; /* Reset sensor frame size */ if (sensor->set_framesize(width, height) != 0) { return 1; } /* Realloc framebuffer memory if frame size is changed */ if ((fb.width != width) || (fb.height != height)) { for (index=0; index<CAMERA_FRAMEBUFFER_NUM; index++) { /* Free current framebuffer memory */ if (fb.disp[index] != NULL) { iomem_free(fb.disp[index]); fb.disp[index] = NULL; } if (fb.ai[index] != NULL) { iomem_free(fb.ai[index]); fb.ai[index] = NULL; } /* Alloc memory for display framebuffer */ fb.disp[index] = (uint8_t *)iomem_malloc(width * height * fb.bpp); if (fb.disp[index] == NULL) { for (rec_index=0; rec_index<index; rec_index++) { iomem_free(fb.disp[rec_index]); fb.disp[rec_index] = NULL; fb.disp[rec_index] = (uint8_t *)iomem_malloc(fb.width * fb.height * fb.bpp); iomem_free(fb.ai[rec_index]); fb.ai[rec_index] = NULL; fb.ai[rec_index] = (uint8_t *)iomem_malloc(fb.width * fb.height * 3); } fb.disp[index] = (uint8_t *)iomem_malloc(fb.width * fb.height * fb.bpp); sensor->set_framesize(fb.width, fb.height); return 1; } /* Alloc memory for AI framebuffer */ fb.ai[index] = (uint8_t *)iomem_malloc(width * height * 3); if (fb.ai[index] == NULL) { for (rec_index=0; rec_index<index; rec_index++) { iomem_free(fb.disp[rec_index]); fb.disp[rec_index] = NULL; fb.disp[rec_index] = (uint8_t *)iomem_malloc(fb.width * fb.height * fb.bpp); iomem_free(fb.ai[rec_index]); fb.ai[rec_index] = NULL; fb.ai[rec_index] = (uint8_t *)iomem_malloc(fb.width * fb.height * 3); } iomem_free(fb.disp[index]); fb.disp[index] = NULL; fb.disp[index] = (uint8_t *)iomem_malloc(fb.width * fb.height * fb.bpp); fb.ai[index] = (uint8_t *)iomem_malloc(fb.width * fb.height * 3); sensor->set_framesize(fb.width, fb.height); return 1; } } /* Recode frame parameters */ fb.width = width; fb.height = height; } /* Reconfigure DVP base parameter */ dvp_set_image_size(width, height); dvp_set_ai_addr((uint32_t)fb.ai[fb.write_index] + (0 * fb.width * fb.height), (uint32_t)fb.ai[fb.write_index] + (1 * fb.width * fb.height), (uint32_t)fb.ai[fb.write_index] + (2 * fb.width * fb.height)); dvp_set_display_addr((uint32_t)fb.disp[fb.write_index]); /* Enable DVP capture */ camera_dvp_run(1); return 0; } 这个函数比较长,不过并不难理解,我们首先要释放原先申请的内存和清空内存地址,再重新申请一片内存和设置内存的大小,防止原先内存不足或者内存过多导致浪费,然后重新设置AI图像和显示图像的内存地址,最后开启DVP中断开始捕获图像数据。 /** * @brief 传递图像的地址 * @param display :RGB565数据指针的地址 * @param ai :RGB888数据指针的地址 * @retval 0,捕获成功 1,捕获失败 */ int camera_snapshot(uint8_t **display, uint8_t **ai) { /* Return error if framebuffer is empty */ if (fb.empty == 1) { return 1; } /* Get data */ if (display != NULL) { if (fb.pixformat == PIXFORMAT_RGB565) { uint32_t index; uint32_t size = fb.width * fb.height * fb.bpp; uint32_t loop = size >> 2; uint32_t *data = (uint32_t *)fb.disp[fb.read_index]; for (index=0; index<loop; index++) { data[index] = ((data[index] & 0x0000FFFF) << 16) | ((data[index] & 0xFFFF0000) >> 16); } } *display = fb.disp[fb.read_index]; } if (ai != NULL) { // uint32_t index; // uint32_t size = fb.width * fb.height * 3; // uint32_t loop = size >> 1; // uint16_t *data = (uint16_t *)fb.ai[fb.read_index]; // for (index=0; index<loop; index++) // { // data[index] = ((data[index] & 0x00FF) << 8) | ((data[index] & 0xFF00) >> 8); // } *ai = fb.ai[fb.read_index]; } return 0; } 这个函数的主要功能是将AI图像和显示图像的指针地址分别赋值给一个新的指针变量,以便我们能方便地访问图像数据。操作完成该函数会返回0作为成功标识。特别指出的是,鉴于Kendryte K210芯片的SPI接口与LCD显示器之间的兼容性问题,对于RGB565格式的图像数据,在传递之前需要将每个像素点相邻的两个16位数据进行交换处理,否则在LCD上显示会出现条纹现象。 /** * @brief 释放当前捕获的图像数据 * @param 无 * @retval 0,成功 1,失败 */ int camera_snapshot_release(void) { /* Return error if framebuffer is empty */ if (fb.empty == 1) { return 1; } /* Update framebuffer read index for next framebuffer read */ fb.read_index++; if (fb.read_index == CAMERA_FRAMEBUFFER_NUM) { fb.read_index = 0; } /* Check framebuffer is empty or not */ if (fb.read_index == fb.write_index) { /* * If read index is increased to equal read index, * it means framebuffer is empty */ fb.empty = 1; } /* Set next capture's framebuffer and clean framebuffer's full flag, * cause there is a framebuffer to be released when framebuffer is full */ if (fb.full == 1) { dvp_set_ai_addr((uint32_t)fb.ai[fb.write_index] + (0 * fb.width * fb.height), (uint32_t)fb.ai[fb.write_index] + (1 * fb.width * fb.height), (uint32_t)fb.ai[fb.write_index] + (2 * fb.width * fb.height)); dvp_set_display_addr((uint32_t)fb.disp[fb.write_index]); fb.full = 0; } return 0; } 此函数是用于释放当前捕获的图像缓存,清空后便可开启下一次中断捕获。摄像头相关驱动代码就介绍到这,接下来介绍main.c文件。 19.3.2 main.c代码 main.c中的代码如下所示: #define CAMERA_WIDTH 320 #define CAMERA_HEIGHT 240 int main(void) { uint8_t *disp; sysctl_pll_set_freq(SYSCTL_PLL0, 800000000); sysctl_pll_set_freq(SYSCTL_PLL1, 400000000); sysctl_pll_set_freq(SYSCTL_PLL2, 45158400); sysctl_set_power_mode(SYSCTL_POWER_BANK6, SYSCTL_POWER_V18); sysctl_set_power_mode(SYSCTL_POWER_BANK7, SYSCTL_POWER_V18); sysctl_set_spi0_dvp_data(1); lcd_init(); lcd_set_direction(DIR_YX_LRUD); camera_init(0); camera_set_pixformat(PIXFORMAT_RGB565); camera_set_framesize(CAMERA_WIDTH, CAMERA_HEIGHT); while (1) { if (camera_snapshot(&disp, NULL) == 0) { lcd_draw_picture(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT, (uint16_t *)disp); camera_snapshot_release(); } } } 可以看到首先对LCD和摄像头进行了初始化。 接着是配置摄像头输出的帧大小以及输出格式。 最后就是在一个循环中不断地获取摄像头输出的图像数据,然后将图像在LCD显示屏上进行显示,显示完成之后释放当前捕获的图像数据便可重新进入下一帧图像采集。 19.4 运行验证 将DNK210开发板连接到电脑主机,通过VSCode将固件烧录到开发板中,可以看到LCD上实时地显示这摄像头采集到的画面,如下图所示: 图19.4.1 LCD显示摄像头采集图像
|