简介:这份由周立功推出的CAN例程与学习资料,围绕Philips/NXP SJA1000控制器展开,面向嵌入式开发者和CAN总线入门者,系统讲解CAN协议基础、SJA1000硬件连接、驱动编写与通信调试方法,涵盖从原理到落地的完整学习路径。压缩包共44个文件,包含asm汇编源码、c驱动程序和h头文件等核心代码,hex烧录文件与lst列表文件便于查看编译结果和调试,uv2/opt/plg等Keil工程文件可帮助还原开发环境,还附带一份PDF技术文档,整体约490KB,小巧但内容完整。已有4273人浏览学习。资料内含BasicCAN模块V1.00测试程序,并附有《CAN控制器SJA1000的控制模块》PDF说明,读者可结合源码与文档理解初始化配置、收发流程、错误处理和接收滤波等关键环节,既能作为学习CAN总线和SJA1000开发的入门参考,也可为实际项目中的驱动移植与调试提供直接借鉴。
1. 一套能把 SJA1000 调到不翻车的 CAN 例程与资料
做嵌入式的人对 CAN 总线的感情很复杂:协议本身不复杂,但真到了板子上,波特率对不上、总线静默、偶发丢帧,每一件都够折腾半天的。SJA1000 作为经典的独立 CAN 控制器,市面上资料不少,但多数是手册翻译和残缺例程,照着抄往往连初始化都过不去。业内某厂商整理的那套 CAN 例程和资料,几乎是围绕 SJA1000 最完整的一份合集:从寄存器手册到可编译的驱动源码,从波特率计算到验收滤波配置,全都摊开摆在明面上。这篇文章就按「原理 → 初始化 → 收发 → 排错 → 验证」这条路径,把这份资源的用法拆给你看。读完你不仅知道例程怎么跑,还能搞明白每个参数背后的依据,遇到问题也知道该往哪个寄存器里查。
2. SJA1000 的模式与寄存器地图:拿到资料先看这几页
2.1 为什么 SJA1000 到现在仍是调试者的“标准答案”
SJA1000 是 NXP(原 Philips)推出的独立 CAN 协议控制器,芯片本身不承担收发器的电气转换,只负责协议层:帧格式解析、错误检测、仲裁、验收滤波都在内部完成。外部只需要搭配一个 PCA82C250 或 TJA1050 这类物理层收发器,就能直接把差分信号接到总线上。和 MCU 内置 CAN 外设相比,独立控制器最大的优势在于调试时的“隔离感”——协议处理和业务代码完全解耦,总线出问题可以先排除控制器的嫌疑,把焦点集中在配置上。这套资料里的例程正是基于这种架构写的,代码里看不到任何 MCU 厂商的抽象层包装,寄存器操作是裸的,反而更适合学习。
SJA1000 有两种工作模式:BasicCAN 和 PeliCAN,由时钟分频寄存器 CDR 的 CAN 模式位控制。BasicCAN 是兼容早期 82C200 的模式,验收滤波只能做单字节掩码比较,报文标识符只参与 11 位标准帧;PeliCAN 是扩展模式,支持 29 位扩展帧、单双滤波切换、错误计数读取和自检模式。绝大多数工程直接选 PeliCAN,我建议你也是。
2.2 寄存器地图:把地址和状态位先背熟
SJA1000 的寄存器按地址 0x00 到 0x1F 排布,但在复位模式和工作模式下,部分地址的功能是重叠切换的。下面是 PeliCAN 模式下最核心的寄存器速查表:
| 地址 | 复位模式功能 | 工作模式功能 | 关键位说明 |
|---|---|---|---|
| 0x00 | 模式寄存器 MOD | 模式寄存器 MOD | bit0: RM 复位请求,置 1 进入复位模式 |
| 0x01 | 命令寄存器 CMR | 命令寄存器 CMR | bit0: TR 发送请求,bit2: RRB 释放接收缓冲区 |
| 0x02 | 状态寄存器 SR | 状态寄存器 SR | bit3: TBS 发送缓冲空闲,bit0: RBS 接收缓冲有数据 |
| 0x03 | 中断寄存器 IR | 中断寄存器 IR | bit2: TI 发送中断,bit0: RI 接收中断 |
| 0x04 | 验收码寄存器 ACR | 验收码寄存器 ACR | 期望接收的 ID 位模式 |
| 0x05 | 验收屏蔽寄存器 AMR | 验收屏蔽寄存器 AMR | 对应位为 1 表示该 ID 位不参与比较 |
| 0x06 | 总线定时寄存器 BTR0 | 总线定时寄存器 BTR0 | 波特率预分频和 SJW |
| 0x07 | 总线定时寄存器 BTR1 | 总线定时寄存器 BTR1 | TSEG1、TSEG2 决定采样点位置 |
| 0x08 | 输出控制寄存器 OCR | 输出控制寄存器 OCR | 控制 TX0/TX1 输出模式 |
| 0x1F | 时钟分频寄存器 CDR | 时钟分频寄存器 CDR | bit7: PeliCAN 模式,bit3: 关闭 CLKOUT |
复位模式和工作模式的切换由 MOD.RM 位控制。写寄存器前必须先置 RM=1,所有配置写完后清 RM=0,控制器才真正进入总线通信状态。这个「先复位再配置再释放」的顺序,就是 SJA1000 初始化逃不掉的三板斧。很多刚开始接触的人把配置代码写在正常工作模式下,寄存器写进去静默失败,读回来全是默认值,就是这个原因。
2.3 例程资料的阅读顺序:先看什么,后看什么
这套资源拿到手,不要从头到尾顺序读。我一般按这样的路径消化:先打开寄存器手册,重点看模式寄存器、状态寄存器和中断寄存器三个章节,把位定义弄清;然后翻例程里的初始化函数,对照寄存器表逐行验证;再看发送和接收两个接口函数,注意帧结构体是怎么填充的;最后才看应用层 demo。资料里的例程基本覆盖三件事:寄存器初始化、标准帧收发、扩展帧收发。有的版本还附带了一个简单的自检例程,直接用 STM 模式做回环测试,那个是验证硬件连接是否正常的好工具,后面我会专门说。
提示:资料里如果同时出现 BasicCAN 和 PeliCAN 两套例程,优先吃透 PeliCAN 版。当前实际项目中 PeliCAN 是绝对主流,BasicCAN 只在老产品兼容维护时会碰到。
3. 寄存器初始化实战:从复位请求到波特率参数的完整配置
3.1 第一步:进入复位模式并做基础时钟配置
SJA1000 上电后的默认状态是进入复位模式,但保险起见,初始化代码里仍然要显式地写一遍复位请求,防止芯片处在异常状态。整个配置流程的骨架是一个「操作寄存器 → 读回校验」的结构。下面是一段 16MHz 晶振下的 PeliCAN 初始化代码:
// sja1000_init.c void sja1000_init(void) { uint8_t tmp; // 进入复位模式:置 MOD.RM = 1 MOD = 0x01; // 等待进入复位模式 while (!(SR & 0x01)) { // SR.bit0 = 1 表示当前处于复位模式 } // 验收码:期望接收 ID = 0x123 // 标准帧下 ACR 只用到高 11 位 ACR = 0x12; // 高字节 ID 位 // 验收屏蔽:只有 bit4 位置 0,其他位都参与比较 AMR = 0xD0; // 波特率:16MHz 晶振,500kbps // BTR0 = 预分频 4 -> TQ = 4 * 2 / 16MHz = 250ns BTR0 = 0x03; // bit6:5 = 00, SJW = 1TQ; 低 6 位 = 3, 预分频 = 4 // BTR1 = TSEG1=5, TSEG2=2 -> 单 bit 时间 8 个 TQ BTR1 = 0x41; // 采样点 = (1 + 5) / 8 = 75% // 输出控制:正常输出模式,TX0 推挽输出 OCR = 0x1A; // 时钟分频:PeliCAN 模式,关闭 CLKOUT 输出 CDR = 0xC8; // 退出复位模式,进入工作状态 MOD = 0x00; // 确认成功退出复位 while ((SR & 0x01) != 0) { // 如果一直读不到 0,大概率是硬件复位引脚被拉低了 } }这里有个关键顺序:先配 ACR/AMR,再配波特率、输出控制,最后配时钟分频,整个顺序不能乱。ACR 和 AMR 依赖复位模式下的地址映射,而 CDR 的 PeliCAN 位一旦设置,后续所有寄存器映射都会变化,所以 CDR 必须放在验收滤波之后。实际工程里有人图省事把所有配置倒过来写,导致 AC R 写入对不上,滤波形同虚设。
3.2 波特率 BTR0/BTR1 的数学逻辑:算清楚再填
波特率配置是 SJA1000 初始化里大家最容易“照抄学”的地方。抄别人的初始化代码,数据能通,但换一个晶振或换一个波特率就懵。BTR0 的低 6 位是预分频器,决定 TQ 的长度,高 2 位是同步跳转宽度 SJW;BTR1 的低 4 位是 TSEG2,高 4 位是 TSEG1(实际值要加 1)。单个 bit 时间 = 1 + TSEG1 + TSEG2 个 TQ。
常见做法是按「目标波特率 → 反推 TQ 数量 → 再拆 TSEG1/TSEG2」的顺序来。以 16MHz 晶振、500kbps 为例:需要 1 bit 时间 = 2us。如果选预分频 4,则 TQ = 16MHz / 4 = 4MHz,即 250ns,一个 bit 就需要 2us / 250ns = 8 个 TQ。TSEG1 + TSEG2 = 8 - 1 = 7。采样点位置决定了系统对总线延时的容忍能力,一般选在 75% 附近,所以 TSEG1 = 5、TSEG2 = 2,写回寄存器时各减 1,得到 BTR1 = 0x41。SJW 取 1 个 TQ,BTR0 高位置 00,低 6 位是 3。
如果你拿到一套资料,例程里的波特率参数和你的板子晶振不匹配,先别急着怀疑代码逻辑。算一遍这套公式,改完预分频即可。常见的错法是直接把 BTR 参数从某个论坛帖子复制过来,晶振差了一倍,总线直接没有任何 ACK 波形。
3.3 验收滤波 AMR/ACR:屏蔽位不是“使能位”
验收滤波是 SJA1000 里语义最容易反的一个配置。AMR 的某一位为 1,代表该位不参与比较;为 0,代表该位必须与 ACR 对应位一致。上面代码里 AMR = 0xD0,二进制就是 1101 0000,意味着高两位和 bit4 位被屏蔽,只有低 5 位需要精确匹配。很多人一开始会把 AMR 和 ACR 的关系记反,下意识以为 AMR 置 1 是「必须相等」,结果总线上所有报文都能进接收缓冲。
滤波的匹配发生在接收帧的 ID 写入后。标准帧下,SJA1000 把 11 位 ID 左对齐放在 ACR 的高位,低 5 位自动补 0,这就是上面代码只给了 ACR=0x12 的原因。扩展帧的滤波则要用满 4 个字节 ACR,并且注意 ID 位序是高低字节错位排布的,直接用标准帧的思路套会遇见滤波失效的问题。资料里通常会给一组标准帧和一组扩展帧的 ACR/AMR 示例,建议在板子上分别验证,只测通了标准帧不代表扩展帧配置正确。
3.4 完整初始化序列:把参数核对工作做进代码里
初始化代码写完,建议加一段读回校验逻辑,把所有配置过的寄存器读出来和期望值比对。这一步在调试初期能省下大量时间。SJA1000 的寄存器在复位模式下可被正常读回,如果写入后立即读出与期望值不符,说明芯片没有真正进入复位模式,或者硬件复位引脚有问题。
// sja1000_config_check.c uint8_t config_selftest(void) { uint8_t ok = 1; ok &= (ACR == 0x12); ok &= (AMR == 0xD0); ok &= (BTR0 == 0x03); ok &= (BTR1 == 0x41); ok &= (OCR == 0x1A); ok &= (CDR == 0xC8); return ok; }这段读回校验在实际工作中非常有价值。初始化失败最常见的原因不是参数算错,而是操作时序根本没生效——要么没等待复位模式标志,要么 CDR 写入后芯片进入了非预期模式。把校验函数放在初始化后调用,每次上电都跑一遍,能把这些「玄学」直接变成可定位的问题。
4. 报文收发与中断处理:跑通发送、接收、释放三段关键流程
4.1 发送路径:从帧结构体到总线的完整动作
SJA1000 的发送接口核心是「写帧 → 请求发送 → 等待完成」,但每个环节都有状态位需要照看。发送缓冲区在地址 0x10 到 0x1C,标准帧只用前 2 字节 ID 和 8 字节数据。先检查状态寄存器 SR.bit3(TBS),只有缓冲空闲才能写入,否则直接覆盖会把上一帧还在排队的数据冲掉。写完后置 CMR.bit0(TR)启动发送,硬件会自动完成帧编码和位仲裁。
// sja1000_send.c uint8_t can_send_std(uint16_t id, uint8_t *data, uint8_t len) { uint8_t i; // 检查发送缓冲区是否空闲 if (!(SR & 0x08)) { return 0; // 缓冲区忙,返回失败 } // 填充标准帧 ID:SJA1000 需要左移 2 位 TXID1 = (uint8_t)(id >> 3); TXID2 = (uint8_t)((id & 0x07) << 5); // 写入数据长度和数据内容 TXDLC = (len & 0x0F); for (i = 0; i < len; i++) { TXDB[i] = data[i]; } // 请求发送 CMR = 0x01; // 等待发送完成或出现错误 while (!(SR & 0x08)) { // 发送期间 TBS 会被硬件拉低 // 如果等太久,检查总线是否短路或没有节点 ACK } return 1; }发一个 11 位标准帧时,ID 要左移 2 位再拆成两个字节,这个细节很多人会漏。SJA1000 的 TXID1 低 3 位是无效位,TXID2 高 5 位才是 ID 的最低 5 位。直接填 ID 的低字节会导致总线上看到的 ID 完全不对。接收方向同样有这个位序问题。整段代码里对 SR.bit3 的等待隐含一个前提——发送如果持续失败,状态寄存器的错误位会置位,这时需要的不是继续等,而是去读错误代码寄存器。
4.2 接收路径:中断里读完一帧后必须“释放”
接收流程由中断驱动比较常见。SJA1000 收到一帧合法报文后,会把数据写入接收缓冲区(FIFO),并置位 SR.bit0(RBS)和 IR.bit0(RI)。中断服务程序里做三件事:读帧、存数据、释放缓冲区。释放动作对应 CMR.bit2(RRB),这个命令必须写在读完数据之后。漏掉释放,硬件认为缓冲区仍被占用,后续所有报文都会被硬件丢弃,表现为「只收到第一帧,之后再也不进中断」。
// sja1000_isr.c void CAN_IRQHandler(void) { uint8_t irq, i; // 读中断寄存器,一次处理全部中断源 irq = IR; if (irq & 0x01) { // 接收中断 if (SR & 0x01) { // 读取标准帧 ID 和数据 rx_id = ((uint16_t)RXID1 << 3) | (RXID2 >> 5); rx_len = RXDLC & 0x0F; for (i = 0; i < rx_len; i++) { rx_data[i] = RXDB[i]; } // 释放接收缓冲区 CMR = 0x04; } } if (irq & 0x04) { // 发送中断,一帧发送完成 send_done_flag = 1; } }接收中断里还有一个隐藏逻辑:进入中断后要再次确认 RBS 状态位。中断标志置位和状态位置位之间可能有几个时钟周期的硬件延迟,直接去读 FIFO 有可能读到空数据。这个「先读状态再读数据」的顺序在高速总线上特别重要。资料里的例程在中断服务函数里基本都有这个判断,如果你发现收到的数据偶尔全是 0x00,检查一下是不是少了这个状态确认。
4.3 错误状态与总线恢复:不能只靠“重发一次”
SJA1000 的故障管理机制很值得利用。状态寄存器 SR.bit6(ES)为 1 表示错误状态(错误计数超过 96),SR.bit7(BS)为 1 表示总线关闭。总线关闭后控制器完全从总线上隔离,不再参与任何收发,需要等待 128 次总线空闲恢复。工程上常见做法是检测到 BS 置位后,进入复位模式重新初始化,而不是简单重发数据。
// sja1000_recover.c void can_bus_recover(void) { // 如果进入总线关闭状态 if (SR & 0x80) { // 进入复位模式 MOD = 0x01; // 等待复位完成 while (!(SR & 0x01)); // 恢复关键配置:波特率参数必须重新写 BTR0 = 0x03; BTR1 = 0x41; OCR = 0x1A; // 退出复位模式 MOD = 0x00; } }错误计数寄存器 ECC(地址 0x1C)在总线异常时很有诊断价值。它记录了最近一次错误的原因,比如位错误、格式错误、填充错误。看这个寄存器比瞎猜快得多。很多现场问题排查到最后,发现是收发器芯片的终端电阻没焊,导致位回读不一致,ECC 显示的全是位错误。这个细节资料里不一定会直接提示,但寄存器手册里会写,需要自己对照错误码表推一遍。
5. 排查与避坑:SJA1000 调试里最常见的五个翻车点
5.1 寄存器写不进,读回全 0xFF
现象:初始化函数执行完,读回校验一片红,所有寄存器默认值都没变。 原因:SJA1000 没有真正进入复位模式。硬件复位引脚被自动下载器拉低,或上电时序不满足要求,导致 MOD 寄存器写入不生效。整个初始化过程对复位模式的进入是有时序依赖的。 解决:初始化前先确认复位引脚电平状态,上电后延时 10ms 再操作寄存器。进复位模式后,不要急着连续写寄存器,先读一次 MOD,确认 bit0=1 再继续。如果读回一直是 0xFF,大概率不是软件问题,用示波器量一下复位引脚和晶振引脚。
5.2 波特率配好了,但总线上全是错误帧
现象:自测模式收发正常,接入总线后报错不断,用 CAN 分析仪看到的帧全是异常帧。 原因:采样点位置不对。有些人只按预分频算出 bit 时间,TSEG1 和 TSEG2 随便分配,导致接收采样点落在位边沿附近,总线稍有容性负载就容易采样错。这个属于配置合法但不合理的典型情况。 解决:采样点选在 75%~80%。按前面公式重新分配 TSEG1 和 TSEG2。总线上节点越多、线缆越长,采样点越要偏向 80%。如果晶振偏差大,还要把 SJW 调大到 2 个 TQ,增强同步容错。
5.3 验收滤波配了没效果,什么帧都能进
现象:只希望接收 ID=0x123 的帧,实际 ID=0x456 也进来了,程序里过滤逻辑形同虚设。 原因:AMR 和 ACR 的位对应关系弄反。AMR 置 1 表示屏蔽比较,置 0 表示必须匹配。很多人把 AMR 当使能寄存器,以为置 1 的位参与比较。 解决:重新梳理掩码逻辑。期望精确匹配的位,AMR 对应位必须为 0。另外要注意复位模式下写入 ACR 和 AMR 的顺序,改完滤波后必须重新进入复位模式再写,否则写的是工作模式下的映射地址,效果等同于没配。
5.4 自检模式通了,接总线后一帧都发不出
现象:STM 自检模式自发自收完全正常,接入外部节点后 ACK 一直等不到,发送超时。 原因:自检模式下的回环发生在芯片内部,CAN_TX 直接回连 CAN_RX,根本不经过外部收发器。总线侧没有其他节点应答 ACK,SJA1000 的发送会因无应答而反复报错。 解决:确认当前代码没有意外使能 STM 位。接入总线调试时 MOD 必须为 0x00 正常模式。这个坑很容易在调试初期栽进去——自检正常给了信心,结果一接真总线就翻车,让人误以为是收发器烧了。
5.5 收发器输出波形异常,看起来像没有驱动能力
现象:示波器量收发器 TX 引脚有波形,但总线差分电压很小,远端的设备收不到。 原因:OCR 输出模式配置不当,或收发器型号与输出模式不匹配。SJA1000 的 TX0/TX1 配置成推挽还是开漏,直接影响与收发器输入级的配合。有的收发器需要 TX1 反转模式。 解决:先确认板子上收发器的输入引脚是单端还是差分型,再回到 OCR 寄存器做对应配置。资料里的例程一般会覆盖几种常用输出模式,直接抄之前先看懂硬件设计,不能不管不顾照搬。
6. 验证三板斧:回环、示波器、长时间压力测试
协议栈调通后,真正检验配置的是验证手段。第一板斧是回环测试。SJA1000 的 PeliCAN 模式支持 STM 自检位,置位后发送帧会被芯片内部直接接收,不经过总线。这个测试能确认控制器本身的收发路径和验收滤波是否配置正确。测完记得把 STM 位清掉,不然接到真总线上行为会很诡异。
第二板斧是示波器量物理层。在确认软件收发正常后,把探头点在收发器的 CAN_H 和 CAN_L 之间。正常波形是 2V 左右的差分电压翻转,显性电平 2V、隐性电平 0V。如果波形幅值明显偏低,检查 120 欧终端电阻是否缺失;如果有回波振铃,检查总线分支线是否过长。这一板斧能直接定位到物理层的隐性故障,是 SJA1000 调试里最有效的外部验证手段。
第三板斧是长时间压力测试。在固定波特率下让两个节点互发,持续跑满 8 字节数据帧,观察错误帧计数。SJA1000 的收发错误计数可以从 ECC 和 SR 读到。如果连续运行数小时错误计数为零,基本可以判断配置和物理层都处于合理状态。全套验证完成后,把那串经过验证的 BTR 参数和 ACR/AMR 组合记录下来,形成你的标准配置模板。从那以后我每次接手新板子都强制走一遍「回环 → 示波器 → 压力测试」这个流程,再也没出现过「代码跑一年突然丢帧」的玄学问题。这份资料把寄存器手册、例程和调试心得绑在一起,拿来按这套路径走一遍,你会省下不少自己摸索的时间。希望帮到你。
本文还有配套的精品资源,点击获取