4. CAN标识符与滤波:标准帧与扩展帧的深度解析

各位同学,今天我们来聊聊CAN总线里一个绕不开的话题——标识符与滤波。说实话,我在飞控开发初期,就被ID分配坑过好几次。你想想看,一架无人机上十几个CAN节点,如果ID冲突或者滤波没配好,那总线上的数据就跟菜市场一样乱。

这一节,我会把标准帧(11位ID)和扩展帧(29位ID)的区别、ID分配策略,以及硬件滤波和软件滤波的实现,掰开了揉碎了讲清楚。嗯,咱们开始吧。

4.1 标准帧 vs 扩展帧:11位ID与29位ID

CAN总线有两种帧格式:标准帧和扩展帧。它们的核心区别,说白了就是ID的长度不同。

特性 标准帧(Base Frame) 扩展帧(Extended Frame)
ID位数 11位 29位
ID范围 0x000 ~ 0x7FF(2048个) 0x00000000 ~ 0x1FFFFFFF(约5.3亿个)
仲裁场长度 12位(ID + RTR) 32位(ID + SRR + IDE + RTR)
总线利用率 较高(帧头短) 较低(帧头多2字节)
兼容性 所有CAN控制器都支持 需控制器支持CAN 2.0B

关键点来了:标准帧的ID优先级更高。为什么?因为扩展帧在仲裁时,IDE位(标识符扩展位)是显性(0),而标准帧的IDE位是隐性(1)。所以标准帧永远比扩展帧先抢到总线。我在做飞控时,就把心跳包、紧急指令这些高优先级消息,全部用标准帧发送。

我的经验:飞控系统中,建议把实时性要求高的消息(如电机控制指令、IMU数据)用标准帧,配置类、日志类消息用扩展帧。这样既能保证关键数据的低延迟,又能利用扩展帧的丰富ID空间做设备管理。

4.2 ID分配策略:别让总线变成战场

ID分配,是CAN总线设计的第一步,也是最容易出问题的一步。我见过一个项目,因为ID分配没规划好,导致两个传感器同时发送相同ID的数据,接收端根本分不清谁是谁。

这里我分享一套在飞控项目中验证过的分配策略:

  1. 按优先级分配:ID值越小,优先级越高。把紧急消息(如急停、故障上报)放在0x000~0x0FF区间。
  2. 按功能分组:比如0x100~0x1FF给传感器,0x200~0x2FF给执行器,0x300~0x3FF给控制器。
  3. 按节点编号分配:每个CAN节点分配一个唯一编号,ID = 功能基地址 + 节点编号。这样调试时一眼就能看出是谁发的。
  4. 预留扩展空间:别把ID用满,留20%的余量给未来升级。

避坑指南:我曾经在项目中把所有ID都按顺序排好,结果后来加了一个新传感器,发现ID区间已经用完了。最后不得不重新分配,改了一堆代码。所以,ID分配一定要留余量,而且最好用宏定义或枚举,别写死数字

4.3 硬件滤波:让CAN控制器帮你干活

硬件滤波,说白了就是让CAN控制器在硬件层面直接过滤掉不关心的消息,减少CPU的负担。飞控系统里,CPU资源很宝贵,能省一点是一点。

大多数CAN控制器(如STM32的bxCAN、SJA1000)都支持两种滤波模式:

  • 标识符列表模式:只接收ID完全匹配的消息。适合节点只关心少数几个ID的场景。
  • 标识符掩码模式:通过掩码指定哪些位必须匹配,哪些位可以忽略。适合需要接收一组ID的场景。

举个例子,假设飞控主控需要接收所有传感器数据(ID范围0x100~0x1FF),用掩码模式配置如下:

// 以STM32 bxCAN为例
// 滤波器组0,掩码模式,32位宽度
CAN_FilterInitStructure.CAN_FilterNumber = 0;
CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;
CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit;

// 期望ID:0x100(高16位),掩码:0x1FF(只匹配高9位)
CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0100 >> 3;  // 右移3位对齐
CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;
CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x01FF >> 3;
CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;

CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0;
CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;
CAN_FilterInit(&CAN_FilterInitStructure);

这样配置后,只有ID在0x100~0x1FF范围内的消息才会进入FIFO,CPU只需要处理这些消息就行。

注意:硬件滤波虽然高效,但灵活性有限。比如你想同时接收0x100~0x1FF和0x300~0x3FF两组不连续的ID,单个滤波器可能搞不定。这时候就需要多个滤波器组合,或者用软件滤波来兜底。

4.4 软件滤波:最后的防线

软件滤波,就是在中断或任务里,对收到的消息再做一次ID判断。虽然会消耗CPU,但胜在灵活。

我一般这样用软件滤波:

  • 硬件滤波做粗筛,过滤掉90%不关心的消息。
  • 软件滤波做精筛,处理那些硬件滤波搞不定的复杂场景。

代码实现很简单:

// 软件滤波函数
uint8_t SoftwareFilter(uint32_t id, uint8_t ide) {
    // 只接收标准帧,且ID在0x100~0x1FF或0x300~0x3FF
    if (ide == CAN_ID_STD) {
        if ((id >= 0x100 && id <= 0x1FF) ||
            (id >= 0x300 && id <= 0x3FF)) {
            return 1;  // 接收
        }
    }
    return 0;  // 丢弃
}

// 在CAN接收中断中调用
void CAN_RX_IRQHandler(void) {
    CanRxMsg rxMsg;
    CAN_Receive(CAN_FIFO0, &rxMsg);
    
    if (SoftwareFilter(rxMsg.ExtId, rxMsg.IDE)) {
        // 处理消息
        ProcessMessage(&rxMsg);
    }
}

我的建议:软件滤波不要做太复杂的逻辑,否则中断服务程序执行时间过长,会影响其他中断的响应。如果滤波规则很复杂,可以考虑用查表法,把允许的ID列表放在一个数组里,用二分查找来判断。

4.5 知识体系总览

为了让大家更直观地理解这一节的内容,我画了一张图,把标准帧与扩展帧的区别、ID分配策略、硬件滤波与软件滤波的关系串起来。

CAN标识符与滤波知识体系 CAN标识符与滤波 标准帧(11位ID) vs 扩展帧(29位ID) 标准帧特点 ID范围:0x000~0x7FF 扩展帧特点 ID范围:0x00000000~0x1FFFFFFF ID分配策略 按优先级分配 按功能分组 预留扩展空间 硬件滤波 vs 软件滤波

这张图把这一节的核心逻辑都串起来了。你从上往下看,先搞清楚标准帧和扩展帧的区别,然后规划好ID怎么分配,最后根据需求选择硬件滤波还是软件滤波。嗯,思路就清晰了。

4.6 总结

这一节的内容,说白了就是三件事:

  • 标准帧和扩展帧怎么选——高优先级用标准帧,复杂场景用扩展帧。
  • ID怎么分配——按优先级、按功能、留余量,别拍脑袋。
  • 滤波怎么配——硬件滤波做粗筛,软件滤波做精筛,两者结合最稳妥。

我在飞控项目里,一直用这套方法,没出过ID冲突的问题。你按照这个思路来,也能少踩坑。


公众号:蓝海资料掘金营,微信deep3321