当前位置: 首页 > news >正文

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 → PA5
  • SPI1_MISO → PA6 (接收数据)
  • SPI1_MOSI → PA7 (发送数据)
  • Flash片选 → PA4

2.2 DMA通道选择

根据映射表:

  • SPI1_RX → DMA1_Channel2
  • SPI1_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 常见错误排查

  1. 数据错位:检查DMA_PeripheralDataSize和DMA_MemoryDataSize是否与SPI数据宽度一致(通常都是8位)
  2. 只发不收:确保SPI的RX DMA和TX DMA都正确使能
  3. 传输不完整:在DMA传输完成中断中检查DMA_CNDTR寄存器值

3. I2C传感器读取的DMA陷阱

I2C的DMA使用比SPI更复杂,因为I2C协议本身就有严格的时序要求。以BMP280气压传感器为例,使用I2C1接口。

3.1 特殊配置要点

  • I2C1_RX → DMA1_Channel6
  • I2C1_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 必须注意的细节

  1. DMA传输长度:I2C的DMA传输长度寄存器必须在I2C通信开始前设置好
  2. 重复启动条件:读取传感器通常需要先写寄存器地址,再读数据,这涉及I2C的重复启动
  3. 中断协调:DMA传输完成中断和I2C事件中断需要配合使用

经验之谈:在实际项目中,我发现I2C的DMA传输不如SPI稳定,特别是在时钟速度较高时。如果遇到问题,可以尝试降低I2C时钟频率到100kHz以下。

4. USART高速通信的DMA优化

USART的DMA配置相对简单,但有些细节容易忽略。我们以USART1与PC通信为例,实现115200波特率的双向数据传输。

4.1 完整配置流程

  1. 引脚配置

    • USART1_TX → PA9
    • USART1_RX → PA10
  2. DMA通道选择

    • USART1_TX → DMA1_Channel4
    • USART1_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 性能优化技巧

  1. 双缓冲技术:在高速通信中,使用两个缓冲区交替接收数据,避免处理数据时丢失新数据
  2. 空闲中断:配合USART的空闲中断来判定一帧数据接收完成
  3. 内存对齐:确保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%的常见错误:

  1. 通道选择

    • 确认外设使用的DMA通道是否正确
    • 检查是否有其他外设正在使用同一通道
  2. 传输方向

    • PeripheralSRC → 从外设读取数据
    • PeripheralDST → 向外设写入数据
  3. 数据宽度

    • 确保PeripheralDataSize和MemoryDataSize匹配
    • 8位:DMA_PeripheralDataSize_Byte
    • 16位:DMA_PeripheralDataSize_HalfWord
    • 32位:DMA_PeripheralDataSize_Word
  4. 地址递增

    • 存储器地址通常递增(DMA_MemoryInc_Enable)
    • 外设地址通常固定(DMA_PeripheralInc_Disable)
  5. 模式选择

    • 普通模式(DMA_Mode_Normal):传输一次后停止
    • 循环模式(DMA_Mode_Circular):自动重复传输
  6. 中断配置

    • 使能必要的中断(TC、HT、TE)
    • 在中断服务程序中清除标志位
  7. 外设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不工作时,第一件事就是检查通道分配是否正确。这比调试代码逻辑要高效得多。

http://www.jsqmd.com/news/555125/

相关文章:

  • 【工业Python网关配置黄金法则】:20年资深工程师亲授5大避坑指南与实时生效配置模板
  • 终极指南:gdown如何绕过Google Drive安全确认页实现大文件下载
  • PostgreSQL CPU飙升95%?别慌,手把手教你定位并解决那个“元凶”SQL
  • Python 3.14 JIT vs PyPy 8.3 vs GraalPython:金融风控场景下GC暂停时间对比实测(数据全部脱敏)
  • mpMath:重新定义微信生态中的LaTeX公式排版体验
  • LFM2.5-1.2B-Thinking-GGUF进行MATLAB算法思路验证与代码转换
  • 从零开始用AWS搭建三层云架构:手把手教你配置EC2+RDS+S3完整链路
  • 总结2026年全铝全屋整装定制服务,好用的品牌有哪些 - 工业品网
  • ESP32S3端口死活不识别?别急着换线,先试试这个USB驱动修复大法
  • Idle Master Extended:Steam交易卡牌自动收集工具全攻略
  • MAA_Punish:战双帕弥什自动化助手的全方位解析
  • Windows界面定制革命:用ExplorerPatcher重塑你的工作环境
  • 4步掌握网盘直链解析:面向开发者与普通用户的效率提升指南
  • 宝塔面板新手避坑指南:从服务器选购到LNMP环境一键部署全流程
  • 2026年分析值得推荐的不锈钢楼宇门供应商,怎么收费 - 工业品牌热点
  • AI-on-the-edge-device智能唤醒终极指南:基于ESP32-CAM的超低功耗物联网实现方案
  • LAMDA实战手册:视频解析与流媒体提取从入门到精通
  • 2026年朝阳区靠谱的儿童口才培训品牌推荐,天才声打造优质课程 - myqiye
  • Godot 4 Open RPG完整指南:快速构建回合制角色扮演游戏 [特殊字符]
  • 2026年全铝整屋定制推荐厂商,北京地区性价比排行 - 工业设备
  • DeepSeek-R1背后的功臣:GRPO算法如何省下一个大模型的计算成本
  • libmill实战教程:构建高性能TCP服务器的10个技巧
  • 儿童口才培训服务口碑哪家好,天才声表现如何 - 工业推荐榜
  • Vouch Proxy贡献者指南:如何参与开源SSO项目开发
  • 从HLS到RTL:我们的YOLOv3 FPGA加速项目如何演进(附AX7350工程代码)
  • 【KingbaseES】sys_restore实战:从备份到恢复的完整流程解析
  • SuperTuxKart社区贡献指南:从新手到核心开发者的完整成长路径
  • 从GCC命令行到CMake一键构建:我的VSCode C语言工作流进化史
  • Cmder终极使用指南:5分钟打造你的Windows超级终端
  • 突破难关:AI专著撰写工具应用技巧,助你快速著书立说