1. 为什么波特率相同,节拍还是对不齐:从一次现场故障说起
做汽车电子和工业通信的工程师,很少有没被CAN总线"玄学故障"折磨过的——波形看着正常、波特率也都对,但报文就是偶尔错一帧,严重的时候整个网络直接卡死。我曾经在某设备联调现场耗掉一整个下午,排查的就是这样一个问题:三个节点单独跑都稳稳当当,接在同一根总线上之后,接收错误计数一路涨,错误帧隔几秒就蹦出来一次。用示波器抓波形,电平、波特率、帧格式全都没毛病,最后打开三个节点的位时序配置寄存器,我才找到了真正的差异——三个节点的采样点分别是68%、80%和87.5%,波特率都是500kbps,但它们对"一个bit到底怎么切分"的理解完全不同。
这正是CAN总线同步机制要解决的核心问题。总线上每个节点都有自己的本地时钟,没有任何一根独立的时钟线把大家拉到完全一致,CAN能工作,靠的是每个节点把位时间切成小段,然后在信号跳变沿上不断做"硬同步"和"重同步",把彼此的节奏校准到同一个节拍上。采样点只要错开几个百分点,长距离、高负载场景下就会隐藏不住,最终变成误码和重传。
这篇文章我想把CAN总线同步机制从头到尾拆开讲透:位时间为什么必须切成多段、硬同步和重同步到底怎么工作、采样点和SJW应该怎么配、传播延迟和时钟误差对同步有什么影响,最后再给三个现场排查案例。内容适合刚接触CAN的初学者,也适合那些被偶发错误帧困扰、想搞明白"为什么明明波特率一样还是不同步"的工程师。
1.1 故障现场:三个节点各自正常,组网却出错
那次联调环境并不复杂,A节点是一个主控板,B和C是两个执行器,总线总长不到两米,终端电阻也都在。单独把B接上去,连续跑一整天,错误帧计数纹丝不动;单独把C接上去也一样。但只要A、B、C三个节点同时上电,问题就来了。
最先出现的其实不是通信错误本身,而是执行器的动作偶发不响应。查应用层看不出问题,示波器抓波形也看不出明显的电平畸变。直到我把各个节点的错误状态寄存器读出来,才发现B节点的接收错误计数在持续增长,而且时不时报出CRC错误。按照经验,CRC错误大多意味着某个节点在某一位上采样到了错误的电平值,而不是数据真的被干扰了。
1.2 先看真凶:位时序被"默认值"坑了
逐节点读位时序寄存器之后,真相很明显了:三个节点虽然都配置成500kbps,但内部对位时间的切分方式完全不同。A节点的采样点落在68%,B节点是80%,C节点是87.5%。
问题就出在采样点不一致上。A节点采样太早,总线信号从远端的B节点传播过来需要时间,边沿刚刚落下、电平还没完全稳定,A节点就已经采样了。在某些位组合下,它读到的电平和实际发送的电平不一致,于是CRC校验失败。把A节点的采样点调成和B、C接近之后,错误帧立刻消失。
这让我后来养成了一个习惯:任何多节点组网的项目,第一件事就是统一所有节点的位时序参数,而不只是波特率。波特率相同只是"每个bit的时间长度一样",但采样点差多少、同步能力差多少,完全取决于位时序的具体配置。
2. 位时间的四段结构:一个bit是怎么被切开的
要说清楚同步机制,绕不开位时间结构。CAN总线上传输一个bit,在时间轴上并不是简简单单占一个固定时长,而是被切成了四个先后衔接的时间段。理解这四个段,采样点这个概念就自然清楚了。
2.1 先认识最小时间单位:tq
位时间的最小颗粒叫时间量子,简称tq(Time Quantum)。一个tq的长短由控制器时钟和预分频器共同决定:
tq = 1 / (控制器时钟频率 / 预分频值)
举例来说,控制器外设时钟是40MHz,预分频器设成4,那么每个tq就是100ns。每个bit时间由一个整数个tq组成,典型值在8到25个tq之间。自己算的时候,记得先确认你的芯片有没有额外的分频,很多芯片把BRP(波特率预分频)和时钟源选择分开,配错了会导致tq和你预期完全对不上。
tq这个概念的引入,让CAN控制器可以用一个计数器来管理位时间:每过一个tq,计数器加1,走到采样点就采一次电平,按完整个位时间后清零重新开始。同步机制对这个计数器的干预,就是后面要讲的硬同步和重同步。
2.2 四段位时间的分工与采样点
一个完整的CAN位时间,按标准分为四段:
| 段名 | 英文缩写 | 作用 | 典型长度(tq) |
|---|---|---|---|
| 同步段 | SYNC_SEG | 用于检测信号跳变沿,总线跳变应该落在这里 | 固定1 |
| 传播段 | PROP_SEG | 补偿信号在总线上的传播延迟 | 1~8 |
| 相位缓冲段1 | PHASE_SEG1 | 采样点之前可被延长的时间段 | 1~8 |
| 相位缓冲段2 | PHASE_SEG2 | 采样点之后可被缩短的时间段 | 1~8 |
四段加起来就是一个位时间,采样点就位于PHASE_SEG1和PHASE_SEG2的边界处。换句话说,采样点的位置 = (SYNC_SEG + PROP_SEG + PHASE_SEG1) / (四段之和) × 100%。
拿一个常见的500kbps配置举例:外部时钟40MHz、预分频4,tq为100ns,一个500kbps的位时间正好是2000ns,也就是20个tq。把这20个tq按"1 + 3 + 13 + 3"分配,采样点就是(1 + 3 + 13) / 20 = 85%。
SYNC_SEG固定为1个tq,这是所有CAN控制器的共同规定。跳变沿只要落在这个段里,就说明本节点的位计时相位和总线上实际传输的位置基本对齐。
PROP_SEG负责吸收传播延迟。信号从发送节点到最远接收节点需要时间,接收节点做出反应再传回来又需要时间,所以这个段至少要覆盖两倍的端到端传播延迟。总线越长,这个段占的tq就要越多。
PHASE_SEG1和PHASE_SEG2是重同步的"可调节区"。当检测到边沿偏移时,控制器可以把PHASE_SEG1延长,或者把PHASE_SEG2缩短,从而微调采样时机。这也是整个同步机制最核心的执行机构。
2.3 为什么采样点必须在"中间偏后"
采样点的位置不是拍脑袋定的。CAN协议要求采样点放在位时间的后半段,实际工程中绝大多数配置落在75%~90%之间,85%和87.5%是最常见的两个值。
原因有两个。第一,总线信号从发送节点到接收节点有传播延迟,边沿到达接收节点时已经比发送时刻晚了一段时间,采样点太靠前,看到的电平可能还是旧的。第二,边沿附近往往存在振铃和过冲,采样点太靠近边沿,容易采到正在变化中的不稳定电平。把采样点推到中后段,相当于等信号真正稳定下来之后再去读取。
但也不是越靠后越好。采样点太靠后(比如做到95%以上)会压缩PHASE_SEG2的长度,导致重同步几乎没有反向调整的余地。这就像合唱时指挥给了节拍,你只留了一小段反应时间,稍微抢拍就纠正不回来了。
3. 硬同步与重同步:一次"对表"和无数次"微调"
CAN总线的同步机制分为两种:硬同步和重同步。两者都利用总线上的隐性到显性跳变沿(在物理电平上表现为下降沿),但作用时机和调整幅度完全不同。
3.1 硬同步:从SOF沿重新起跑
硬同步只发生在总线空闲状态后的第一个跳变沿上,也就是一帧报文起始位SOF的下降沿。检测到这个沿时,每个节点的CAN控制器都会把自己内部的位时间计数器立即复位到SYNC_SEG的起点,相当于大家一起把表盘拨回到12点整。
硬同步是一次性的、彻底的复位,不涉及任何微调。它的作用是消除节点之间在空闲期积累的任意相位差——因为总线空闲时没有任何边沿信号,每个节点只能靠自己的晶振自由运转,经过一段时间后相位漂移可能很大。SOF沿一到,所有人的计时器从同一个位置重新起跑,这一帧报文的基础节奏就建立起来了。
有一点值得注意:硬同步之后,尽管大家从同一条起跑线出发,但各自的晶振频率并不是绝对一致,若干个bit之后相位偏差又会慢慢累积起来,所以还需要重同步不断修正。
3.2 重同步:晚到就延长,早到就缩短
重同步发生在报文传输过程中。接收节点每检测到一次隐性到显性的跳变沿,就判断这个沿落在自己位时间的哪个位置:
- 边沿落在SYNC_SEG之后(相位误差为正,也就是边沿来晚了),说明本节点跑得比发送节点快,控制器会延长PHASE_SEG1,把采样点往后推;
- 边沿落在SYNC_SEG之前(相位误差为负,也就是边沿来早了),说明本节点跑得比发送节点慢,控制器会缩短PHASE_SEG2,让位时间提前结束,采样点相应前移。
这里的关键点在于:重同步只改变位时间内部的相位段长度,不会像硬同步那样把整个计数器复位。每次调整的量也受SJW限制,而不是一步到位。所以重同步更像是在合唱中跟着指挥的手势不断微调自己的节拍,越跑越偏就会越纠越准。
协议里还有一个细节:一个位时间内如果出现多个跳变沿,只取第一个有效边沿作为同步参考。而且同步只利用隐性到显性的沿,这是因为显性电平在总线上具有优先级覆盖作用,这个沿是所有节点公认的"换挡时刻",显性到隐性的沿则可能夹杂着仲裁丢失导致的电平变化,不够可靠。
3.3 SJW与位填充对同步的隐形影响
SJW(同步跳宽度)指的是每次重同步允许调整的最大tq数量。如果说PHASE_SEG1和PHASE_SEG2是调整空间,SJW就是每次调整的"步长上限"。
SJW设置得越小,每次重同步能纠正的相位误差就越小,遇到大幅度的相位跳变时跟不上,误差会在多个bit上累积。SJW设置得越大,纠错能力越强,但采样点的位置也会随着每次重同步发生较大的前后摆动,反而可能引入额外的不稳定。常规建议是SJW取1~4个tq,一般不超过较小的那个相位缓冲段。
位填充(bit stuffing)对同步的影响也经常被忽略。CAN协议规定,连续发送5个相同电位的位之后,必须插入一个反向位。很多人以为这是为了直流平衡,其实它还有一重身份:同步参考的"密度保障"。没有位填充,长串的连续相同位会导致总线上很久没有跳变沿,接收节点就算有重同步机制也没有参考边沿可用。填充位保证了每5到6个bit必然出现一次跳变,让重同步可以稳定地持续进行。
4. 采样点与SJW怎么配:计算、选型与实测
这一节直接给可以落地的配置方法。我见过不少工程师在初始化代码里直接抄例程的寄存器值,从不关心这些数字怎么来的,结果换了主控芯片或者改了总线长度,问题就开始冒头。
4.1 一套可以"抄作业"的配置流程
配置位时序,我一般按下面这个流程走:
- 确定目标波特率和控制器时钟。比如目标500kbps,外设时钟40MHz。
- 计算tq,选择预分频值,使位时间的tq数落在16~25之间。tq太少,四段分配空间不够;tq太多,采样点的位置粒度太粗,同步调整的分辨率也会下降。
- 确定物理层的传播延迟预算。计算总线最长距离和收发器环路延迟,据此设定PROP_SEG。
- 在剩余空间中分配PHASE_SEG1和PHASE_SEG2,把采样点落在85%左右。
- 设置SJW,一般从1个tq开始,长线或干扰较多的环境放宽到2~3个tq。
举个例子,某项目控制器时钟40MHz,目标波特率500kbps,预分频4得到tq=100ns,位时间20个tq。总线长度约10米,双绞线信号往返延迟按每米10ns估算,加上收发器延迟大约200ns,总传播延迟约300ns,需要至少3个tq的传播段。所以我用"SYNC=1,PROP=3,PS1=13,PS2=3"这个配置,采样点85%,SJW=2。实测跑了好几天没有任何错误帧。
如果是8MHz时钟的经典控制器,500kbps位时间只能分到16个tq,常见的配置是"1+1+12+2",采样点87.5%。这套配置在短总线上非常经典,但总线一旦超过20米,PROP_SEG只有1个tq就明显不够了。
4.2 不同采样点的时钟容错对比
采样点选择不仅影响误码率,还直接决定整个网络对节点时钟偏差的容忍度。有一个在CAN一致性测试中常用的估算关系:在采样点位于75%~100%的区间时,允许的振荡器容差约为:
Δf ≤ (2 × (1 - 采样点位置)) / (10 × 位时间tq数)
按20个tq计算,不同采样点的容差对比如下:
| 采样点 | 振荡器容差上限 | 工程判断 |
|---|---|---|
| 80% | 0.20% | 余量一般,短距离可用 |
| 85% | 0.15% | 常规推荐 |
| 87.5% | 0.125% | 经典配置,常见于16tq |
| 90% | 0.10% | 对时钟要求更严格 |
看到这个表,你就明白为什么晶振选择很关键了。常见的无源晶振精度约±100ppm(0.01%),离极限远得很;但如果有人图便宜用了±3000ppm的陶瓷谐振器(0.3%),单独一个节点可能还能跑,接上网络之后波特率稍有偏差,重同步又来不及补偿,误码就会不请自来。
4.3 三采样模式要不要开
不少CAN控制器内置了"连续采样三次、多数表决"的模式,也就是标称采样点前后各半个采样周期各采一次,三个结果二比一判定位值。这个模式对滤除窄毛刺很有用,代价是采样点在时间上被"摊开"了一些,同时收紧了时序余量。
我的习惯是:总线上有大量电感性负载、波形边沿不够干净的时候,开三采样能明显改善误码;如果总线很短、波形干净,就没必要开,保持单点采样更利于精确控制时序。判断依据很简单,示波器抓一下波形,看边沿附近有没有明显振铃——有就开,干净就不开。
5. 隐藏在物理层的两个"隐形杀手":传播延迟与时钟容差
很多人把同步机制理解成纯粹的控制器内部逻辑,认为只要寄存器配对了,同步就自然没问题。实际上,位时序参数永远要跟物理层条件放在一起算,传播延迟和时钟容差是两个最容易出问题的外部变量。
5.1 传播延迟真的会"吃"掉采样点
信号在总线上的传播速度大约是光速的2/3,换算到双绞线里,大概每米延迟5ns左右。再加上CAN收发器的电路延迟,通常单向总延迟可以按"线缆延迟 + 收发器延迟"来估算,收发器环路延迟普遍在200~300ns的量级。
为什么PROP_SEG要覆盖两倍的传播延迟?因为发送节点需要保证:自己发出的显性位传到最远端节点后,最远端节点的响应信号还能在同一个位时间里被其他节点稳定看到。只覆盖单程延迟,远端节点就可能在采样点上收到"还没到达"的电平。总线越长,PROP_SEG需要占用的tq越多。
很多人在短总线上的配置拿到长总线上直接用,结果发现远端节点丢帧。原因基本就是PROP_SEG不够,采样点算出来虽然是85%,但实际采到的电平还是边沿跳变前的旧值。这时候不是改SJW能救的,必须重新分配传播段,或者降低波特率以增大位时间内的tq数量。
5.2 时钟容差:为什么±100ppm够用而陶瓷谐振器可能翻车
每个节点的晶振都有误差,不同节点之间的实际波特率并不完全相等。同步机制能容忍一定范围内的偏差,超过这个范围,位时间就会被不断积累的相位误差拖垮。
前面那个容差公式估算过:在20tq、采样点87.5%的配置下,容差约0.125%,也就是1250ppm。单个节点用无源晶振,误差通常只有几十ppm,看起来余量很大,但别忘了极端的现场情况:温度漂移、老化、两端的误差方向相反,误差会叠加。多节点组网时,不同节点的晶振误差相互独立,最坏情况下两个节点的相对偏差接近各自误差上限的和。
我建议在任何CAN网络中,至少保证所有节点使用精度不低于±100ppm的时钟源。这个精度等级在现在的产业链里成本很低,没必要为了省几分钱在可靠性上冒险。批量生产的产品还要注意晶振的负载电容匹配,偏离标称值会导致频率偏移,这在高温环境下尤其明显。
5.3 CAN FD时代:TDC是怎么"补偿自己"的
讲同步机制不能只停留在经典CAN。到了CAN FD,数据段的位时间被大幅压缩,波特率动辄几Mbps,一个位时间可能只有几十ns,而收发器环路延迟就占了其中很大一部分。此时如果还靠接收节点边沿同步、再等信号稳定后采样,时序就完全不够用了。
CAN FD引入了一个叫TDC(发送延迟补偿)的机制。发送节点知道自己的信号什么时候发出、什么时候在总线上被自己看到,用这个时间差减去固定延迟,算出数据段采样点应该额外偏移多少。说白了,经典CAN是大家一起对表,CAN FD快节奏下各节点往往各发各的,发送延迟补偿让发送节点精确知道自己听到的总线是什么时候的自己。
如果你用CAN FD做设计,除了仲裁段的采样点,还要额外关注数据段的TDC配置。很多控制器需要在初始化时做一次基于测量或手工估计的延迟补偿设置,这部分在芯片手册里通常有专门的寄存器,千万不能沿用经典CAN的思路只看采样点。
6. 三个现场问题的定位与修复实录
最后用三个实际排查过的案例收尾,都是同步机制出了问题但表象各不相同的情况。排查思路比答案本身更有价值。
6.1 偶发CRC错误:采样点不一致引发的"误读"
回到开头那个故障。三个节点各自单独测试都正常,组网后B节点报CRC错误,A节点所在地离B节点最远。读寄存器的结果是A节点采样点68%,B和C都是85%以上。
这个场景最迷惑人的地方在于:单独通信时只有两个节点,距离很短,传播延迟小,A节点采样早的问题被掩盖了。三节点组网后,B节点发出的信号传到A节点需要更长时间,A节点那个早得离谱的采样点就采到了边沿跳变过程中的不确定电平,位值误判导致CRC对不上。修复方式是把A节点的采样点改成和B、C一致,用的是上一节那套20tq配置。改完之后跑了一整天,错误计数为零。
这件事给我最大的教训是:不要用"单独测试正常"来证明同步配置没问题。单节点通信只是必要不充分条件,多节点组网时的相位差才是真正的考验。
6.2 远端节点丢帧:PROP_SEG只给了1个tq的代价
另一个项目用的是16tq的经典配置"SYNC=1,PROP=1,PS1=12,PS2=2",采样点87.5%,短距离测试全部通过。后来总线为了绕线多走了将近30米,远端节点开始丢SOF和首个数据字段。
30米双绞线的单程延迟约150ns,加上收发器延迟,单程总延迟接近250~300ns,往返就接近600ns。而当时tq是125ns,PROP_SEG只有一个tq,连往返的一半都覆盖不到。
这个案例的修复方案有两类:如果必须保持500kbps,就把tq数增大到20个,给PROP_SEG分配到3个tq来覆盖300ns,再配合采样点后移到合理位置;如果波特率可以降,降到250kbps会让位时间翻倍,同样能把PROP_SEG扩大。最终为了兼容网络上的其他设备,选了后者。修复之后远端节点不再丢帧,错误计数归零。
6.3 长分支线接入后错误帧飙升:反射与SJW的博弈
第三个案例比较隐蔽。一个原本用菊花链连接的现场总线,为接入一个执行器加了一条1.5米的分支线,之后整条网络上错误帧明显增多。起初怀疑是终端电阻位置不对,检查后发现终端电阻还挂在总线两端,没有因为分支线改变。
问题出在分支线末端产生的信号反射上。反射波叠加在原信号上,让本来就受传播延迟影响的边沿附近出现了额外的抖动。节点的重同步参考位置变得不稳定,而当时SJW只设了1个tq,每次只能纠正很小一段偏差,反射造成的相位跳变远远超出一个tq的补偿能力。
处理办法有两步:第一步把SJW从1放宽到3个tq,给重同步更大的纠错空间;第二步是把分支线长度尽量缩短或者改成T型连接点,从物理上减小反射路径。只改软件也能缓解,但根治还得靠布线。这个案例提醒我,同步机制能不能胜任,和网络拓扑是强耦合的,别把问题都推给位时序参数。
7. 我这些年配置位时序的一些习惯
写到这里,分享几个我长期在用的实操习惯,全都是踩过坑之后沉淀下来的。
第一,任何新项目的CAN初始化,我都不直接抄例程,而是先算出tq和位时间的tq数,再按"传播延迟预算→采样点→SJW"的顺序逐个确定参数。这个过程只需要几分钟,但能让你对每条总线的边界条件心中有数。第二,多节点组网时,我会把所有节点的采样点和SJW统一成同一组参数。哪怕有的节点采样晚一点也能工作,从设计上消除差异隐患才是最省事的做法。第三,每次改完位时序,我都会把采样点、SJW、tq数和总线长度记录在测试文档里,下次排查故障时这些数据能省掉大量重新测量和试错的时间。
还有一个容易被忽视的地方:CAN控制器的位时序寄存器在不同芯片上字段可能合并或重排,有些把PROP_SEG和PHASE_SEG1合成了一个字段,有些增加了额外的TQ精度位。换芯片平台时,务必重新按公式推导寄存器值,而不是把旧值平移过去。芯片手册里的时序图配合示波器抓出来的实际波形,是验证配置正确与否最直接的方法。
CAN总线的同步机制说复杂很复杂,但拆到底就是一句话:用精细的位时间切分和持续的边沿校准,让所有的节点在共享一条总线时保持同一个采样节拍。把握好采样点、SJW、传播预算和时钟精度这四个变量,这个节拍就能稳定下来,那些"玄学"错误帧自然也会离你远去。