4. DMA控制器基础:DMA工作原理、传输类型与通道请求
直接存储器访问(Direct Memory Access, DMA)是嵌入式系统中实现高速数据传输的关键硬件机制。在SPI等外设驱动中,DMA能够在不占用CPU的情况下完成大量数据的搬移,是提升系统吞吐量的核心手段。本节将深入剖析DMA控制器的工作原理、三种传输类型以及通道与请求的映射关系。
4.1 DMA工作原理
DMA控制器本质上是一个专用的硬件状态机,能够在系统总线(如AHB/AXI)上扮演总线主控角色,直接控制存储器与外设之间的数据流动。其核心工作流程如下:
- CPU配置DMA:软件设置源地址、目的地址、传输宽度、传输计数、触发源等参数。
- 外设发起请求:当SPI接收FIFO非空或发送FIFO空时,向DMA控制器发送硬件请求信号。
- DMA仲裁与响应:DMA控制器根据优先级仲裁,获取总线控制权,向外设返回应答信号。
- 数据传输:DMA在源与目的之间执行单次或突发传输,每次传输后更新地址和计数。
- 传输完成:当计数归零时,DMA产生完成中断,通知CPU数据已就绪。
关键概念:DMA传输的最小单位是节拍(Beat),通常为1、2、4字节。多个节拍组成突发(Burst),突发长度由硬件设计决定,常见为4、8、16节拍。
4.2 DMA传输类型
根据数据流动的方向和参与方,DMA传输分为三种基本类型。在SPI驱动联调中,理解这三种类型是正确配置DMA通道的前提。
| 传输类型 | 源地址 | 目的地址 | 典型应用场景 | SPI联调示例 |
|---|---|---|---|---|
| 内存→内存 | 内存缓冲区A | 内存缓冲区B | 数据拷贝、内存搬运、帧缓冲处理 | SPI接收数据从临时buf搬移到应用buf |
| 内存→外设 | 内存缓冲区 | 外设数据寄存器 | 外设发送数据(如SPI发送、UART发送) | SPI发送:DMA将tx_buf数据写入SPI_DR |
| 外设→内存 | 外设数据寄存器 | 内存缓冲区 | 外设接收数据(如SPI接收、ADC采集) | SPI接收:DMA将SPI_DR数据读入rx_buf |
实战要点:在SPI驱动中,内存→外设和外设→内存是最常用的两种类型。全双工SPI传输需要同时配置两个DMA通道(一个发送、一个接收),且必须确保两个通道的同步启动。
4.3 DMA通道与请求
DMA控制器内部包含多个独立的通道(Channel),每个通道可以独立配置源地址、目的地址、传输参数和触发源。通道与硬件请求之间的映射关系是驱动配置的核心。
4.3.1 通道结构
每个DMA通道通常包含以下寄存器组:
- 源地址寄存器(SAR):存放数据源地址,支持递增/递减/固定模式。
- 目的地址寄存器(DAR):存放数据目的地址,支持递增/递减/固定模式。
- 控制寄存器(CR):配置传输宽度、突发长度、优先级、中断使能等。
- 传输计数寄存器(NDTR):记录剩余传输节拍数,每传输一次自动减1。
- 请求选择寄存器(RSR):选择该通道绑定的硬件外设请求信号。
4.3.2 请求映射机制
在典型SoC中,DMA控制器通过请求多路选择器(Request MUX)将多个外设的请求信号映射到有限的DMA通道上。映射关系通常在芯片参考手册中以表格形式给出。
| DMA通道 | 默认外设请求 | 可选外设请求 | SPI典型映射 |
|---|---|---|---|
| CH0 | SPI1_TX | UART1_RX, I2C1_TX | SPI1发送通道 |
| CH1 | SPI1_RX | UART1_TX, I2C1_RX | SPI1接收通道 |
| CH2 | SPI2_TX | UART2_RX, TIM1_UP | SPI2发送通道 |
| CH3 | SPI2_RX | UART2_TX, TIM2_UP | SPI2接收通道 |
| CH4 | ADC1 | SPI3_TX, UART3_RX | 备用SPI发送 |
| CH5 | ADC2 | SPI3_RX, UART3_TX | 备用SPI接收 |
联调陷阱:多个外设共享同一个DMA通道时,必须确保外设请求不会同时触发。在Android HAL层,如果SPI和UART同时使用同一通道,需要在驱动中实现互斥访问或使用DMA控制器支持的通道优先级抢占机制。
4.4 通道配置实战要点
在Android HAL层配置DMA通道时,需要关注以下关键参数:
SPI DMA通道配置示例(伪代码):
/* 配置SPI发送DMA通道 */
dma_channel_config_t tx_cfg = {
.channel_id = DMA_CH0, /* 选择通道0 */
.src_addr = (uint32_t)tx_buf, /* 内存发送缓冲区 */
.dst_addr = (uint32_t)&SPI1->DR, /* SPI数据寄存器地址 */
.src_inc = DMA_ADDR_INC, /* 源地址递增 */
.dst_inc = DMA_ADDR_FIXED, /* 目的地址固定(外设寄存器) */
.data_width = DMA_WIDTH_16BIT, /* SPI_DR为16位 */
.burst_len = DMA_BURST_4, /* 4节拍突发 */
.transfer_cnt = len / 2, /* 总传输节拍数 */
.request_src = DMA_REQ_SPI1_TX, /* 绑定SPI1发送请求 */
.priority = DMA_PRIO_HIGH,
.irq_enable = true /* 使能传输完成中断 */
};
dma_channel_init(&tx_cfg);
4.4.1 通道优先级与仲裁
当多个DMA通道同时请求总线时,DMA控制器根据优先级进行仲裁。常见策略包括:
- 固定优先级:通道编号越小优先级越高(CH0 > CH1 > ...)。
- 可编程优先级:每个通道可独立配置为低/中/高/最高四级。
- 轮询仲裁:所有通道轮流获得总线使用权,防止低优先级通道饿死。
4.4.2 通道状态监控
驱动开发中常用的通道状态寄存器包括:
| 状态位 | 含义 | 驱动处理方式 |
|---|---|---|
| TCIF(传输完成) | 当前通道传输计数归零 | 触发完成回调,启动下一批传输 |
| HTIF(半传输) | 传输计数达到一半 | 可用于双缓冲切换(Ping-Pong) |
| TEIF(传输错误) | 总线错误或FIFO溢出 | 停止DMA,复位通道,上报错误 |
| DMEIF(直接模式错误) | FIFO模式下数据丢失 | 检查FIFO阈值配置 |
| BUSY(通道忙) | 通道正在传输中 | 等待或返回EBUSY |
联调建议:在Android HAL层实现DMA驱动时,建议在
dma_channel_init()之后立即读取通道状态寄存器确认配置生效,并在每次传输完成后检查TEIF位。很多SPI通信异常的根本原因是DMA通道配置错误或请求映射冲突。