2. CMS32 CAN模块初始化:时钟使能、GPIO复用配置与波特率计算
好,咱们直接进入正题。CAN模块初始化,说白了就是让芯片知道“我要用CAN了”,然后把收发引脚接好,再告诉它用多快的速度说话。这三步缺一不可,顺序也基本固定。我刚开始接触CMS32的时候,就因为在时钟使能之前配了GPIO,结果折腾了半天——嗯,从那以后我再也没犯过这种低级错误。
2.1 时钟使能——先让CAN模块“通电”
任何外设要工作,第一步都是打开它的时钟。CMS32的CAN模块挂在APB总线上,你需要找到对应的时钟控制寄存器。
核心操作:设置时钟控制寄存器中的CANEN位,使能CAN模块时钟。
我个人习惯在系统时钟初始化之后、配置任何外设之前,先把CAN时钟打开。代码很简单,就一两行:
/* 使能CAN模块时钟 */
SYS->CLKEN |= (1 << 5); // 假设CANEN位在bit5,具体位号请查手册
小提示:不同型号的CMS32,时钟使能位的位置可能不一样。我建议你每次换芯片型号时,都重新确认一下寄存器定义,别直接复制旧代码。
2.2 GPIO复用配置——把CAN_TX和CAN_RX“接出来”
时钟打开了,但CAN模块的TX和RX信号还憋在芯片内部呢。你得把对应的GPIO引脚配置成复用功能,让信号能走到外面去。
CMS32的CAN接口通常复用在一组固定的引脚上。比如,CAN_TX可能是PA12,CAN_RX可能是PA11。你需要做两件事:
- 配置引脚模式:将TX引脚设为推挽输出复用模式,RX引脚设为浮空输入或上拉输入模式。
- 选择复用功能:通过GPIO的复用功能选择寄存器,把引脚连接到CAN模块。
/* 配置CAN_TX (PA12) 为复用推挽输出 */
GPIOA->MODER &= ~(3 << 24); // 清除原模式
GPIOA->MODER |= (2 << 24); // 设为复用模式
/* 配置CAN_RX (PA11) 为复用输入 */
GPIOA->MODER &= ~(3 << 22);
GPIOA->MODER |= (2 << 22);
/* 选择复用功能为CAN */
GPIOA->AFR[1] |= (9 << 16); // 假设AF9对应CAN,具体值看数据手册
注意:我曾经在一个项目里,因为复用功能号写错了,CAN一直收不到数据。查了两天才发现是AFR寄存器配成了别的外设。所以,配完GPIO之后,我建议你读一下寄存器确认一下。
2.3 波特率计算与设置——让CAN节点“说同一种语言”
波特率是CAN通信的基础。同一个网络上的所有节点,波特率必须完全一致,否则谁也听不懂谁。
CAN的波特率由三个参数决定:
- BS1 (时间段1):采样点之前的时间段,通常设为4~16个时间量子。
- BS2 (时间段2):采样点之后的时间段,通常设为2~8个时间量子。
- SJW (同步跳转宽度):用于补偿时钟偏差,通常设为1~4个时间量子。
计算公式很简单:
波特率 = CAN时钟频率 / ( (1 + BS1 + BS2) × 分频系数 )
举个例子,假设CAN时钟是48MHz,我想要1Mbps的波特率:
- 先选分频系数,比如4分频,得到12MHz的CAN时钟。
- 再算时间量子总数:12MHz / 1Mbps = 12个时间量子。
- 分配BS1和BS2:比如BS1=7,BS2=4,再加上1个同步段,正好12个。
| 参数 | 推荐值 | 说明 |
|---|---|---|
| BS1 | 4~16 | 采样点位置,通常设为7~9 |
| BS2 | 2~8 | 采样点后时间,通常设为4~6 |
| SJW | 1~4 | 时钟偏差补偿,通常设为1 |
| 采样点位置 | 75%~87.5% | BS1/(BS1+BS2+1) 计算 |
经验之谈:采样点位置建议设在75%~87.5%之间。我一般选80%左右,也就是BS1=8,BS2=2(加上同步段共11个时间量子)。这样既能容忍一定的时钟偏差,又能保证采样稳定。
/* 波特率配置示例:1Mbps,CAN时钟48MHz,4分频 */
CAN->BTR |= (3 << 16); // 分频系数 = 4 (实际值 = 3+1)
CAN->BTR |= (7 << 20); // BS1 = 8 (实际值 = 7+1)
CAN->BTR |= (3 << 24); // BS2 = 4 (实际值 = 3+1)
CAN->BTR |= (0 << 28); // SJW = 1 (实际值 = 0+1)
2.4 工作模式选择——Normal、Loopback还是Silent?
CAN模块有好几种工作模式,不同场景用不同的模式。我刚开始学的时候,经常搞混,后来总结了一张表:
| 模式 | 发送 | 接收 | ACK应答 | 典型用途 |
|---|---|---|---|---|
| Normal | 正常发送 | 正常接收 | 正常应答 | 实际通信 |
| Loopback | 发送到内部回路 | 只收自己发的 | 自己应答自己 | 自测、调试 |
| Silent | 禁止发送 | 正常接收 | 不应答 | 监听总线 |
| Loopback+Silent | 发送到内部回路 | 只收自己发的 | 不应答 | 静默自测 |
我个人习惯,在开发阶段先用Loopback模式验证发送和接收逻辑。等确认没问题了,再切到Normal模式去接真实的总线。
避坑指南:我曾经在调试时忘了从Loopback切回Normal,结果板子连上总线后,其他节点都收不到它的数据。查了半天才发现模式没改。所以,调试完一定要记得切模式。
/* 设置工作模式:Normal */
CAN->MCR &= ~(1 << 0); // 清除Loopback位
CAN->MCR &= ~(1 << 1); // 清除Silent位
/* 设置工作模式:Loopback */
CAN->MCR |= (1 << 0); // 置位Loopback位
CAN->MCR &= ~(1 << 1); // 清除Silent位
2.5 初始化流程总览
说了这么多,咱们把整个初始化流程串起来。下面这张图,是我自己画的一个流程图,你一看就明白了:
你看,整个流程就是一条直线,从时钟到GPIO,再到波特率和模式。每一步都依赖上一步的结果。我个人习惯在每步之后加个寄存器回读检查,确保配置生效了再往下走。
总结一下:CAN模块初始化,说白了就是“开时钟、配引脚、设波特率、选模式”这四步。把这四步走对了,CAN就能正常工作了。剩下的就是收发数据的逻辑了,咱们后面再聊。