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:
- 先算 Tq 数量:
48MHz / 500kbps = 96。这 96 就是总 Tq 数。 - 分配 Tq:同步段 1,BS1 取 15,BS2 取 8。加起来 1+15+8=24。
- 预分频值 = 96 / 24 = 4。
所以配置就是:预分频=4,BS1=15,BS2=8。采样点位置在 (1+15)/(1+15+8) = 66.7%。这个值对于 500kbps 来说,挺合适的。
3.1.3 SJW 的设置
SJW(同步跳转宽度)决定了节点能容忍多大的晶振偏差。公式是:
SJW ≤ min(4, BS2)
我通常直接设成 1 Tq。除非总线很长(超过 100 米)或者晶振精度很差(超过 0.5%),我才考虑设成 2 或 3。设大了反而容易引入误同步。
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 的帧都能通过。掩码模式特别适合分组通信的场景。
3.2.3 两种模式对比
| 特性 | 标识符列表模式 | 掩码模式 |
|---|---|---|
| 灵活性 | 低,只能精确匹配 | 高,支持范围匹配 |
| CPU 负载 | 最低 | 较低 |
| 配置复杂度 | 简单 | 中等,需理解掩码逻辑 |
| 适用场景 | 固定 ID 通信 | 动态 ID、分组通信 |
3.3 初始化流程总览
下面这张图是我自己总结的初始化步骤,你可以照着这个顺序来:
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); // 等待退出完成
}
好了,这一节的内容就这些。波特率和滤波器是 CAN 通信的基石,花点时间吃透它们,后面的调试会顺畅很多。