4. CMS32 CAN初始化配置

各位同学,咱们今天聊点硬核的——CAN控制器的初始化。说实话,我见过不少工程师,写应用层代码溜得很,一到底层寄存器配置就发怵。其实CAN初始化就那么几件事:时钟、波特率、滤波器、工作模式。捋顺了,一点都不难。

4.1 初始化流程概览

先看整体流程。我个人习惯把CAN初始化分成五个步骤,缺一不可:

  1. 时钟使能——让CAN模块先“通电”
  2. 复位模式进入——配置寄存器前必须进复位模式
  3. 波特率配置——设置位时序参数
  4. 滤波器配置——决定哪些报文能进接收缓冲区
  5. 工作模式切换——回到正常模式,开始通信

核心原则:所有配置必须在CAN控制器处于复位/初始化模式下完成。离开复位模式后,大部分寄存器写操作无效。

嗯,这里要注意:不同厂家的CAN控制器,复位模式的名字可能不一样。CMS32里叫初始化模式,有些芯片叫复位模式或配置模式。本质一回事。

CAN控制器初始化流程图 1. 时钟使能 2. 进入初始化模式 3. 配置波特率 4. 配置滤波器 5. 退出初始化模式 ⚠ 避坑提醒 配置顺序不能乱 波特率→滤波器→模式切换 中途不要提前退出复位模式

4.2 寄存器级初始化流程

咱们以CMS32的CAN模块为例,直接看寄存器操作。说白了,就是往几个关键地址写值。

4.2.1 时钟使能

先打开CAN模块的外设时钟。CMS32里通常挂在APB总线上:

// 使能CAN模块时钟
CAN_CLK_EN |= (1 << CAN_EN_BIT);   // 假设CAN挂在时钟控制寄存器的bit 3

小提示:我遇到过有人配了半天波特率,CAN死活不工作。最后发现是时钟没开。先查时钟,能省一半调试时间。

4.2.2 进入初始化模式

CMS32的CAN控制器通过模式选择寄存器切换工作状态:

// 进入初始化模式(复位模式)
CAN_MODE_REG |= (1 << INIT_BIT);    // 置位初始化位

// 等待模式切换完成
while(!(CAN_MODE_REG & (1 << INIT_ACK_BIT)));  // 等待确认位

为什么要等确认位?因为模式切换不是瞬间完成的。控制器需要把当前总线上的事务处理完,才能安全进入配置状态。你想想看,如果正在收发报文时突然改了波特率,总线不就乱套了吗?

4.2.3 配置波特率

波特率配置是CAN初始化的核心。CMS32的位时序寄存器通常包含三个字段:

字段 位域 说明
BRP 波特率预分频器 决定一个时间量子(Tq)的长度
SEG1 相位缓冲段1 包含传播段和相位段1,通常3~16 Tq
SEG2 相位缓冲段2 通常2~8 Tq
SJW 同步跳转宽度 重同步时的最大调整量,通常1~4 Tq

4.3 波特率计算与配置

波特率计算的公式其实很简单:

CAN波特率 = 系统时钟频率 / (BRP + 1) / (1 + SEG1 + SEG2)

其中:1个位时间 = 1 (同步段) + SEG1 + SEG2,单位是Tq

举个例子。假设系统时钟是48MHz,目标波特率500kbps:

  1. 先选BRP。我一般从BRP=0开始试,不行再往上加。
  2. 48MHz / (0+1) = 48MHz → 每个Tq约20.8ns
  3. 位时间 = 1 / 500kbps = 2000ns
  4. 需要的Tq数 = 2000 / 20.8 ≈ 96个Tq → 太多了,不合理

所以BRP得加大。试试BRP=5:

48MHz / (5+1) = 8MHz → 每个Tq = 125ns
位时间 = 2000ns / 125ns = 16个Tq

分配:
同步段 = 1 Tq (固定)
SEG1   = 10 Tq
SEG2   = 5 Tq
总计   = 1 + 10 + 5 = 16 Tq ✓

SJW    = 2 Tq (通常取SEG2的一半或更小)

对应的寄存器配置:

// 假设位时序寄存器格式:BRP[9:0] | SJW[1:0] | SEG2[2:0] | SEG1[3:0]
// 注意:实际寄存器布局请参考CMS32数据手册

CAN_BTR_REG = (5 << 0)   |   // BRP = 5
              (2 << 10)  |   // SJW = 2
              (4 << 12)  |   // SEG2 = 5 (寄存器值 = 5-1 = 4)
              (9 << 16);     // SEG1 = 10 (寄存器值 = 10-1 = 9)

⚠ 常见错误:有些芯片的SEG1和SEG2寄存器存的是“实际值-1”。比如SEG1=10个Tq,寄存器里写9。我曾经在这个坑里折腾了一下午,切记看数据手册的寄存器描述。

4.4 滤波器模式设置

滤波器的作用,说白了就是让CAN控制器只接收你关心的报文,其他的统统丢掉。CMS32支持两种模式:

模式 原理 适用场景
掩码模式 一个掩码寄存器 + 一个验收寄存器
掩码位=1表示该位必须匹配,=0表示忽略
需要接收一组连续的ID范围
列表模式 多个验收寄存器,每个存一个完整ID
报文ID匹配任意一个即接收
只接收几个固定的ID

4.4.1 掩码模式配置

假设我只想接收标准帧ID=0x100~0x10F范围内的报文:

// 标准帧11位ID
// 0x100 = 001 0000 0000
// 0x10F = 001 0000 1111
// 高7位固定(0010000),低4位任意

CAN_ACR_REG = 0x100;           // 验收寄存器:基准ID
CAN_MASK_REG = 0x7F0;          // 掩码:高7位必须匹配,低4位忽略
// 掩码位=1表示检查,=0表示忽略

经验之谈:掩码模式特别适合做“分组过滤”。比如一个节点需要接收所有0x1xx开头的报文,一个掩码搞定。我在做车载网关项目时,用掩码模式把报文过滤效率提升了3倍。

4.4.2 列表模式配置

如果我只想收三个特定ID:0x100、0x200、0x300:

// 假设有4个验收寄存器
CAN_ACR0_REG = 0x100;   // 第一个ID
CAN_ACR1_REG = 0x200;   // 第二个ID
CAN_ACR2_REG = 0x300;   // 第三个ID
CAN_ACR3_REG = 0x000;   // 未使用,写0

// 使能列表模式(假设模式选择位在CAN_MODE_REG的bit 3)
CAN_MODE_REG |= (1 << LIST_MODE_BIT);

列表模式的好处是精确,坏处是寄存器数量有限。CMS32一般提供4~8个验收寄存器,超过这个数就得用掩码模式了。

4.5 完整初始化代码示例

最后,给一个完整的初始化函数骨架。这是我个人比较习惯的写法:

void CAN_Init(void)
{
    // 1. 时钟使能
    CAN_CLK_EN |= (1 << CAN_EN_BIT);

    // 2. 进入初始化模式
    CAN_MODE_REG |= (1 << INIT_BIT);
    while(!(CAN_MODE_REG & (1 << INIT_ACK_BIT)));

    // 3. 配置波特率 500kbps (48MHz系统时钟)
    CAN_BTR_REG = (5 << 0) | (2 << 10) | (4 << 12) | (9 << 16);

    // 4. 配置滤波器 - 掩码模式,接收0x100~0x10F
    CAN_ACR_REG  = 0x100;
    CAN_MASK_REG = 0x7F0;
    CAN_MODE_REG &= ~(1 << LIST_MODE_BIT);  // 选择掩码模式

    // 5. 退出初始化模式,进入正常模式
    CAN_MODE_REG &= ~(1 << INIT_BIT);
    while(CAN_MODE_REG & (1 << INIT_ACK_BIT));  // 等待退出完成
}

调试建议:初始化完成后,先读一下模式寄存器确认状态。如果INIT_ACK位一直不消失,说明配置有问题,或者总线有异常。我曾经在CANH/CANL短路的情况下,控制器死活退不出初始化模式——硬件问题也会导致初始化失败。