STM32F105换GD32F305踩坑实录:5个CAN驱动移植的坑点与填坑指南
从STM32F105切换到GD32F305的过程看似简单,但实际移植过程中遇到的CAN驱动问题却让我这个老嵌入式工程师踩了不少坑。国产MCU在寄存器命名和功能实现上的细微差异,往往会导致原本在STM32上运行良好的代码在GD32上出现各种诡异行为。本文将详细复盘我在移植过程中遇到的5个典型问题,包括现象描述、原因分析和解决方案,希望能帮助正在进行类似移植的同行少走弯路。
1. CAN初始化失败:SLEEP位的隐藏陷阱
移植后遇到的第一个问题就是CAN初始化失败。在GD32F305上调用HAL_CAN_Init()函数时,总是返回错误状态。经过仔细排查,发现问题出在SLEEP位的处理上。
现象描述:
- STM32F105上CAN初始化正常
- GD32F305上
HAL_CAN_Init()返回错误 - 调试发现卡在等待
CAN_MSR.INAK置位的循环中
原因分析: 两个芯片在初始化后CAN_MCR.SLEEP位都默认为1,但行为却有差异:
- STM32:设置
CAN_MCR.INRQ后,CAN_MSR.INAK会立即置1,不受SLEEP位影响 - GD32:
CAN_MCR.INRQ置1后,只有在SLEEP位为0时CAN_MSR.INAK才会置1
解决方案: 在调用HAL_CAN_Init()前,需要先清除SLEEP位:
// 在HAL_CAN_MspInit()函数末尾添加 CLEAR_BIT(canHandle->Instance->MCR, CAN_MCR_SLEEP);或者直接调用HAL_CAN_WakeUp()函数唤醒CAN控制器。
2. 连续发送数据异常:发送邮箱逻辑差异
第二个坑出现在连续发送CAN报文时。在GD32上,连续调用HAL_CAN_AddTxMessage()发送两包数据时,第二包数据总是无法发出。
现象描述:
- STM32上连续发送两包数据正常
- GD32上只能发送第一包数据
- 无错误标志,但第二包数据未出现在总线上
原因分析: 问题根源在于两个芯片对发送邮箱状态的处理逻辑不同:
| 特性 | STM32F105 | GD32F305 |
|---|---|---|
| 状态寄存器 | CAN_TSR | CAN_TSTAT |
| 邮箱号字段 | CODE[1:0] | NUM[1:0] |
| 行为描述 | 返回下一个空邮箱或优先级最低的邮箱 | 返回下一个将要发送的邮箱或最后一个邮箱 |
解决方案: 需要修改HAL_CAN_AddTxMessage()中的邮箱选择逻辑:
// 原代码 transmitmailbox = (tsr & CAN_TSR_CODE) >> CAN_TSR_CODE_Pos; // 修改后代码 if(CAN_TSR_TME0 == (tsr & CAN_TSR_TME0)) { transmitmailbox = 0; } else if(CAN_TSR_TME1 == (tsr & CAN_TSR_TME1)) { transmitmailbox = 1; } else if(CAN_TSR_TME2 == (tsr & CAN_TSR_TME2)) { transmitmailbox = 2; } else { transmitmailbox = 3; }3. CAN接收异常:过滤器配置的坑
第三个问题更加隐蔽:GD32的CAN0(对应STM32的CAN1)能发送数据,却接收不到任何报文。
现象描述:
- STM32上CAN1收发正常
- GD32上CAN0能发送但接收不到数据
- 总线信号正常,其他节点能收到GD32发送的数据
原因分析: 问题出在过滤器配置上。两个芯片的过滤器分配机制有差异:
- STM32的
CAN_FMR.CAN2SB和GD32的CAN_FCTL.HBC1F控制CAN1/CANb的过滤器起始位置 - 默认值都是14(0x0E),但代码中未初始化
sFilterConfig1.SlaveStartFilterBank - STM32存在与文档不符的行为,即使
CAN2SB=0也能接收数据 - GD32严格遵循文档,
HBC1F=0时CANa无法使用任何过滤器
解决方案: 在调用HAL_CAN_ConfigFilter()前正确初始化过滤器配置:
sFilterConfig1.SlaveStartFilterBank = 14; // 保持与复位默认值一致4. 第二个CAN接口异常:过滤器配置遗漏
解决了CANa的接收问题后,又发现GD32的CANb(对应STM32的CAN2)无法正常通信。
现象描述:
- 修改过滤器配置后,CANa工作正常
- 但CANb无法收发数据
- 总线终端电阻配置正确
原因分析: 原因在于原代码中只配置了CANa的过滤器,没有配置CANb的过滤器。当CAN_FMR.CAN2SB被正确设置后,影响了CANb的默认过滤器行为。
解决方案: 需要为CANb添加独立的过滤器配置:
CAN_FilterTypeDef sFilterConfig2; // 其他配置... sFilterConfig2.FilterBank = 15; // 使用过滤器15,与SlaveStartFilterBank=14匹配 HAL_CAN_ConfigFilter(&hcan2, &sFilterConfig2);5. 数据发送丢失:超时处理的临界条件
最后一个问题是GD32在连续发送多包数据时会出现数据丢失。
现象描述:
- 连续发送4包数据时,第3包丢失
- 现象在特定超时值下出现
- STM32上即使触发超时也能完整发送
原因分析: 问题出在发送超时处理上:
- 原代码使用固定超时值200,对于GD32来说太小
- GD32执行速度更快,在超时触发时可能正好完成发送
- STM32因速度较慢,超时触发时数据还未发送完,实际未终止发送
- GD32严格执行终止操作,导致数据被真正丢弃
测试数据对比:
| 芯片 | 超时值范围 | 行为表现 |
|---|---|---|
| STM32 | <190 | 丢失第3包 |
| STM32 | 190-300 | 触发超时但完整发送 |
| STM32 | >300 | 正常发送 |
| GD32 | <255 | 丢失第3包 |
| GD32 | 255-395 | 触发超时但完整发送 |
| GD32 | >395 | 正常发送 |
解决方案: 增大超时值,考虑最坏情况:
// 将各处超时值统一修改为足够大的值 uint32_t timeout = 10000; // 考虑低波特率情况注意:更完善的解决方案应考虑波特率和时钟频率的影响,动态计算超时值。
移植过程中发现,GD32F305在CAN外设行为上与STM32F105存在多处细微但关键的差异。这些差异往往不会在数据手册中特别标注,需要开发者通过实际测试来发现。建议在移植时:
- 仔细对比每个寄存器的位定义
- 不要假设默认行为一致
- 对关键操作添加充分的调试信息
- 边界条件测试要覆盖各种极端情况