1、STM32图像接收接口

使用stm32芯片,128kB RAM,512kB Rom,资源有限,接摄像头采集图像,这种情况下,内存利用制约程序设计。

STM32使用DCMI接口读取摄像头,协议如下。行同步信号指示了一行数据完成,场同步信号指示了一帧图像传输完成。所以出现了两种典型的数据接收方式,按照行信号一行一行处理,按照场信号一次接收一副图像。

2、按行读取

以网络上流行的野火的demo为例,使用行中断,用DMA来读取一行数据。

//记录传输了多少行
static uint16_t line_num =;
//DMA传输完成中断服务函数
void DMA2_Stream1_IRQHandler(void)
{
if ( DMA_GetITStatus(DMA2_Stream1,DMA_IT_TCIF1) == SET )
{
/*行计数*/
  line_num++;
  if (line_num==img_height)
  {
  /*传输完一帧,计数复位*/
  line_num=;
  }
  /*DMA 一行一行传输*/
  OV2640_DMA_Config(FSMC_LCD_ADDRESS+(lcd_width**(lcd_height-line_num-)),img_width*/);
  DMA_ClearITPendingBit(DMA2_Stream1,DMA_IT_TCIF1);
  }
} //帧中断服务函数,使用帧中断重置line_num,可防止有时掉数据的时候DMA传送行数出现偏移
void DCMI_IRQHandler(void)
{
  if ( DCMI_GetITStatus (DCMI_IT_FRAME) == SET )
  {
  /*传输完一帧,计数复位*/
  line_num=;
  DCMI_ClearITPendingBit(DCMI_IT_FRAME);
  }
}

DMA中断服务函数中主要是使用了一个静态变量line_num来记录已传输了多少行数据,每进一次DMA中断时自加1,由于进入一次中断就代表传输完一行数据,所以line_num的值等于lcd_height时(摄像头输出的数据行数),表示传输完一帧图像,line_num复位为0,开始另一帧数据的传输。line_num计数完毕后利用前面定义的OV2640_DMA_Config函数配置新的一行DMA数据传输,它利用line_num变量计算显存地址的行偏移,控制DCMI数据被传送到正确的位置,每次传输的都是一行像素的数据量。

当DCMI接口检测到摄像头传输的帧同步信号时,会进入DCMI_IRQHandler中断服务函数,在这个函数中不管line_num原来的值是什么,它都把line_num直接复位为0,这样下次再进入DMA中断服务函数的时候,它会开始新一帧数据的传输。这样可以利用DCMI的硬件同步信号,而不只是依靠DMA自己的传输计数,这样可以避免有时STM32内部DMA传输受到阻塞而跟不上外部摄像头信号导致的数据错误。

图像按帧读取比按行读取效率更高,那么为什么要按行读取呢?上面的例子是把图像送到LCD,如果是送到内存,按帧读取就需要芯片有很大的内存空间。以752*480的分辨率为例,需要360kB的RAM空间,远远超出了芯片RAM的大小。部分应用不需要摄像头全尺寸的图像,只需要中心区域,比如为了避免畸变影响一般只用图像中间的部分,那么按行读取就有一个好处,读到一行后,可以把不需要的丢弃,只保留中间部分的图像像素。

那么问题来了?为什么不直接配置摄像头的属性,来实现只读取图像的中间部分呢,全部读取出来然后在arm的内存中裁剪丢弃不要的像素,第一浪费了读取时间,第二浪费了读取的空间。更优的做法是直接配置摄像头sensor,使用sensor的裁剪功能输出需要的像素区域。

3、图像裁剪--使用STM32 crop功能裁剪

STM32F4系列的DCMI接口支持裁剪功能,对摄像头输出的像素点进行截取,不需要的像素部分不被DCMI传入内存,从硬件接口一侧就丢弃了。

HAL_DCMI_EnableCrop(&hdcmi);
HAL_DCMI_ConfigCrop(&hdcmi, CAM_ROW_OFFSET, CAM_COL_OFFSET, IMG_ROW-, IMG_COL-);

裁剪的本质如下所述,从接收到的数据里选择需要的矩形区域。所以STM32 DCMI裁剪功能可以完成节约内存,只选取需要的图像存入内存的作用。

此方法相比于一次读一行,然后丢弃首尾部分后把需要的区域图像像素存入buffer后再读下一行,避免了时序错误,代码简洁了,DCMI硬件计数丢掉不要的像素,也提高了程序可靠性、可读性。

成也萧何败也萧何,如上面所述,STM32的crop完成了选取特定区域图像的功能,那么也要付出代价,它是从接收到的图像数据里进行选择的,这意味着那些不需要的数据依然会传输到MCU一侧,只不过MCU的DCMI对数据进行计数是忽略了它而已,那么问题就来了,哪些不需要的数据的传输会带来什么问题呢?

有图为证,下图是使用了STM32 crop裁剪的时序图,通道1启动采集IO置高,frame中断里拉低,由于使用dma传输,那么被crop裁剪后dma计数的数据量变少,所以DCMI frame中断能在行数据传输完成前到达,通道1高电平部分就代表一有效分辨率的帧的采集时间。通道2 曝光信号管脚,通道3是行扫描信号。其中通道1下降沿到通道3下降沿4.5ms。代表单片机已经收到crop指定尺寸的图像,采集有效区域(crop区域)的图像完成,但是line信号没有结束还有很多行没传输,即CMOS和DCMI接口要传输752*480图像还没完成。

举例说明,如果使用752*480分辨率采集图像,你只取中间的360*360视野,有效分辨率是360*360,但是总线上的数据依然是752*480,所以帧率无法提高,多余的数据按说就不应该传输出来,如何破解,问题追到这里,STM32芯片已经无能为力了,接下来需要在CMOS一侧发力了。

4、图像裁剪--配置COMOS寄存器裁剪

下图是MT9V034 摄像头芯片的寄存器手册,Reg1--4配置CMOS的行列起点和宽度高度。

修改寄存器后,摄像头CMOS就不再向外传输多余的数据,被裁剪丢弃的数据也不会反应在接口上,所以STM32 DCMI接收到的数据都是需要保留的有效区数据,极大地减少了数据输出,提高了传输效率。本人也在STM324芯片上,实现了220*220分辨率120帧的连续采集。

下面是序图,通道1高电平代表开始采集和一帧结束,不同于使用STM32 的crop裁剪,使用CMOS寄存器裁剪有效窗口,使得帧结束时行信号也同时结束,后续没有任何需要传输的行数据。

5、一帧数据一次性传输

一帧数据一次全部读入到MCU的方式,其实是最简单的驱动编写方式,但是对于没有压缩功能的cmos芯片来说,一般都无力实现。对部分有jpg压缩功能的cmos芯片而言,比如OV2640可以使用这种方式,一次性读出一帧图像。

__align() u32 jpeg_buf[jpeg_buf_size];    //JPEG buffer
//JPEG 格式
const u16 jpeg_img_size_tbl[][]=
{
,, //QCIF
,, //QQVGA
,, //CIF
,, //QVGA
,, //VGA
,, //SVGA
,, //XGA
,, //SXGA
,, //UXGA
};

//DCMI 接收数据
void DCMI_IRQHandler(void)
{
  if(DCMI_GetITStatus(DCMI_IT_FRAME)==SET)// 一帧数据
  {
    jpeg_data_process();  
    DCMI_ClearITPendingBit(DCMI_IT_FRAME); 
  }
}

STM32内存受限情况下摄像头驱动方式与图像裁剪的选择的更多相关文章

  1. 模拟APP存储空间、内存不足情况下软件正常运行

    1.进行临界测试,手机盘空间存满的条件下应用会有何表现: 方法一:adb shell dd if=/dev/zero of=/mnt/sdcard/bigfile 方法二:哆啦A工具生成文件 2.内存 ...

  2. git 提交各种情况下的处理方式

    自己总结: 01.若在提交过程中有冲突,解决冲突后,git add . git rebase —continue git push for   02.git rebase vs git merge g ...

  3. 【转载】为什么不建议<=3G的情况下使用CMS GC

    之前曾经有讲过在heap size<=3G的情况下完全不要考虑CMS GC,在heap size>3G的情况下也优先选择ParallelOldGC,而不是CMS GC,只有在暂停时间无法接 ...

  4. JVM自动内存管理机制——Java内存区域(下)

    一.虚拟机参数配置 在上一篇<Java自动内存管理机制——Java内存区域(上)>中介绍了有关的基础知识,这一篇主要是通过一些示例来了解有关虚拟机参数的配置. 1.Java堆参数设置 a) ...

  5. Linux下通过二进制方式安装mysql5.7版本和系统优化

    本文主要介绍MySQL二进制软件包的安装/启动/关闭过程. 也许有人要问为什么要选择二进制的安装方式呢? 其实答案很简单,官方版本中已经把所有功能都配置好了,我们可以很方便地拿来使用. 官方MySQL ...

  6. DNS分别在什么情况下使用UDP和TCP

    DNS同时占用UDP和TCP端口53是公认的,这种单个应用协议同时使用两种传输协议的情况在TCP/IP栈也算是个另类.但很少有人知道DNS分别在什么情况下使用这两种协议.     如果用wiresha ...

  7. 一步步学Mybatis-实现单表情况下的CRUD操作 (3)

    今天这一章要紧接上一讲中的东西,本章中创建基于单表操作的CRUD与GetList操作,此示例中以Visitor表为范例,为了创建一点测试数据我们先弄个Add方法吧 继续在上次的IVisitorOper ...

  8. MySQL分页优化中的“INNER JOIN方式优化分页算法”到底在什么情况下会生效?

    本文出处:http://www.cnblogs.com/wy123/p/7003157.html 最近无意间看到一个MySQL分页优化的测试案例,并没有非常具体地说明测试场景的情况下,给出了一种经典的 ...

  9. phpExcel导入大数据量情况下内存溢出解决方案

    PHPExcel版本:1.7.6+ 在不进行特殊设置的情况下,phpExcel将读取的单元格信息保存在内存中,我们可以通过 PHPExcel_Settings::setCacheStorageMeth ...

随机推荐

  1. Java四种权限修饰符

    四种权限修饰符

  2. SpringMVC(二)返回值设置、数据在域中的保存与SpringMVC案例

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.返回值的设置 1.返回 String [1]返回 String 默认情况 @RequestMappi ...

  3. Java实现 LeetCode 557 反转字符串中的单词 III(StringBuilder的翻转和分割)

    557. 反转字符串中的单词 III 给定一个字符串,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序. 示例 1: 输入: "Let's take LeetCode c ...

  4. Java实现 蓝桥杯VIP 算法训练 乘法表

    问题描述 输出九九乘法表. 输出格式 输出格式见下面的样例.乘号用""表示. 样例输出 下面给出输出的前几行: 11=1 21=2 22=4 31=3 32=6 33=9 41=4 ...

  5. Java实现 LeetCode 279 完全平方数

    279. 完全平方数 给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, -)使得它们的和等于 n.你需要让组成和的完全平方数的个数最少. 示例 1: 输入: n = 12 输出: ...

  6. Java实现 LeetCode 187 重复的DNA序列

    187. 重复的DNA序列 所有 DNA 都由一系列缩写为 A,C,G 和 T 的核苷酸组成,例如:"ACGAATTCCG".在研究 DNA 时,识别 DNA 中的重复序列有时会对 ...

  7. Java实现第九届蓝桥杯字母阵列

    字母阵列 题目描述 仔细寻找,会发现:在下面的8x8的方阵中,隐藏着字母序列:"LANQIAO". SLANQIAO ZOEXCCGB MOAYWKHI BCCIPLJQ SLAN ...

  8. install-package : 由于无法加载项目 mydemo 的详细信息,操作失败

    安装nuget package包提示错误 install-package : 由于无法加载项目 mydemo 的详细信息,操作失败 解决方法 项目用dotnet cli 命令dotnet new mv ...

  9. java关键字tranisent用法详解

    作为java中的一个关键字,tranisent用的并不是很多,但是在某些关键场合,却又起着极为重要的作用,因此有必要对它进行一些必要的了解. 一.定义:声明不用序列化的成员域.(源自百度百科) 二.作 ...

  10. 开发者大赛 | aelf轻型DApp开发训练大赛结果公布!

    6月9日,由aelf基金会发起的轻型DApp开发训练大赛圆满收官.本次训练赛基于aelf公开测试网展开,主要针对轻型DApp,旨在激励更多的开发者参与到aelf生态中来. 活动于4月21日上线后,ae ...