STM32F407ZG开发板学习(11)
直接存储器访问 DMA
简介
直接存储器访问(Direct Memory Access,DMA)。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路, 能使 CPU 的效率大为提高。
STM32F4最多有 2 个 DMA 控制器( DMA1 和 DMA2), 共 16 个数据流(每个控制器 8 个), 每
一个 DMA 控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8 个通道(或称请求)。每个数据流通道都有一个仲裁器,用于处理 DMA 请求间的优先级。
原理及框图
因为采用AHB主总线,DMA 可以控制 AHB 总线矩阵来启动 AHB 事务。它可以执行下列事务:
- 外设到存储器的传输
- 存储器到外设的传输
- 存储器到存储器的传输
这里特别注意一下,存储器到存储器需要外设接口可以访问存储器,而仅DMA2的外设接口可以访问存储器,所以仅DMA2控制器支持存储器到存储器的传输,DMA1不支持。
如图所示,经过通道选择和仲裁器,我们最多可以有8×8=64种组合。其中通道选择由下图所示。
两个DMA的各个通道映射不一致,实验中我使用了USART3的发送,它在DMA1中,如下图:
寄存器
中断状态寄存器 LISR HISR
分别管理8个通道的中断标志,两个寄存器是只读的。
中断标志清零寄存器 LIFCR HIFCR
由于ISR是只读的,需要采用其他方法清零,因此这里使用另两个中断标志清零寄存器,来对ISR的各位进行清除。
数据流 x 配置寄存器 DMA_SxCR (x = 0…7)
DMA_ SxCR 是 DMA 传输的核心控制寄存器。该寄存器控制着 DMA 的很多相关信息,包括数据宽度、外设及存储器的宽度、优先级、增量模式、传输方向、中断允许、使能等。
详情见官方文档。
数据 x 数据项数寄存器
这个寄存器控制 DMA 数据流 x 的每次传输所要传输的数据量。其设置范围为 0~65535 。并且该寄存器的值会随着传输的进行而减少,当该寄存器的值为 0 的时候就代表此次数据传输已经全部发送完成了。所以可以通过这个寄存器的值来知道当前 DMA 传输的进度。 特别注意,这里是数据项数目,而不是指的字节数。比如设置数据位宽为 16 位,那么传输一次(一个项)就是 2 个字节。
数据流 x 外设地址寄存器 DMA_SxPAR (x = 0…7)
该寄存器用来存储 STM32F4 外设的地址。
数据流 x 存储器地址寄存器
包括 数据流 x 存储器 0 地址寄存器 DMA_SxM0AR 和 数据流 x 存储器 1 地址寄存器 DMA_SxM1AR ,后者仅在双缓冲模式下有效。
数据流 x FIFO 控制寄存器 SxFCR (x = 0…7)
实验
DMA1 配置步骤
- 使能 DMA1 时钟。另外,要对配置寄存器( DMA_SxCR )进行设置,必须先等待其最低位为 0 (也就是 DMA 传输禁止了),才可以进行配置。
- 创建 DMA_InitTypeDef 类型的结构体初始化 DMA1 数据流 3 (USART3_TX)。
- 使能串口 USART3 的 DMA 发送。USA RT_DMACmd(USART3, USART_DMAReq_Tx, ENABLE);
- 使能 DMA1 的 数据流3 。即 DMA_Cmd(DMA1_Stream3, ENABLE);
- 在DMA 传输过程中,查询 DMA 传输通道的状态,使用的函数有:
是否传输完成:FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
当前剩余数据量:uint16_t DMA_GetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx);
设置数据流传输的数据量:void DMA_SetCurrDataCounter(DMA_Stream_TypeDef* DMAy_Streamx, uint16_t Counter);
代码
dma.c
/*
函数名:配置DMA
参数:DMAy_Streamx 选择数据流 DMA1_Stream0~7/DMA2_Stream0~7
channelx 通道选择 DMA_Channel_0~7
par 外设地址
mar 存储器地址
ndtr 数据传输量
返回值:无
*/
void mydma_config(DMA_Stream_TypeDef *DMAy_Streamx, uint32_t channelx, uint32_t par, uint32_t mar, uint16_t ndtr)
{
DMA_InitTypeDef DMA_InitStructure;
//根据参数使能DMA1/DMA2时钟
if((uint32_t)DMAy_Streamx > (uint32_t)DMA2)
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
}
else {
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
}
DMA_DeInit(DMAy_Streamx);
//等待到可以配置
while(DMA_GetCmdStatus(DMAy_Streamx) != DISABLE);
DMA_InitStructure.DMA_BufferSize = ndtr; //数据量
DMA_InitStructure.DMA_Channel = channelx; //DMA通道选择
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //存储器到外设模式
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //FIFO禁止
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; //FIFO阈值水平
DMA_InitStructure.DMA_Memory0BaseAddr = mar; //存储器0地址基址
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //存储器单词传输
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //存储器数据长度8位
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器增量模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //普通模式
DMA_InitStructure.DMA_PeripheralBaseAddr = par; //外设地址
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //外设单次传输
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设数据长度8位
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设非增量模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级
DMA_Init(DMAy_Streamx, &DMA_InitStructure);
}
void mydma_enable(DMA_Stream_TypeDef *DMAy_Streamx, uint16_t ndtr)
{
DMA_Cmd(DMAy_Streamx, DISABLE);
while(DMA_GetCmdStatus(DMAy_Streamx) != DISABLE);
DMA_SetCurrDataCounter(DMAy_Streamx, ndtr);
DMA_Cmd(DMAy_Streamx, ENABLE);
}
main.c
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "dma.h"
#define SEND_BUF_SIZE 24000
uint8_t send_buf[SEND_BUF_SIZE];
const uint8_t send_str[] = "八个细胞 DMA 串口实验";
int main(void)
{
uint8_t t = 0, mask = 0;
uint8_t len;
uint16_t i;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置系统中断优先级分组2
delay_init(168); //延时初始化
usart_init(); //串口初始化波特率为115200
mydma_config(DMA1_Stream3, DMA_Channel_4, (uint32_t)&USART3->DR, (uint32_t)send_buf, SEND_BUF_SIZE);
KEY_Init();
LED_Init(); //初始化与LED连接的硬件接口
len = sizeof(send_str);
for(i=0; i < SEND_BUF_SIZE; i++) //填充 ASCII 字符集数据
{
if(t >= len) //加入换行符
{
if(mask)
{
send_buf[i] = 0x0a;
t=0;
}
else
{
send_buf[i] = 0x0d;
mask++;
}
}
else
{
mask = 0;
send_buf[i] = send_str[t];
t++;
}
}
i = 0;
mask = 0;
while(1)
{
t = KEY_Scan(0);
if(t == KEY0_PRES)
{
LED0 = 0;
USART_DMACmd(USART3, USART_DMAReq_Tx, ENABLE); //使能串口 3 的 DMA 发送
mydma_enable( DMA1_Stream3, SEND_BUF_SIZE); //开始一次 DMA 传输!
while(1)
{
if(DMA_GetFlagStatus(DMA1_Stream3, DMA_FLAG_TCIF3) != RESET)
{
DMA_ClearFlag(DMA1_Stream3, DMA_FLAG_TCIF3);
LED1 = 1;
break;
}
mask++;
delay_ms(10);
if(mask == 20)
{
LED1 = !LED1;
mask = 0;
}
}
}
i++;
delay_ms(10);
if(i == 20)
{
LED0 = !LED0;
i = 0;
}
}
}
实验结果
当按下一次按钮0时,使能一次 DMA 发送到串口,发送期间 LED0 常亮,LED1 闪烁,非发送时间 LED0 闪烁,LED1 熄灭。
串口助手接收如下: