11. DMA传输
11.1 什么是DMA
DMA(Direct Memory Access)控制器提供了一种硬件的方式在外设和存储器之间或者存储器和存储器之间传输数据,而无需CPU的介入,避免了CPU多次进入中断进行大规模的数据拷贝,最终提高整体的系统性能。
DMA是一种能够在无需CPU参与的情况下,将数据块在内存和外设之间高效传输的硬件机制。实现这种功能的集成电路单元叫做DMA Controller,即DMA控制器。
DMA控制器在没有CPU参与的情况下从一个地址向另一个地址传输数据,它支持多种数据宽度,突发类型,地址生成算法,优先级和传输模式,可以灵活的配置以满足应用的需求。
11.2 数据传输方式
一般情况下实现存储器和外设之间的数据传输,有三种常用的方法:轮询法(polling),中断法(interrupt)以及DMA。
轮询法(polling): 在主循环中,CPU不断检查外设的相关标志位,来判断其是否需要进行数据的传输,如果需要,则CPU将数据在外设和内存之间搬运,实现数据传输。当数据传输服务请求频繁或者传输的数据量很大时,会影响其他任务的实时性。
中断法(interrupt): 当外设需要传输数据时,会触发中断,CPU会暂停正在处理的任务,转而去执行中断服务函数,接着处理外设的数据传输任务。CPU无需反复检查外设的标志位,中断机制会指示CPU何时去处理外设数据,但是依然需要CPU去完成数据搬运和传输过程。当外设数据传输服务不频繁且数据量不大时,中断法也是不错的选择。当中断连续不断且频繁发生时,中断法变得不再高效,因为在恢复主流程的执行和中断相应的上下文切换会占用大量的CPU时间。
DMA: DMA控制器是单片机中的硬件单元,它在存储器和外设之间有专用的通道,允许外设和存储器之间高效传输数据,且传输过程无需CPU参与。
综上可见DMA是一种高效的数据传输方式。
11.3 DMA介绍
MSPM0L1306的DMA控制器具有以下特点:
- 7个独立的传输通道;
- 可以配置的DMA通道优先级;
- 支持8位(byte),16位(short word)、32位(word)和64位(long word)或者混合大小(byte 和 word)传输;
- 支持最大可达64K任意数据类型的数据块传输;
- 可配置的DMA传输触发源;
- 6种灵活的寻址模式;
- 单次或者块传输模式; 它共有7个DMA通道,各个通道可以独立配置,多种多样的数据传输模式可以适应不同应用场景的数据传输需要。
通过查看TI的数据手册,DMA功能除了常见的内存与外设间的地址寻址方式,还提供了Fill Mode和Table Mode两种拓展模式,DMA通道分为基本类型和全功能类型两种。
通过查看数据手册,只有全功能类型的DMA通道支持重复传输、提前中断以及拓展模式,基本功能的DMA通道支持基本的数据传输和中断,但是足够满足简单的数据传输要求。
11.4 DMA传输方式
MSPM0L1306的DMA支持四种传输模式,每个通道可单独配置其传输模式。例如,通道 0 可配置为单字或单字节传输模式,而通道 1 配置为块传输模式,通道 2 采用重复块传输模式。 传输模式独立于寻址模式进行配置。 任何寻址模式都可以与任何传输模式一同使用。可以传输三种类型的数据,可以通过 .srcWidth 和 .destWidth 控制位进行选择。源位置和目标位置可以是字节、短字或字数据。也支持以字节到字节、短字到短字、字到字或任何组合的方式进行传输。如下:
- 单字或单字节传输:每次传输都需要一个单独的触发。当 DMAxSZ 传输已经生成时 DMA 会被自动关闭。
- 块传输:一个整块将会在一个触发后传输。在块传输结束时 DMA 会被自动关闭。
- 重复单字或单字节传输:每次传输都需要一个单独的触发,DMA保持启用状态。
- 重复块传输:一个整块将会在一个触发后传输,DMA保持启用状态。
基于这三种模式,MSPM0L1306提供了6种寻址方式,如下:
11.5 DMA的配置
本案例将使用ADC采集3.3V和GND的电压,通过DMA将ADC采集的数据直接搬运到指定地址中作为实验案例。
11.5.1 开启SYSCONFIG配置工具
这里使用上一个章节的ADC例程作为模板进行介绍。打开ADC工程,在Keil的主界面打开empty.syscfg文件,在empty.syscfg文件打开的情况下,再打开SYSCONFIG的GUI界面。
11.5.2 ADC参数的配置
这里ADC的配置与上一章节相同。
配置ADC的分辨率和中断。本案例不使用中断。
11.5.3 DMA参数的配置
将以上配置保存,然后到Keil中编译更新。
11.6 DMA搬运ADC数据实验
在empty.c(要main函数的文件)中编写如下代码:
#include "ti_msp_dl_config.h"
#include "stdio.h"
volatile unsigned int delay_times = 0;
volatile bool gCheckADC; //ADC采集成功标志位
volatile uint16_t ADC_VALUE[10];
void delay_ms(unsigned int ms); //搭配滴答定时器的精准毫秒级延时
unsigned int adc_getValue(unsigned int number);//读取ADC的数据
/******************************串口重定向***************************************/
#if !defined(__MICROLIB)
//不使用微库的话就需要添加下面的函数
#if (__ARMCLIB_VERSION <= 6000000)
//如果编译器是AC5 就定义下面这个结构体
struct __FILE
{
int handle;
};
#endif
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
#endif
//printf函数重定义
int fputc(int ch, FILE *stream)
{
//当串口0忙的时候等待,不忙的时候再发送传进来的字符
while( DL_UART_isBusy(UART_0_INST) == true );
DL_UART_Main_transmitData(UART_0_INST, ch);
return ch;
}
/*********************************************************************/
int main(void)
{
unsigned int adc_value = 0;
unsigned int voltage_value = 0;
int i = 0;
SYSCFG_DL_init();
//清除串口中断标志
NVIC_ClearPendingIRQ(UART_0_INST_INT_IRQN);
//开启串口中断
NVIC_EnableIRQ(UART_0_INST_INT_IRQN);
//设置DMA搬运的起始地址
DL_DMA_setSrcAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t) &ADC0->ULLMEM.MEMRES[0]);
//设置DMA搬运的目的地址
DL_DMA_setDestAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t) &ADC_VALUE[0]);
//开启DMA
DL_DMA_enableChannel(DMA, DMA_CH0_CHAN_ID);
//开启ADC转换
DL_ADC12_startConversion(ADC_VOLTAGE_INST);
printf("adc_dma Demo start\r\n");
while (1)
{
//获取ADC数据
adc_value = adc_getValue(10);
printf("adc value:%d\r\n", adc_value);
//将ADC采集的数据换算为电压
voltage_value = (int)((adc_value/4095.0*3.3)*100);
printf("voltage value:%d.%d%d\r\n",
voltage_value/100,
voltage_value/10%10,
voltage_value%10 );
delay_ms(1000);
}
}
//延时函数
void delay_ms(unsigned int ms)
{
delay_times = ms;
while( delay_times != 0 );
}
//读取ADC的数据
unsigned int adc_getValue(unsigned int number)
{
unsigned int gAdcResult = 0;
unsigned char i = 0;
//采集多次累加
for( i = 0; i < number; i++ )
{
gAdcResult += ADC_VALUE[i];
}
//均值滤波
gAdcResult /= number;
return gAdcResult;
}
//滴答定时器的中断服务函数
void SysTick_Handler(void)
{
if( delay_times != 0 )
{
delay_times--;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
实验结果:向PA27引脚输出2.5V的电压,ADC将数据采集装载到ADC结果寄存器0后,DMA被触发,DMA将会把数据搬运到内存变量ADC_VALUE中,通过直接调用ADC_VALUE即可得到数据。