嵌入式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都适用,只是寄存器名字不同。

初始化步骤

  1. 硬件复位:拉低复位引脚,等待至少10ms
  2. 进入配置模式:设置模式寄存器,进入复位/配置模式
  3. 配置波特率:设置BTR0/BTR1(SJA1000)或CNF1/CNF2/CNF3(MCP2515)
  4. 配置验收滤波器:设置ACR/AMR(SJA1000)或RXF0-RXF5(MCP2515)
  5. 配置中断:使能需要的中断源(发送完成、接收、错误)
  6. 退出配置模式:切换到工作模式
  7. 检查状态:读取状态寄存器,确认进入工作模式

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总线上一帧都发不出去。后来加了状态检查才搞定。记住:配置完一定要确认模式切换成功。

初始化流程图

下面这张图把初始化流程串起来了,你写代码时可以照着这个逻辑走:

CAN控制器初始化流程图 CAN控制器初始化流程图 硬件复位 进入配置模式 配置波特率 配置验收滤波器 配置中断 退出配置模式 检查状态 注意事项 • 配置模式才能写波特率 • 验收滤波器可设全屏蔽 • 中断按需使能 • 状态检查不能省略

常见问题与避坑

初始化这块,我踩过的坑不少。挑几个典型的说说:

  • 波特率配不对:SJA1000的BTR0和BTR1要一起算,不能只改一个。我建议用官方工具算,别手算。
  • 验收滤波器设错了:MCP2515有6个滤波器,默认是关闭的。如果你没使能,所有报文都会被过滤掉。我刚开始调试时,总收不到数据,查了半天才发现是滤波器没开。
  • 中断没清标志:CAN控制器中断触发后,必须读中断寄存器清标志。不清的话,中断会一直触发。这个坑我至少见过三次。
  • SPI时序问题:MCP2515的SPI时钟不能太高,我一般用4MHz。超过10MHz容易出错,尤其是长线缆时。

总结一下:初始化流程其实不复杂,但细节多。我的建议是:先配好波特率,再配滤波器,最后使能中断。顺序别搞反了,否则可能出奇怪的问题。