3. CAN总线物理层与数据链路层:CAN差分信号、CAN帧结构(标准帧/扩展帧)、位时序与同步、CAN控制器初始化
好,咱们进入CAN总线最核心的部分。物理层和数据链路层,说白了就是CAN通信的“公路”和“交通规则”。很多新手一上来就调应用层,结果通信死活不通,最后发现是物理层电平不对,或者帧格式配错了。我当年也踩过这个坑,所以这一章咱们把底子打扎实。
3.1 CAN差分信号:为什么CAN能抗干扰?
CAN总线用两根线:CAN_H和CAN_L。它不靠单端电平传数据,而是靠两根线的电压差。这就是差分信号。
显性电平(Dominant): CAN_H ≈ 3.5V,CAN_L ≈ 1.5V,差分电压 ≈ 2V,逻辑0。
隐性电平(Recessive): CAN_H ≈ 2.5V,CAN_L ≈ 2.5V,差分电压 ≈ 0V,逻辑1。
为什么差分能抗干扰?你想想看,外界噪声同时耦合到两根线上,差分电压基本不变。我在一个电机驱动项目里遇到过,共模干扰有十几伏,但CAN通信纹丝不动。换成RS485早挂了。
个人习惯: 布线时CAN_H和CAN_L尽量走在一起,双绞线最好。终端电阻120Ω,一个在总线最左端,一个在最右端。少了反射,多了功耗。
3.2 CAN帧结构:标准帧 vs 扩展帧
CAN总线上的数据以“帧”为单位。最常见的两种帧:数据帧和远程帧。咱们先看数据帧,它分标准帧和扩展帧。
| 字段 | 标准帧(11位ID) | 扩展帧(29位ID) |
|---|---|---|
| SOF | 1位显性 | 1位显性 |
| 仲裁场 | 11位ID + RTR + IDE + r0 | 11位基ID + SRR + IDE + 18位扩展ID + RTR + r1 + r0 |
| 控制场 | DLC(4位) | DLC(4位) |
| 数据场 | 0~8字节 | 0~8字节 |
| CRC场 | 15位CRC + 1位界定符 | 15位CRC + 1位界定符 |
| ACK场 | 2位 | 2位 |
| EOF | 7位隐性 | 7位隐性 |
标准帧ID只有11位,最多2032个节点(实际去掉保留位)。扩展帧ID有29位,适合复杂网络。我建议:如果系统节点少于50个,用标准帧就够了,省带宽。扩展帧多出来的18位ID,在仲裁时会影响实时性。
避坑指南: 我曾经在一个项目里混用了标准帧和扩展帧,结果CAN控制器死活不响应。后来发现是IDE位没配对。记住:发送方和接收方的帧格式必须一致,否则仲裁场对不上。
3.3 位时序与同步:CAN的“心跳”
CAN总线是异步通信,没有单独的时钟线。那怎么保证所有节点步调一致?靠位时序和同步机制。
一个CAN位由4个段组成:
- 同步段(SS): 1个Tq,用于检测边沿。
- 传播段(PTS): 1~8个Tq,补偿物理延迟。
- 相位缓冲段1(PBS1): 1~8个Tq,用于重同步。
- 相位缓冲段2(PBS2): 1~8个Tq,用于重同步。
采样点通常在PBS1和PBS2之间。我习惯把采样点设在87.5%左右,这样抗干扰最好。为什么?因为信号在总线上的传播需要时间,采样点靠后能避开前沿的振铃。
位时间计算公式:
位时间 = (1 + PTS + PBS1 + PBS2) × Tq
波特率 = 1 / 位时间
例如:系统时钟40MHz,预分频4,Tq = 100ns。PTS=3,PBS1=3,PBS2=2,位时间 = 1+3+3+2 = 9Tq = 900ns,波特率 ≈ 1.11Mbps。
同步分硬同步和重同步。硬同步在SOF边沿触发,所有节点调整到同一个SS段起始。重同步则在后续边沿微调,PBS1延长或PBS2缩短。嗯,这里要注意:同步跳转宽度(SJW)不能超过PBS1和PBS2的最小值,否则会同步过头。
3.4 CAN控制器初始化:从寄存器到通信
以CMS32的CAN控制器为例,初始化步骤我总结为“五步法”:
- 时钟使能: 打开CAN模块时钟,配置GPIO为CAN功能引脚。
- 复位模式: 设置CAN控制器进入初始化模式(Init=1),才能配置位时序。
- 配置位时序: 写BTR寄存器,设置预分频、PTS、PBS1、PBS2、SJW。
- 配置验收滤波: 设置ID掩码,只接收需要的帧。我建议一开始把滤波全开,调试通了再收紧。
- 退出初始化: 清除Init位,进入正常模式。等待总线空闲,就可以收发数据了。
代码片段(CMS32初始化):
void CAN_Init(void)
{
// 1. 时钟使能
RCC->APB1ENR |= RCC_APB1ENR_CANEN;
GPIO_Config(CAN_TX_PIN, GPIO_MODE_AF);
GPIO_Config(CAN_RX_PIN, GPIO_MODE_AF);
// 2. 进入初始化模式
CAN->MCR |= CAN_MCR_INRQ;
while(!(CAN->MSR & CAN_MSR_INAK));
// 3. 配置位时序:500kbps,采样点87.5%
CAN->BTR = (4 << CAN_BTR_BRP_Pos) | // 预分频5
(3 << CAN_BTR_TS1_Pos) | // PBS1=4Tq
(2 << CAN_BTR_TS2_Pos) | // PBS2=3Tq
(1 << CAN_BTR_SJW_Pos); // SJW=2Tq
// 4. 配置验收滤波(全通)
CAN->FMR |= CAN_FMR_FINIT;
CAN->FA1R = 0;
CAN->FS1R = 0;
CAN->FM1R = 0;
CAN->FFA1R = 0;
CAN->FMR &= ~CAN_FMR_FINIT;
// 5. 退出初始化
CAN->MCR &= ~CAN_MCR_INRQ;
while(CAN->MSR & CAN_MSR_INAK);
}
我曾经犯过的错: 配置完位时序后直接发数据,结果总线一直报错。后来发现是退出初始化模式后,需要等待至少11个隐性位(总线空闲)才能发送。CAN控制器内部有个状态机,别急。
3.5 本章知识体系图
下面这张图把物理层、帧结构、位时序和初始化串起来了。你可以把它当思维导图用。