mc4win

集合C语言,Kinetis,STM32,PPC

【原创】Kinetis妙用eDMA之UART

2
阅读(2205) 评论(4)

【原创】Kinetis妙用eDMA之UART

平台:IAR Kinetis_K60

简述

  UART是一个慢速设备,但有时候却会传输大量数据,但如果传输数据时候均采用字符中断的方式,会造成CPU极大的浪费,这里以115200bps速率进行一项计算:得出每隔78us就有一次CPU中断出现,如果CPU此时执行其他任务得不到响应该字符中断,就会造成该字符丢失。

  假如波特率再高一点以256000bps速率或者更高的话,CPU中断间隔会更小,也会造成更大的问题,但好在UART很多都有几个字节的FIFO,能够缓解这种情况,但是如果系统资源设计很紧凑的时候,留不出UART给板子使用,像我们这里使用的是lpuart。这个UART连个FIFO也没有了。还想保证数据收发的准确性就比较困难了。

  这里我们的需求就是:既想要保证数据的传输速度,还想要CPU中断次数大大降低。

  基于这个需求,这里提出了一种使用eDMA充当lpuart缓存,一定数据量再产生中断提醒CPU进行取出数据

分析

  K60中包含有lpuart模块和eDMA模块,其中eDMA是飞思卡尔第二代DMA模块,功能更为强大,并且中断源支持也很多,包含了常用的源,且传输描述表(TCD)更是具有32个通道,其中最主要的具有两个中断,一个满中断,一个半满中断。基于这些特性,完全有理由完成以上需求。

  LPUART不仅具有字符中断,还有一个idle line(空闲中断) 中断,正是利用ILIE中断可以实现我们的需求。

实现思路

  LPUART存在一个ILIE中断(空闲中断),eDMA存在数据搬运满中断和半满中断,结合这三个中断便可以实现一定数据量/一个数据包到达的时候再通知CPU进行取出数据。这里先科普下概念,eDMA叫做增强型直接内存访问,是一种不经过CPU处理能够将数据从指定地址搬运到指定地址的一个模块。这里我们lpuart选择字符触发DMA。DMA将当前收到的数据搬运到我们设定的全局变量Buff[128]中,当该段数据传输完成或者达到DMA半满/满中断,均会有相应中断的产生,CPU将会执行相关代码,将Buff中暂存的数据进行取出操作。这样子不论是数据量大小,都能够满足需求。(语言功底有限,这里表述可能有点难懂,海涵)。

具体操作

  • 先定义一个接收数据的buff(全局变量)

    volatile unsigned long uartBuff[128] = {1};
  • 再配置好LPURAT波特率,时钟,校验位等,再打开ILIE中断,关闭其他中断(步骤较多,这里不贴代码)

  • 然后配置好eDMA模块

    SIM_SCGC6 |= SIM_SCGC6_DMAMUX_MASK;
    SIM_SCGC7 |= SIM_SCGC7_DMA_MASK ;
    
    //参考数据手册得到的规律
    DMAMUX_CHCFG6           = DMAMUX_CHCFG_ENBL_MASK|DMAMUX_CHCFG_SOURCE(58);
    DMA_TCD6_SADDR          = (uint32_t)&LPUARTx_DATA;
    DMA_TCD6_SOFF           = 0x00  ;
    DMA_TCD6_SLAST          = 0x00  ;
    DMA_TCD6_DADDR          = (uint32_t)&uartBuff[0];
    DMA_TCD6_DOFF           = 0X01  ;
    DMA_TCD6_DLASTSGA       = (uint32_t) -128;
    DMA_TCD6_NBYTES_MLNO    = 0x01  ;
    DMA_TCD6_BITER_ELINKNO  = 128   ;
    DMA_TCD6_CITER_ELINKNO  = 128   ;
    DMA_TCD6_ATTR           = DMA_ATTR_SSIZE(0)|DMA_ATTR_DSIZE(0);
    
    DMA_TCD6_CSR            = 0     ;
    DMA_CINT                = DMA_CINT_CINT(6) ; 
    
    /*打开半满中断和满中断,主要为了保证数据的完整性*/
    DMA_TCD6_CSR            = DMA_CSR_INTHALF_MASK|DMA_CSR_INTMAJOR_MASK;   
    
    DMA_ERQ                |= DMA_ERQ_ERQ6_MASK;
    
    /*根据平台不同,代码不同,这里就不贴出来了,以下打开相应的中断*/
    
    /*以上为打开相应中断函数*/
  • 开始写idle中断函数:这里主要注意以下几点:1.idle中断产生是总线空闲一段时间(具体空闲多少请查阅相关手册)会有中断。2.需要定几个标志位。(1)记录上次是否发生idle中断 (2)上次发生idle中断后读取(DMA_TCDn_DADDR)的数据并记录取到了哪里。

  • 开始写eDMA中断函数,这里要注意好几点:就是eDMA中断无论是满中断还是半满中断,没有相关标志位可以查阅,但是可以通过TCD表中的目的地址进行判断。DMA每搬运一次,目的地址(DMA_TCDn_DADDR)就会有相应的变化,通过计算buff[0]地址和DMA目的地址差值就可以判断此次中断是满中断还是半满中断,比如这里我的buff大小是128字节的。如果差值在[63-126]这个闭区间内,将认为是半满中断,如果在[127]U[0-62]这个并集的闭区间内认为是满中断,这里有一个问题,就是为什么是一个范围而不是一个确定的数值。这是因为eDMA模块是独立于CPU运行的,如果此时发生了中断,实际上到了真正中断处理函数的时候,eDMA已经又搬运了部分数据了。所以不会是一个确定的数值。

  重点仍然是记录当前DADDR的数值,并从上一个读取的值开始读取到这个值,将数据发送到上层应用(具体怎么发,看上层应用API怎么写的吧。)

提示:写的不是特别的详细,受限于部分原因,这东西是实际项目上使用,并且效果非常良好。这个东西对于新手来说是有点稍微绕,不懂的读者可以跟帖发文,对于老鸟来说这应该一看就懂了。


  1. 你提供的资料真是太有用了

  2. 学习了,长见识了

  3. 写的很好,很有借鉴意义!

  4. 思路不错!