4. CAN通信协议详解:CAN总线物理层、CAN报文格式、CAN驱动配置、CAN收发器控制
大家好,我是老张。今天咱们聊聊CAN通信协议。说实话,在TCU开发中,CAN总线就是整车的神经系统。没有它,发动机、变速箱、ABS这些控制器就是一个个孤岛。我最早接触CAN是在一个商用车项目上,当时被总线错误帧折磨得够呛,后来才慢慢摸透了它的脾气。
4.1 CAN总线物理层:为什么两根线就能搞定?
CAN总线物理层,说白了就是一对双绞线——CAN_H和CAN_L。为什么两根线就能实现可靠通信?这里有个关键概念:差分信号。
差分信号的工作原理很简单:
- 显性电平(逻辑0):CAN_H = 3.5V,CAN_L = 1.5V,压差2V
- 隐性电平(逻辑1):CAN_H = 2.5V,CAN_L = 2.5V,压差0V
你想想看,如果外界有电磁干扰,两根线上会同时感应出相同的噪声。但差分接收器只关心两根线的电压差,噪声就被抵消掉了。这就是CAN能在发动机舱这种恶劣环境下稳定工作的原因。
关键参数速查表
| 参数 | 高速CAN(ISO 11898-2) | 低速CAN(ISO 11898-3) |
|---|---|---|
| 最大速率 | 1 Mbps | 125 kbps |
| 总线长度(1Mbps) | 40m | 500m |
| 终端电阻 | 120Ω(两端各一个) | 可选 |
| 显性电平 | CAN_H=3.5V, CAN_L=1.5V | CAN_H=4V, CAN_L=1V |
我在项目中遇到过一个问题:总线长度超过40米,通信就频繁出错。后来发现是终端电阻没匹配好。嗯,这里要注意——终端电阻必须装在总线两端,不能只装一个,也不能装在中间。
4.2 CAN报文格式:数据是怎么打包的?
CAN报文格式,我习惯把它分成两类:标准帧(11位ID)和扩展帧(29位ID)。TCU开发中,标准帧用得最多,因为整车厂通常把ID分配得很紧凑。
一个标准CAN数据帧长这样:
帧起始(1bit) + 仲裁场(12bit) + 控制场(6bit) + 数据场(0~8字节) + CRC场(16bit) + 应答场(2bit) + 帧结束(7bit)
这里有个容易踩的坑——仲裁场。CAN总线用ID来决定谁先发,ID越小优先级越高。我曾经在调试时发现,两个节点同时发数据,优先级低的那个总是发不出去。后来查了手册才明白,CAN的仲裁机制是「线与」逻辑:显性位(0)会覆盖隐性位(1)。
个人经验:分配CAN ID时,建议把关键报文(如转速、扭矩)放在低ID段,诊断报文放在高ID段。这样紧急数据总能抢到总线。
数据场最多8个字节,够用吗?说实话,对于TCU来说基本够用。比如发动机转速用2字节,车速用2字节,档位用1字节,剩下的还能塞点状态位。如果数据量超过8字节,就得用CAN FD或者多帧传输了。
4.3 CAN驱动配置:底层怎么初始化?
CAN驱动配置,说白了就是让MCU的CAN外设跑起来。以NXP S32K1系列为例,我一般这么配:
// 1. 使能时钟
PCC->PCCn[PCC_FlexCAN0_INDEX] |= PCC_PCCn_CGC_MASK;
// 2. 复位CAN模块
CAN0->MCR |= CAN_MCR_SOFTRST_MASK;
while(CAN0->MCR & CAN_MCR_SOFTRST_MASK);
// 3. 配置波特率(500kbps,时钟40MHz)
CAN0->CTRL1 = CAN_CTRL1_PRESDIV(3) | // 预分频:40M/(3+1)=10M
CAN_CTRL1_PROPSEG(6) | // 传播段:7个Tq
CAN_CTRL1_PSEG1(4) | // 相位段1:5个Tq
CAN_CTRL1_PSEG2(3); // 相位段2:4个Tq
// 总Tq = 1+7+5+4 = 17,波特率 = 10M/17 ≈ 588kbps
// 实际配500kbps需要调整预分频
// 4. 设置工作模式(正常模式)
CAN0->MCR &= ~CAN_MCR_HALT_MASK;
while(CAN0->MCR & CAN_MCR_FRZ_ACK_MASK);
避坑指南:我曾经在波特率配置上栽过跟头。采样点位置很关键,一般建议在75%~85%之间。如果采样点太靠前,容易误判总线上的毛刺;太靠后,又可能错过真正的边沿。我习惯把采样点设在80%左右。
波特率计算公式:
- 时钟频率 / (预分频+1) = 时间量子Tq频率
- 波特率 = Tq频率 / (1 + 传播段 + 相位段1 + 相位段2)
- 采样点 = (1 + 传播段 + 相位段1) / (1 + 传播段 + 相位段1 + 相位段2) × 100%
4.4 CAN收发器控制:怎么让信号上总线?
CAN收发器,比如TJA1040、SN65HVD230,负责把MCU的CAN_TX/CAN_RX信号转换成CAN_H/CAN_L差分信号。收发器控制其实就三件事:
- 使能/禁能:通过STB(待机)或EN(使能)引脚控制
- 模式切换:正常模式、待机模式、休眠模式
- 故障检测:检测总线短路、开路等异常
我常用的收发器初始化代码:
// 以TJA1040为例
void CAN_Transceiver_Init(void) {
// 配置STB引脚为输出,拉低进入正常模式
GPIO_WritePin(STB_PIN, 0);
// 配置INH引脚(如果有),用于控制收发器供电
GPIO_WritePin(INH_PIN, 1);
// 等待收发器稳定
delay_ms(10);
}
// 进入待机模式(低功耗)
void CAN_Transceiver_Standby(void) {
GPIO_WritePin(STB_PIN, 1); // 拉高进入待机
}
// 唤醒收发器
void CAN_Transceiver_Wake(void) {
GPIO_WritePin(STB_PIN, 0); // 拉低回到正常模式
delay_ms(5); // 等待唤醒
}
重要提醒:TCU在车辆熄火后通常要进入低功耗模式。这时候CAN收发器必须进入待机或休眠状态,否则会一直消耗电池电量。我见过一个项目,因为收发器没进休眠,整车静态电流超标,最后被客户投诉了。
收发器还有个隐藏功能——总线诊断。通过检测CAN_H和CAN_L的电压,可以判断总线状态:
- CAN_H=2.5V, CAN_L=2.5V:总线空闲
- CAN_H=3.5V, CAN_L=1.5V:显性位
- CAN_H=0V, CAN_L=0V:总线对地短路
- CAN_H=5V, CAN_L=5V:总线对电源短路
嗯,这些诊断功能在实车调试时特别有用。我一般会在TCU的故障码里定义几个CAN总线相关的DTC,比如U0073(CAN总线关闭)、U0100(与发动机失去通信)。这样售后工程师一看就知道问题出在哪。
好了,CAN通信协议就聊到这。下一章咱们讲CAN报文收发流程和中断处理,到时候我会分享一个实际项目中的CAN通信故障排查案例,保证让你印象深刻。