DMA简介
DMA 全称Direct Memory Access,即直接存储器访问。 DMA传输将数据从一个地址空间复制到另一个地址空间。当CPU初始化这个传输动作,传输动作本身是由DMA控制器来实现和完成的。 DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。
作用:为CPU减负。
DMA原理
STM32F4最多有2个DMA控制器,2个DMA控制器总共有16个数据流(每个控制器8个)。每个DMA控制器都用于管理一个或者多个外设的存储器访问请求。每个数据流总共可以有多达8个通道(或请求),每个通道都有一个仲裁器,用于处理DMA请求间的优先级。
DMA框图
DMA配置参数
- 通道
- 优先级
- 数据传输方向
- 存储器/外设 数据宽度
- 存储器/外设 地址是否增量
- 循环模式
- 数据传输量
程序源码
dma.h
#ifndef __DMA_H
#define __DMA_H
#include "sys.h"
void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr);//配置DMAx_CHx
void MYDMA_Enable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr); //使能一次DMA传输
#endif
dma.c
#include "dma.h"
#include "delay.h"
// DMAx的各通道配置
// 这里的传输形式是固定的,这点要根据不同的情况来修改
// 从存储器->外设模式/8位数据宽度/存储器增量模式
// DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
// chx:DMA通道选择,@ref DMA_channel DMA_Channel_0~DMA_Channel_7
// par:外设地址
// mar:存储器地址
// ndtr:数据传输量
void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx, u32 chx, u32 par, u32 mar, u16 ndtr)
{
DMA_InitTypeDef DMA_InitStructure;
if ((u32)DMA_Streamx > (u32)DMA2) // 得到当前stream是属于DMA2还是DMA1
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); // DMA2时钟使能
}
else
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); // DMA1时钟使能
}
DMA_DeInit(DMA_Streamx);
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE)
{
} // 等待DMA可配置
/* 配置 DMA Stream */
DMA_InitStructure.DMA_Channel = chx; // 通道选择
DMA_InitStructure.DMA_PeripheralBaseAddr = par; // DMA外设地址
DMA_InitStructure.DMA_Memory0BaseAddr = mar; // DMA 存储器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; // 存储器到外设模式
DMA_InitStructure.DMA_BufferSize = ndtr; // 数据传输量
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设非增量模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 存储器增量模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 外设数据长度:8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 存储器数据长度:8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 使用普通模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; // 中等优先级
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; // 存储器突发单次传输
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; // 外设突发单次传输
DMA_Init(DMA_Streamx, &DMA_InitStructure); // 初始化DMA Stream
}
// 开启一次DMA传输
// DMA_Streamx:DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
// ndtr:数据传输量
void MYDMA_Enable(DMA_Stream_TypeDef *DMA_Streamx, u16 ndtr)
{
DMA_Cmd(DMA_Streamx, DISABLE); // 关闭DMA传输
while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE)
{
} // 确保DMA可以被设置
DMA_SetCurrDataCounter(DMA_Streamx, ndtr); // 数据传输量
DMA_Cmd(DMA_Streamx, ENABLE); // 开启DMA传输
}
main.c
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "lcd.h"
#include "key.h"
#include "dma.h"
#define SEND_BUF_SIZE 8200 // 发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍.
u8 SendBuff[SEND_BUF_SIZE]; // 发送数据缓冲区
const u8 TEXT_TO_SEND[] = {"ALIENTEK Explorer STM32F4 DMA 串口实验"};
int main(void)
{
u16 i;
u8 t = 0;
u8 j, mask = 0;
float pro = 0; // 进度
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置系统中断优先级分组2
delay_init(168); // 初始化延时函数
uart_init(115200); // 初始化串口波特率为115200
LED_Init(); // 初始化LED
LCD_Init(); // LCD初始化
KEY_Init(); // 按键初始化
// 配置DMA,使用DMA2的第7个数据流和第4个通道,将USART1的数据寄存器作为外设,SendBuff作为存储器,传输长度为SEND_BUF_SIZE
MYDMA_Config(DMA2_Stream7, DMA_Channel_4, (u32)&USART1->DR, (u32)SendBuff, SEND_BUF_SIZE); // DMA2,STEAM7,CH4,外设为串口1,存储器为SendBuff,长度为:SEND_BUF_SIZE.
POINT_COLOR = RED;
LCD_ShowString(30, 50, 200, 16, 16, "Explorer STM32F4");
LCD_ShowString(30, 70, 200, 16, 16, "DMA TEST");
LCD_ShowString(30, 90, 200, 16, 16, "ATOM@ALIENTEK");
LCD_ShowString(30, 110, 200, 16, 16, "2014/5/6");
LCD_ShowString(30, 130, 200, 16, 16, "KEY0:Start");
POINT_COLOR = BLUE; // 设置字体为蓝色
// 显示提示信息
j = sizeof(TEXT_TO_SEND); // 获取TEXT_TO_SEND字符串的长度,并赋值给变量j
for (i = 0; i < SEND_BUF_SIZE; i++) // 循环从0到SEND_BUF_SIZE-1,填充SendBuff数组
{
if (t >= j) // 加入换行符
{
if (mask)
{
SendBuff[i] = 0x0a; // 插入换行符
t = 0;
}
else
{
SendBuff[i] = 0x0d; // 插入回车符
mask++;
}
}
else // 复制TEXT_TO_SEND语句
{
mask = 0;
SendBuff[i] = TEXT_TO_SEND[t];
t++;
}
}
POINT_COLOR = BLUE; // 设置字体为蓝色
i = 0;
while (1)
{
t = KEY_Scan(0); // 清除按键标记
if (t == KEY0_PRES) // KEY0按下
{
printf("\r\nDMA DATA:\r\n");
LCD_ShowString(30, 150, 200, 16, 16, "Start Transimit....");
LCD_ShowString(30, 170, 200, 16, 16, " %"); // 显示百分号
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); // 使能串口1的DMA发送
MYDMA_Enable(DMA2_Stream7, SEND_BUF_SIZE); // 启动一次DMA传输
// 等待DMA传输完成,此时我们来做另外一些事,点灯
// 实际应用中,传输数据期间,可以执行另外的任务
while (1)
{
// 通过DMA_GetCurrDataCounter函数获取当前还剩余多少个数据未传输,然后计算传输的百分比
if (DMA_GetFlagStatus(DMA2_Stream7, DMA_FLAG_TCIF7) != RESET) // 等待DMA2_Steam7传输完成
{
DMA_ClearFlag(DMA2_Stream7, DMA_FLAG_TCIF7); // 清除DMA2_Steam7传输完成标志
break;
}
pro = DMA_GetCurrDataCounter(DMA2_Stream7); // 得到当前还剩余多少个数据
pro = 1 - pro / SEND_BUF_SIZE; // 得到百分比
pro *= 100; // 扩大100倍
LCD_ShowNum(30, 170, pro, 3, 16);
}
LCD_ShowNum(30, 170, 100, 3, 16); // 显示100%
LCD_ShowString(30, 150, 200, 16, 16, "Transimit Finished!"); // 提示传送完成
}
i++;
delay_ms(10);
if (i == 20)
{
LED0 = !LED0; // 提示系统正在运行
i = 0;
}
}
}
实验效果
说明:
串口首先打印TFT屏的驱动IC(按下RESET键复位)
按下KEY0后,TFT屏显示“Transimit Finished!”并显示传输进度,串口重复打印“ALIENTEK Explorer STM32F4 DMA 串口实验”直至达到SEND_BUF_SIZE的值
DMA实验效果