离上次更新从零入手系列又是一个月了,哎,时间过的好快的来,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_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)~