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模式,功能更全。
初始化步骤其实不复杂,但顺序错了就完蛋。我列一下核心流程:
- 进入复位模式(设置MOD.0=1)
- 配置时钟分频寄存器(CDR)
- 设置总线定时寄存器(BTR0、BTR1)——这决定了波特率
- 配置验收码和屏蔽码(ACR、AMR)——过滤报文用的
- 退出复位模式(设置MOD.0=0)
- 使能中断(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操作寄存器:
- 复位芯片(SPI指令0xC0)
- 配置CANCTRL寄存器——设置工作模式和CLKOUT
- 配置CNF1、CNF2、CNF3——设置波特率和采样点
- 配置RXBnCTRL——设置接收过滤
- 清除中断标志
- 设置CANINTE使能中断
- 进入正常模式(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数据采集的重头戏。