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实现 LeetCode 831 隐藏个人信息(暴力)

    831. 隐藏个人信息 给你一条个人信息字符串 S,它可能是一个 邮箱地址 ,也可能是一串 电话号码 . 我们将隐藏它的隐私信息,通过如下规则: 电子邮箱 定义名称 name 是长度大于等于 2 (l ...

  2. Java实现 蓝桥杯 算法训练 数字三角形

    算法训练 数字三角形 时间限制:1.0s 内存限制:256.0MB 问题描述 (图3.1-1)示出了一个数字三角形. 请编一个程序计算从顶至底的某处的一条路 径,使该路径所经过的数字的总和最大. ●每 ...

  3. Java实现 计蒜客 拯救行动

    拯救行动 公主被恶人抓走,被关押在牢房的某个地方.牢房用 N \times M (N, M \le 200)N×M(N,M≤200) 的矩阵来表示.矩阵中的每项可以代表道路(@).墙壁(#).和守卫( ...

  4. Android中如何使用对话框(单选对话框和多选对话框)

    在主XML中声明两个Button,声明Id package com.example.myapplication; import androidx.appcompat.app.AlertDialog; ...

  5. Java实现 洛谷 P1567 统计天数

    import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.i ...

  6. java实现Floyd算法

    1 问题描述 何为Floyd算法? Floyd算法功能:给定一个加权连通图,求取从每一个顶点到其它所有顶点之间的最短距离.(PS:其实现功能也称完全最短路径问题) Floyd算法思想:将顶点i到j的直 ...

  7. java实现第六届蓝桥杯九数组分数

    九数组分数 九数组分数 1,2,3...9 这九个数字组成一个分数,其值恰好为1/3,如何组法? 下面的程序实现了该功能,请填写划线部分缺失的代码. public class A { public s ...

  8. Pi-star MMDVM双工板介绍

    Pi-star MMDVM双工板介绍(2020/2) pi-star里控制模式选择:双工模式(DUPLEX Mode)/单工模式(SIMPLE Mode) 双工板工作频率范围:144-148,219- ...

  9. 如何让json_encode不转义斜杠

    当服务器返回一些数据时需要返回一些地址,但是默认的json_code是会对 / 转义成 \/ 的处理... 解决办法: 1. 正则替换: echo str_replace("\\/" ...

  10. EIGRP-15-其他和高级的EIGRP特性-1-路由器ID

    与很多协议一样, EIGRP也使用了路由器ID (RTD)的概念,用一个4字节的编号来标识某个路由器实例.每个地址家族实例拥有自已独立的RID.工程师可以在一台路由器上,为多个EIGRP进程和地址家族 ...