4. CAN报文接收实战:接收FIFO/邮箱配置、接收中断使能、过滤与屏蔽寄存器设置
好,咱们进入CAN接收部分。说实话,发送相对简单——你把数据塞进邮箱,启动发送,等它发完就行。但接收不一样,你得考虑:报文从哪来?怎么存?哪些要收哪些要丢?收太快溢出了怎么办?
这一章,我把这些坑一个一个给你讲透。
4.1 接收FIFO vs 邮箱:你该怎么选?
CMS32的CAN模块,接收端有两种存储机制:FIFO和邮箱。很多人一开始搞混,我简单说下区别。
| 特性 | 接收FIFO | 接收邮箱 |
|---|---|---|
| 存储深度 | 多级(通常3~8级) | 单级(每个邮箱1帧) |
| 溢出风险 | 较低(有缓冲) | 较高(未及时读取即覆盖) |
| 过滤灵活性 | 通常绑定一组过滤规则 | 每个邮箱可独立配置过滤 |
| 适用场景 | 多节点广播、高频率接收 | 特定ID的精确接收 |
我个人习惯:如果项目里CAN总线上的报文种类多、频率高,比如整车控制器(VCU)那种,我优先用FIFO。如果只是监控几个关键传感器,每个传感器ID固定,用邮箱更省心。
小提示:CMS32的FIFO深度可以配置。我一般设4级,既保证缓冲,又不浪费RAM。你想想看,如果深度设太大,中断响应不及时,FIFO里堆一堆旧数据,反而麻烦。
4.2 接收中断使能:别让CPU空转
接收中断,说白了就是让硬件告诉你:“嘿,有报文来了,快来拿!”
配置步骤其实不复杂:
- 使能CAN模块时钟
- 配置CAN引脚(TX/RX)为复用功能
- 设置接收FIFO或邮箱的中断使能位
- 在NVIC中使能CAN中断通道
- 编写中断服务函数
代码示例(基于CMS32标准库):
// 使能CAN接收中断
CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE); // FIFO0报文挂起中断
NVIC_EnableIRQ(CAN1_IRQn); // 使能NVIC中断
// 中断服务函数
void CAN1_IRQHandler(void)
{
if(CAN_GetITStatus(CAN1, CAN_IT_FMP0) != RESET)
{
CAN_Receive(CAN1, CAN_FIFO0, &rxMsg);
// 处理接收到的报文
ProcessCanMessage(&rxMsg);
CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0);
}
}
注意:我曾经在一个项目里忘了清中断标志位,结果CPU一直在中断里出不来,系统直接“假死”。排查了半天才发现是这问题。所以,清标志位这个动作,千万别漏。
4.3 过滤与屏蔽寄存器:只收你想要的
CAN总线上报文很多,如果你全收,CPU得累死。过滤与屏蔽寄存器就是干这个的——只让符合条件的报文进入FIFO或邮箱。
CMS32的过滤机制,我把它分成三种模式:
4.3.1 单ID过滤
只接收一个特定ID的报文。比如你只关心0x123这个ID:
// 设置过滤组0,只接收ID=0x123
CAN_FilterConfig(CAN1, CAN_FilterNumber_0,
CAN_FilterMode_IdMask,
CAN_FilterScale_32Bit,
0x123 << 5, // 滤波寄存器:ID左移5位
0x7FF << 5, // 屏蔽寄存器:全匹配
CAN_FilterFIFO0);
4.3.2 多ID过滤(列表模式)
接收多个指定的ID。比如0x123、0x456、0x789:
// 列表模式下,每个过滤组可以匹配2个ID(32位配置)
// 这里用两个过滤组实现3个ID的匹配
CAN_FilterConfig(CAN1, CAN_FilterNumber_0,
CAN_FilterMode_IdList,
CAN_FilterScale_32Bit,
(0x123 << 5) | (0x456 << 5), // 两个ID
0x00000000, // 屏蔽寄存器置0(列表模式)
CAN_FilterFIFO0);
4.3.3 范围过滤
接收某个范围内的所有ID。比如0x100到0x1FF:
// 范围过滤:屏蔽高3位,只匹配低8位
// 0x100 = 0001 0000 0000
// 0x1FF = 0001 1111 1111
// 屏蔽高3位,即屏蔽码为 1110 0000 0000 = 0x700
CAN_FilterConfig(CAN1, CAN_FilterNumber_0,
CAN_FilterMode_IdMask,
CAN_FilterScale_32Bit,
0x100 << 5, // 基准ID
0x700 << 5, // 屏蔽高3位
CAN_FilterFIFO0);
关键点:屏蔽寄存器里,位为1表示“必须匹配”,位为0表示“忽略”。所以上面0x700的二进制是111 0000 0000,高3位必须匹配,低9位任意——正好实现0x100~0x1FF的范围过滤。
4.4 接收溢出处理:别让数据丢了
溢出,就是报文来得太快,FIFO满了,新报文没地方放。CMS32会给你一个溢出标志,你得处理它。
我遇到过最典型的情况:CAN总线波特率500kbps,某个节点突发发送大量报文,FIFO深度只有3级,CPU还在处理其他中断,结果——溢出。
处理策略有三种:
- 丢弃旧数据:溢出时,丢弃FIFO里最旧的报文,保留最新的。适合实时性要求高的场景。
- 丢弃新数据:溢出时,丢弃新来的报文,保留FIFO里的旧数据。适合数据完整性要求高的场景。
- 软件缓冲:在RAM里开一个大缓冲区,中断里把FIFO数据搬到缓冲区。适合高频率接收。
代码示例(溢出中断处理):
void CAN1_IRQHandler(void)
{
if(CAN_GetITStatus(CAN1, CAN_IT_FOV0) != RESET) // FIFO0溢出
{
// 策略:丢弃最旧的数据,读取最新数据
CAN_Receive(CAN1, CAN_FIFO0, &rxMsg); // 先读出一帧(丢弃)
CAN_Receive(CAN1, CAN_FIFO0, &rxMsg); // 再读最新的一帧
overflowCount++;
CAN_ClearITPendingBit(CAN1, CAN_IT_FOV0);
}
}
避坑指南:我曾经在一个项目中,溢出后只清了标志位,没读FIFO。结果FIFO一直处于满状态,后续报文全进不来。所以,溢出后一定要读FIFO,把空间腾出来。
4.5 知识结构总览
下面这张图,把CAN报文接收的整个流程串起来了。你对照着看,思路会更清晰。
4.6 实战建议
最后,给你几个我在项目中总结的经验:
- 先配过滤,再开中断:顺序别搞反。否则中断一开,乱七八糟的报文全涌进来,你连过滤都来不及设。
- 溢出计数一定要加:哪怕只是打印出来调试用。我见过太多项目,上线后才发现CAN丢数据,就是因为溢出没监控。
- 中断服务函数要短:别在中断里做复杂处理。收到报文后,设个标志位,丢到主循环里处理。中断里待太久,其他中断会饿死。
我的习惯:调试阶段,我会把接收到的报文ID和长度通过串口打印出来,确认过滤配置对不对。等验证通过,再把打印关掉,避免影响实时性。
嗯,CAN报文接收这部分,说白了就是:配置好存储结构,设好过滤规则,开中断,处理溢出。每一步都有坑,但只要你按上面的流程走一遍,基本不会出大问题。