4. CMS32 CAN初始化配置
各位同学,咱们今天聊点硬核的——CAN控制器的初始化。说实话,我见过不少工程师,写应用层代码溜得很,一到底层寄存器配置就发怵。其实CAN初始化就那么几件事:时钟、波特率、滤波器、工作模式。捋顺了,一点都不难。
4.1 初始化流程概览
先看整体流程。我个人习惯把CAN初始化分成五个步骤,缺一不可:
- 时钟使能——让CAN模块先“通电”
- 复位模式进入——配置寄存器前必须进复位模式
- 波特率配置——设置位时序参数
- 滤波器配置——决定哪些报文能进接收缓冲区
- 工作模式切换——回到正常模式,开始通信
核心原则:所有配置必须在CAN控制器处于复位/初始化模式下完成。离开复位模式后,大部分寄存器写操作无效。
嗯,这里要注意:不同厂家的CAN控制器,复位模式的名字可能不一样。CMS32里叫初始化模式,有些芯片叫复位模式或配置模式。本质一回事。
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:
- 先选BRP。我一般从BRP=0开始试,不行再往上加。
- 48MHz / (0+1) = 48MHz → 每个Tq约20.8ns
- 位时间 = 1 / 500kbps = 2000ns
- 需要的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短路的情况下,控制器死活退不出初始化模式——硬件问题也会导致初始化失败。