jicheng0622

【原创】从零入手Kinetis系统开发(十)之eDMA模块

0
阅读(13074) 评论(27)

    离上次更新从零入手系列又是一个月了,哎,时间过的好快的来,30个一睁眼一闭眼的日子就这么过去了,呵呵。不要怪俺更新的慢,的确是写这个比较累人,另外为保证这个系列的质量,还是要靠时间磨一磨的,当然磨的好还是坏,群众的眼光是雪亮雪亮的,哈哈~

    (本段是牢骚,嘿嘿,不想看的直接略过,asm(jump “正题”))本来是想着昨天就写出来的,结果15号貌似是个好日子的来,一个是飞思卡尔的寻龙活动,一个是小米手机青春版发布,自己就等着这两个较期待的事。果不其然,期望越大失望越大,哎...先说说第一个活动吧,感觉那哪是寻龙啊,一打开网页几乎占满半个窗口的火龙就赤裸裸的跳出来了,哪还用的着去“寻”,直接捉就是的嘛,木有难度,让俺本来颗期待寻宝找刺激的心拔凉拔凉的了,哎,估计向来无缘奖品的我是没戏了。另一个更让人窝火,苦苦等到10点,结果最后出来还是买不起,说是给俺们学生准备的,结果价格有点高不说还人人可以抢,哎,又让小米赚了个噱头了,俺们穷学生上哪说理去...呼呼,又是一顿牢骚,嘿嘿,作为学生还是专心搞自己的技术吧,下面进入正题,asm( call "正题" )。。。

正题:

    欢迎跳转到此,嘿嘿,是不是有点汇编编程的赶脚,有木有!

    言归正传,上次说过要写篇DMA的介绍来,结果还是耽误了,这次就补上了,另外这里预告一下,下篇的从零入手打算写一下FlexBus的软件部分(前面说了说它的硬件设计),如果有用到的话敬请期待,不会用到或者早就已经熟用了就不用等了,当然期间还会穿插一些其他的工程技巧的,不要错过哦,亲...(咳咳,借用下淘宝体)下面就正式介绍下Kinetis的DMA:

1.先介绍下DMA。DMA(Direct Memory Access)是一种直接存储器访问技术,工作过程中不需要CPU干预,也不需要像中断处理方式那样需要保留现场、恢复现场之类的麻烦事,简单理解为一条直接连通外设与RAM的硬件通道(这句话需要仔细琢磨,下面文章里会重点提一下),所以DMA技术可以提高系统运行效率(即CPU可以干其他的事去,算是一种简单的并行模式吧)。而对Kinetis来说,我们会看到eDMA,其实就是多了个e(enhanced,即增强型),嘿嘿,至于为什么会多了个e,从下面对Kinetis的eDMA特性的分析就可以看出来;

2.Kinetis的eDMA包括两部分,即DMA Muliplexer(DMA多路转换器,就是个矩阵开关,路由DMA触发源的)和DMA Controller(DMA控制器,这个是重点,用来配置DMA控制引擎(Engine)和DMA传输控制描述区(TCD)),其内部框图如下所示:

DMAMUX框图

DMA Controller框图

3.下面介绍下Kinetis的eDMA的一些特性,有点多,就挑重点和特色的来说了:

(1)16个独立可配置的DMA通道,其中前四个通道可配置成周期性触发(需要用到PIT模块),如上图DMAMUX框图所示;

(2)52个外设触发slots(这个我担心翻译不好误人子弟了就直接用该单词替代了,呵呵,用过Qt的人都这是个槽的概念,大家权当触发源来理解吧),10个直通slots,每一个slot可以通过软件编程路由到16个DMA通道中的任意一个(这个通过配置DMAMUX_CONFIGn得到),这方面比STM32的M3好些,STM32是固定的,所以Kinetis灵活性好些(咳咳,该言论不代表贬低STM32,毕竟它还是很强大的);

(3)独立可编程的源地址、目标地址和传输宽度(8bit,16bit,32bit,另外支持16byte的缓存),支持外设到RAM,RAM到外设,RAM到RAM之间的传输;

(4)每一个通道都有一个11个寄存器的TCD(Tranfer control descripter),注意这11个寄存器(包括16位和32位宽度的寄存器)才是我们编写驱动的重点对象;

(5)固定的优先级模式和时间轮询(round-robin)优先级模式(注意:如果不通过软件设置优先级的话,系统默认为每个通道的优先级等于它的通道号,即0通道的优先级为0,且优先级号越小,其优先级越低);

(6)每个通道包括了三个中断标志,即DMA半传输完成标志、DMA传输完成标志和DMA传输出错标志,3个标志逻辑或成一个中断请求(所以如果都使能了,那可以通过查询相关标志寄存器来判断当前的中断类型);

(7)可软件中断取消DMA传输(通过配置DMA_CR_CX位)。

4.DMA工作流程,这里先给出清华的一个功能框图,我觉着还是挺清晰的,便于理解,然后接下来的三幅图分别代表了一个DMA完整传输的流程,这里一句两句没法说清楚就只能上图了,不懂的可以下面留言,呵呵:

整体框图

part1

part2

part3

5.在具体软件编程之前,先说下次循环(minor loop)和主循环(major loop)这两个概念,用图来表达我觉着好些,上图,呼呼:

6.软件编程,前面做了那么多的准备,就是为了这一步能轻松的编写Kinetis的DMA驱动,我通过一个实例说下DMA的软件编写方法(嘿嘿,其实大家都喜欢看例子),该例子实现的功能是实现放在RAM区的一块数据(自己定义一个数组)通过DMA方式传输到UART,然后通过超级终端接收实际数据,挺好玩的吧,下面就实现它:

(1)首先为使UART支持DMA中断请求,需要在UART的初始化函数里加入下面三行代码(就不另介绍UART的初始化函数了,这里用的就是我曾经上传的开源开发框架代码里的UART函数):

 

    /* 以下为使能UART的DMA功能 */  
    UART_C2_REG(uartch) |= UART_C2_TIE_MASK;      /* 使能UART发送中断或者DMA请求 */
    UART_C2_REG(uartch) &= ~UART_C2_TCIE_MASK;    /* 禁止发送中断,只使能DMA请求*/
    UART_C5_REG(uartch) |= UART_C5_TDMAS_MASK;    /* 打开UART发送的DMA请求 */
 
(2)这一步完成之后就需要自己编写DMA的配置函数了,废话不多说,直接上代码了,由于程序里我已经加上了足够详细的著述了所以就不多做解释了,呵呵:
/*******************************************************************************
**File Name: DMA.c
**Description: The Driver of DMA module of Kinetis
**Editor: jicheng at Shandong University
**Date: 2012.05.02
**History: V1.0
**Notes: 无
*******************************************************************************/
#include "Kinetis_Config.h"
#include "DMA.h"
/********************************************************************************
**Routine: myDMA_Config
**Description: DMA配置函数
               DMA_CHn——指定的DMA通道号,范围0~15;
               DMAMUX_Source——DMA触发源,在DMA.h文件里有枚举定义
               S_Addr——DMA传送源地址
               D_Addr——DMA传送目的地址
               Block_Size——一次DMA传输的数据块大小(/byte)
**Notes: 默认情况下,固定优先级模式,每个有通道分配的优先级等于该通道的通道号,
        所以通道号小的优先级低。(本例程默认优先级配置)
********************************************************************************/
void myDMA_Config(uint8 DMA_CHn, uint8 DMAMUX_Source, uint32 S_Addr, uint32 D_Addr, uint16 Block_Size)
{  
  /* the corresponding SIM clock gate to be enabled befor the DMAMUX module 
     register being initialized */
  SIM_SCGC6 |= SIM_SCGC6_DMAMUX_MASK;
  /* Config DMAMUX for channel n */
  DMAMUX_CHCFG_REG(DMAMUX_BASE_PTR, DMA_CHn) = (0
                                                | DMAMUX_CHCFG_ENBL_MASK              /* 使能DMA通道 */
                                                //| DMAMUX_CHCFG_TRIG_MASK              /* 打开周期性触发模式,注意只有0~3通道支持 */
                                                | DMAMUX_CHCFG_SOURCE(DMAMUX_Source)  /* 指定DMA触发源 */
                                               );
    
  /* enable the DMA clock gate in SIM */ 
  SIM_SCGC7 |= SIM_SCGC7_DMA_MASK;                            /* DMA时钟门控上电默认是打开的,所以这步可加可不加 */
  DMA_CR = 0;                                                 /* 默认配置,需要在DMA被激活之前配置此寄存器 */
//DMA_DCHPRIn                                                 /* 默认优先级配置,这里不另更改 */
  DMA_BASE_PTR->TCD[DMA_CHn].SADDR = S_Addr;                  /* 分配DMA源地址 */
  DMA_BASE_PTR->TCD[DMA_CHn].DADDR = D_Addr;                  /* 分配DMA目标地址 */
  DMA_BASE_PTR->TCD[DMA_CHn].NBYTES_MLNO = 1;                 /* 每次minor loop传送1个字节 */
  DMA_BASE_PTR->TCD[DMA_CHn].ATTR  =(0 
                                     |DMA_ATTR_SMOD(0)        /* Source modulo feature disabled */
                                     | DMA_ATTR_SSIZE(0)      /* Source size, 8位传送 */
                                     | DMA_ATTR_DMOD(0)       /* Destination modulo feature disabled */
                                     | DMA_ATTR_DSIZE(0)      /* Destination size, 8位传送 */
                                    );
  DMA_BASE_PTR->TCD[DMA_CHn].SOFF  = 0x0001;                  /* 每次操作完源地址,源地址增加1 */
  DMA_BASE_PTR->TCD[DMA_CHn].DOFF  = 0x0000;                  /* 每次操作完目标地址,目标地址不增加  */
  DMA_BASE_PTR->TCD[DMA_CHn].SLAST = 0x00;                    /* DMA完成一次输出之后即major_loop衰减完之后不更改源地址 */
  DMA_BASE_PTR->TCD[DMA_CHn].DLAST_SGA = 0x00;                /* DMA完成一次输出之后即major_loop衰减完之后不更改目标地址 */
  DMA_BASE_PTR->TCD[DMA_CHn].CITER_ELINKNO = DMA_CITER_ELINKNO_CITER(Block_Size); /* 1个major loop, 即一次传输量=major_loop*minor_loop,最大为2^15=32767 */
  DMA_BASE_PTR->TCD[DMA_CHn].BITER_ELINKNO = DMA_CITER_ELINKNO_CITER(Block_Size); /* BITER应该等于CITER */
  
  DMA_BASE_PTR->TCD[DMA_CHn].CSR = 0;                         /* 先清零CSR,之后再设置 */
  DMA_INT &=~(1<<DMA_CHn);                                    /* 关闭DMA相应通道的传输完成中断,与TCD_CSR_INTMAJOR或者TCD_CSR_INTHALF搭配 */
  DMA_BASE_PTR->TCD[DMA_CHn].CSR &= ~DMA_CSR_INTMAJOR_MASK;   /* 关闭DMA major_loop完成中断 */
  DMA_BASE_PTR->TCD[DMA_CHn].CSR |= DMA_CSR_DREQ_MASK;        /* major_loop递减为0时自动关闭DMA,即只进行一次DMA传输 */
  
  /* DMA_ERQ寄存器很重要,置位相应的位即开启DMA工作 */
  DMA_ERQ &= ~(1 << DMA_CHn);                                 /* 关闭相应通道的DMA请求,在配置阶段先关闭,再调用myDMA_Start函数开启DMA */
}
/********************************************************************************
**Routine: myDMA_Start
**Description: 开启DMA请求,使能DMA工作
**Notes: 无
********************************************************************************/
void myDMA_Start(uint8 DMA_CHn)
{
  DMA_ERQ |= (1 << DMA_CHn);                                  /* 开启相应通道的DMA */
}
/********************************************************************************
**Routine: myDMA_Close
**Description: 关闭相应通道的DMA请求,停止DMA工作
**Notes: 
********************************************************************************/
void myDMA_Close(uint8 DMA_CHn)
{
  DMA_ERQ &= ~(1 << DMA_CHn);                                 /* 停止相应通道的DMA */
}
 
(3)这里需要强调的是,触发源,下图为部分触发源安排,这些我在头文件里进行了枚举定义:
 
(4)一切准备就绪,下面看看再application里的实际调用吧,嘿嘿,如下:
uint8 DataToSend[]={   /* 此处数组定义必须定义在RAM里,定义成const放在flash里是不行的,容易跟CPU抢flash总线 */
                      "The DMA test data is transfering...\r\n"
                      "\r\n"
                      "Congratulations! The transport is completed successfully!\r\n"
                      "\r\n"  
                      "                           jicheng at shandong university\r\n"
                      "                                               2012.5.16"
                     };
void main(void)
{
  //---------------insert your code in the following
  /* 初始化外设UART */
  UART_Init(UART3,(Kinetis_CORE_CLK/Kinetis_DIV_BusClk)*1000,9600);
  /* 初始化配置DMA */
  myDMA_Config(1,DMA_UART3_T,(uint32)DataToSend,0x4006D007,sizeof(DataToSend)); 
  /* 启动DMA传送 */
  myDMA_Start(1); 
 
  EnableInterrupts;
  
  while(1)
  {
 
  }
  
}
(5)嘿嘿,下面见效果图,呼呼:
 
    咳咳,喝口水,这次是真写多了,没停下来,大脑都有点停滞了,不过总算是完成了,哈哈。这里只是给了个简单的例子,DMA的应用灰常灰常的广泛,就看大家怎么去发挥了,反正模板现在有了,之后的就天高任鸟飞海阔凭鱼跃了,嘿嘿,修行在个人嘛...希望自己这点拙见可以给大家些许帮助,enjoy it~
 
    呵呵,这篇可真算是自己呕心沥血之作了,希望大家转载或应用注明出处和作者信息,对俺的劳动给点支持,本来想听从人的意见把代码和文章做成PDF格式的,省得被人copy了去连个转载都不加,不过后来想想还是算了,开源需要勇气,原创需要毅力,大家给点支持就好,哈哈哈~
 
    附件为完整的DMA驱动文件(.c和.h)~
  1. 这个平台是KEIL吗?在IAR平台上不行欸,

    这个语句 DMA_BASE_PTR->TCD[DMA_CHn].SADDR =(uint32)SADDR; 在IAR平台上编译提示:

    type name is not allowed


  2. 你好,附件找不到

  3. 看不到附件在哪 麻烦楼主再发一下~

  4. ***此内容已被管理员屏蔽***

  5. @usernamez

    关于第一个问题,弄成两个环个人感觉更灵活些,main和minor两个环互相搭配也可以搞成一个loop了,呵呵;

    第二个问题,需要控制dma传输给uart缓冲器的速度,但是UART发送就不用管了,它的速度只跟波特率有关。

  6. 你好,看了你的问题,我又两个疑问:

    1.硬件上,dma传输为什么要分为main loop 和minor loop,弄成一个loop也可以吧?

    2.如果用dma将内存的数据传给uart,接收端的接收速度会不会很快?

  7. K60的板上如何用SPI开启DMA模式?

  8. 来学习了,谢谢楼主!!

  9. 回复:回复
    回复:回复
    回复:回复

    程序我调通了,PIT暂时还不能周期触发DMA,目前算是个bug吧,勘误手册上有写,后来改为FTM了,害我花了两天时间

    我晕啊,呵呵,还真没试来,谢谢分享你的经验,另外勘误手册在哪,我也去瞅瞅吧~

    官方上搜kinetis_4n30d.pdf第e4588,你看下。 

    好的,谢谢~

  10. 回复:回复
    回复:回复

    程序我调通了,PIT暂时还不能周期触发DMA,目前算是个bug吧,勘误手册上有写,后来改为FTM了,害我花了两天时间

    我晕啊,呵呵,还真没试来,谢谢分享你的经验,另外勘误手册在哪,我也去瞅瞅吧~

    官方上搜kinetis_4n30d.pdf第e4588,你看下。 

  11. 回复:回复

    程序我调通了,PIT暂时还不能周期触发DMA,目前算是个bug吧,勘误手册上有写,后来改为FTM了,害我花了两天时间

    我晕啊,呵呵,还真没试来,谢谢分享你的经验,另外勘误手册在哪,我也去瞅瞅吧~

  12. 程序我调通了,PIT暂时还不能周期触发DMA,目前算是个bug吧,勘误手册上有写,后来改为FTM了,害我花了两天时间

  13. 回复:回复
    回复:回复

    你好,dma的周期定时触发该怎么用,现在想把u8 temp[100]的数组用pit1周期的触发发送到DAC0,该怎么配置,能把不同的地方告一下吗?。现在pit和da初始化都完成了,就dma没配置出来。

    DMAMUX_CHCFG_REG(DMAMUX_BASE_PTR, DMA_CHn) = (0

    | DMAMUX_CHCFG_ENBL_MASK /* 使能DMA通道 */
    | DMAMUX_CHCFG_TRIG_MASK /* 打开周期性触发模式,注意只有0~3通道支持 */
    | DMAMUX_CHCFG_SOURCE(54) /* 指定DMA触发源 */
    );
    最关键的一句我也进行了如上修改。

    你修改的没问题,你用PIT1的话只能使能通道1,这个你确定吗?还有数组不要用const定义

    我用的就是pit1,但还是有些问题。

    对这句话不明白DMA_BASE_PTR->TCD[DMA_CHn].CITER_ELINKNO = DMA_CITER_ELINKNO_CITER(Block_Size); /* 1个major loop, 即一次传输量=major_loop*minor_loop,最大为2^15=32767 */

    看数据手册BITER是minor_loop,CITER是major_loop如果按注释的意思,你每次传输量就是 Block_Size*Block_Size了,会很大的啊。

    对这三个寄存器的意思还是不太理解NBYTES,BITER,CITER,lz能说一下它们之间的关系吗?

  14. 回复:回复

    你好,dma的周期定时触发该怎么用,现在想把u8 temp[100]的数组用pit1周期的触发发送到DAC0,该怎么配置,能把不同的地方告一下吗?。现在pit和da初始化都完成了,就dma没配置出来。

    DMAMUX_CHCFG_REG(DMAMUX_BASE_PTR, DMA_CHn) = (0

    | DMAMUX_CHCFG_ENBL_MASK /* 使能DMA通道 */
    | DMAMUX_CHCFG_TRIG_MASK /* 打开周期性触发模式,注意只有0~3通道支持 */
    | DMAMUX_CHCFG_SOURCE(54) /* 指定DMA触发源 */
    );
    最关键的一句我也进行了如上修改。

    你修改的没问题,你用PIT1的话只能使能通道1,这个你确定吗?还有数组不要用const定义

  15. 你好,dma的周期定时触发该怎么用,现在想把u8 temp[100]的数组用pit1周期的触发发送到DAC0,该怎么配置,能把不同的地方告一下吗?。现在pit和da初始化都完成了,就dma没配置出来。

    DMAMUX_CHCFG_REG(DMAMUX_BASE_PTR, DMA_CHn) = (0

    | DMAMUX_CHCFG_ENBL_MASK /* 使能DMA通道 */
    | DMAMUX_CHCFG_TRIG_MASK /* 打开周期性触发模式,注意只有0~3通道支持 */
    | DMAMUX_CHCFG_SOURCE(54) /* 指定DMA触发源 */
    );
    最关键的一句我也进行了如上修改。