STM32F407ZG开发板学习(11)
- 直接存储器访问 DMA
- 简介
- 原理及框图
- 寄存器
- 中断状态寄存器 LISR HISR
- 中断标志清零寄存器 LIFCR HIFCR
- 数据流 x 配置寄存器 DMA_SxCR (x = 0..7)
- 数据 x 数据项数寄存器
- 数据流 x 外设地址寄存器 DMA_SxPAR (x = 0..7)
- 数据流 x 存储器地址寄存器
- 数据流 x FIFO 控制寄存器 SxFCR (x = 0..7)
- 实验
- DMA1 配置步骤
- 代码
- 实验结果
直接存储器访问 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~7channelx 通道选择 DMA_Channel_0~7par 外设地址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); //设置系统中断优先级分组2delay_init(168); //延时初始化usart_init(); //串口初始化波特率为115200mydma_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 熄灭。
串口助手接收如下: