3. DMA控制器基础:DMA传输原理、通道配置、源/目的地址与传输宽度
DMA(Direct Memory Access,直接存储器访问)控制器是嵌入式系统中负责在无需CPU干预的情况下,在外设与内存之间、或内存与内存之间高速搬运数据的硬件引擎。在UART驱动中引入DMA,可以将串口数据流从逐字节的CPU中断处理中解放出来,实现“CPU只做控制,DMA负责搬运”的协同工作模式。
3.1 DMA传输原理
DMA传输的核心思想是:CPU配置好传输参数后,DMA控制器接管总线控制权,自动完成数据搬运,传输完成后通过中断通知CPU。整个过程CPU可以执行其他任务,从而大幅提升系统吞吐量。
关键概念: DMA传输是“硬件线程”级别的数据搬运,其传输效率远高于CPU逐字节拷贝,尤其适合UART等连续数据流场景。
3.2 DMA通道配置
DMA控制器通常包含多个独立通道,每个通道可以独立配置并服务于不同的外设。以常见的ARM Cortex-M系列MCU为例,DMA通道配置涉及以下关键参数:
| 配置项 | 说明 | 典型值(UART场景) |
|---|---|---|
| 通道编号 | 选择DMA控制器中的物理通道 | Channel 1 ~ 7 |
| 请求源 | 触发DMA传输的外设请求信号 | UART_RX / UART_TX |
| 优先级 | 多个通道同时请求时的仲裁优先级 | 高/中/低 |
| 传输模式 | 单次传输/循环传输/内存到内存 | 循环模式(UART接收) |
| 数据宽度 | 每次搬运的数据位宽 | 8位(字节) |
| 突发传输 | 连续传输的数据单元数量 | 4/8/16 字节 |
配置要点: UART接收场景通常使用循环模式,这样DMA可以在缓冲区满后自动回绕到起始地址,实现环形缓冲区的硬件化。发送场景则常用单次模式,每次发送完成后重新配置。
3.3 源地址与目的地址
DMA传输的两个核心地址域:
- 源地址(Source Address):数据读取的起始位置。对于UART接收,源地址是UART外设的数据寄存器地址(固定不变);对于UART发送,源地址是内存中待发送数据的缓冲区地址(可递增)。
- 目的地址(Destination Address):数据写入的目标位置。对于UART接收,目的地址是内存缓冲区地址(递增);对于UART发送,目的地址是UART外设的数据寄存器地址(固定不变)。
地址模式分为两种:
| 地址模式 | 描述 | UART应用场景 |
|---|---|---|
| 固定地址 | 每次传输后地址不变 | 外设数据寄存器(UART_DR) |
| 递增地址 | 每次传输后地址增加(+1/+2/+4) | 内存缓冲区(数组/链表) |
3.4 传输宽度
传输宽度(Transfer Width)决定了DMA每次搬运操作的数据位宽。常见选项包括:
- 字节(8位):每次搬运1字节,适用于UART等字符设备。
- 半字(16位):每次搬运2字节,适用于16位外设或音频数据。
- 字(32位):每次搬运4字节,适用于高速外设或内存拷贝。
在UART场景中,传输宽度必须与UART数据寄存器宽度匹配。大多数UART外设的数据寄存器为8位或9位,因此通常配置为8位传输宽度。
宽度不匹配风险: 如果源地址宽度与目的地址宽度不一致,DMA控制器会自动进行数据对齐或截断。例如,从8位外设读取数据写入32位内存时,DMA会按字节填充,但可能导致内存对齐问题。务必确保源和目的的数据宽度一致,或确认DMA控制器支持宽度自动转换。
3.5 DMA传输配置代码示例
以下是一个基于STM32 HAL库的UART DMA接收配置示例,展示了通道、地址、宽度的实际配置过程:
/* UART DMA 接收配置示例(基于STM32 HAL库) */
void UART_DMA_RX_Config(void)
{
/* 1. 声明DMA配置结构体 */
DMA_HandleTypeDef hdma_usart1_rx;
/* 2. 使能DMA时钟 */
__HAL_RCC_DMA1_CLK_ENABLE();
/* 3. 配置DMA通道参数 */
hdma_usart1_rx.Instance = DMA1_Channel5; /* 选择DMA通道5 */
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; /* 外设到内存 */
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设地址固定 */
hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE; /* 内存地址递增 */
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; /* 8位宽度 */
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; /* 8位宽度 */
hdma_usart1_rx.Init.Mode = DMA_CIRCULAR; /* 循环模式 */
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_HIGH; /* 高优先级 */
/* 4. 初始化DMA */
HAL_DMA_Init(&hdma_usart1_rx);
/* 5. 将DMA通道与UART外设关联 */
__HAL_LINKDMA(&huart1, hdmarx, hdma_usart1_rx);
/* 6. 启动DMA接收(源:UART_DR,目的:rx_buffer,长度:BUFFER_SIZE) */
HAL_UART_Receive_DMA(&huart1, rx_buffer, BUFFER_SIZE);
}
配置要点解析:
•
•
•
•
•
PeriphInc = DMA_PINC_DISABLE:外设地址(UART_DR)固定不变。•
MemInc = DMA_MINC_ENABLE:内存地址(rx_buffer)每次传输后递增。•
PeriphDataAlignment = DMA_PDATAALIGN_BYTE:外设数据宽度为8位。•
Mode = DMA_CIRCULAR:循环模式,缓冲区满后自动回绕。
3.6 DMA传输与CPU中断的协同边界
理解DMA与CPU中断的职责划分是本章的核心:
| 职责 | DMA控制器 | CPU(中断处理) |
|---|---|---|
| 数据搬运 | 负责从外设到内存的逐字节拷贝 | 不参与,可执行其他任务 |
| 传输完成通知 | 产生DMA传输完成中断 | 响应中断,处理数据帧 |
| 半传输通知 | 产生DMA半传输中断(可选) | 利用双缓冲机制处理数据 |
| 错误处理 | 检测总线错误/传输错误 | 响应错误中断,恢复或重置 |
| 配置管理 | 无自主决策能力 | 初始化、启动、停止、重新配置 |