3. 初始化配置实战:波特率计算与设置、滤波器模式配置

好,咱们直接进入正题。CAN 模块的初始化,说白了就是两件事:让总线上的节点能听懂彼此的话(波特率一致),以及让节点只收自己该收的消息(滤波器配置)。这两步要是搞错了,总线要么静悄悄,要么乱成一锅粥。

我个人习惯把初始化拆成两个独立阶段来调试。先搞定波特率,再折腾滤波器。这样出了问题,定位也快。

3.1 波特率计算:为什么必须精确到纳秒?

CAN 总线没有时钟线,所有节点靠的是位同步。每个节点用自己的晶振去采样总线上的电平变化。如果波特率偏差超过 1.5%,节点就会逐渐失步,最终报错或离线。

我曾经在一个项目里,用了两个不同品牌的开发板。一个晶振是 8MHz,另一个是 8.0005MHz。看着差别不大吧?结果总线跑 500kbps,半小时后从节点就开始疯狂报 Error Passive。后来一算,位时间偏差刚好踩在临界点上。

所以,别偷懒,老老实实算。

3.1.1 位时间结构

一个 CAN 位时间被分成四段:

段名称 作用 长度范围(Tq)
同步段(SS) 检测总线边沿,固定 1 Tq 1
传播段(PTS) 补偿物理延迟(线缆、收发器) 1~8
相位缓冲段 1(PBS1) 采样点前调整,补偿晶振误差 1~8
相位缓冲段 2(PBS2) 采样点后调整,决定再同步 1~8

你看到的 BS1 和 BS2,其实就是这里的 PBS1 和 PBS2。有些手册把传播段和 PBS1 合并叫 BS1,注意区分。

3.1.2 计算公式

核心公式就一个:

波特率 = 时钟频率 / ((1 + BS1 + BS2) × 预分频值)

其中 1 是固定的同步段。举个例子,CMS32 的 CAN 外设挂载在 APB 总线上,假设时钟是 48MHz,目标波特率 500kbps:

  1. 先算 Tq 数量:48MHz / 500kbps = 96。这 96 就是总 Tq 数。
  2. 分配 Tq:同步段 1,BS1 取 15,BS2 取 8。加起来 1+15+8=24。
  3. 预分频值 = 96 / 24 = 4。

所以配置就是:预分频=4,BS1=15,BS2=8。采样点位置在 (1+15)/(1+15+8) = 66.7%。这个值对于 500kbps 来说,挺合适的。

我的小习惯:采样点尽量靠近 70%~80%。太靠前容易采到信号毛刺,太靠后抗干扰能力差。我一般优先保证采样点在 75% 附近,再微调 BS1 和 BS2。

3.1.3 SJW 的设置

SJW(同步跳转宽度)决定了节点能容忍多大的晶振偏差。公式是:

SJW ≤ min(4, BS2)

我通常直接设成 1 Tq。除非总线很长(超过 100 米)或者晶振精度很差(超过 0.5%),我才考虑设成 2 或 3。设大了反而容易引入误同步。

注意:SJW 不是越大越好。我曾经在一个项目里把 SJW 设成 4,结果总线上的短干扰脉冲被当成有效边沿,导致连续错误帧。后来改回 1,问题消失。

3.2 滤波器模式配置:别让你的 CPU 被垃圾帧淹没

CAN 总线上的每个节点都能收到所有报文。如果不用滤波器,CPU 就得处理每一条消息。对于 1Mbps 的总线,满负载时每秒超过 8000 帧。你的 CPU 还要不要干别的了?

CMS32 的 CAN 控制器提供两种滤波模式:标识符列表模式和掩码模式。

3.2.1 标识符列表模式

这种模式最简单。你给出一组 ID 白名单,只有匹配的帧才能通过。比如:

// 只接收 ID 为 0x123 和 0x456 的标准帧
CAN_FilterConfig.FilterMode = CAN_FILTERMODE_IDLIST;
CAN_FilterConfig.ID1 = 0x123;
CAN_FilterConfig.ID2 = 0x456;
// 配置寄存器...

优点:配置直观,CPU 负载最低。缺点:滤波器数量有限(CMS32 通常只有 2~4 个滤波器组),每个组只能放 2 个 ID。

我一般只在节点数量少、通信关系固定的场景用这个模式。比如一个传感器节点,只收主机的配置帧和校准帧。

3.2.2 掩码模式

掩码模式更灵活。你定义一个基准 ID 和一个掩码。掩码位为 1 表示这一位必须匹配,为 0 表示忽略。

举个例子:

// 接收 ID 范围 0x100 ~ 0x10F
CAN_FilterConfig.FilterMode = CAN_FILTERMODE_MASK;
CAN_FilterConfig.ID = 0x100;      // 基准 ID
CAN_FilterConfig.Mask = 0x7F0;    // 掩码:高 7 位必须匹配,低 4 位忽略
// 配置寄存器...

这样,ID 为 0x100、0x101、0x102……0x10F 的帧都能通过。掩码模式特别适合分组通信的场景。

避坑指南:我曾经犯过一个低级错误——把掩码设反了。掩码位为 1 表示“必须匹配”,不是“忽略”。当时调试了一下午,总觉得滤波器没生效。后来用逻辑分析仪抓了波形,才发现掩码写反了。嗯,从那以后我每次配置完滤波器,都会先发一条测试帧验证一下。

3.2.3 两种模式对比

特性 标识符列表模式 掩码模式
灵活性 低,只能精确匹配 高,支持范围匹配
CPU 负载 最低 较低
配置复杂度 简单 中等,需理解掩码逻辑
适用场景 固定 ID 通信 动态 ID、分组通信

3.3 初始化流程总览

下面这张图是我自己总结的初始化步骤,你可以照着这个顺序来:

CAN 模块初始化流程 1. 使能 CAN 时钟 2. 配置 CAN_TX / CAN_RX 引脚 3. 设置波特率(BS1/BS2/SJW) 4. 配置滤波器(列表/掩码) 5. 使能接收/错误中断 6. 退出初始化模式,进入正常模式 关键提醒 每一步都要检查寄存器 确认配置生效后再继续 否则出错很难定位

3.4 实战代码片段

下面是一段 CMS32 的 CAN 初始化代码。我加了详细的注释,你可以直接参考:

void CAN_Init(void)
{
    // 1. 使能 CAN 时钟
    RCC->APB1ENR |= RCC_APB1ENR_CANEN;

    // 2. 配置引脚:PA12 = CAN_TX,PA11 = CAN_RX
    GPIOA->MODER &= ~(GPIO_MODER_MODE11 | GPIO_MODER_MODE12);
    GPIOA->MODER |= (GPIO_MODER_AF_MODE11 | GPIO_MODER_AF_MODE12);
    GPIOA->AFR[1] |= (0x09 << 12) | (0x09 << 16);  // AF9 = CAN

    // 3. 请求进入初始化模式
    CAN->MCR |= CAN_MCR_INRQ;
    while(!(CAN->MSR & CAN_MSR_INAK));  // 等待确认

    // 4. 配置波特率:48MHz,500kbps,BS1=15,BS2=8,预分频=4
    CAN->BTR = (4-1) |                    // 预分频值
               ((15-1) << 16) |          // BS1
               ((8-1) << 20) |           // BS2
               (1 << 24);                 // SJW = 1 Tq

    // 5. 配置滤波器:掩码模式,接收 0x100~0x10F
    CAN->FMR |= CAN_FMR_FINIT;            // 进入滤波器初始化模式
    CAN->FS1R |= CAN_FS1R_FSC0;           // 滤波器组 0 工作在掩码模式
    CAN->FM1R &= ~CAN_FM1R_FBM0;          // 掩码模式
    CAN->sFilterRegister[0].FR1 = 0x100 << 21;  // 基准 ID
    CAN->sFilterRegister[0].FR2 = 0x7F0 << 21;  // 掩码
    CAN->FFA1R |= CAN_FFA1R_FFA0;         // 关联到 FIFO 0
    CAN->FA1R |= CAN_FA1R_FACT0;          // 激活滤波器
    CAN->FMR &= ~CAN_FMR_FINIT;           // 退出滤波器初始化模式

    // 6. 使能接收中断
    CAN->IER |= CAN_IER_FMPIE0;

    // 7. 退出初始化模式
    CAN->MCR &= ~CAN_MCR_INRQ;
    while(CAN->MSR & CAN_MSR_INAK);     // 等待退出完成
}
调试小技巧:每次配置完波特率,先发一条自测帧(Loopback 模式),看看能不能收到自己的消息。如果能,说明波特率没问题。然后再切到正常模式,接上总线。这样一步步验证,比一次性全配完再查错要快得多。

好了,这一节的内容就这些。波特率和滤波器是 CAN 通信的基石,花点时间吃透它们,后面的调试会顺畅很多。