找回密码
 立即注册
查看: 1098|回复: 4

使用PWM触发ADC和DMA-M2M加速CCD摄像头读取

[复制链接]
  • 打卡等级:以坛为家II
  • 打卡总天数:532
  • 最近打卡:2025-07-31 14:15:17
已绑定手机
已实名认证

116

主题

2499

回帖

6452

积分

版主

积分
6452
发表于 2024-8-30 14:45:44 | 显示全部楼层 |阅读模式
本帖最后由 王昱顺 于 2024-8-30 15:12 编辑

使用PWM触发ADC和DMA-M2M加速CCD摄像头读取

CCD摄像头,就是只有一行像素的摄像头。因为可以获得的信息相对正常的数字摄像头较少。所以极限的传输速度也就可以变得更快。

所以,应该如何利用好STC的硬件外设和DMA功能来加速这一个过程呢?

首先需要讲解的就是PWM触发ADC功能,这个功能仅适用于PWMA组的PWM管脚。通过设置PWMA_CR2,即可设定对应的通道来启动ADC功能。
那么,这种读取方式相对于普通的IO翻转读取有什么好处呢?
最重要的就是不会堵塞其他任务的进行,因为使用IO读取的时候,通常都是使用一个for循环。来将所有的ADC输入存入一个图像数组中。在这个for循环中,其他的代码其实无法运行的。这就对执行效率产生了极大的浪费。
所以,我们可以使用PWM触发ADC+ADC中断存储图像数据的方式。
file:///C:/Users/Administrator/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg
这里我使用的是比较脉冲作为PWM的TRGO(触发输出),来触发ADC。也就是说,除了可以通过设置PWM的周期来确定ADC采样的频率以外,还可以通过设置PWM输出的CCR寄存器来偏移ADC采样点,以避免掉噪声和获得更稳定的ADC数据信息。

经过上面的改进,此时的CCD采样已经可以避免掉长时间的代码执行时间占用问题了。但是采样-处理这个过程中的处理时间仍然占用了相当大的一部分时间。
所以,接下来介绍的就是使用了双缓冲技术的图像读取,并且通过DMA-M2M实现了图像缓冲区和实际的图像操作区域隔离。
先来看一段代码:

adc.c文件
  1. #include"./H_Group/adc.h"
  2. u16xdata adc_tmp[128] = {0};
  3. u16xdata ao_1[128] = {0};
  4. u16xdata ao_2[128] = {0};
  5. bitADC_Num = 0;    // 标识当前操作的ADC缓冲区
  6. bitOUT_Flag = 0;   // 标识输出缓冲区是否被操作
  7. u16ADC_Cnt = 0;    // ADC缓冲计数
  8. bitADC_Finish = 0; // 成功置1
  9. voidStart_Get_Image(void)
  10. {
  11.     PWMA_CR1 &= ~0x01; // 暂时关闭PWMA
  12.     PWMA_CNTRH = 0x00;
  13.     PWMA_CNTRL = 0x00; // 计数清零
  14.     PWMA_ENO = 0x00;   // 关闭PWMA端口输出
  15.     CCD_CLK = 1;
  16.     CCD_SI = 0;
  17.     CCD_CLK = 0;
  18.     CCD_SI = 1;
  19.     CCD_CLK = 1;
  20.     CCD_SI = 0;
  21.     CCD_CLK = 0;
  22.     ADC_START = 1;
  23.     PWMA_ENO = 0x01;    // 使能输出,小脉冲
  24.     ADC_Cnt = 0;        // 从零开始计数
  25.     ADC_Num = ~ADC_Num; // 取反标志位
  26.     // 判断标志位,启动M2M
  27.     if (OUT_Flag == 0)
  28.     {
  29.         // 空闲状态才允许开始搬运
  30.         DMA_M2M_CFG = 0x80; // r++ = t++
  31.         DMA_M2M_STA = 0x00;
  32.         DMA_M2M_AMT = 0xff; // 设置传输总字节数256
  33.         if (ADC_Num)
  34.         {
  35.             DMA_M2M_TXAH = (u8)((u16)&ao_2>> 8);
  36.             DMA_M2M_TXAL =(u8)((u16)&ao_2);
  37.         }
  38.         else
  39.         {
  40.             DMA_M2M_TXAH = (u8)((u16)&ao_1>> 8);
  41.             DMA_M2M_TXAL =(u8)((u16)&ao_1);
  42.         }
  43.         DMA_M2M_RXAH = (u8)((u16)&adc_tmp>> 8);
  44.         DMA_M2M_RXAL = (u8)((u16)&adc_tmp);
  45.     }
  46.     DMA_M2M_CR = 0xc0; // bit7 1:使能 M2M_DMA, bit6 1:开始 M2M_DMA 操作
  47.     PWMA_CR1 |= 0x01;  // 启动PWMA定时器计数
  48. }
  49. voidM2M_DMA_Interrupt(void) interrupt 47
  50. {
  51.     if (DMA_M2M_STA & 0x01) // 发送完成
  52.     {
  53.         DMA_M2M_STA &= ~0x01;
  54.         OUT_Flag = 1; // 标识M2M一次操作完成,等待图像处理完成后置0
  55.     }
  56. }
  57. voidADC_Isr(void) interrupt 5
  58. {
  59.     ADC_FLAG = 0;
  60.     if (ADC_Cnt >= 128)
  61.     {
  62.         PWMA_CR1 &= ~0x01; // 暂时关闭PWMA
  63.         Start_Get_Image(); // 直接开始下一帧的获取
  64.     }
  65.     else
  66.     {
  67.         if (ADC_Num)
  68.             ao_1[ADC_Cnt] = ((u16)ADC_RES<< 8 | ADC_RESL);
  69.         else
  70.             ao_2[ADC_Cnt] = ((u16)ADC_RES<< 8 | ADC_RESL);
  71.         ADC_Cnt++;
  72.     }
  73. }
复制代码

这部分代码便是ADC.c中的全部程序。其中,ao_1和ao_2为读取的缓冲区,通过ADC_Num切换使用哪个缓冲区来读取。ADC_Cnt为进入中断的计数,通过计数溢出来切换缓冲区的使用。
同时,在下一帧获取起始前,会对空闲的缓冲区进行DMA-M2M复制,以保护读取缓冲区不与图像处理冲突,图像处理通过OUT_Flag进行保护,仅在执行完图像处理后,才允许M2M操作,以防止存储混乱。
这样,只需要保证一帧的时间内,可以完成一帧的图像处理和一帧的图像复制。即可让图像读取变得稳定且连续。

回复

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:514
  • 最近打卡:2025-08-01 07:33:38
已绑定手机

87

主题

5532

回帖

1万

积分

超级版主

DebugLab

积分
10127
发表于 2024-9-24 17:39:19 | 显示全部楼层
wnag*** 发表于 2024-8-30 22:32
CCD摄像头为什么只有一行?我看网上的CCD也能拍图形啊?

线阵CCD
DebugLab
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:7
  • 最近打卡:2025-07-04 10:53:38

1

主题

24

回帖

125

积分

注册会员

积分
125
发表于 2025-7-3 10:13:53 | 显示全部楼层
想问一下,AI8051,然后我用的这个ccd是需要四个PWM周期触发一次ADC,目前看到说是可以用更新事件触发,但我不知道代码写的对吗,有没有什么调试方法可以查看这个PWM触发过程吗(手上现在有STC家的仿真器,但不知道怎么看
  1. void PWM_config(void)
  2. {
  3.     PWMA_CCER1 = 0x00; //写 CCMRx 前必须先清零 CCxE 关闭通道
  4.     PWMA_CCER2 = 0x00;
  5.         
  6.     PWMA_CCMR4 = 0x60; //设置 PWM4 模式1 输出
  7.         
  8.     PWMA_CCER1 = 0x55; //配置通道输出使能和极性
  9.     PWMA_CCER2 = 0x55;//使能 CC4E 通道, 低电平有效
  10.         
  11.         PWMA_CCMR4 |= 0x08;
  12.     PWMA_ARRH = 0x00; //设置周期时间
  13.     PWMA_ARRL = 0x14;//0x28;
  14.     PWMA_CCR4H = 0;
  15.     PWMA_CCR4L = 10; //设置占空比时间
  16.         
  17. //         // 配置触发输出(TRGO)为OC4REF信号(CCR比较事件)
  18. //    PWMA_CR2 |= 0x20;   // MMS[2:0] = 010b → OC4REF作为触发输出
  19.     PWMA_PS = 0x00;  //高级 PWM 通道 4N 输出脚选择位, 0x00:P1.7, 0x40:P0.7, 0x80:P2.7
  20.     PWMA_ENO |= 0x40; //使能 PWM4N 输出
  21.         
  22. //        // 配置中断控制寄存器,设置4周期中断
  23.     PWMA_DTR = 0x00;    // 死区时间设置为0
  24.     PWMA_RCR = 0x03;    // 重复计数寄存器设为3,每(3+1)=4个周期触发一次更新事件
  25.          // 配置触发输出(TRGO)为更新事件
  26.         PWMA_CR2 &= ~(0x70);  // 先清除 MMS[2:0] 位
  27.     PWMA_CR2 |= 0x20;   // MMS[2:0] = 000b → 选择更新事件作为触发输出
  28.         
  29.     PWMA_BKR = 0x80; //使能主输出
  30.     //PWMA_IER = 0x10; //使能中断
  31.     PWMA_CR1 |= 0x81;  //使能ARR预装载,开始计时
  32. }
复制代码

  1. ////////////////////////////////////////
  2. // ADC中断服务程序
  3. // 入口参数: 无
  4. // 函数返回: 无
  5. ////////////////////////////////////////
  6. void ADC_ISR(void) interrupt ADC_VECTOR
  7. {
  8.     //<<AICUBE_USER_ADC_ISR_CODE1_BEGIN>>
  9.     // 在此添加中断函数用户代码
  10.     ADC_ClearFlag();                    //清除ADC转换完成中断标志
  11.            fADCConverted = 1;                  //设置转换结束标志
  12.         if(ADC_Read_flag==1)
  13.         {
  14.                 pu8ADCDMABuffer[ADC_time] = (u16)ADC_RES * 256 + (u16)ADC_RESL;        //读ADC结果        
  15.             ADC_time = (ADC_time >= 1546) ? 0 : ADC_time + 1;
  16.                 ADC_Read_flag = (ADC_time >= 1546) ? 0 : 1;
  17.        //if(ADC_Read_flag= 0) PWMA_BKR &= ~0x80;               
  18.         }
  19.         
  20.     //<<AICUBE_USER_ADC_ISR_CODE1_END>>
  21. }
  22. ////////////////////////////////////////
  23. // ADC初始化函数
  24. // 入口参数: 无
  25. // 函数返回: 无
  26. ////////////////////////////////////////
  27. void ADC_Init(void)
  28. {
  29.         //开启ADC电源
  30.         ADC_POWER = 1;
  31.     ADC_SetClockDivider(0);             //设置ADC时钟
  32.     ADC_ResultRightAlign();             //设置ADC结果右对齐(12位结果)
  33.     ADC_DisableRepeatConv();            //关闭ADC自动重复转换功能
  34.     ADC_SetCSSetupCycles(0);            //设置ADC通道选择建立时间
  35.     ADC_SetCSHoldCycles(1);             //设置ADC通道选择保持时间
  36.     ADC_SetSampleDutyCycles(9);         //设置ADC通道采样时间
  37.         
  38.     //启用PWM触发模式
  39.     ADC_EnablePWMTrig();
  40.     ADC_EnableETR();                    //禁止ADC外部触发功能
  41.         ADC_ETRFalling();
  42.     ADC_SetIntPriority(1);              //设置中断为较低优先级
  43.     ADC_EnableInt();                    //使能ADC中断
  44.     fADCConverted = 0;                  //初始化转换结束标志
  45.     ADC_ActiveChannel(11);              //选择ADC通道
  46.     ADC_Enable();                       //使能ADC功能
  47.     //<<AICUBE_USER_ADC_INITIAL_BEGIN>>
  48.     // 在此添加用户初始化代码
  49.     //<<AICUBE_USER_ADC_INITIAL_END>>
  50. }
复制代码

点评

可以看一下这个仿真教程 https://www.stcaimcu.com/forum.php?mod=viewthread&tid=3488&highlight=%E4%BB%BF%E7%9C%9F&page=1&extra=#pid23022 或者技术手册里面也有第二章的教学内容。 使用仿真的单步就可以观察是  详情 回复 发表于 2025-7-3 11:38
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:532
  • 最近打卡:2025-07-31 14:15:17
已绑定手机
已实名认证

116

主题

2499

回帖

6452

积分

版主

积分
6452
发表于 2025-7-3 11:38:25 | 显示全部楼层
AN*** 发表于 2025-7-3 10:13
想问一下,AI8051,然后我用的这个ccd是需要四个PWM周期触发一次ADC,目前看到说是可以用更新事件触发,但 ...

可以看一下这个仿真教程
https://www.stcaimcu.com/forum.p ... amp;extra=#pid23022
或者技术手册里面也有第二章的教学内容。
使用仿真的单步就可以观察是否是跟随触发了。或者直接使用IO电平反转的方式,然后用示波器测量外部管脚进行观察
回复 支持 反对

使用道具 举报 送花

  • 打卡等级:初来乍到
  • 打卡总天数:7
  • 最近打卡:2025-07-04 10:53:38

1

主题

24

回帖

125

积分

注册会员

积分
125
发表于 2025-7-3 14:39:54 | 显示全部楼层
王*** 发表于 2025-7-3 11:38
可以看一下这个仿真教程
https://www.stcaimcu.com/forum.php?mod=viewthread&tid=3488&highlight=%E4%BB ...

嗯呢嗯呢好了,感谢
回复 支持 反对

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-8-1 08:44 , Processed in 0.128411 second(s), 77 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

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