查看: 3809|回复: 2

[求助] Kinetis 16-bit ADC+DMA+定时器 AutoScan自动通道扫描采样

[复制链接]

该用户从未签到

9

主题

80

帖子

0

注册会员

Rank: 2

积分
179
最后登录
2018-5-9
发表于 2015-7-8 17:39:07 | 显示全部楼层 |阅读模式
原作者: jicheng

之前不止在一篇博客里面提到过飞思卡尔Kinetis独有的片内16位高精度ADC(其他家大多都是12位的),这个16位分辨率的ADC,虽然其不可能精度达到全16位,但是其最高13/14位的精度也不是其他12位ADC能比的(而且其还支持单端和差分输入,自带PGA和硬件平均功能),所以Kinetis在一些比如温度、重量和电池电压监控等测量方面的应用有其独特的优势。但是让人又爱又恨的是,这个16位ADC不支持通道自动扫描的功能,也就是说每次转换一个通道都需要软件或硬件触发一次(估计上这是一种成本上的妥协和折衷吧),这个问题对上面所提到的温度和重量等大滞环系统没有影响(他们本身对你时间没有太高的要求,一个个慢慢转换就是了),但是在一些对信号的采样率和实时性要求比较高的场合,如果使用查询等待的方式会干耗掉CPU的资源,而使用中断方式的话又会有太多的中断响应。那有没有一种比较优化的方式实现来满足这种对采样率和实时性要求较高的需求呢,咳咳,有点废话了,没有的话我写这篇文章干嘛,呵呵,所以本篇的主要目的就是通过使用ADC+DMA+定时器来灵活实现Kinetis内部ADC自动通道扫描的方法,下面开整,走着:

测试平台:YL_KL26(优龙做的基于KL26的开发板,与FSL官方的FRDM-KL26很相近)

开发环境:IARv7.3

测试代码:基于FRDM-KL26Z_SC\FRDM-KL26Z_SC_Rev_1.0\klxx-sc-baremetal\build\iar\hello_world代码修改

功能描述:以100ksps的采样率对ADC的Channel0(接到YL-KL26板载的变阻器)和Channel26(KL26内部温度传感器)以DMA方式扫描采样,对一个Buffer存满10次采样数据后切换到另一个Buffer继续存,而对之前的Buffer进行处理(数组的偶数元素存的是Channel0的数据,奇数元素存的是Channel26的数据),从而实现双缓存。

(1)首先定义需要用到的变量和宏,如下所示,其中BUFFER_SIZE定义了Buffer的存储深度(为了测试,我先只定义了10个数组,其中5个for Channel0的数据,5个for Channel26的数据),pCounter用来做双缓存的切换计数,ADC_Channel数组为要采样的Channel list(至于本来是2个通道,为什么定义成4个数组,这是由于DMA的Circular Buffer模数计数最小为16个字节构成循环,一个Channel占用4个字节,所以最少定义4个元素,凑足16个字节),而剩下的Result_Buffer和Buffer_Address则为数据缓存和缓存的首地址,用来做DMA目的地址;

  1. #define BUFFER_SIZE     10
  2. unsigned char pCounter=0;               // Counter used for Dual buffer switch
  3. uint32 ADC_Channel[4]={0, 26, 0, 26};   //Channel list for Channel0 and Channel 26
  4. uint8 Result_Buffer1[BUFFER_SIZE]={0};  //Buffer1
  5. uint8 Result_Buffer2[BUFFER_SIZE]={0};  //Buffer2
  6. uint32 Buffer_Address[2]={(uint32)&Result_Buffer1, (uint32)&Result_Buffer2};    //Buffer list
复制代码



(2)其次初始化ADC模块(为了方便我定义成8位分辨率),禁止ADC转换完成中断,使能ADC转换完成触发DMA,同时使能单次软件触发模式(即每次写入ADC0_SC1的通道号触发一次ADC转换)。需要注意的是ADC的输入时钟源要小于18MHz;

  1. void init_ADC8(void)
  2. {
  3.    
  4.   // Turn on the ADC0 clock
  5.   SIM_SCGC6 |= (SIM_SCGC6_ADC0_MASK );
  6.             
  7.   ADC0_CFG1 = ADC_CFG1_ADIV(1)          //divide ratio is 2 and the clock rate is (input clock)/2.
  8.               | ADC_CFG1_MODE(0)        // single-end 8bit mode
  9.               | ADC_CFG1_ADICLK(0);     //Bus clock         
  10.    
  11.   ADC0_CFG2 = 0;
  12.    
  13.   ADC0_CV1 =  0x1234u;                  // can be anything
  14.   ADC0_CV2 = 0x5678u;                   // can be anything
  15.                
  16.   ADC0_SC2 = 0 | ADC_SC2_DMAEN_MASK;    //ADC conversion complete DMA enable
  17.    
  18.   ADC0_SC3 = 0;                         // Hardware average disable and once conversion
  19. }
复制代码



(3)然后初始化TPM定时器模块,该模块配置成自由计数模式,溢出中断触发DMA,其中MOD寄存器即溢出时间决定ADC的采样率,本例程配置成每100kHz的频率溢出一次;

  1. void init_TPM1(void)
  2. {
  3.   SIM_SCGC6 |= (SIM_SCGC6_TPM1_MASK );
  4.   SIM_SOPT2 |= SIM_SOPT2_TPMSRC(1); //input clock source is MCGPLL/2=24MHz
  5.   TPM1_SC = TPM_SC_DMA_MASK | TPM_SC_PS(3);//Divide by 3M=24M/8
  6.   TPM1_CNT = 0;
  7.   TPM1_MOD = 30;        //timer interrupt triger with 100ksps
  8.    
  9.   TPM1_SC |= TPM_SC_CMOD(1);    //start the counter
  10. }
复制代码



(4)这部分是DMA的初始化,本例程的核心部分,本例程的实现需要使用两个Channel的DMA,其中一个channel的DMA与TPM定时器配合实现周期性触发ADC转换,即每次TPM定时器溢出触发一次DMA将Channellist的通道号写到ADC_SC1寄存器从而触发一次ADC转换,然后ADC转换完成触发另一个DMA Channel将ADC的结果传到其中一个Buffer,具体配置俺就不多说了,看下面的程序注释就行了,至于TPM和ADC在DMA中的触发源可以在KL16的参考手册中找到,如下图,其中ADC0的触发源为40,TPM1溢出触发源为55;

  1. void DMA_2ChanelADC_Init(void)
  2. {
  3.   SIM_SCGC6 |= SIM_SCGC6_DMAMUX_MASK;
  4.   SIM_SCGC7 |= SIM_SCGC7_DMA_MASK;
  5.    
  6.   enable_irq(INT_DMA1-16);
  7.    
  8.   DMA_SAR0 = (uint32) &ADC_Channel;    //Set source address to ADC_Channel
  9.   DMA_DSR_BCR0 = DMA_DSR_BCR_BCR(4*BUFFER_SIZE);    //Set BCR to know how many bytes to

  10. transfer, 8 bytes for two 32-bit transfer
  11.   DMA_DCR0 &= ~(DMA_DCR_SSIZE_MASK | DMA_DCR_DSIZE_MASK);    //Clear source size and

  12. destination size fields
  13.   /* Set DMA as follows:
  14.        Source size is 32-bit size
  15.        Destination size is 32-bit size
  16.        Cycle steal mode
  17.        External requests are enabled
  18.        source address increments 1 automatically
  19. */
  20.   DMA_DCR0 |= (DMA_DCR_SSIZE(0)
  21.              | DMA_DCR_DSIZE(0)
  22.              | DMA_DCR_CS_MASK  
  23.              | DMA_DCR_ERQ_MASK  
  24.              | DMA_DCR_SINC_MASK
  25.              | DMA_DCR_SMOD(1)); //16 bytes circular buffer
  26.    
  27.   DMA_DAR0 = (uint32) &ADC0_SC1A;//0x4003B000;    //Set source address to ADC0_SC1A
  28.   DMAMUX0_CHCFG0 = DMAMUX_CHCFG_SOURCE(55);    //Select TPM1 overflow as channel source
  29.   DMAMUX0_CHCFG0 |= DMAMUX_CHCFG_ENBL_MASK;    //Enable the DMA MUX channel
  30.    
  31.   /*********************************************************************/
  32.   DMA_SAR1 = (uint32) &ADC0_RA;//0x4003B010;    //Set source address to ADC0_RA
  33.   DMA_DSR_BCR1 = DMA_DSR_BCR_BCR(BUFFER_SIZE);    //Set BCR to know how many bytes to

  34. transfer
  35.   DMA_DCR1 &= ~(DMA_DCR_SSIZE_MASK | DMA_DCR_DSIZE_MASK);    //Clear source size and

  36. destination size fields
  37.   /* Set DMA as follows:
  38.        Source size is 8-bit size
  39.        Destination size is 8-bit size
  40.        Cycle steal mode
  41.        External requests are enabled
  42.        Destination address increments 1 automatically
  43.        BCR Bytes trasfer completed interrupt enable
  44. */
  45.   if ((DMA_DSR_BCR1 & DMA_DSR_BCR_DONE_MASK) == DMA_DSR_BCR_DONE_MASK)
  46.     DMA_DSR_BCR1 |= DMA_DSR_BCR_DONE_MASK;
  47.    
  48.   DMA_DCR1 |= (DMA_DCR_SSIZE(1)
  49.              | DMA_DCR_DSIZE(1)
  50.              | DMA_DCR_CS_MASK  
  51.              | DMA_DCR_ERQ_MASK  
  52.              | DMA_DCR_DINC_MASK
  53.              | DMA_DCR_EINT_MASK);
  54.    
  55.   DMA_DAR1 = (uint32) &Result_Buffer1;    //Set source address to result buffer
  56.   DMAMUX0_CHCFG1 = DMAMUX_CHCFG_SOURCE(40);    //Select ADC0 COCO flag as channel source
  57.   DMAMUX0_CHCFG1 |= DMAMUX_CHCFG_ENBL_MASK;    //Enable the DMA MUX channel
  58. }
复制代码


Kinetis 16-bit ADC DMA 定时器实现AutoScan自动通道扫描采样 1.png

(5)最后是DMA中断服务函数的配置,这也是一个核心部分,主要涉及到双缓存的切换(可以看到DMA Channel1的目的地址在两个buffer地址之间切换),另外一定记得每进一次DMA完成中断都要重新填一次DMA_DSR_BCR寄存器让其重新开始计数,还有可能有博友比较疑惑的是为啥BCR0的size是BCR1的一倍,这是因为DMA Channel0每次传输的是32位即4个字节的ADC采样通道号(写32位的ADC_SC1寄存器),而DMA Channel1每次传输8位即一个字节的ADC结果(因为我前面把其配置成8位单端模式了),所以这个比例是4:1,如果配置成16位ADC模式的话,这个比例就该改成2:1了,而且前面DMA初始化部分也需要做相应修改。

  1. void DMA1_IRQHandler(void)
  2. {
  3.    
  4.   /* Create pointer & variable for reading DMA_DSR register */   
  5.   if ((DMA_DSR_BCR1 & DMA_DSR_BCR_DONE_MASK) == DMA_DSR_BCR_DONE_MASK)
  6.   {         
  7.     DMA_DSR_BCR0 |= DMA_DSR_BCR_DONE_MASK;      //Clear Done bit
  8.     DMA_DSR_BCR1 |= DMA_DSR_BCR_DONE_MASK;      //Clear Done bit
  9.      
  10.     if(++pCounter>=2)
  11.     {
  12.       pCounter=0;
  13.     }
  14.      
  15.     DMA_DAR1 = (uint32) Buffer_Address[pCounter];    //Set source address to result buffer
  16.      
  17.     DMA_DSR_BCR0 = DMA_DSR_BCR_BCR(4*BUFFER_SIZE);      //Reset BCR
  18.     DMA_DSR_BCR1 = DMA_DSR_BCR_BCR(BUFFER_SIZE);      //Reset BCR
  19.    
  20.   }  
  21. }
复制代码



(6)好了,一切准备完毕(当然还有一些具体的细节操作,包括把DMA1中断函数加载到中断向量表等,我就不多做介绍了,可以参考附件中的样例工程),编译链接整个工程,然后下载到板子并运行。结果如下图所示,可以看到每进一次中断(即BCR值减到0)切换一次Buffer地址,实现双缓存,效果还是不错的,呵呵。此外,该工程也比较灵活,通过修改BUFFER_SIZE宏来修改采样数据的存储深度,修改TPM1_MOD值来配置ADC的采样率,当然需要注意这个采样率不要超过ADC在当前输入时钟频率下不要超过其最大的转换率,至于原因,你懂的。。。

Kinetis 16-bit ADC DMA 定时器实现AutoScan自动通道扫描采样 2.png

例程:
KL26_DMA_2ChannelADC.zip (1.22 MB, 下载次数: 92)
回复

使用道具 举报

该用户从未签到

10

主题

40

帖子

0

注册会员

Rank: 2

积分
96
最后登录
2018-5-11
发表于 2015-7-11 20:25:59 | 显示全部楼层
学习一下,很不错的帖
回复 支持 反对

使用道具 举报

  • TA的每日心情
    开心
    2018-7-23 21:04
  • 签到天数: 103 天

    连续签到: 1 天

    [LV.6]常住居民II

    228

    主题

    5379

    帖子

    0

    金牌会员

    Rank: 6Rank: 6

    积分
    16715
    最后登录
    1970-1-1
    发表于 2015-9-1 17:06:11 | 显示全部楼层
    多谢,学习了
    回复 支持 反对

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /3 下一条

    Archiver|手机版|小黑屋|恩智浦技术社区

    GMT+8, 2025-7-28 12:12 , Processed in 0.096066 second(s), 26 queries , MemCache On.

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.

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