STM32F103 DMA通道分配避坑指南:SPI、I2C、USART外设到底该用哪个通道?(附映射表与实战配置)
STM32F103 DMA通道分配避坑指南SPI、I2C、USART外设到底该用哪个通道附映射表与实战配置当你第一次在STM32F103上配置DMA时是否遇到过这样的场景代码编译通过但数据就是传不过去或者更诡异的是SPI和USART同时使用时只有一个外设能正常工作这些问题90%的根源在于DMA通道分配错误。本文将带你彻底理清DMA通道与外设的映射关系并通过三个典型实战案例手把手教你避开那些教科书上不会告诉你的坑。1. DMA通道分配的核心逻辑STM32F103的DMA控制器就像一个有7条专用车道通道的高速公路每个车道只能被特定类型的车辆外设使用。但这里有个隐藏规则同一时间一条车道只能跑一辆车。这就是为什么SPI1_RX和TIM1_CH1不能同时使用DMA1_Channel2的根本原因。1.1 通道与外设的硬连接关系查看STM32F103参考手册的DMA章节你会发现一个关键表格——DMA请求映射表。这个表决定了每个DMA通道可以被哪些外设使用DMA通道可分配的外设请求源通道1ADC1, TIM2_CH3, TIM4_CH1通道2SPI1_RX, TIM1_CH1, TIM2_UP通道3SPI1_TX, TIM1_CH2, TIM3_CH3通道4SPI2_RX, TIM1_CH4, TIM4_CH2通道5SPI2_TX, TIM1_UP, TIM2_CH1通道6I2C1_RX, TIM3_CH1, TIM3_TRIG通道7I2C1_TX, TIM3_CH4关键发现SPI1的发送和接收被分配到了不同的通道通道3和通道2这意味着它们可以同时工作。但I2C1的发送和接收却共用通道6和7需要特别注意时序控制。1.2 通道冲突的典型表现当DMA通道配置冲突时通常会出现以下症状外设无任何反应就像没上电一样只有部分数据被传输后续数据丢失两个外设交替工作但不能同时正常运行程序跑飞或进入HardFault// 错误示例同时配置SPI1_RX和TIM1_CH1使用DMA1_Channel2 DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)SPI1-DR; DMA_Cmd(DMA1_Channel2, ENABLE); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)TIM1-CCR1; DMA_Cmd(DMA1_Channel2, ENABLE); // 这里会覆盖之前的配置2. 实战案例SPI通信的DMA配置以SPI1连接W25Q128 Flash芯片为例我们需要同时配置发送和接收DMA。这是很多开发者第一次栽跟头的地方。2.1 硬件连接检查在写代码前先确认硬件连接SPI1_SCK → PA5SPI1_MISO → PA6 (接收数据)SPI1_MOSI → PA7 (发送数据)Flash片选 → PA42.2 DMA通道选择根据映射表SPI1_RX → DMA1_Channel2SPI1_TX → DMA1_Channel3// SPI1 RX DMA配置DMA1_Channel2 DMA_DeInit(DMA1_Channel2); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)SPI1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)rxBuffer; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; // 外设为数据源 DMA_InitStructure.DMA_BufferSize BUFFER_SIZE; DMA_Init(DMA1_Channel2, DMA_InitStructure); // SPI1 TX DMA配置DMA1_Channel3 DMA_DeInit(DMA1_Channel3); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)SPI1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)txBuffer; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralDST; // 外设为数据目标 DMA_Init(DMA1_Channel3, DMA_InitStructure);2.3 常见错误排查数据错位检查DMA_PeripheralDataSize和DMA_MemoryDataSize是否与SPI数据宽度一致通常都是8位只发不收确保SPI的RX DMA和TX DMA都正确使能传输不完整在DMA传输完成中断中检查DMA_CNDTR寄存器值3. I2C传感器读取的DMA陷阱I2C的DMA使用比SPI更复杂因为I2C协议本身就有严格的时序要求。以BMP280气压传感器为例使用I2C1接口。3.1 特殊配置要点I2C1_RX → DMA1_Channel6I2C1_TX → DMA1_Channel7// I2C1 TX DMA配置发送传感器寄存器地址 DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)I2C1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)regAddr; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize 1; // 只发送1字节的寄存器地址 DMA_Init(DMA1_Channel7, DMA_InitStructure); // I2C1 RX DMA配置读取传感器数据 DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)I2C1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)sensorData; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize 6; // 读取6字节数据 DMA_Init(DMA1_Channel6, DMA_InitStructure);3.2 必须注意的细节DMA传输长度I2C的DMA传输长度寄存器必须在I2C通信开始前设置好重复启动条件读取传感器通常需要先写寄存器地址再读数据这涉及I2C的重复启动中断协调DMA传输完成中断和I2C事件中断需要配合使用经验之谈在实际项目中我发现I2C的DMA传输不如SPI稳定特别是在时钟速度较高时。如果遇到问题可以尝试降低I2C时钟频率到100kHz以下。4. USART高速通信的DMA优化USART的DMA配置相对简单但有些细节容易忽略。我们以USART1与PC通信为例实现115200波特率的双向数据传输。4.1 完整配置流程引脚配置USART1_TX → PA9USART1_RX → PA10DMA通道选择USART1_TX → DMA1_Channel4USART1_RX → DMA1_Channel5// USART1 TX DMA配置 DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)USART1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)txBuffer; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_Mode DMA_Mode_Normal; // 普通模式 DMA_Init(DMA1_Channel4, DMA_InitStructure); // USART1 RX DMA配置 DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)USART1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)rxBuffer; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; // 循环模式持续接收 DMA_Init(DMA1_Channel5, DMA_InitStructure);4.2 性能优化技巧双缓冲技术在高速通信中使用两个缓冲区交替接收数据避免处理数据时丢失新数据空闲中断配合USART的空闲中断来判定一帧数据接收完成内存对齐确保DMA缓冲区地址按照数据宽度对齐32位传输时地址需4字节对齐// 双缓冲配置示例 __align(4) uint8_t rxBuffer1[256]; __align(4) uint8_t rxBuffer2[256]; void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_IDLE)) { USART_ReceiveData(USART1); // 清除IDLE标志 // 切换缓冲区 if(DMA_GetCurrentMemoryTarget(DMA1_Channel5)) { processData(rxBuffer1); } else { processData(rxBuffer2); } } }5. 终极避坑指南DMA配置检查清单每次配置DMA时按照以下清单逐项检查可以避免90%的常见错误通道选择确认外设使用的DMA通道是否正确检查是否有其他外设正在使用同一通道传输方向PeripheralSRC → 从外设读取数据PeripheralDST → 向外设写入数据数据宽度确保PeripheralDataSize和MemoryDataSize匹配8位DMA_PeripheralDataSize_Byte16位DMA_PeripheralDataSize_HalfWord32位DMA_PeripheralDataSize_Word地址递增存储器地址通常递增DMA_MemoryInc_Enable外设地址通常固定DMA_PeripheralInc_Disable模式选择普通模式DMA_Mode_Normal传输一次后停止循环模式DMA_Mode_Circular自动重复传输中断配置使能必要的中断TC、HT、TE在中断服务程序中清除标志位外设DMA使能别忘了调用USART_DMACmd、SPI_I2S_DMACmd等函数使能外设的DMA请求// 完整的DMA初始化模板 void DMA_InitTemplate(void) { DMA_InitTypeDef DMA_InitStructure; // 1. 使能DMA时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 2. 复位DMA通道 DMA_DeInit(DMA1_ChannelX); // 3. 配置基本参数 DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)Peripheral-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)Buffer; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; // 或PeripheralDST DMA_InitStructure.DMA_BufferSize BufferSize; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode DMA_Mode_Normal; DMA_InitStructure.DMA_Priority DMA_Priority_Medium; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_ChannelX, DMA_InitStructure); // 4. 使能中断可选 DMA_ITConfig(DMA1_ChannelX, DMA_IT_TC, ENABLE); // 5. 使能DMA通道 DMA_Cmd(DMA1_ChannelX, ENABLE); // 6. 使能外设DMA请求 Peripheral_DMACmd(Peripheral, Peripheral_DMAReq, ENABLE); }经过这几个实战案例的分析相信你已经掌握了STM32F103 DMA通道分配的精髓。记住当DMA不工作时第一件事就是检查通道分配是否正确。这比调试代码逻辑要高效得多。