DMA基础
直接存储访问(Direct Memory Access,DMA),允许不同速度的硬件装置之间的通信,不依赖于CPU。下面两张图形象地描述了DMA方式与非DMA方式的区别:
DMAC具有独立的控制三大总线(数据总线、地址总线、控制总线)来访问存储器和I/O端口的能力,它能像CPU一样提供数据传送所需的地址信息和读写控制信息。DMAC和CPU都挂在系统总线上,当进入DMA方式时,DMAC成为总线主控。在总线上,可以控制其他部件的部件成为总线主控或主控(bus master),被控制部件称为从控(slave)。任意时刻,总线上只有一个主控。DMA操作之前,应先对DMAC编程,把要传送的数据块长度、数据块在存储器中的起始地址,数据传送方向等信息发送给DMAC。
CC2530的DMA
CC2530中的DMA控制器协调所有的DMA传送,确保DMA请求和CPU存储器访问之间按照优先级协调、合理地进行。DMA控制器含有若干可编程的DMA通道,用来实现存储器-存储器的数据传送。DMA控制器控制整个XDATA存储空间的数据传送,由于大多数SFR寄存器(特殊寄存器)映射到DMA存储器空间,这些灵活的DMA通道的操作能够以创新的方式减轻CPU的负担,而在CC2530的数据手册中2.2.3物理存储器这节描述了哪个SFR寄存器映射到XDATA存储空间。
DMA控制器的主要功能如下:
- 5个独立的DMA通道
- 3个可以配置的DMA通道优先级
- 32个可以配置的传送触发事件
- 源地址和目标地址的独立控制
- 单独传送、数据块传送和重复传送模式
- 支持传输数据的长度域,设置可变传输长度
- 即可以工作在子模式,又可以工作在字节模式
DMA操作
DMA控制器有5个通道,即DMA通道0到通道4。每个DMA通道都能够从DMA存储器空间的一个位置传送数据到另一个位置,比如XDATA位置之间。为了使用DMA通道,必须先DMA配置参数和DMA配置安装,DMA状态图如下:
当DMA通道配置完毕后,在允许任何传输发起之前,必须进入工作状态。
DMA通道通过将DMA通道工作状态寄存器DMAARM中指定位置1,就可以进入工作状态。
一旦DMA通道进入工作状态,当配置的DMA触发事件发生时,传送就开始了。注意一个通道准备工作(即获得配置数据)的时间需要9个系统时钟,因此如果相应的DMAARM位设置,触发在需要配置通道的时间内出现,期望的触发将丢失。如果多于一个DMA通道同时进入工作状态,所有通道配置的时间将长一些(按顺序读取内存)。如果所有5个通道都进入工作状态,需要45个系统时钟,通道1首先准备好,然后是通道2,最后是通道0(所有都在最后8个系统时钟内)。可能的DMA触发事件有32个,如下表所示,例如UART传输、定时器溢出等。DMA通道要使用的触发事件有DMA通道配置设置,因此直到配置被读取之后,才能知道。使用这个触发器源必须符合端口中断使能位PICTL.P0IENL/H和P1IEN,注意所有中断使能的端口引脚都将产生一个触发。
为了通过DMA触发事件开始DMA传送,用户软件可以设置对应的DMAREQ位,强制使一个DMA传送开始。
应该注意如果之前配置的触发器源在DMA正在配置的时候产生了触发事件,就被当着错过的事件,一旦DMA通道准备好,传送就立即开始。即使新的触发和之前的触发不同也是这样。在一些情况下,这会导致传输错误,为了纠正这一点,触发源0必须是重新配置之间的触发源。这通过设置虚拟源和目标地址、使用一个字节的固定长度、块传输和触发源0实现。使能软件触发器(DMAREQ)清除错过的触发数,从存储器中取出一个新的配置之前(除非软件为该通道写DMAREQ),不产生新的触发。DMAREQ位只能在相应的DMA传输发生时清除,当通道解除准备工作状态时,DMAREQ位不被清除。
DMA配置参数
DMA运行的安装和控制由用户软件完成,本节描述了DMA通道能够使用之前,必须配置的参数。
这5个DMA通道每一个的行为通过下列参数配置:
源地址:
DMA通道要读的数据的首地址
目标地址:
DMA通道从源地址读出的要写数据的首地址。
用户必须确定该目标地址为可写
传送长度:
在DMA通过重新进入工作状态或解除工作状态之前,以及警告CPU即将有中断请求到来之前,要传送的长度。长度可以在配置中定义,或可以如下所述定义为VLEN设置。
可变长度(VLEN)设置:
DMA通道可以利用源数据中的第一个字节或字作为传送长度进行可变长度传输。使用可变长度传输时,要给出关于如何计算要传输的字节数的各种选项。
优先级:
DMA通道的DMA传送的优先级与CPU、其他DMA通道和访问端口有关
触发事件:
所有DMA传输通过所谓的DMA触发事件来发起。这个触发可以启动一个DMA块传输或单个DMA传输。除了已经配置的触发,DMA通道总是可以通过设置它的指定DMAREQ.DMAREQx标志来触发,DMA触发的源描述在上表8-1中。
源地址和目标地址增量:
源地址和目标地址可以空位增量或减少,或不改变
传送模式:
传送模式确定传送是否是单个传输、或块传输、或是它们的重复传输
中断屏蔽:
在完成DMA通道传送是,产生一个中断请求。这个中断屏蔽位控制中断产生是使能还是禁用
M8:
这个域的值,决定是否采用7位还是8位长的字节来传送数据,此模式仅仅适用于字节传送。
DMA配置安装
以上描述的DMA通道参数(诸如地址模式、传送模式和优先级等)必须在DMA通道进入工作状态之前并激活。
参数不直接通过SFR寄存器配置,而是通过写入存储器中特殊的DMA配置数据结构中配置,即CC2530中不能用设置寄存器的方式配置DMA参数,而要使用一种特殊的DMA配置结构体,DMA Configure Data Structure结构体,IAR编译环境中位域字段默认采用小端模式,配置结构体声明前使用#pragma bitfields = reversed取反向,声明结束后恢复IAR默认方式。对于使用的每个DMA通道,需要有它自己的DMA配置数据结构。DMA配置数据结构包含8个字节,对于每个DMA通道,DMA数据结构由8个字节组成。配置数据结构描述如下表所示:
/********************************************************
* IAR编译环境中位域字段默认取向采用小端模式,
* 配置结构体声明前使用#pragma bitfields=reversed取反向,
* 声明结束后恢复IAR默认方式
********************************************************/
#pragma bitfields = reversed
typedef struct {
uint8 SRCADDRH; //源地址高字节
uint8 SRCADDRL; //源地址低字节
uint8 DESTADDH; //目的地址高字节
uint8 DESTADDL; //目的地址低字节
uint8 VLEN:3; //变成传输模式
uint8 LENH:5; //传输长度高字节
uint8 LENL:8; //传输长度低字节
uint8 WORDSIZE:1; //字节传输或字传输
uint8 TMODE:2; //传输模式
uint8 TRIG:5; //触发时间选择
uint8 SRCINC:2; //源地址增量方式选择
uint8 DESTINC:2; //目的地址增量方式选择
uint8 IRQMASK:1; //中断屏蔽位
uint8 M8:1; //字节传输模式时用,8或7bit传输,仅仅适合在字节传输模式下
uint8 PRIORITY:2; //优先级选择
}DMA_DESC;
#pragma bitfields = default
DMA配置数据结构可以存放在由用户软件设定的任何位置,而地址通过一组SFR,DMAxCFGH:DMAxCFGL送到DMA控制器。一旦DMA通道进入工作状态,DMA控制器就会读取该通道的配置数据结构,由DMAxCFGH:DMAxCFGL地址给出。需要注意的是,指定DMA配置数据结构开始地址的方法十分重要,这些地址对于DMA通道0和DMA通道1~4是不同的:
DMA0CFGH:DMA0CFGL给出DMA通道0配置数据结构的开始地址
DMA1CFGH:DMA1CFGL给出DMA通道1配置数据结构的开始地址,其后跟着通道2-4的配置数据结构
因此DMA控制器希望DMA通道1-4的DMA配置数据结构存在于存储器 连续的区域内,以DMA1CFGH:DMA1CFGL所保存的地址开始,包含32个字节。
停止DMA传输
使用DMAARM寄存器来解除DMA通道工作状态,停止正在运行的DMA传送或进入工作状态的DMA。将1写入DMAARM.ABORT寄存器位,就会停止一个或多个进入工作状态的DMA通道,同时通过设置相应的DMAARM.DMAARMx为1选择停止哪个DMA通道。
当设置DMAARM.ABORT为1,非停止通道的DMAARM.DMAARMx位必须写入0。
DMA中断
每个DMA通道可以配置为一旦完成DMA传送,就产生中断到CPU。该功能由IRQMASK位在通道配置时实现。当中断产生时,SFR寄存器DMAIRQ中所对应的中断标志位置1。
一旦DMA通道完成传送,不管在通道配置中IRQMASK位是何值,中断标志都会置1.这样,当通道重新进入工作状态且IRQMASK的设置改变时,软件必须总是检测(并清除)这个寄存器。如果这样做失败,将会根据存储的中断标志产生一个中断。
DMA寄存器
有关CC2530的GPIO基本知识、普通GPIO操作有关寄存器的介绍、IAR Embedded Workbench IDE软件使用:
TI CC2530基础实验(普通GPIO操作——点亮led灯)
程序:
/******************************************
* 基础实验只需要添加以下头文件
******************************************/
#include <ioCC2530.h>
#define uint8 unsigned char //或typedef unsigned char uint;
#define uint16 unsigned int
/********************************************************
* IAR编译环境中位域字段默认取向采用小端模式,
* 配置结构体声明前使用#pragma bitfields=reversed取反向,
* 声明结束后恢复IAR默认方式
********************************************************/
#pragma bitfields=reversed
typedef struct {
uint8 SRCADDRH; //源地址高字节
uint8 SRCADDRL; //源地址低字节
uint8 DESTADDRH; //目的地址高字节
uint8 DESTADDRL; //目的地址低字节
uint8 VLEN:3; //变长传输模式
uint8 LENH:5; //传输长度高字节
uint8 LENL:8; //传输长度低字节
uint8 WORDSIZE:1; //字节传输或字传输
uint8 TMODE:2; //传输模式
uint8 TRIG:5; //触发时间选择
uint8 SRCINC:2; //源地址增量方式选择
uint8 DESTINC:2; //目的地址增量方式选择
uint8 IRQMASK:1; //中断屏蔽位
uint8 M8:1; //字节传输模式时用,8或7bit传输,仅仅适合在字节传输模式下
uint8 PRIORITY:2; //优先级选择
}DMA_DESC;
#pragma bitfields=default
#define DATA_AMOUNT 32
/*****************************************************
* CC2530数据手册中对DMA数据结构的介绍,对以下常量赋值
****************************************************/
#define DMA_VLEN_USE_LEN 0x00 // Use LEN for transfer count
#define DMA_WORDSIZE_BYTE 0x00 // Transfer a byte at a time
#define DMA_WORDSIZE_WORD 0x01 // Transfer a 16-bit word at a time
#define DMA_TMODE_SINGLE 0x00 // Transfer a single byte/word after each DMA trigger 单个传输模式
#define DMA_TMODE_BLOCK 0x01 // Transfer block of data (length len) after each DMA trigger 块传输模式
#define DMA_TMODE_SINGLE_REPEATED 0x02 // Transfer single byte/word (after len transfers, rearm DMA) 重复单个传输模式
#define DMA_TMODE_BLOCK_REPEATED 0x03 // Transfer block of data (after len transfers, rearm DMA) 重复块传输模式
#define DMA_TRIG_NONE 0 // No trigger, setting DMAREQ.DMAREQx bit starts transfer 无触发
#define DMA_SRCINC_0 0x00 // Increment source pointer by 0 bytes/words after each transfer源地址递增0字节/字
#define DMA_SRCINC_1 0x01 // Increment source pointer by 1 bytes/words after each transfer源地址递增1字节/字
#define DMA_DESTINC_1 0x01 // Increment destination pointer by 1 bytes/words after each transfer目标地址递增1字节/字
#define DMA_IRQMASK_DISABLE 0x00 // Disable interrupt generation 通道的中断屏蔽
#define DMA_IRQMASK_ENABLE 0x01 // Enable interrupt generation upon DMA channel done 通道的中断使能
#define DMA_M8_USE_8_BITS 0x00 // Use all 8 bits for transfer count 采用所有8位作为传输长度
#define DMA_M8_USE_7_BITS 0x01 // Use 7 LSB for transfer count 采用字节地7位作为传输长度
#define DMA_PRI_LOW 0x00 // Low, CPU has priority 低优先级,CPU优先
#define DMA_PRI_GUARANTEED 0x01 // Guaranteed, DMA at least every second try 保证级,DMA至少在每秒一次的尝试中优先
#define DMA_PRI_HIGH 0x02 // High, DMA has priority 高优先级,DMA优先
#define DMA_PRI_ABSOLUTE 0x03 // Highest, DMA has priority. Reserved for DMA port access.
#define DMAARM_DMAARM0 0x01
#define DMAREQ_DMAREQ0 0x01
#define DMAIRQ_DMAIF0 0x01
#define NOP() asm("NOP")
//DMA配置参数
static DMA_DESC dmaConfig0;
//此数据是用来复制到内存的其他区域
static char data[DATA_AMOUNT] = "DMA man trigger!";
//用来保存复制来的数据区域
static char copy[DATA_AMOUNT];
//数据长度
void UartInit(void)
{
PERCFG = 0x00;
P0SEL = 0x0c;
P2DIR &= ~0xc0;
U0CSR |= 0x80;
U0GCR |= 11;
U0BAUD |= 216;
UTX0IF = 0;
}
void UartSendString(char *Data, uint16 len)
{
uint16 j;
for (j = 0; j < len; j++)
{
U0DBUF = *Data++;
for (; 0 == UTX0IF;);
UTX0IF = 0;
}
}
void Delay_ms(uint16 ms)
{
uint16 i,j;
for(i = 0; i < ms; i++)
{
for(j = 0;j < 1774; j++);
}
}
void main()
{
CLKCONCMD &= ~0x40;
for(; CLKCONSTA & 0x40;);
CLKCONCMD &= ~0X47;
UartInit();
UartSendString(data,sizeof(data));
dmaConfig0.SRCADDRH = ((uint16)&data >> 8) & 0x00FF; //获取到data地址(源地址)的高8位地址
dmaConfig0.SRCADDRL = ((uint16)&data) & 0x00FF; //获取到data地址(源地址)的低8位地址
dmaConfig0.DESTADDRH = ((uint16)© >> 8) & 0x00FF; //获取到copy地址(目的地址)的高8位地址
dmaConfig0.DESTADDRL = ((uint16)©) & 0x00FF; //获取到copy地址(目的地址)的低8位地址
dmaConfig0.VLEN = DMA_VLEN_USE_LEN; //设置可变长度为0
dmaConfig0.LENH = (DATA_AMOUNT >> 8) & 0x00FF; //获取传输长度的高5位
dmaConfig0.LENL = (DATA_AMOUNT) & 0x00FF; //获取传输长度的低8位
dmaConfig0.WORDSIZE = DMA_WORDSIZE_BYTE; //设置为字节传输模式,赋值0
dmaConfig0.TMODE = DMA_TMODE_BLOCK; //块传输模式
dmaConfig0.TRIG = DMA_TRIG_NONE; //无触发模式,即无触发源
dmaConfig0.SRCINC = DMA_SRCINC_1; //源地址1个字节/字递增
dmaConfig0.DESTINC = DMA_DESTINC_1; //目的地址1个字节/字递增
dmaConfig0.IRQMASK = DMA_IRQMASK_DISABLE; //通道的中断屏蔽
dmaConfig0.M8 = DMA_M8_USE_8_BITS; //采用所有8位作为传输长度
dmaConfig0.PRIORITY = DMA_PRI_HIGH; //高优先级,DMA优先
DMA0CFGH = ((uint16)&dmaConfig0 >> 8) & 0x00FF; //DMA通道0配置地址高8位地址
DMA0CFGL = ((uint16)&dmaConfig0) & 0x00FF; //DMA通道0配置地址低8位地址
/*DMA进入工作模式通道0*/
DMAARM |= DMAARM_DMAARM0;//为了任何DMA传输能够在该通道上发生,该位必须置1,对于非重复传输模式,一旦完成传送,该位自动清0
/*一个通道准备工作(即获得配置数据)的时间需要9个系统时钟*/
NOP();NOP();NOP();NOP();NOP();NOP();NOP();NOP();NOP(); // 9 NOPs
/*DMA通道0传送请求,即触发DMA传送*/
DMAREQ |= DMAREQ_DMAREQ0;//设置为1,激活DMA通道0(与一个触发事件具有相同的效果),当DMA传输开始清除该位
/*等待DMA通道0传送完毕*/
for (; !(DMAIRQ & DMAIRQ_DMAIF0););//当DMA通道0传送完成,DMAIRQ:DMAIF0位置1,与上DMAIRQ_DMAIF0(0x01),取非后为0退出循环
/*清除中断标志*/
DMAIRQ = ~DMAIRQ_DMAIF0;
Delay_ms(5);
UartSendString("DMA transfer after copy-->",sizeof("DMA transfer after copy-->"));
Delay_ms(5);
UartSendString(copy,sizeof(copy));
for (;;);
}
在DMA通道0配置DMA参数地址时,当时代码如下:
所有,DMA通道配置DMA参数地址一定不能错。还有以上串口打印之间需要一点延时时间,否则会出错。
总结:
1、创建一个DMA数据结构体
2、初始化DMA数据结构体
3、DMA通道n配置地址,将DMA数据结构体地址赋值给DMAnCFGH和DMAnCFGL寄存器
4、配置好DMA参数后,开启DMA通道n进入工作模式,不要忘了一个通道准备工作需要9个系统时钟
5、DMA通道n请求传送,即触发传送
6、等待DMA通道n传送完毕,完成后硬件将其对应为置1
7、清除传送完毕,将对应位置0