4. 报文发送机制:发送邮箱管理、发送优先级策略、发送中断与轮询模式、发送超时处理

报文发送,说白了就是怎么把数据从芯片里发到CAN总线上。CMS32的CAN控制器在这方面设计得挺灵活,但也藏着不少坑。我刚开始用的时候,就因为在发送优先级上没搞明白,导致高优先级报文被堵在邮箱里出不去,现场总线直接乱套。

这一节,咱们就把发送机制掰开揉碎了讲。你想想看,如果连报文怎么发出去的都搞不清,那后面定位故障就是瞎忙活。

4.1 发送邮箱管理:三个邮箱,各有分工

CMS32的CAN控制器有3个独立的发送邮箱。每个邮箱都能独立存放一帧完整的报文。它们不是简单的“三个坑”,而是有明确分工的。

邮箱编号 优先级 典型用途 我踩过的坑
邮箱0 最高 紧急控制报文(如刹车、故障码) 别把周期报文放这里,会饿死其他邮箱
邮箱1 中等 实时性要求较高的数据(如转速、扭矩) 注意和邮箱0的优先级差距,别设反了
邮箱2 最低 诊断请求、配置参数等非实时报文 超时处理主要针对这个邮箱

每个邮箱都有自己的一套状态寄存器。我习惯用轮询的方式检查邮箱状态,因为中断在发送场景下容易频繁触发,反而影响效率。

核心原则:发送邮箱不是越多越好。3个邮箱刚好覆盖“紧急-周期-非实时”三种场景。我曾经见过有人把所有报文都塞进邮箱0,结果总线仲裁时全是高优先级,低优先级节点直接“失联”。

4.2 发送优先级策略:谁先走,谁后走?

CMS32的发送优先级,不是简单的“谁先请求谁先发”。它有一套自己的仲裁逻辑。说白了,就是看邮箱编号和报文ID的组合。

具体规则是这样的:

  1. 邮箱编号优先:编号小的邮箱,优先级高。邮箱0 > 邮箱1 > 邮箱2。
  2. 报文ID仲裁:如果多个邮箱同时请求发送,且邮箱编号相同(不可能,因为编号唯一),才比较报文ID。
  3. 内部调度:控制器内部有一个“发送请求寄存器”,它会按顺序扫描邮箱,找到第一个有发送请求的邮箱,然后发送。

嗯,这里要注意:邮箱编号的优先级,高于报文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没有硬件超时计数器,所以超时处理得我们自己写。

我一般用软件定时器来实现超时检测。流程是这样的:

  1. 启动定时器:在请求发送的同时,启动一个软件定时器。
  2. 检查状态:在定时器中断或主循环里,检查邮箱的“发送完成”标志。
  3. 超时处理:如果超时了还没发送完成,就执行超时逻辑。

超时处理逻辑,我总结了几种策略:

策略 做法 适用场景
重发 清除发送请求,重新加载报文,再次请求 总线短暂繁忙
丢弃 直接放弃该报文,记录错误日志 非实时报文,超时后已无意义
降级 降低报文优先级,或者改用其他邮箱发送 紧急报文,但总线负载过高
复位 复位CAN控制器,重新初始化 邮箱锁死,硬件故障

我个人的习惯是:先重发一次,再丢弃。如果重发还超时,说明总线可能真的有问题,这时候再重发只会加重负载。

避坑指南:我曾经在项目里遇到一个奇怪的问题:报文偶尔发不出去,但重发一次就好了。后来发现是邮箱的“发送请求”位没有及时清除,导致邮箱一直处于“忙”状态。所以,每次发送完成后,一定要检查并清除发送请求位。

超时时间的设置,我一般这样算:

  • 对于周期报文,超时时间设为发送周期的1.5倍。
  • 对于紧急报文,超时时间设为1ms(根据总线波特率调整)。
  • 对于非实时报文,超时时间可以设长一点,比如10ms。

下面这张图,是我自己画的发送机制流程图,你可以参考一下:

CMS32 CAN 报文发送机制流程图 CMS32 CAN 报文发送机制流程图 应用层请求发送 选择发送邮箱 加载报文到邮箱 设置发送请求位 启动超时定时器 等待发送完成 发送完成 清除请求位 超时 超时处理 说明:发送完成后,清除请求位;超时后,执行重发、丢弃或降级策略。

最后,再强调一点:发送超时处理,不是可有可无的“锦上添花”,而是必须有的“保命机制”。没有超时处理,一旦总线出问题,你的报文就会一直卡在邮箱里,整个系统都可能瘫痪。