1.DMA结构框架
2.DMA名称的详细解析
通道:数据的高速通道 ,每个通道可以链接指定的外设,(DMA2)可以将通道链接到内存。
流:理解为货车,DMA的使用单位就是流,使能dma就是以流作为基本单位(让那个货车可以发车)判断优先级,就是判断流的优先级。(可以使用多个流,但是一个流只能绑定一个通道)
FIFO:突发模式的前提条件,将数据寄存起来,当满足突发的数据量后发送。
突发模式:批量的装卸货物,简单理解就是一次性给目标地址发送几个字节的数据,比如一次给目标发送4个字节的数据,如果不使用突发则就是一次发送一个字节。
将它分化成三个部分
- 第一部分: MSIZE(内存数据宽度):相当于“包裹的大小”。
- 字节= 小包(8位)
- 半字= 中包(16位)
- 字= 大包(32位)
- 第二部分:FIFO 容量:STM32 的 DMA FIFO 总容量固定是4 个字(即 16 个字节)。
- 第三部分:MBURST(内存突发):相当于“卸货方式”。
- INCR4= 一口气卸 4 次货。
- INCR8= 一口气卸 8 次货。
- INCR16= 一口气卸 16 次货。
- 节拍:其实就是字节,四节拍就是四个字节
举例:
1.将MSIZE设置成字节,FIFO级别设置成1/4, MBURST设置成4个节拍突发1次。
分析:单位是字节,级别是1/4 16/4=4字节,MBURST 突发传输四个字节且只突发一次
总结:一次性传输4个字节到目标源。
2.将MSIZE设置成字节,FIFO级别设置成1/2, MBURST设置成4个节拍突发2次。
分析:单位字节,级别1/216/2=8, MBURST突发传输四个字节且突发两次(发送8字节)
总结:分2次突发,发送8个字节到目标源。
虽然从宏观上看,这 8 个字节是一起发出去的,但在总线时序上,它们是两个独立的 INCR4 事务
3.编程实战
//在usart中开启空闲中断
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
void dma_initconfig(void)
{
DMA_InitTypeDef dma_inittypedef;
// 1. 必须先开启 DMA2 的时钟!(STM32F4中DMA2挂载在AHB1总线上)
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
DMA_DeInit(DMA2_Stream1);while(DMA_GetCmdStatus(DMA2_Stream1) == DISABLE)
{
//等待dma流初始化完成。
}
//选定流通道
dma_inittypedef.DMA_Channel =DMA_Channel_2;
//外设到内存 传输放向
dma_inittypedef.DMA_DIR = DMA_DIR_PeripheralToMemory;
//DMA缓冲区500
dma_inittypedef.DMA_BufferSize = 500;
//单次传输
dma_inittypedef.DMA_Mode =DMA_Mode_Normal;
//DMAL流的优先级 当总线冲突时,高优先级的流会先传输
dma_inittypedef.DMA_Priority = DMA_Priority_Low ;
//外部内存通道地址 内存目标地址
dma_inittypedef.DMA_PeripheralBaseAddr =(uint32_t)&(USART1->DR);
//外设传输单位大小
dma_inittypedef.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
//外设地址不递增
dma_inittypedef.DMA_PeripheralInc =DMA_PeripheralInc_Disable;
//外设端突发传输 一次传输几个传输单元 配合FIFO寄存器
dma_inittypedef.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
//内存源地址
dma_inittypedef.DMA_Memory0BaseAddr = (uint32_t)buf;
//内存传输单位大小
dma_inittypedef.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
//内存地址递增
dma_inittypedef.DMA_MemoryInc = DMA_MemoryInc_Enable;
//内存端突发传输 一次传输几个传输单元 可以提高传输量 配合FIFO寄存器
dma_inittypedef.DMA_MemoryBurst = DMA_MemoryBurst_Single;
//FIFO配置 不开启
dma_inittypedef.DMA_FIFOMode = DMA_FIFOMode_Disable;
//dma_inittypedef.DMA_FIFOThreshold =
DMA_Init(DMA2_Stream1, &dma_inittypedef);
//开启DMR 开启流1
DMA_Cmd(DMA2_Stream1, ENABLE);
}extern uint8_t buf[500];
void USART1_IRQHandler(void) {
// 判断是否是串口1触发了空闲中断(即一帧数据接收完毕)
if (USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)
{
// 【第一步】必须先读取 SR 和 DR 寄存器来清除 IDLE 标志位
// 标准库中调用一次 USART_ReceiveData 即可自动完成这个硬件清除流程USART_ReceiveData(USART1);
// 【第二步】暂停当前的 DMA 传输流,防止后续数据继续覆盖 buf 数组DMA_Cmd(DMA2_Stream1, DISABLE);
// 【第三步】计算这一帧到底接收了多少个字节的数据
// 原理:DMA 初始配置的总大小(500) - 当前剩余还没搬运的数量 = 实际已接收数量uint16_t data_len = 500 - DMA_GetCurrDataCounter(DMA2_Stream1);
// 【第四步】在这里处理你刚刚接收到的不定长数据
// 例如:解析协议、打印日志、转发数据等
process_received_data(buf, data_len);
// 【第五步】重新设置 DMA 下一次要搬运的总量(恢复为500)
DMA_SetCurrDataCounter(DMA2_Stream1, 500);
// 【第六步】再次使能 DMA 流,准备接收下一帧数据
DMA_Cmd(DMA2_Stream1, ENABLE); }
}