# 基于 RT-Thread 的 SPI 设备驱动框架下 DMA 传输超时故障定位手册 ## 1. 问题背景与现象 在使用 RT-Thread 的 SPI 设备驱动框架时,开发者常通过 `rt_spi_transfer_message()` 或 `rt_spi_send_then_recv()` 等接口发起 DMA 传输。当传输数据量较大(如 > 16 字节)时,DMA 模式被自动启用,但有时程序会卡死在等待传输完成的信号量上,最终触发超时错误(如 `-RT_ETIMEOUT`)。典型现象包括: - 传输函数返回超时,但 SPI 总线上有部分数据波形。 - 偶发性超时,尤其在系统负载高或中断优先级配置不当的情况下。 - 使用逻辑分析仪发现 DMA 传输未完成,但 SPI 外设已空闲。 ## 2. 框架与 DMA 传输机制 RT-Thread 的 SPI 驱动框架将底层操作抽象为 `struct rt_spi_ops`,其中 `xfer` 函数负责实际传输。当配置了 DMA 时,驱动通常会在 `xfer` 中启动 DMA 传输,并挂起当前线程等待一个完成信号量。DMA 传输完成中断(或 SPI 传输完成中断)会释放该信号量。 关键点: - 传输超时通常意味着信号量未在预期时间内被释放。 - 信号量释放依赖于中断服务程序(ISR)正确执行。 - 中断优先级、DMA 配置、SPI 参数均可能影响传输完成。 ## 3. 故障根因分类与排查步骤 ### 3.1 硬件层面 - **DMA 通道冲突**:确保 DMA 通道未被其他外设占用,且中断能正常触发。检查 DMA 中断是否在 NVIC 中使能。 - **SPI 引脚复用**:错误的 GPIO 复用可能导致 SPI 时钟或数据线异常,DMA 无法完成传输。 - **电气连接**:片选信号不稳定或从设备无响应,可能导致 SPI 等待从设备时钟拉伸(如果支持),但 DMA 仍会完成,需注意。 ### 3.2 软件配置层面 - **DMA 配置错误**:数据宽度、方向、缓冲区地址对齐等。例如,SPI DMA 要求缓冲区地址按 4 字节对齐(部分 MCU)。 - **SPI 参数不匹配**:时钟极性、相位、速率设置错误,导致从设备无响应,但 DMA 仍会完成传输,超时可能源于等待从设备 ACK(如 EEPROM 的写周期)。 - **RT-Thread 设备配置**:检查 SPI 设备是否已正确注册,DMA 通道是否在 `board.h` 或驱动中初始化。 ### 3.3 中断与优先级问题 - **中断优先级过低**:如果 DMA 完成中断优先级低于当前线程或其它高频中断,可能导致 ISR 延迟,信号量迟迟未释放。 - **中断丢失**:某些 MCU 的 DMA 中断标志需要软件清除,若未清除,后续传输无法触发中断。 - **信号量超时时间设置**:`rt_spi_transfer_message` 中的超时参数可能过短,尤其在系统繁忙时。 ## 4. 系统化定位步骤 ### 步骤 1:确认 DMA 是否真正启动 在 `xfer` 函数中打印或断点检查 DMA 控制寄存器,确认 DMA 已使能且缓冲区地址正确。 ```c // 示例:在 stm32_spi.c 的 xfer 函数中增加调试 rt_kprintf("DMA TX addr: 0x%08X, count: %d\n", (unsigned int)send_buf, count); ``` ### 步骤 2:检查中断标志与信号量 在 DMA 完成中断服务函数中打印标志,确认中断是否触发。 ```c void DMA1_Channel3_IRQHandler(void) { if (DMA_GetITStatus(DMA1_IT_TC3)) { DMA_ClearITPendingBit(DMA1_IT_TC3); rt_sem_release(&spi_dma_sem); rt_kprintf("DMA TC\n"); // 调试输出 } } ``` ### 步骤 3:验证信号量释放路径 如果中断触发但信号量未释放,检查信号量是否被其他线程消耗,或初始化是否正确。 ### 步骤 4:调整超时与优先级 - 将 DMA 中断优先级设为最高(如 0),避免延迟。 - 增加超时时间,如从 100ms 改为 1s,观察是否仍超时。 ## 5. 完整代码示例(以 STM32 为例) 以下是一个基于 RT-Thread 的 SPI DMA 传输配置示例,包含初始化与错误处理。 ```c #include #include #include "spi_flash.h" // 假设为 SPI 从设备 #define SPI_DEV_NAME "spi1" #define DMA_SEM_TIMEOUT rt_tick_from_millisecond(1000) static struct rt_semaphore spi_dma_sem; // DMA 完成中断回调(由驱动调用) static void spi_dma_complete(void *param) { rt_sem_release(&spi_dma_sem); } // SPI 设备初始化 static int spi_dma_init(void) { rt_err_t res; rt_device_t spi_dev; struct rt_spi_device *spi_device; // 查找 SPI 设备 spi_dev = rt_device_find(SPI_DEV_NAME); if (!spi_dev) { rt_kprintf("SPI device %s not found\n", SPI_DEV_NAME); return -RT_ERROR; } // 配置 SPI 参数 struct rt_spi_configuration cfg; cfg.data_width = 8; cfg.mode = RT_SPI_MODE_0 | RT_SPI_MSB | RT_SPI_MASTER; cfg.max_hz = 1000000; // 1MHz rt_spi_configure((struct rt_spi_device *)spi_dev, &cfg); // 初始化信号量(二值信号量) rt_sem_init(&spi_dma_sem, "spi_dma", 0, RT_IPC_FLAG_FIFO); // 注册 DMA 完成回调(假设驱动支持) // 实际驱动中,可能通过 rt_spi_register_callback 或直接修改驱动 // 此处仅为示例,需根据具体驱动调整 // spi_device->parent.user_data = &spi_dma_sem; return RT_EOK; } INIT_APP_EXPORT(spi_dma_init); // DMA 传输函数 static rt_err_t spi_dma_transfer(rt_device_t dev, rt_uint8_t *tx_buf, rt_uint8_t *rx_buf, rt_size_t len) { rt_err_t res; struct rt_spi_message msg; msg.send_buf = tx_buf; msg.recv_buf = rx_buf; msg.length = len; msg.cs_take = 1; msg.cs_release = 1; // 发送前复位信号量 rt_sem_control(&spi_dma_sem, RT_IPC_CMD_RESET, 0); // 调用 SPI 传输接口(内部会启动 DMA) res = rt_spi_transfer_message((struct rt_spi_device *)dev, &msg); if (res != RT_EOK) { rt_kprintf("SPI transfer error: %d\n", res); return res; } // 等待 DMA 完成信号量 res = rt_sem_take(&spi_dma_sem, DMA_SEM_TIMEOUT); if (res != RT_EOK) { rt_kprintf("SPI DMA timeout!\n"); return -RT_ETIMEOUT; } return RT_EOK; } // 测试函数 void spi_dma_test(void) { rt_uint8_t tx_buf[32] = {0xAA, 0x55, 0x01, 0x02}; rt_uint8_t rx_buf[32] = {0}; rt_err_t res; res = spi_dma_transfer(rt_device_find(SPI_DEV_NAME), tx_buf, rx_buf, 32); if (res == RT_EOK) { rt_kprintf("DMA transfer OK\n"); } else { rt_kprintf("DMA transfer failed\n"); } } MSH_CMD_EXPORT(spi_dma_test, spi dma test); ``` ## 6. 注意事项与最佳实践 - **缓冲区对齐**:DMA 要求缓冲区地址按 4 字节对齐,否则可能触发硬件错误。使用 `rt_align(4)` 或 `__attribute__((aligned(4)))`。 - **超时时间设置**:根据传输长度和 SPI 速率估算传输时间,设置合理超时,避免过长导致系统卡死。 - **中断优先级**:DMA 完成中断优先级应高于任何可能长时间阻塞的中断,但低于系统节拍中断(如果可能)。 - **调试建议**:使用逻辑分析仪观察 SPI 波形,确认 DMA 是否实际传输;在 ISR 中打印标志,避免优化器移除。 - **驱动兼容性**:不同 BSP 的 SPI 驱动实现可能不同,需阅读源码确认 DMA 完成回调的注册方式。 ## 7. 总结 SPI DMA 超时问题往往源于中断配置、DMA 参数或信号量管理。通过系统化排查,从硬件到软件逐步验证,可快速定位根因。本文提供的方法和示例代码适用于大多数 RT-Thread 平台,希望帮助开发者减少调试时间,提升开发效率。