别看现在到处都在讲“新总线”、“新协议”,什么车载以太网、TSN、SOME/IP,说得天花乱坠。但只要你还做嵌入式、做汽车电子、做工业控制、做医疗器械,有一个基本功你是绕不开的,那就是CAN协议通信。我从刚开始接触单片机那会儿就折腾这东西,后来在好几个实际项目里不管是做车身控制、BMS数据上报,还是电机控制器之间的内部通信,用的都是CAN。说句不好听的,很多新项目宣称要革了CAN的命,结果你看量产车型里,动力域、底盘域、车身域的核心骨架,串来串去还是那两根双绞线。
这篇东西我打算写得实一点,不扯什么高深理论,就从一个干过几年活儿的人的角度,把CAN总线从物理层到报文层到调试现场的关键点都捋一遍。适合谁看?正打算用CAN做第一个项目的学生或者初学者,被CAN通信问题搞得焦头烂额的工程师,还有想系统梳理一下知识点准备面试的人。
1. 为什么一根双绞线上的“老古董”至今没被淘汰
先说说最根本的问题,CAN是上世纪八十年代搞出来的东西,它的通信速率跟现在的车载以太网比起来简直就是“电报”水平,最高1Mbit/s,常规民用车型用500kbit/s都已经算比较快的了。但为什么四十多年过去了,它仍然是汽车和工控领域最普及的现场总线?
答案只有四个字:稳定、便宜。CAN总线的物理层非常简单,两根线(CAN_H和CAN_L)双绞,不需要时钟线,节点只需要一个CAN收发器加控制器,成本几块钱就能搞定。而车载以太网虽然快,但它的物理层需要更复杂的编解码芯片,组网要交换机,PoE供电和线束要求也高,成本和服务质量不是同一个量级。
再说说咱们平时的嵌入式小项目,比如做一个平衡车底盘,用CAN把主控和四个轮的电机驱动器接起来,所有节点在同一根线上自主收发,根线就解决了原来需要好几路USART并用一堆IO口做片选逻辑的问题,而且CAN的抗干扰能力比USART强太多,工业现场电机一启动那干扰波形大家都懂的,普通串口在这种环境基本活不过三秒,CAN却稳如老狗。
所以我的看法是:CAN不是被淘汰的老技术,而是你看不懂的年轻协议太多,能稳定干活的太少。只要你还需要在恶劣环境下多点通信,CAN几乎都是第一选项。
2. 物理层设计:那两根线上的门道比你想的深
CAN的物理层用的也是差分信号,但跟RS485那种双极性差分不一样,CAN用的是“显性”和“隐性”两种电平状态。所谓显性就是CAN_H和CAN_L之间有电压差,逻辑上对应0;隐性就是两根线电压差不多,逻辑上对应1。这里最关键的是显性位会覆盖隐性位,这是后面仲裁机制的地基。
2.1 终端电阻不是随便焊上去的
很多人第一次画CAN电路时说,“两端各加120Ω”,然后就把电阻焊上了。其实这里有个细节:终端电阻是为了匹配线缆特性阻抗、抑制反射。大多数标准CAN线缆的特性阻抗大概是120Ω,所以规范要求在总线物理末端各接一个120Ω电阻,这样并联下来就是60Ω。但如果你用的是短距离、低速的场合,比如同一个板子上两个CAN节点之间,不接终端电阻往往也能工作,不过我不建议这么干,因为波形反射会在高速通信时造成乱码。
我还碰到过一种情况,有些板子自带了120Ω终端电阻,结果你把这个板子挂到一台已经在两端分别接了120Ω的总线上,总阻抗就变成了40Ω(两个120并联,再并一个内建的120),总线电平被拉低,通信直接异常。所以在实际项目里,每个节点板卡上最好做一个可选的跳线或者排阻开关,到底接不接电阻由“这个节点是否在总线末端”决定,而不是一律焊死。
2.2 电平阈值与收发器类型
CAN收发器负责把控制器送过来的显隐逻辑转换成物理电平。经典的高速CAN标准(ISO 11898-2)定义的显性电平大概在2V左右(CAN_H比CAN_L高约2V),隐性电平两根线都在2.5V附近。市面上常用的收发器像TJA1050、MCP2551、SN65HVD230这些,都用这个标准。但要注意,这是“高速CAN”,速率覆盖125k到1M。还有一种低速容错CAN(ISO 11898-3)采用不同的电平方案,最大速率125k,主要用于车身舒适域,两种方案不能直接混接。
从实际选型来看,我给个小建议:如果你的系统是5V单片机,配TJA1050或者MCP2551比较省事;如果是3.3V的单片机(比如STM32的很多系列),那最好直接选带3.3V兼容逻辑的收发器,例如TJA1051T/3或者SN65HVD230。不要图便宜随便拿一个,否则逻辑电平不匹配,通信时好时坏极难查。
2.3 供电与隔离容易踩坑
CAN收发器需要一个稳定的电源,通常在5V左右。如果你设计的节点分布在电机附近,或者跨设备远距离连接,那最好用隔离收发器,比如ISO1050这类,实现总线侧和逻辑侧的电气隔离。不隔离的后果是什么?总线两端地电位差过大的时候,会产生共模电流;几十米线跑下来,运气差的直接把收发器甚至MCU的IO击穿。前阵子有人问我为什么CAN节点老是“随机损坏”,一问说你没做隔离,总线长二十几米跨两栋楼,那不坏才怪。
实测经验:距离超过十米、节点间电源不共地、或者处在高干扰环境,直接用隔离方案,不要犹豫。虽然成本高一点,但比起返场维修,这点成本根本不算什么。
3. 报文格式与“11位ID”怎么读
CAN的数据链路层是现代协议栈最精彩的部分。它没有传统意义上的“地址”,每个报文只有ID加数据,ID同时承担了优先级标识和内容标识的双重身份。
3.1 标准帧与扩展帧的区别
标准帧采用的是11位ID,可以编码2048个不同ID,对于大多数应用来说已经够用。但如果系统特别复杂,比如整车的PDU(动力域)加上车身域舒适域再加上诊断,11位ID可能不够,于是就有29位ID的扩展帧。扩展帧是在11位ID之后又加了18位扩展ID,所以总ID长度29位。看CAN帧头的第二字节就能区分:标准帧的IDE位是“显性”,扩展帧是“隐性”。这个细节在做解析逻辑的时候特别重要,我见过不止一次把扩展帧当成标准帧解析,结果ID和数据全是乱的。
3.2 数据场和字节序的坑
一个标准数据帧能带0到8字节数据。为什么是8?因为CAN的位填充规则和帧长度有上限约束,数据段要是超过8字节,整个帧的位填充开销就太大了,误码率会上去,实时性也受影响。早期的CAN协议因此就把DL C(数据长度码)锁死在0到8。你在实际项目里要传更长的数据,就得自己分帧,或者用CAN-FD扩展帧(最多64字节),那个是后话。
字节序问题是我在调试中遇到最多的无语场景。CAN报文本身只定义了位顺序,厂商里A家默认高字节在前(大端),B家默认低字节在前(小端),两边协议一对接,数据对不上。同一个温度值,0x1A2B,一方读出来是0x1A2B,另一方读出来是0x2B1A,数值差了十万八千里。所以拿到协议文档第一件事,先确认传输方向是“Motorola格式”还是“Intel格式”,然后用代码里显式的移位拼接去处理,不要想当然。
比如解析一个16位电压值,数据在byte[2]和byte[3]里,小端格式的正确写法:
uint16_t voltage = data[2] | (uint16_t)data[3] << 8;不要写成“先拿高字节低字节”的通用逻辑就完事了,一定要去回看模块的DBC或通信矩阵里的定义。
3.3 位填充规则为什么存在
CAN帧在从SOF到CRC之间,如果连续出现五个相同的位,会自动插入一个相反电平的位,这叫“位填充”。插入位可不是为了浪费带宽,而是保证收发双方能从位流中提取同步时钟——因为CAN没有专用的时钟线。STM32的CAN外设或者独立的MCP2515控制器都自动处理位填充,不需要你操心,但理解这件事有助于你明白为什么CAN波特率的误差要求必须那么严格。
4. 非破坏性仲裁:怎么决定先听谁的
如果你把多个节点都接到同一条CAN总线上,它们同时往外发数据怎么办?是像以太网那样“先发先得,撞了就退避”吗?不是,CAAN用了一套非常优雅的机制叫非破坏性比特级仲裁。
4.1 仲裁原理一句话版
每个节点在发送每个位的同时,也在监听总线上的实际电平。如果它想发“显性”(0),但总线上同时有别的节点发了“隐性”(1),显性位会覆盖隐性位,所以它读回总线看到的是0,跟它想发的一样,它就把“我继续说明这个位是0”作为仲裁依据——只有当某个节点想发隐性位而总线上却是显性位时,它才明白“有更高优先级的节点在抢总线”,于是它立刻停止发送转为接收。
这意味着什么?ID值越小,优先级越高。因为ID靠前的位置发现冲突时,ID小的那些位置都是显性0,覆盖了优先级低节点的隐性1,低优先级节点只能让路,而高优先级节点整个过程根本没被打断,这就是“非破坏性”的由来。
4.2 怎么分配ID才合理
ID分配这事,说实话很多新人上来就乱来。这里给一套我常用的分配逻辑:
- 周期性控制类报文,比如电机转速、转向角,用低ID,保证抢占优先。
- 状态上报类报文,比如温度、电量、故障等级,放中段ID。
- 诊断类报文,比如读取序列号、固件版本,用高ID,因为这些报文本来就“不着急”。
- 尽量给每个报文功能一个“语义化”的ID,而不是临时起意用连续的编号硬凑,否则后期加报文要改一大堆。
另外多说一句,29位扩展帧并不是“一定比标准帧优先级低”的意思,优先级高低完全取决于ID编码后的位序列在仲裁段的比较结果,扩展帧只是因为头几位包含IDE位,位序上吃点亏,但不代表你必须把它排后面。
4.3 仲裁失败影响业务吗
低优先级节点在仲裁失败后会自动转入接收状态,不需要你什么“重发逻辑”。发完数据等它下次调度周期再来就行。很多嵌入式工程师一开始不理解,说“那我这个报文不是丢了吗?”其实不是,它只是“晚一点发”。只要你的ID优先级分配合理,不会出现饿死的情况。
5. 位时序、采样点、波特率:CAN之所以稳的核心
刚才说CAN没有时钟线,那它怎么保证每个节点采样某个位的时刻一致?答案是:每个节点都用自己的本地时钟,但通过同步段和重同步来不断校正采样点,让彼此的“节拍”尽量一致。这就涉及到波特率配置的细节了,绝大部分CAN不稳定问题,最终都能追溯到这儿。
5.1 位时间拆成四段,采样点落在哪很关键
CAN的每一个“位时间”被分成同步段(SYNC_SEG)、传播时间段(PROP_SEG)、相位缓冲段1(PHASE_SEG1)和相位缓冲段2(PHASE_SEG2)。采样点位于PHASE_SEG1和PHASE_SEG2的交界处,也就是大约在位的70%到80%位置。
为什么采样点不能放正中间?因为从发送端到接收端的信号经过总线传播有延迟,还有节点时钟漂移,你把采样点放在位的尾声,留给信号稳定更长时间,才能容忍更大的物理延迟和误差。一般我们推荐采样点设置在75%到85%之间,车规级总线宏定义很多直接取80%。
在实际配置中,你用的STM32CubeMX或者别的配置工具,会让你填预分频、时间段1、时间段2对应的量化时间单位数量。比如你想跑500kbps,外设时钟36MHz,这里可能就是要:同步段=1,时间段1=13,时间段2=6,重同步跳转宽度=1,最终位时间=20个量子,波特率=36MHz/(20×预分频值)等等。匹配上公式:波特率 = 外设时钟 /(预分频 × (1 + 时间段1 + 时间段2))。
5.2 为什么两台设备波特率明明“一样”却不通信
这个问题我调试过不止一次。两个设备都配了500kbps,示波器看起来帧格式也对,但连上就不出数据,或者频繁报错帧。原因往往是两个设备内部的采样点相差太远:一个是55%,一个是85%,虽然位时间一样,但每个节点采样判断电平的时刻差别太大,网络再有一点点反射干扰,误码就冒出来了。
排查办法:用CAN分析仪看总线波特率,然后分别读两个设备的寄存器配置,算出各自的采样点位置,把配置调到接近(差3%以内)再试。总线很短的时候影响不明显,总线一长、节点一多,马上现原形。
5.3 重同步机制是干嘛的
CAN还设计了“硬同步”和“重同步”。收到SOF帧起始位时的同步叫硬同步,强制把当前位时间与SOF边沿对齐;而后续报文中的边沿变化如果与预期时间有偏差,就通过调整同步跳转宽度(SJW)来修正。SJW的配置决定了每个位时间的同步能力,一般取1到2个时间量子就行,不需要太大,大反而让同步后采样点抖动增加。我见过有人为了“更稳”把SJW调到等于时间段长度,结果整个位时间变形,出错率更高,这就是典型的画蛇添足。
6. 错误处理与故障界定:CAN自己就是一套诊断系统
CAN的容错机制在工控现场总线里算非常强悍的,它定义了五种错误类型:位错误、填充错误、CRC错误、格式错误、应答错误。每个节点都会实时监测总线并统计错误计数。注意,它有“发送错误计数器”和“接收错误计数器”,两个计数器的增减规则不同。
6.1 错误状态机
当一个节点的发送错误计数或接收错误计数超过127,它会进入“错误被动”状态。在这个状态下,这个节点虽然还能收发数据,但不能再主动发送“活动错误标志”,只能发“被动错误标志”,说白了就是“犯了错但还留着一条小命,低调点”。
如果发送错误计数继续涨,超过255,节点直接进入“总线关闭”状态。此时它的收发器被控制器强制隔离,不再参与总线通信。这状态很像工厂里犯了重大事故被停职查看,等应用层检测到总线关闭,执行恢复流程(连续128次收到11位隐性位)才能重新上线。
6.2 错误帧不代表就是“出错”
很多人一看到CAN分析仪上一堆红色错误帧就慌,觉得通信坏了。其实错误帧是CAN为保证一致性故意暴露问题的机制,“宁可让所有节点都看到这个帧已损坏,也绝不让任何一个节点盲目采纳错误数据”。同时,错误帧也可能是其他节点攻击性发送导致总线上噪声太大。这个时候你要看的是错误帧的类型和出错位置,而不是单纯焦虑。
我在现场调BMS数据时,发现总有周期性的CRC错误帧,位置固定在某个特定区域。排查下来发现是一名现场维护人员把总线的一段拖链折成了锐角,并和动力电缆捆在一起走了好几米,信号被动力电缆的干扰“打”得一塌糊涂。处理方式:分开布线,换上屏蔽双绞线,错误帧立刻消失。所以说,出错先查物理层,别整天怀疑代码。
7. 验收过滤:别让MCU每帧都处理
如果总线上挂着几十个节点,每个节点每秒发几百帧数据,而你的主控只关心其中两三个ID的信号,怎么办?最简单粗暴的办法是收到每一帧都做一次软件判断,但这会让MCU的负载飙升,尤其当你在跑很重的控制算法时明显不够用。
所以CAN控制器几乎都内置了验收过滤器,也叫硬件滤波器。在STM32里,它有很多个过滤组,每个组你都可以配置成“掩码模式”或“列表模式”。
- 掩码模式:你指定一个ID掩码,对应位为1的必须匹配,为0的忽略。比如掩码为0x7FF表示全部11位都必须匹配,这等于只放行一个ID;掩码为0x000表示不关心任何位,所有帧都能进。
- 列表模式:最多配置几个指定的ID值,这几个ID的帧能进,其他全挡。
实际项目里我最常用的做法是:数据帧用掩码模式,把同一条功能域的ID放进来;诊断帧用列表模式,只放行特定两个ID。这样MCU只需要处理那些真正关心的报文,不用在中断里做一大堆无用的if判断。
要注意的是,不同系列的MCU过滤器的分组数目和位宽规则不一样。老款的STM32F1的过滤器有32位宽选项,可以覆盖整个扩展帧ID,而F4系列的过滤器组织方式又不同。写代码前先对着手册查一下,否则你以为自己写了掩码,实际过滤效果完全不是那么回事。
8. 从裸CAN帧到高层协议:为什么不建议自己定协议
很多人一开始做CAN系统,都是自己定义帧格式,比如“第一个字节是目标地址,第二字节是功能码……”,这样做可以理解,毕竟最简单。但等你系统变大,就会发现纯裸帧没有统一规范会导致维护灾难:每个模块的报文格式都不一样,A模块发的数据B模块解析错,还要拉上供应商开会对齐协议,一个突变报文就够你改三天。
所以当你的节点数超过三五个、或者产品需要跟别人调试时,尽量直接使用成熟的高层协议,无非几种选择:
| 协议 | 定位 | 适用场景 |
|---|---|---|
| CANopen | 基于对象字典+PDO/SDO | 工业设备、运动控制、伺服驱动 |
| J1939 | 面向重型车辆/发动机 | 卡车、农用机械、船舶动力 |
| UDS(ISO 14229) | 诊断协议 | 汽车售后诊断、刷写、标定 |
| DeviceNet | 基于CAN的应用层协议 | PLC与现场设备互联 |
我这里重点说下CANopen,因为它对嵌入式设备非常友好,PDO用于周期/事件触发的高实时数据交换,SDO用于读写对象字典,整个系统靠节点ID加对象字典就能对上含义。我自己做的某套传感器采集系统,用的就是CANopen的子集,把十六路模拟量周期上报到控制器,全程不需要写解析协议的逻辑,只要配置好映射关系,数据就到了该到的数组里——这种确定性和省心度,自定协议真给不了。
9. 一条CAN报文从发到收的完整旅程
我觉得到了这一步,可以把前面所有点整合起来,看一下一条标准帧从发送端到接收端是怎么流转的。
- 应用层往控制器里填入ID、数据长度和数据,比如ID=0x123,数据data[0]=0x5A,data[1]=0x01。
- 控制器计算出CRC校验,然后开始按位发送。发送过程中:先发SOF(一个显性位),再发仲裁段(11位ID+RTR+IDE等),接着控制段(DLC),然后是数据段,最后CRC段、ACK段、EOF。
- 在发送仲裁段时,如果还有别的节点也在发,那么每个位都实时比较,低优先级主动让路。
- 到达接收端后,接收端的硬件滤波器会筛选该ID;如果匹配,则将帧存入对应邮箱/缓冲区,同时置位接收标志。
- 软件层面收到标志后,从缓冲区取数据并做字节序转换、按位掩码提取有效位,更新对应变量。
- 任何一帧如果CRC错、格式错,节点会发错误帧,并更新错误计数,该帧直接丢弃。
这个过程已经高度硬件化,写代码的人不需要手动去拼每一个位,但理解了这条链路,你排查问题至少能确定“是卡在物理层还是数据链路层还是应用层”。
10. 现场调试必备:示波器、CAN盒与分析仪
说点实际的,真到调试阶段,光靠看灯号和打印日志是不够的。
10.1 最低限度:用USB-CAN分析仪抓帧
市面上的USB-CAN分析仪很多,配合PC工具就能看到总线上所有的报文、错误帧和负载率。这东西类似串口调试助手,但强大之处在于:能看总线负载、能看错误帧的分布、能按DBC解析报文。我最常干的事是“录一段总线波形,回放给新同事看”,这种回溯能力对排查偶发问题太重要了。
10.2 进阶手段:示波器看物理波形
分析仪能告诉你“报文内容不对”,但报不了“波形为什么变形”。这时候还要上示波器,直接看CAN_H和CAN_L的波形:显性电平幅度够不够(高速CAN大概1.5V到3.5V区间),隐性电平两线是否基本重合,位时间是否均匀,有没有反射导致的振铃。如果示波器能看到显性电平偏低、波形圆头,多半是终端电阻接错或者接触不良、节点太多导致负载过重。
10.3 实战中多次救命的排查清单
下面这份清单来自我多年的调试笔记,按概率从高到低排序:
- 检查终端电阻,总线上看等效阻抗是否为60Ω左右,用万用表在节点上量(断电后量)。
- 检查CAN_H和CAN_L是否接反,这在DIY杜邦线连接时特别常发生。
- 检查节点共地情况,隔离没隔离,地电位差会不会过大。
- 检查波特率和采样点配置,确认所有节点实际配置完全一致。
- 检查是否有节点电源不稳,尤其是收发器供电低至3.xV时,显性电平会不够导致间歇性通信失败。
- 检查屏蔽层或双绞线是否被布线成“单点接地多点接地”引出环路。
- 检查软件里是否错误屏蔽了中断,导致长期不进CAN接收中断,溢出丢帧。
- 最后才怀疑代码逻辑本身,比如DLC不匹配、ID配置错误、字节序反了。
备注一下:配置完一个新节点,最好先挂到分析仪上看看它单独发出来的帧内容是否正确,再并入系统。很多人图省事,直接把新节点插到总线里调,结果总线被它拉死,排查半天才发现是新节点的位时序配置有问题,导致错误帧刷爆。
11. 一套我常用的 CAN 初始化参考(以STM32为例)
虽然各家MCU配置方法不同,但初始化CAN外设的核心步骤大同小异。我拿STM32的bxCAN为例,给出一段简化但完整的初始化流程作为参考,重点不在代码能不能直接编译,而是帮你看清初始化到底做了哪几件事。
CAN_InitTypeDef CAN_InitStruct; CAN_FilterTypeDef CAN_FilterInitStruct; // 1. 使能时钟并配置引脚为复用推挽 __HAL_RCC_CAN1_CLK_ENABLE(); // 2. 配置CAN时序(波特率500k,采样点约75%) // 以APB1时钟36MHz为例,预分频=4,时间段1=13,时间段2=6 CAN_InitStruct.Prescaler = 4; CAN_InitStruct.SyncJumpWidth = CAN_SJW_1TQ; CAN_InitStruct.TimeSeg1 = CAN_BS1_13TQ; CAN_InitStruct.TimeSeg2 = CAN_BS2_6TQ; CAN_InitStruct.Mode = CAN_MODE_NORMAL; // 正常模式 CAN_InitStruct.AutoBusOff = DISABLE; CAN_InitStruct.AutoWakeUp = DISABLE; CAN_InitStruct.TimeTriggeredMode = DISABLE; CAN_InitStruct.ReceiveFifoLocked = DISABLE; HAL_CAN_Init(&CAN_InitStruct); // 3. 配置过滤器,只放行ID=0x123和0x456 CAN_FilterInitStruct.FilterIdHigh = 0x123 << 5; // 标准帧把ID放在高16位 CAN_FilterInitStruct.FilterIdLow = 0x456 << 5; CAN_FilterInitStruct.FilterMaskIdHigh = 0x7FF << 5; CAN_FilterInitStruct.FilterMaskIdLow = 0x7FF << 5; CAN_FilterInitStruct.FilterFIFOAssignment = CAN_FIFO0; CAN_FilterInitStruct.FilterActivation = ENABLE; HAL_CAN_ConfigFilter(&CAN_FilterInitStruct); // 4. 启动CAN并开启中断 HAL_CAN_Start(&CAN_Handle); HAL_CAN_ActivateNotification(&CAN_Handle, CAN_IT_RX_FIFO0_MSG_PENDING);这里要特别注意,FilterIdHigh和FilterIdLow的位置,不同HAL版本定义不太一样,但按标准帧ID左移5位这个套路能适配绝大多数场景。另外启动CAN时一定先配置过滤器再Start,否则前几帧可能漏进FIFO。
发送的时候直接往邮箱装消息,比如:
CAN_TxHeaderTypeDef TxHeader; uint8_t Data[8] = {0x5A, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; TxHeader.ExtId = 0; TxHeader.StdId = 0x123; TxHeader.IDE = CAN_ID_STD; TxHeader.RTR = CAN_RTR_DATA; TxHeader.DLC = 2; // 只发2字节 HAL_CAN_AddTxMessage(&CAN_Handle, &TxHeader, Data, &TxMailbox);发送时要检查HAL_CAN_AddTxMessage的返回值,如果返回的是HAL_BUSY说明邮箱都满着,你别不管三七二十一继续往里灌,灌多了除了丢帧没有别的结果。我一般会做一个发送队列,在发送函数里加一个简单信号量或标志位,确保上层不会覆盖未发送完成的缓存。
12. 几条我自己踩过的老坑
最后聊几个特别典型的坑,有些让我折腾到半夜,写出来大家避一避。
坑一:错误被动后自己把自己“禁言”的节点。之前有个节点老是掉线,检测发现它进入总线关闭状态后,按规范要等128个隐性位才能恢复,但因为我没开自动恢复功能,它就永远回不来。解决办法是开启自动总线关闭恢复,或者在错误中断里手动调用恢复函数。有的控制器默认自动恢复是关的,做完初始化一定要检查这个位。
坑二:误以为CAN收发器输出电平兼容3.3V。有个项目用3.3V单片机,收发器却选成5V逻辑版本,通信偶发性失败,查了好久才发现是逻辑电平接近阈值。后来换成支持3.3V逻辑的型号,再也没出过问题。收发器的选型不是只看“能不能收发”,还要看逻辑侧和MCU是否匹配。
坑三:双绞线绞距也有讲究。为了省成本,有人直接用普通排线代替双绞线,短距离可能没感觉,但线长拉长到五米以上,共模噪声和串扰就开始突显,错误帧数量一路走高。CAN规定用特性阻抗约120Ω的双绞线,目的是让两根线上的噪声尽量等量、等相位,在接收端做差分相减时互相抵消。你别小看这点,工业现场差一米都会出事。
13. 再做一次“为什么”的总结
不搞一堆宏大的展望,就说说我个人的习惯:把CAN协议看成一套完整的工程语言,它对布线有要求,对帧ID有语义要求,对时序有精度要求,但这些要求恰恰换来的是无主站、多节点、故障自诊断、强抗干扰这些实打实的优势。你越尊重它的物理特性和数据链路特性,它就越少给你找麻烦。
在实际操作中,我慢慢养成一个习惯:遇到CAN通信故障,先别急着翻代码,而是先在脑子里过一遍物理层、数据链路层、应用层的分层模型,从终端电阻、线缆、收发器查起,再查过滤器、波特率、采样点,最后才去代码里挑逻辑。大多数我经历的疑难杂症,都倒在了前面两层。这种排查思路写下来供你参考,希望能让你少走点弯路。