1.1 STM32 DMA 简介
DMA控制器支持绕过CPU直接传输数据。STM32一般有DMA1、DMA2两个控制器。
DMA支持各种外设、闪存、SRAM作为数据源和数据目标,具体可查表。 同时仅一个有效。
DMA(直接存储器访问)允许数据在地址空间间高效复制,由DMA控制器执行,无需CPU干预。STM32可配备2个DMA控制器(DMA1有7通道,DMA2有5通道,DMA2仅在大容量产品中存在),各通道管理外设对存储器的访问请求,并通过仲裁器协调优先级,提高CPU效率。
STM32的DMA特性包括:
- 专用硬件请求与软件触发配置灵活。
- 四级优先权设置,同优先权时硬件决定顺序。
- 支持字节、半字、全字传输宽度,需对齐源和目标地址。
- 循环缓冲管理功能。
- 三事件标志(半传输、完成、出错)可组合为中断请求。
- 支持存储器间、外设与存储器间传输。
- 闪存、SRAM、APB1/2、AHB外设均可作为源和目标。
- 可编程传输数目,最大65536。
STM32F103ZET6 有两个 DMA 控制器,DMA1 和 DMA2。
外设(如TIMx、ADC、SPIx、I2Cx、USARTx)产生的DMA请求经逻辑或处理后输入DMA控制器,确保同时仅一个有效,通过仲裁器和DMA_CCRx的优先级配置裁决哪个先执行。

通道1的多个DMA1请求(如ADC1、TIM2_CH3、TIM4_CH1)通过逻辑或运算合并,确保同一时间仅一个请求能被响应,其他通道同理。
例如我们想使用 USART1,查表可知需用到DMA1的通道4和通道5。
中断状态寄存器(DMA_ISR)
由于DMA1有7个通道,每个通道有传输错误标志、半传输标志、传输完成标志、全局中断标志,因此DMA_ISR作为32位寄存器使用了4*7=28位。
半传输指数据已经传输缓冲区的一半。
全局中断指传输错误、半传输、传输完成任意一个满足都触发。
最常用的是传输完成标志。如果开启了这些中断,达到条件后就会跳到中断服务函数。
可通过查询中断状态寄存器DMA_ISR来获得当前DMA的状态。

中断标志清除寄存器(DMA_IFCR)
DMA_ISR是只读寄存器,可以用来获得DMA的传输错误、半传输、传输完成、全局中断标志。要清除 DMA_ISR的标志位需要使用中断标志清除寄存器DMA_IFCR。
传输错误、半传输、传输完成、全局中断标志位,仍然是32位寄存器使用了4*7=28。

通道x配置寄存器(DMA_CCRx)
通道x配置寄存器DMA_CCRx用来配置
- 模式(存储器到存储器模式,寄存器到存储器模式)、
- 通道优先级(低、中、高、最高),
- 存储器数据宽度(8位、16位、32位、保留),
- 外设数据宽度(8位、16位、32位、保留),
- 存储器地址增量模式(0,1), 传输完成后地址自增,用于连续传输
- 外设地址增量模式(0,1),
- 循环模式(0,1),
- 数据传输方向(从外设读,从存储器读),
- 允许错误传输中断(0,1),
- 允许半传输中断(0,1),
- 允许传输完成中断(0,1),
- 通道开启(0,1)

通道x数据量寄存器(DMA_CNDTRx)
通道x传输数据量寄存器DMA_CNDTRx,用来控制DMA通道x每次要传输的数据量,范围为0~65535,字节。该寄存器的值会随着传输的进行而减少,数据量寄存器的值为0表示传输完成。所以可通过传输量寄存器的值得知当前传输进度。Channel x Number of Data to Transfer Register

通道x外设地址寄存器(DMA_CPARx)
通道x外设地址寄存器(DMA_CPARx),用来存储外设的地址,比如使用串口1,那么该寄存器必须写入 0x40013804(其实就是&USART1_DR)。如果使用其他外设,修改成相应地址即可。
DMA Channel x Peripheral Address Register

通道x存储器地址寄存器(DMA_CMARx),用来放存储器的地址,比如使用 SendBuf[5200]数组来做存储器,那么我们在DMA_CMAR寄存器写入&SendBuf就可以了。
1.2 串口1发送配置步骤
1)使能DMA时钟:
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
2)初始化DMA通道4参数:
创建并填充DMA_InitTypeDef结构体,
设置外设基地址(如&USART1->DR)、
内存基地址、
传输方向(如DMA_DIR_PeripheralDST)、
传输数据量、
地址递增模式、
数据宽度、
传输模式(如DMA_Mode_Normal)、
优先级、
是否存储器到存储器传输等。
调用
DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)
函数进行初始化。
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; // 外设地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)cmar; // 内存地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 内存到外设
DMA_InitStructure.DMA_BufferSize = 64; // 传输数据量
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_M2M = DMA_M2M_Disable; // 非内存到内存
DMA_Init(DMA1_Channel4, &DMA_InitStructure); // 初始化DMA1通道4
3)使能串口DMA发送
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); // 使能USART1的DMA发送
4)启动DMA传输
DMA_Cmd(DMA1_Channel4, ENABLE); // 启动DMA1通道4
5)查询DMA传输状态
// 查询DMA1通道4是否传输完成
FlagStatus status = DMA_GetFlagStatus(DMA1_FLAG_TC4);
// 获取DMA1通道4剩余数据量
uint16_t remainingData = DMA_GetCurrDataCounter(DMA1_Channel4);