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 Mbps125 kbps
总线长度(1Mbps)40m500m
终端电阻120Ω(两端各一个)可选
显性电平CAN_H=3.5V, CAN_L=1.5VCAN_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差分信号。收发器控制其实就三件事:

  1. 使能/禁能:通过STB(待机)或EN(使能)引脚控制
  2. 模式切换:正常模式、待机模式、休眠模式
  3. 故障检测:检测总线短路、开路等异常

我常用的收发器初始化代码:

// 以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通信故障排查案例,保证让你印象深刻。