搜索
bottom↓
回复: 0

《DNK210使用指南 -SDK版 V1.0》第十九章 摄像头图像捕获实验

[复制链接]

出0入234汤圆

发表于 前天 12:01 | 显示全部楼层 |阅读模式
2.jpg
1)实验平台:正点原子DNK210开发板
2)购买链接:https://detail.tmall.com/item.htm?id=782801398750
3)全套实验源码+手册+视频下载地址:http://openedv.com/thread-348335-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子手把手教你学DNK210快速入门视频教程:https://www.bilibili.com/video/BV1kD421G7fu
6)正点原子FPGA交流群:132780729
1.png
3.png

第十九章 摄像头图像捕获实验


       本章将介绍如何运用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的特点有:
       高灵敏度、低电压适合嵌入式应用
       标准的SCCB接口,兼容IIC接口
       支持RawRGB、RGB(RGB565/RGB555)、GRB422、YUV(422/420)和YCbCr(422)输出格式
       支持UXGA、SXGA、SVGA以及按比例缩小到从SXGA到40*30的任何尺寸
       支持自动曝光控制、自动增益控制、自动白平衡、自动消除灯光条纹、自动黑电平校准等自动控制功能。同时支持色饱和度、色相、伽马、锐度等设置。
       支持闪光灯
       支持图像缩放、平移和窗口设置
       支持图像压缩,即可输出JPEG图像数据
       自带嵌入式微处理器
       OV2640的功能框图如图19.1.1.1所示:

第十九章 摄像头图像捕获实验942.png
图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信号的过程,并控制一些图像质量:
       边缘锐化(二维高通滤波器)
       颜色空间转换(原始信号到RGB或者YUV/YCbYCr)
       RGB色彩矩阵以消除串扰
       色相和饱和度的控制
       黑/白点补偿
       降噪
       镜头补偿
       可编程的伽玛
       十位到八位数据转换

       5.输出格式模块(Output Formatter)
       该模块按设定优先级控制图像的所有输出数据及其格式。

       6.压缩引擎(Compression Engine)
       压缩引擎框图如下图所示:

第十九章 摄像头图像捕获实验1493.png
图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位连接方式,如下图所示:

第十九章 摄像头图像捕获实验1926.png
图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所示:

第十九章 摄像头图像捕获实验2964.png
图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所示:

第十九章 摄像头图像捕获实验3467.png
图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所示:

第十九章 摄像头图像捕获实验3748.png
图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初始化,如下代码所示:
  1. void dvp_init(uint8_t reg_len)
  2. {
  3.     g_sccb_reg_len = reg_len;
  4.     sysctl_clock_enable(SYSCTL_CLOCK_DVP);
  5.     sysctl_reset(SYSCTL_RESET_DVP);
  6.     dvp->cmos_cfg &= (~DVP_CMOS_CLK_DIV_MASK);
  7.     dvp->cmos_cfg |= DVP_CMOS_CLK_DIV(3) | DVP_CMOS_CLK_ENABLE;
  8.     dvp_sccb_clk_init();
  9.     dvp_reset();
  10. }
复制代码
       首先我们要使能DVP时钟,然后重新配置系统DVP,最后初始化SCCB和DVP配置,简单来说,这个函数用于完成DVP驱动的必要初始化配置。

       2,dvp_set_xclk_rate函数
       该函数用于设置DVP时钟频率,如下代码所示:
  1. uint32_t dvp_set_xclk_rate(uint32_t xclk_rate);
复制代码
       函数只有一个参数,用于设置DVP的时钟频率,设置成功后函数返回实际的DVP时钟频率值。

       3,dvp_start_convert函数
       该函数用于启动DVP转换,如下代码所示:
  1. void dvp_start_convert(void)
  2. {
  3.     dvp->sts = DVP_STS_DVP_EN | DVP_STS_DVP_EN_WE;
  4. }
复制代码
       当图像缓存buf未满时,启动图像捕获。

       4,dvp_set_image_size函数
       该函数用于设置图像分辨率大小,如下代码所示:
  1. void dvp_set_image_size(uint32_t width, uint32_t height)
  2. {
  3.     uint32_t tmp;

  4.     tmp = dvp->dvp_cfg & (~(DVP_CFG_HREF_BURST_NUM_MASK | DVP_CFG_LINE_NUM_MASK));

  5.     tmp |= DVP_CFG_LINE_NUM(height);

  6.     if(dvp->dvp_cfg & DVP_CFG_BURST_SIZE_4BEATS)
  7.         tmp |= DVP_CFG_HREF_BURST_NUM(width / 8 / 4);
  8.     else
  9.         tmp |= DVP_CFG_HREF_BURST_NUM(width / 8 / 1);

  10.     dvp->dvp_cfg = tmp;
  11. }
复制代码
       该函数设置捕获图片分辨率大小,有两个参数,第一个参数设置图像宽的大小,第二个参数设置图像高的大小。

       5,dvp_set_ai_addr函数
       该函数用于设置AI图像的缓存地址,用于AI模块的算法处理,如下代码所示:
  1. void dvp_set_ai_addr(uint32_t r_addr, uint32_t g_addr, uint32_t b_addr)
  2. {
  3. #if FIX_CACHE
  4.     configASSERT(!is_memory_cache((uintptr_t)r_addr));
  5.     configASSERT(!is_memory_cache((uintptr_t)g_addr));
  6.     configASSERT(!is_memory_cache((uintptr_t)b_addr));
  7. #endif
  8.     dvp->r_addr = r_addr;
  9.     dvp->g_addr = g_addr;
  10.     dvp->b_addr = b_addr;
  11. }
复制代码
       该函数用于设置AI的RGB地址,AI图像常用RGB888格式,图像的R、G、B三个颜色分量数据分别存放在连续的内存中(不同于RGB565)。函数有三个参数,第一个参数为存放R分量的内存地址,第二个参数存放G分量的内存地址,第三个参数存放B分量的内存地址。

       6,dvp_set_ai_addr函数
       该函数用于设置显示图像的内存地址,用于LCD显示,如下代码所示:
  1. void dvp_set_display_addr(uint32_t addr)
  2. {
  3. #if FIX_CACHE
  4.     configASSERT(!is_memory_cache((uintptr_t)addr));
  5. #endif
  6.     dvp->rgb_addr = addr;
  7. }
复制代码
       该函数用于设置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摄像头模块接口的连接原理图,如下图所示:


第十九章 摄像头图像捕获实验7578.png
图19.2.3.1 ATK-MC2640摄像头模块接口原理图

       正点原子CNK210F核心板上的OV2640等摄像头模组接口的连接原理图,如下图所示:

第十九章 摄像头图像捕获实验7655.png
图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头文件中摄像头接口的宏定义,其定义情况如下:
  1. #if !defined(CAMERA_SDA_PIN)
  2. #define CAMERA_SDA_PIN 40
  3. #endif
  4. #if !defined(CAMERA_SCL_PIN)
  5. #define CAMERA_SCL_PIN 41
  6. #endif
  7. #if !defined(CAMERA_RST_PIN)
  8. #define CAMERA_RST_PIN 42
  9. #endif
  10. #if !defined(CAMERA_VSYNC_PIN)
  11. #define CAMERA_VSYNC_PIN 43
  12. #endif
  13. #if !defined(CAMERA_PWDN_PIN)
  14. #define CAMERA_PWDN_PIN 44
  15. #endif
  16. #if !defined(CAMERA_HSYNC_PIN)
  17. #define CAMERA_HSYNC_PIN 45
  18. #endif
  19. #if !defined(CAMERA_XCLK_PIN)
  20. #define CAMERA_XCLK_PIN 46
  21. #endif
  22. #if !defined(CAMERA_PCLK_PIN)
  23. #define CAMERA_PCLK_PIN 47
  24. #endif
复制代码
       这里是SCCB的硬件引脚和一些控制引脚的宏。OV2640摄像头模块需要连接Kendryte K210 16个引脚,除了上述8个引脚外还有8个DVP专用引脚,专用引脚初始化后便可使用,无需绑定。
       下面介绍camera.c文件的内容。
  1. /**
  2. * @brief       摄像头初始化
  3. * @param       xclk_rate :DVP时钟速率
  4. * @retval      0,操作成功
  5. */
  6. int camera_init(uint32_t xclk_rate)
  7. {
  8.     /* Reset framebuffer */
  9.     camera_fb_reset();

  10.     /* Automatically detect and initialize sensor */
  11.     if (camera_sensor_init(xclk_rate) != 0)
  12.     {
  13.         return 1;
  14.     }

  15.     /* Configure DVP and disable DVP capture */
  16.     camera_dvp_init();
  17.     camera_dvp_run(0);

  18.     return 0;
  19. }
复制代码
       可以看到,函数首先对摄像头进行重置,然后初始化传感器,如果初始化出现问题则退出函数并返回1,传感器初始化完成后重新配置摄像头,最后关闭DVP中断暂停捕获图像数据。
  1. /**
  2. * @brief       设置摄像头输出的图像格式
  3. * @param       format :像素格式结构体参数
  4. * @retval      0,操作成功
  5. */
  6. int camera_set_pixformat(pixformat_t format)
  7. {
  8.     if (format == PIXFORMAT_INVLAID)
  9.     {
  10.         return 1;
  11.     }

  12.     /* Reconfigure sensor's pixel format if that is changed */
  13.     if (fb.pixformat != format)
  14.     {
  15.         if (sensor->set_pixformat(format) != 0)
  16.         {
  17.             return 1;
  18.         }

  19.         /* Recode frame parameters */
  20.         fb.pixformat = format;
  21.         switch (fb.pixformat)
  22.         {
  23.             case PIXFORMAT_RGB565:
  24.             {
  25.                 fb.bpp = 2;
  26.                 break;
  27.             }
  28.             default:
  29.             {
  30.                 break;
  31.             }
  32.         }
  33.     }

  34.     return 0;
  35. }
复制代码
       这个函数是设置输出到显示器的图像格式,我们设置为RGB565格式。
  1. /**
  2. * @brief       设置图像分辨率大小
  3. * @param       width  :宽度
  4. * @param       height :高度
  5. * @retval      0,设置成功 1,设置失败
  6. */
  7. int camera_set_framesize(uint16_t width, uint16_t height)
  8. {
  9.     uint8_t index;
  10.     uint8_t rec_index;

  11.     /* Reset sensor frame size */
  12.     if (sensor->set_framesize(width, height) != 0)
  13.     {
  14.         return 1;
  15.     }

  16.     /* Realloc framebuffer memory if frame size is changed */
  17.     if ((fb.width != width) || (fb.height != height))
  18.     {
  19.         for (index=0; index<CAMERA_FRAMEBUFFER_NUM; index++)
  20.         {
  21.             /* Free current framebuffer memory */
  22.             if (fb.disp[index] != NULL)
  23.             {
  24.                 iomem_free(fb.disp[index]);
  25.                 fb.disp[index] = NULL;
  26.             }
  27.             if (fb.ai[index] != NULL)
  28.             {
  29.                 iomem_free(fb.ai[index]);
  30.                 fb.ai[index] = NULL;
  31.             }

  32.             /* Alloc memory for display framebuffer */
  33.             fb.disp[index] = (uint8_t *)iomem_malloc(width * height * fb.bpp);
  34.             if (fb.disp[index] == NULL)
  35.             {
  36.                 for (rec_index=0; rec_index<index; rec_index++)
  37.                 {
  38.                     iomem_free(fb.disp[rec_index]);
  39.                     fb.disp[rec_index] = NULL;
  40.                     fb.disp[rec_index] = (uint8_t *)iomem_malloc(fb.width * fb.height * fb.bpp);
  41.                     iomem_free(fb.ai[rec_index]);
  42.                     fb.ai[rec_index] = NULL;
  43.                     fb.ai[rec_index] = (uint8_t *)iomem_malloc(fb.width * fb.height * 3);
  44.                 }
  45.                 fb.disp[index] = (uint8_t *)iomem_malloc(fb.width * fb.height * fb.bpp);
  46.                 sensor->set_framesize(fb.width, fb.height);
  47.                 return 1;
  48.             }

  49.             /* Alloc memory for AI framebuffer */
  50.             fb.ai[index] = (uint8_t *)iomem_malloc(width * height * 3);
  51.             if (fb.ai[index] == NULL)
  52.             {
  53.                 for (rec_index=0; rec_index<index; rec_index++)
  54.                 {
  55.                     iomem_free(fb.disp[rec_index]);
  56.                     fb.disp[rec_index] = NULL;
  57.                     fb.disp[rec_index] = (uint8_t *)iomem_malloc(fb.width * fb.height * fb.bpp);
  58.                     iomem_free(fb.ai[rec_index]);
  59.                     fb.ai[rec_index] = NULL;
  60.                     fb.ai[rec_index] = (uint8_t *)iomem_malloc(fb.width * fb.height * 3);
  61.                 }
  62.                 iomem_free(fb.disp[index]);
  63.                 fb.disp[index] = NULL;
  64.                 fb.disp[index] = (uint8_t *)iomem_malloc(fb.width * fb.height * fb.bpp);
  65.                 fb.ai[index] = (uint8_t *)iomem_malloc(fb.width * fb.height * 3);
  66.                 sensor->set_framesize(fb.width, fb.height);
  67.                 return 1;
  68.             }
  69.         }

  70.         /* Recode frame parameters */
  71.         fb.width = width;
  72.         fb.height = height;
  73.     }

  74.     /* Reconfigure DVP base parameter */
  75.     dvp_set_image_size(width, height);
  76.     dvp_set_ai_addr((uint32_t)fb.ai[fb.write_index] + (0 * fb.width * fb.height),
  77.                     (uint32_t)fb.ai[fb.write_index] + (1 * fb.width * fb.height),
  78.                     (uint32_t)fb.ai[fb.write_index] + (2 * fb.width * fb.height));
  79.     dvp_set_display_addr((uint32_t)fb.disp[fb.write_index]);

  80.     /* Enable DVP capture */
  81.     camera_dvp_run(1);

  82.     return 0;
  83. }
复制代码
       这个函数比较长,不过并不难理解,我们首先要释放原先申请的内存和清空内存地址,再重新申请一片内存和设置内存的大小,防止原先内存不足或者内存过多导致浪费,然后重新设置AI图像和显示图像的内存地址,最后开启DVP中断开始捕获图像数据。
  1. /**
  2. * @brief       传递图像的地址
  3. * @param       display :RGB565数据指针的地址
  4. * @param       ai      :RGB888数据指针的地址
  5. * @retval      0,捕获成功 1,捕获失败
  6. */
  7. int camera_snapshot(uint8_t **display, uint8_t **ai)
  8. {
  9.     /* Return error if framebuffer is empty */
  10.     if (fb.empty == 1)
  11.     {
  12.         return 1;
  13.     }

  14.     /* Get data */
  15.     if (display != NULL)
  16.     {
  17.         if (fb.pixformat == PIXFORMAT_RGB565)
  18.         {
  19.             uint32_t index;
  20.             uint32_t size = fb.width * fb.height * fb.bpp;
  21.             uint32_t loop = size >> 2;
  22.             uint32_t *data = (uint32_t *)fb.disp[fb.read_index];
  23.             for (index=0; index<loop; index++)
  24.             {
  25.                 data[index] = ((data[index] & 0x0000FFFF) << 16) | ((data[index] & 0xFFFF0000) >> 16);
  26.             }
  27.         }
  28.         *display = fb.disp[fb.read_index];
  29.     }
  30.     if (ai != NULL)
  31.     {
  32.         // uint32_t index;
  33.         // uint32_t size = fb.width * fb.height * 3;
  34.         // uint32_t loop = size >> 1;
  35.         // uint16_t *data = (uint16_t *)fb.ai[fb.read_index];
  36.         // for (index=0; index<loop; index++)
  37.         // {
  38.         //     data[index] = ((data[index] & 0x00FF) << 8) | ((data[index] & 0xFF00) >> 8);
  39.         // }
  40.         *ai = fb.ai[fb.read_index];
  41.     }

  42.     return 0;
  43. }
复制代码
       这个函数的主要功能是将AI图像和显示图像的指针地址分别赋值给一个新的指针变量,以便我们能方便地访问图像数据。操作完成该函数会返回0作为成功标识。特别指出的是,鉴于Kendryte K210芯片的SPI接口与LCD显示器之间的兼容性问题,对于RGB565格式的图像数据,在传递之前需要将每个像素点相邻的两个16位数据进行交换处理,否则在LCD上显示会出现条纹现象。
  1. /**
  2. * @brief       释放当前捕获的图像数据
  3. * @param       无
  4. * @retval      0,成功 1,失败
  5. */
  6. int camera_snapshot_release(void)
  7. {
  8.     /* Return error if framebuffer is empty */
  9.     if (fb.empty == 1)
  10.     {
  11.         return 1;
  12.     }

  13.     /* Update framebuffer read index for next framebuffer read */
  14.     fb.read_index++;
  15.     if (fb.read_index == CAMERA_FRAMEBUFFER_NUM)
  16.     {
  17.         fb.read_index = 0;
  18.     }

  19.     /* Check framebuffer is empty or not */
  20.     if (fb.read_index == fb.write_index)
  21.     {
  22.         /*
  23.          * If read index is increased to equal read index,
  24.          * it means framebuffer is empty
  25.          */
  26.         fb.empty = 1;
  27.     }

  28.     /* Set next capture's framebuffer and clean framebuffer's full flag,
  29.      * cause there is a framebuffer to be released when framebuffer is full
  30.      */
  31.     if (fb.full == 1)
  32.     {
  33.         dvp_set_ai_addr((uint32_t)fb.ai[fb.write_index] + (0 * fb.width * fb.height),
  34.                         (uint32_t)fb.ai[fb.write_index] + (1 * fb.width * fb.height),
  35.                         (uint32_t)fb.ai[fb.write_index] + (2 * fb.width * fb.height));
  36.         dvp_set_display_addr((uint32_t)fb.disp[fb.write_index]);
  37.         fb.full = 0;
  38.     }

  39.     return 0;
  40. }
复制代码
       此函数是用于释放当前捕获的图像缓存,清空后便可开启下一次中断捕获。摄像头相关驱动代码就介绍到这,接下来介绍main.c文件。


       19.3.2 main.c代码
       main.c中的代码如下所示:
  1. #define CAMERA_WIDTH    320
  2. #define CAMERA_HEIGHT   240

  3. int main(void)
  4. {
  5.     uint8_t *disp;
  6.    
  7.     sysctl_pll_set_freq(SYSCTL_PLL0, 800000000);
  8.     sysctl_pll_set_freq(SYSCTL_PLL1, 400000000);
  9.     sysctl_pll_set_freq(SYSCTL_PLL2, 45158400);
  10.     sysctl_set_power_mode(SYSCTL_POWER_BANK6, SYSCTL_POWER_V18);
  11.     sysctl_set_power_mode(SYSCTL_POWER_BANK7, SYSCTL_POWER_V18);
  12.     sysctl_set_spi0_dvp_data(1);

  13.     lcd_init();
  14.     lcd_set_direction(DIR_YX_LRUD);
  15.     camera_init(0);
  16.     camera_set_pixformat(PIXFORMAT_RGB565);
  17.     camera_set_framesize(CAMERA_WIDTH, CAMERA_HEIGHT);
  18.    
  19.     while (1)
  20.     {
  21.         if (camera_snapshot(&disp, NULL) == 0)
  22.         {
  23.             lcd_draw_picture(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT, (uint16_t *)disp);
  24.             camera_snapshot_release();
  25.         }
  26.     }

  27. }
复制代码
       可以看到首先对LCD和摄像头进行了初始化。
       接着是配置摄像头输出的帧大小以及输出格式。
       最后就是在一个循环中不断地获取摄像头输出的图像数据,然后将图像在LCD显示屏上进行显示,显示完成之后释放当前捕获的图像数据便可重新进入下一帧图像采集。

       19.4 运行验证
       将DNK210开发板连接到电脑主机,通过VSCode将固件烧录到开发板中,可以看到LCD上实时地显示这摄像头采集到的画面,如下图所示:

第十九章 摄像头图像捕获实验17018.png
图19.4.1 LCD显示摄像头采集图像

阿莫论坛20周年了!感谢大家的支持与爱护!!

你熬了10碗粥,别人一桶水倒进去,淘走90碗,剩下10碗给你,你看似没亏,其实你那10碗已经没有之前的裹腹了,人家的一桶水换90碗,继续卖。说白了,通货膨胀就是,你的钱是挣来的,他的钱是印来的,掺和在一起,你的钱就贬值了。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-10-16 16:05

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表