3、CAN总线基础:CAN协议物理层与数据链路层、CAN报文格式(标准帧/扩展帧)、CAN控制器(SJA1000/MCP2515)初始化

各位同学,咱们今天聊聊CAN总线。说实话,在TCU开发里,CAN总线就是命脉。没有它,发动机、变速箱、ABS这些节点就是孤岛。我最早接触CAN是在一个商用车项目上,当时被总线上的干扰搞得焦头烂额,后来才明白——物理层没搞透,上层协议再花哨也白搭。

3.1 CAN物理层:差分信号与总线电平

CAN物理层用的是差分信号。说白了,就是两根线——CAN_H和CAN_L。它们之间的电压差决定了总线状态。

  • 显性电平(Dominant):CAN_H比CAN_L高2V左右。逻辑上代表“0”。
  • 隐性电平(Recessive):CAN_H和CAN_L电压接近。逻辑上代表“1”。

为什么会这样设计?抗干扰啊!两根线绞在一起,外部噪声对两根线的影响几乎相同,差分电压基本不变。我在一个EMC测试不过的项目里,就是靠重新调整CAN收发器的共模扼流圈搞定的。

关键参数:CAN总线终端电阻必须是120Ω,两端各一个。少了反射,多了信号衰减。别问我怎么知道的——我曾经在实验室里用示波器看到过振铃波形,那叫一个惨。

3.2 CAN数据链路层:帧结构与仲裁机制

数据链路层是CAN协议的核心。它定义了报文怎么打包、怎么发送、怎么处理冲突。

CAN的仲裁机制很有意思。多个节点同时发送时,谁的ID优先级高(数值小),谁就赢。你想想看,这就像开会时谁嗓门大谁先说。但CAN用的是“线与”逻辑——显性位会覆盖隐性位。所以发送隐性位的节点发现自己输了,立刻转为接收。

嗯,这里要注意:仲裁过程中不会丢失数据。这是CAN协议最巧妙的地方。

3.3 CAN报文格式:标准帧 vs 扩展帧

报文格式分两种:标准帧(11位ID)和扩展帧(29位ID)。

字段 标准帧 扩展帧
ID长度 11位 29位
数据长度 0~8字节 0~8字节
帧类型标识 IDE=0 IDE=1
应用场景 简单系统 复杂网络

我个人习惯在TCU项目里用标准帧。为什么?因为11位ID足够区分发动机、变速箱、ABS这些节点了。扩展帧虽然ID多,但帧头更长,总线利用率反而低。

避坑指南:我曾经在一个项目里混用了标准帧和扩展帧,结果CAN控制器死活不响应。后来发现——SJA1000的PeliCAN模式下,必须显式配置帧格式。默认是标准帧,你得手动打开扩展帧使能位。

3.4 CAN控制器初始化:SJA1000

SJA1000是经典的独立CAN控制器。它有两种模式:BasicCAN和PeliCAN。我建议直接用PeliCAN模式,功能更全。

初始化步骤其实不复杂,但顺序错了就完蛋。我列一下核心流程:

  1. 进入复位模式(设置MOD.0=1)
  2. 配置时钟分频寄存器(CDR)
  3. 设置总线定时寄存器(BTR0、BTR1)——这决定了波特率
  4. 配置验收码和屏蔽码(ACR、AMR)——过滤报文用的
  5. 退出复位模式(设置MOD.0=0)
  6. 使能中断(IER寄存器)

代码示例(伪代码风格):

// SJA1000初始化(PeliCAN模式)
void SJA1000_Init(void) {
    // 1. 进入复位模式
    MOD = 0x01;
    
    // 2. 配置时钟分频
    CDR = 0x88;  // PeliCAN模式,CLKOUT关闭
    
    // 3. 设置波特率:125kbps(假设晶振16MHz)
    BTR0 = 0x03; // SJW=1,BRP=4
    BTR1 = 0x1C; // TSEG1=7,TSEG2=2
    
    // 4. 设置验收码:接收所有报文
    ACR0 = 0x00;
    ACR1 = 0x00;
    ACR2 = 0x00;
    ACR3 = 0x00;
    AMR0 = 0xFF; // 全屏蔽,不关心ID
    AMR1 = 0xFF;
    AMR2 = 0xFF;
    AMR3 = 0xFF;
    
    // 5. 退出复位模式
    MOD = 0x00;
    
    // 6. 使能接收中断
    IER = 0x01;
}

注意:波特率计算一定要准。BTR0和BTR1的配置直接决定了采样点位置。我建议采样点设在75%~85%之间。太靠前容易采到跳变沿,太靠后抗干扰差。我曾经因为采样点设在90%,结果总线长度超过50米就丢帧。

3.5 CAN控制器初始化:MCP2515

MCP2515是Microchip的SPI接口CAN控制器。它比SJA1000新,支持SPI通信,适合MCU引脚不够的场景。

初始化流程类似,但通过SPI操作寄存器:

  1. 复位芯片(SPI指令0xC0)
  2. 配置CANCTRL寄存器——设置工作模式和CLKOUT
  3. 配置CNF1、CNF2、CNF3——设置波特率和采样点
  4. 配置RXBnCTRL——设置接收过滤
  5. 清除中断标志
  6. 设置CANINTE使能中断
  7. 进入正常模式(CANCTRL.REQOP=0x00)

代码示例:

// MCP2515初始化(125kbps)
void MCP2515_Init(void) {
    // 1. 复位
    SPI_Write(0xC0, 0x00);
    delay_ms(10);
    
    // 2. 配置CANCTRL
    SPI_Write(0x0F, 0x80); // CLKOUT=8MHz,正常模式
    
    // 3. 配置波特率
    SPI_Write(0x2A, 0x03); // CNF1: SJW=1,BRP=4
    SPI_Write(0x2B, 0xBC); // CNF2: TSEG1=7,采样点=87.5%
    SPI_Write(0x2C, 0x05); // CNF3: TSEG2=2,SOF=1
    
    // 4. 接收缓冲器配置
    SPI_Write(0x60, 0x60); // RXB0CTRL: 接收所有报文
    SPI_Write(0x70, 0x60); // RXB1CTRL
    
    // 5. 清除中断
    SPI_Write(0x2C, 0x00); // CANINTF清零
    
    // 6. 使能接收中断
    SPI_Write(0x2B, 0x01); // CANINTE: RX0IE=1
    
    // 7. 进入正常模式
    SPI_Write(0x0F, 0x00); // REQOP=0x00
}

个人经验:MCP2515有个坑——它的SPI时钟频率不能太高。我试过10MHz,结果读回来的数据全是0xFF。后来降到5MHz就正常了。你想想看,SPI线长了,信号质量下降,高频就容易出错。所以建议SPI时钟不要超过晶振频率的1/4。

3.6 两种控制器的对比与选型

特性 SJA1000 MCP2515
接口 并行(8位数据/地址复用) SPI(最高10MHz)
引脚数 28脚 18脚
缓冲区 单发送缓冲,双接收缓冲 双发送缓冲,双接收缓冲
过滤能力 4字节验收码+4字节屏蔽码 2组过滤(每组4字节)
典型应用 工业控制、汽车ECU 嵌入式系统、IoT节点

我个人习惯:如果MCU引脚够用,用SJA1000,因为它并行接口速度快。如果MCU引脚紧张,比如用STM32F103C8T6这种小封装,MCP2515更合适。但不管用哪个,初始化顺序和寄存器配置一定要仔细核对数据手册。

好了,CAN总线基础就讲到这里。下一章咱们聊聊CAN应用层协议——J1939和CANopen,那才是TCU数据采集的重头戏。