嵌入式Linux CAN驱动开发实战 - 第4节
CAN控制器硬件:SJA1000与MCP2515内部架构
做CAN驱动开发,绕不开两个经典芯片:SJA1000和MCP2515。一个是独立CAN控制器,另一个是SPI接口的CAN控制器。我当年刚接触CAN时,就是拿SJA1000练的手。说实话,搞懂这两个芯片的内部架构,后面写驱动就顺了。
SJA1000内部架构
SJA1000是NXP(原Philips)的经典产品。它支持两种模式:BasicCAN和PeliCAN。我建议你直接学PeliCAN模式,因为BasicCAN太老了,功能也弱。
它的内部结构大致分这么几块:
- 接口管理逻辑(IML):负责和CPU打交道,解析地址、读写信号
- 发送缓冲器(TXB):13字节,存放待发送的报文
- 接收缓冲器(RXB,64字节FIFO):存放收到的报文,支持多帧缓存
- 验收滤波器(ACF):硬件过滤报文ID,减轻CPU负担
- 位流处理器(BSP):负责CAN帧的组装/拆解、位填充、CRC校验
- 位时序逻辑(BTL):处理CAN总线的位同步和采样点
关键点:SJA1000的接收FIFO是64字节,不是无限深。如果CPU读得慢,FIFO满了就会丢帧。我在项目里吃过这个亏,后来加了中断处理优先级才解决。
MCP2515内部架构
MCP2515是Microchip的产品,通过SPI接口和主控通信。它的内部结构和SJA1000类似,但多了几个特色:
- SPI接口模块:负责和主控通信,支持最高10MHz的SPI时钟
- 控制逻辑:解析SPI指令,操作内部寄存器
- 发送缓冲器(TXB0/TXB1/TXB2):3个独立的发送缓冲区,每个可存1帧
- 接收缓冲器(RXB0/RXB1):2个接收缓冲区,每个可存1帧
- 验收滤波器(6个):比SJA1000多,过滤更灵活
- CAN协议引擎:和SJA1000的BSP+BTL类似
我的经验:MCP2515的SPI通信有坑。它的SPI指令格式是固定的,读写寄存器前必须先发指令码。我见过有人把SPI时序搞反了,结果读回来的数据全是0xFF。
CAN控制器寄存器映射
寄存器映射是驱动开发的核心。说白了,就是知道哪个地址对应什么功能。我习惯把寄存器分成几类:控制类、状态类、数据类。
SJA1000寄存器映射(PeliCAN模式)
| 地址偏移 | 寄存器名称 | 功能描述 |
|---|---|---|
| 0x00 | MOD | 模式寄存器(复位/工作/睡眠) |
| 0x01 | CMR | 命令寄存器(发送/接收/清除) |
| 0x02 | SR | 状态寄存器(总线状态/错误状态) |
| 0x03 | IR | 中断寄存器(发送/接收/错误中断) |
| 0x04-0x05 | ACR0-ACR1 | 验收代码寄存器 |
| 0x06-0x07 | AMR0-AMR1 | 验收屏蔽寄存器 |
| 0x08 | BTR0 | 总线时序寄存器0(波特率预分频) |
| 0x09 | BTR1 | 总线时序寄存器1(采样点配置) |
| 0x0A | OCR | 输出控制寄存器 |
| 0x10-0x1C | TXB | 发送缓冲区(13字节) |
| 0x14-0x20 | RXB | 接收缓冲区(13字节) |
注意:SJA1000的寄存器在复位模式下才能配置。如果你在运行模式下写BTR0/BTR1,写不进去的。我刚开始调试时,波特率怎么配都不对,折腾了半天才发现是这个原因。
MCP2515寄存器映射
MCP2515的寄存器通过SPI访问,地址空间是8位的。它的寄存器布局和SJA1000不太一样,但功能类似:
| 地址 | 寄存器名称 | 功能描述 |
|---|---|---|
| 0x0F | CANCTRL | CAN控制寄存器(模式选择) |
| 0x0E | CANSTAT | CAN状态寄存器 |
| 0x2C | CNF1 | 配置寄存器1(波特率预分频) |
| 0x2D | CNF2 | 配置寄存器2(采样点) |
| 0x2E | CNF3 | 配置寄存器3(同步跳转宽度) |
| 0x30-0x3F | TXB0/TXB1/TXB2 | 发送缓冲区(每个14字节) |
| 0x60-0x6F | RXB0 | 接收缓冲区0 |
| 0x70-0x7F | RXB1 | 接收缓冲区1 |
小技巧:MCP2515的寄存器地址不是连续的。比如CNF1在0x2C,CNF2在0x2D,但中间跳过了很多地址。写驱动时最好用宏定义,别硬编码地址,否则容易写错。
CAN控制器初始化流程
初始化流程是驱动开发的第一步。我总结了一个通用流程,SJA1000和MCP2515都适用,只是寄存器名字不同。
初始化步骤
- 硬件复位:拉低复位引脚,等待至少10ms
- 进入配置模式:设置模式寄存器,进入复位/配置模式
- 配置波特率:设置BTR0/BTR1(SJA1000)或CNF1/CNF2/CNF3(MCP2515)
- 配置验收滤波器:设置ACR/AMR(SJA1000)或RXF0-RXF5(MCP2515)
- 配置中断:使能需要的中断源(发送完成、接收、错误)
- 退出配置模式:切换到工作模式
- 检查状态:读取状态寄存器,确认进入工作模式
SJA1000初始化代码示例
void sja1000_init(void)
{
// 1. 硬件复位(假设GPIO控制复位引脚)
gpio_set_value(RST_PIN, 0);
mdelay(10);
gpio_set_value(RST_PIN, 1);
mdelay(10);
// 2. 进入复位模式
write_reg(MOD, 0x01); // MOD.0 = 1 进入复位模式
while (!(read_reg(SR) & 0x01)); // 等待复位模式确认
// 3. 配置波特率(125kbps,16MHz晶振)
write_reg(BTR0, 0x03); // 预分频 = 4
write_reg(BTR1, 0x1C); // 采样点 = 87.5%
// 4. 配置验收滤波器(接收所有报文)
write_reg(ACR0, 0x00);
write_reg(ACR1, 0x00);
write_reg(AMR0, 0xFF); // 全屏蔽,不关心ID
write_reg(AMR1, 0xFF);
// 5. 使能中断
write_reg(IER, 0x07); // 使能发送、接收、错误中断
// 6. 退出复位模式,进入工作模式
write_reg(MOD, 0x00); // MOD.0 = 0 进入工作模式
while (read_reg(SR) & 0x01); // 等待退出复位模式
// 7. 检查状态
if (read_reg(SR) & 0x20) {
printf("SJA1000初始化成功\n");
} else {
printf("SJA1000初始化失败\n");
}
}
MCP2515初始化代码示例
void mcp2515_init(void)
{
// 1. 硬件复位(通过SPI指令)
spi_write_cmd(CMD_RESET);
mdelay(10);
// 2. 进入配置模式
spi_write_reg(CANCTRL, 0x80); // 进入配置模式
mdelay(1);
// 3. 配置波特率(125kbps,16MHz晶振)
spi_write_reg(CNF1, 0x03); // 预分频 = 4
spi_write_reg(CNF2, 0x90); // 采样点 = 75%
spi_write_reg(CNF3, 0x02); // 同步跳转宽度 = 2
// 4. 配置验收滤波器(接收所有报文)
spi_write_reg(RXF0SIDH, 0x00);
spi_write_reg(RXF0SIDL, 0x00);
spi_write_reg(RXM0SIDH, 0xFF); // 全屏蔽
spi_write_reg(RXM0SIDL, 0xFF);
// 5. 使能中断
spi_write_reg(CANINTE, 0x07); // 使能发送、接收、错误中断
// 6. 退出配置模式,进入工作模式
spi_write_reg(CANCTRL, 0x00); // 进入工作模式
mdelay(1);
// 7. 检查状态
uint8_t stat = spi_read_reg(CANSTAT);
if ((stat & 0xE0) == 0x00) {
printf("MCP2515初始化成功\n");
} else {
printf("MCP2515初始化失败\n");
}
}
避坑指南:我曾经在MCP2515初始化时,忘了等它退出配置模式就直接发数据。结果CAN总线上一帧都发不出去。后来加了状态检查才搞定。记住:配置完一定要确认模式切换成功。
初始化流程图
下面这张图把初始化流程串起来了,你写代码时可以照着这个逻辑走:
常见问题与避坑
初始化这块,我踩过的坑不少。挑几个典型的说说:
- 波特率配不对:SJA1000的BTR0和BTR1要一起算,不能只改一个。我建议用官方工具算,别手算。
- 验收滤波器设错了:MCP2515有6个滤波器,默认是关闭的。如果你没使能,所有报文都会被过滤掉。我刚开始调试时,总收不到数据,查了半天才发现是滤波器没开。
- 中断没清标志:CAN控制器中断触发后,必须读中断寄存器清标志。不清的话,中断会一直触发。这个坑我至少见过三次。
- SPI时序问题:MCP2515的SPI时钟不能太高,我一般用4MHz。超过10MHz容易出错,尤其是长线缆时。
总结一下:初始化流程其实不复杂,但细节多。我的建议是:先配好波特率,再配滤波器,最后使能中断。顺序别搞反了,否则可能出奇怪的问题。