4. 报文发送机制:发送邮箱管理、发送优先级策略、发送中断与轮询模式、发送超时处理
报文发送,说白了就是怎么把数据从芯片里发到CAN总线上。CMS32的CAN控制器在这方面设计得挺灵活,但也藏着不少坑。我刚开始用的时候,就因为在发送优先级上没搞明白,导致高优先级报文被堵在邮箱里出不去,现场总线直接乱套。
这一节,咱们就把发送机制掰开揉碎了讲。你想想看,如果连报文怎么发出去的都搞不清,那后面定位故障就是瞎忙活。
4.1 发送邮箱管理:三个邮箱,各有分工
CMS32的CAN控制器有3个独立的发送邮箱。每个邮箱都能独立存放一帧完整的报文。它们不是简单的“三个坑”,而是有明确分工的。
| 邮箱编号 | 优先级 | 典型用途 | 我踩过的坑 |
|---|---|---|---|
| 邮箱0 | 最高 | 紧急控制报文(如刹车、故障码) | 别把周期报文放这里,会饿死其他邮箱 |
| 邮箱1 | 中等 | 实时性要求较高的数据(如转速、扭矩) | 注意和邮箱0的优先级差距,别设反了 |
| 邮箱2 | 最低 | 诊断请求、配置参数等非实时报文 | 超时处理主要针对这个邮箱 |
每个邮箱都有自己的一套状态寄存器。我习惯用轮询的方式检查邮箱状态,因为中断在发送场景下容易频繁触发,反而影响效率。
核心原则:发送邮箱不是越多越好。3个邮箱刚好覆盖“紧急-周期-非实时”三种场景。我曾经见过有人把所有报文都塞进邮箱0,结果总线仲裁时全是高优先级,低优先级节点直接“失联”。
4.2 发送优先级策略:谁先走,谁后走?
CMS32的发送优先级,不是简单的“谁先请求谁先发”。它有一套自己的仲裁逻辑。说白了,就是看邮箱编号和报文ID的组合。
具体规则是这样的:
- 邮箱编号优先:编号小的邮箱,优先级高。邮箱0 > 邮箱1 > 邮箱2。
- 报文ID仲裁:如果多个邮箱同时请求发送,且邮箱编号相同(不可能,因为编号唯一),才比较报文ID。
- 内部调度:控制器内部有一个“发送请求寄存器”,它会按顺序扫描邮箱,找到第一个有发送请求的邮箱,然后发送。
嗯,这里要注意:邮箱编号的优先级,高于报文ID的优先级。也就是说,即使邮箱2里放的是紧急报文(ID很小),邮箱0里放的是普通报文(ID很大),邮箱0也会先发。
避坑指南:我曾经在项目里把紧急报文放在邮箱2,结果发现它总是被邮箱0和邮箱1的周期报文“插队”。后来我把紧急报文挪到邮箱0,问题才解决。所以,邮箱分配一定要和报文优先级匹配。
如果你想让某个报文“插队”,可以临时禁用其他邮箱的发送请求。但我不建议这么做,容易导致总线负载失衡。
4.3 发送中断与轮询模式:选哪个?
这是个老生常谈的问题。我个人的经验是:看场景。
先说说两种模式的区别:
| 模式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 中断模式 | 响应快,CPU利用率高 | 中断频繁,容易丢失;调试复杂 | 突发性报文、紧急报文 |
| 轮询模式 | 实现简单,可控性强 | 占用CPU时间,可能错过发送时机 | 周期报文、非实时报文 |
我一般这样用:
- 邮箱0(紧急报文):用中断模式。一旦发送完成,立即通知CPU,准备下一帧。
- 邮箱1(周期报文):用轮询模式。在定时器中断里检查邮箱状态,发送下一帧。
- 邮箱2(非实时报文):也用轮询模式。在主循环里处理,不着急。
中断模式的配置代码,我贴一段出来:
// 使能发送中断
CAN->INT_EN |= (1 << 0); // 使能邮箱0发送完成中断
NVIC_EnableIRQ(CAN_IRQn); // 使能CAN中断向量
// 中断服务函数
void CAN_IRQHandler(void) {
if (CAN->INT_STAT & (1 << 0)) { // 检查邮箱0中断标志
// 邮箱0发送完成
// 可以在这里加载下一帧报文
CAN->TX_BUF[0] = next_msg;
CAN->TX_REQ |= (1 << 0); // 再次请求发送
CAN->INT_STAT |= (1 << 0); // 清除中断标志
}
}
个人经验:中断服务函数里不要做太多事。我一般只做“加载下一帧报文”和“清除标志位”这两件事。其他逻辑,比如统计发送次数、更新状态机,都放到主循环里做。否则,中断嵌套会让你头大。
4.4 发送超时处理:报文发不出去怎么办?
报文发不出去,原因很多。可能是总线忙,可能是仲裁失败,也可能是邮箱被锁住了。CMS32没有硬件超时计数器,所以超时处理得我们自己写。
我一般用软件定时器来实现超时检测。流程是这样的:
- 启动定时器:在请求发送的同时,启动一个软件定时器。
- 检查状态:在定时器中断或主循环里,检查邮箱的“发送完成”标志。
- 超时处理:如果超时了还没发送完成,就执行超时逻辑。
超时处理逻辑,我总结了几种策略:
| 策略 | 做法 | 适用场景 |
|---|---|---|
| 重发 | 清除发送请求,重新加载报文,再次请求 | 总线短暂繁忙 |
| 丢弃 | 直接放弃该报文,记录错误日志 | 非实时报文,超时后已无意义 |
| 降级 | 降低报文优先级,或者改用其他邮箱发送 | 紧急报文,但总线负载过高 |
| 复位 | 复位CAN控制器,重新初始化 | 邮箱锁死,硬件故障 |
我个人的习惯是:先重发一次,再丢弃。如果重发还超时,说明总线可能真的有问题,这时候再重发只会加重负载。
避坑指南:我曾经在项目里遇到一个奇怪的问题:报文偶尔发不出去,但重发一次就好了。后来发现是邮箱的“发送请求”位没有及时清除,导致邮箱一直处于“忙”状态。所以,每次发送完成后,一定要检查并清除发送请求位。
超时时间的设置,我一般这样算:
- 对于周期报文,超时时间设为发送周期的1.5倍。
- 对于紧急报文,超时时间设为1ms(根据总线波特率调整)。
- 对于非实时报文,超时时间可以设长一点,比如10ms。
下面这张图,是我自己画的发送机制流程图,你可以参考一下:
最后,再强调一点:发送超时处理,不是可有可无的“锦上添花”,而是必须有的“保命机制”。没有超时处理,一旦总线出问题,你的报文就会一直卡在邮箱里,整个系统都可能瘫痪。