CAN 位同步:一根没有时钟的线,凭什么人人都有表(以 STM32F4 为例)
《CAN 总线篇》 讲位时序那节的时候,给自己留了一句欠条:「先记住它是『对表时允许的步长上限』」——SJW 是什么、对表对的是什么表,当时都没展开。收发器篇 结尾挂着的那颗 666µs 的炸弹先让它再排一会儿队,这篇先回来还这笔更老的账:总线上没有任何时钟线,各个节点到底是怎么把「每一位」的发送时间点对齐的。
先把这件事的怪异程度摆出来。串口没有时钟线,靠每帧开头的起始位硬对一次表——一帧拢共 10 位,对一次,够用。CAN 的一帧呢?标准帧驮 8 字节时要跑一百多位(上一篇算过,连帧间隔 111 位)。现在两块板的晶振各差 1%——每过一位,采样点就相对错开 1%,到第 100 位,整整错开一位:我在读第 N 位,你已经在读第 N+1 位了,这帧必死无疑。按串口的玩法推演,CAN 这种「百位长帧 + 无时钟线」的组合根本不该存在。可现实是,CAN 节点揣着千分之几精度的便宜晶振甚至陶瓷谐振器,一帧 111 位一字不错,一条报文连发一万个也不乱。
秘密只有一个:CAN 不是「每帧对一次表」,而是每一个电平翻转边沿都对一次表。串口把对表机会放在每帧开头那一位;CAN 把对表机会嵌进了数据本身的每一次翻转里。
这篇分三层:L1 把「对齐」变成可配置的数——两块时钟完全不同的板子,写什么进 BTR 才算「说好了同一节拍」;L2 拆开一个位的解剖结构,看「硬同步」和「重同步」两种对表动作怎么配合;L3 想明白这个设计拿掉了什么、又为什么非拿掉不可。
L1:先让它跑起来——「500kbps」只是合同的第一页
你在第一层。任务:组一条真总线跑 500kbps,一端是 上一篇 那块 42MHz APB1 的 F407,另一端是一块只有 8MHz 晶振的节点——机器人关节电机驱动器里这种货色很常见,它和你的主控板没有任何一个共同的时钟数字。
很多人到这里会冒出一个直觉:两块板得用一样的 tq 才能对上。不需要,也不可能。要签的合同有三页,多数教程只教你第一页:
- 第一页:位宽。 500kbps = 每位 2µs,两边都得是它。
- 第二页:采样点。 在每一位里第几个百分点处读取电平。行业惯例 75%~87.5%(《CAN 总线篇》 的原话),CiA 的建议值落在 87.5% 一档(印象中 CiA 601-3 专门管位时序建议,我没有当场核对文档,欢迎指正)。两边不必完全相同,差一两个百分点无害;差三十个百分点是灾难——症状预告,动手环节实验三当面验尸。
- 第三页:SJW。 每次对表允许拨几个 tq,bxCAN 里 1~4 个,日常 1~2。
逐块板把账算出来。F407 这边,《CAN 总线篇》 实验二已经给过答案:预分频 6,一个位 14 个 tq(1 + 11 + 2),采样点 12/14 ≈ 85.7%:
1
2
3
4
5
CAN_BTR = (10u << 16) /* TS1:BS1 = 11 tq */
| (1u << 20) /* TS2:BS2 = 2 tq */
| (1u << 24) /* SJW = 2 tq */
| (6u - 1); /* BRP:预分频 6,42MHz/6 → tq = 142.9ns */
/* 位时间 = (1+11+2) × 142.9ns = 2µs = 500kbps */
8MHz 那边呢?预分频干脆用 1:8MHz 直接就是 tq 时钟,tq = 125ns,一个位 2µs 装 16 个 tq。采样点想凑 87.5%,16 × 0.875 = 14,所以 BS1 给 13、BS2 给 2:
1
2
3
4
CAN_BTR = (12u << 16) /* BS1 = 13 tq */
| (1u << 20) /* BS2 = 2 tq */
| (1u << 24) /* SJW = 2 tq */
| (1u - 1); /* 预分频 1:tq = 125ns,位 = 16tq = 2µs */
对着看:两块板的预分频不同、tq 宽度不同(142.9ns vs 125ns)、一个位里的 tq 数不同(14 vs 16)——没有任何一个 tq 数字是共享的。它们共享的是位宽 2µs 和采样点位置(85.7% 和 87.5%,同一个量级)。「对齐」对齐的从来不是时钟周期,是位宽和采样点这两个百分比。tq 只是每块板自己数数用的内部最小刻度,刻度多细不写在合同上。
到这一步你会配了。但欠着一串解释:明明没有共同时钟,一个节点凭什么「知道」该在每一位的第 87.5% 处伸手去采?别人的 0 和我的 0,起点从哪儿说起?还有那个 SJW——合同第三页到底在对谁的表?
L2:懂原理——把一个位切成「等、采、缓」
到这一层,问题变了:位时序三个段,凭什么这么切?对表动作到底长什么样?
一、一个位的解剖图
CAN 把每一位切成三段,各司其职:
1
2
3
←SYNC→←────── BS1 ──────→←── BS2 ──→
1 tq (采样点前的路费) (找零窗口)
┊边沿应落在这┊ ↑ 采样点 下一位开始
- SYNC_SEG(同步段,恒为 1 tq):边沿的法定时刻。全网约定:一切电平翻转都「应该」发生在这段里。它像一个国家仪仗队的基准步点——自己不干活,存在的意义就是给别人当参照物。
- BS1:等电平翻转在路上跑完、总线稳定下来,末尾就是采样点。它的长度里藏着 《CAN 总线篇》 实验三算过的那笔路费——信号要跟最远的节点打个来回,都赶在采样前到齐。
- BS2:采样之后到下一位开始的缓冲。采样点为什么不像串口那样放在位中央、而要给后面留一段?因为这一段是对表的找零窗口——下面马上看到,BS2 的长度就是「对表时能拨回来多少」的物理上限之一。
二、两种对表动作:起跑枪与拨表
现在回答标题的问题:节点们怎么对齐每一位的时间点?答案是两个动作,一个管帧首,一个管帧内。
硬同步(hard synchronization):起跑枪。 总线空闲时(全员隐性、没有边沿),任何一个节点发出 SOF,那个隐性→显性的边沿一到,全场所有节点都把自己的位时序计数器清零:SYNC_SEG 从这一刻重新起算。上一篇把 SOF 叫「起跑枪」——现在可以给出它的准确含义了:枪响不是修辞,是一次全网归零的硬件动作。这也是为什么帧间隔只有 3 位、各节点在这 3 位里各漂各的也不用怕——下一帧枪一响,历史欠账全部一笔勾销。每帧就这一次。
重同步(resynchronization):帧内拨表。 硬同步只管开头,帧内一百多位靠的是另一个动作:帧内每一个隐性→显性的边沿,都是一次校表机会。节点边发送/接收边盯总线,每遇到一个边沿就检查:它落在我预期的 SYNC_SEG 附近吗?三种情况:
1
2
3
4
5
6
7
边沿落点 说明 动作
────────────────────────────────────────────────────
在我的 BS1 里 我的表快了 BS1 延长,最多延长 SJW
(本位拉长,采样点后移)
在我的 BS2 里 我的表慢了 BS2 缩短,最多缩短 SJW
(本位提前结束,下一位提前)
刚好在 SYNC_SEG 对得很齐 不动,e = 0
物理上怎么想:对方的边沿总是迟到(我的晶振快了,我的位走得比别人短,别人的翻转落在我这一位的 BS1 里)——那就把我这一位拉长;对方的边沿总是早退(我的晶振慢了,别人的下一个翻转落在我还在 BS2 里磨蹭的时候)——那就把我这一位掐短。拉长和掐短的量,就是相位误差 e,每次最多拨 SJW 个 tq——这就是那句「对表时允许的步长上限」的全部含义。规矩还有两条:一位最多重同步一次;SJW 在 bxCAN 里最多 4 个 tq(BTR 的 SJW 字段就 2 位宽,这回你知道那 2 位的分量了)。
注意一个容易被忽略的事实:发送方自己也拨表。发送节点同样在读总线上回来的边沿——它自己发出的边沿绕总线一圈回来时也带着传播延迟,落点照样有早有晚,照样照此校表。全场没有谁是「权威时钟」,每个节点(包括正在发送的这个)都在跟着总线的边沿微调自己的表。
三、只认「拽」的边沿
重同步有一个铁律:只用隐性→显性的边沿,显性→隐性的不算数。为什么?
把 收发器篇 的账搬过来就通了:显性是「拽」——驱动管主动开通,边沿陡、时刻确定;隐性是「松手」——驱动管关断后靠偏置自然回落,边沿缓、时刻含糊、参数还松(隐性电平合格范围本来就宽)。对表只信「有人主动拽」这个动作,不信「自然回落」这个物理过程。「0 是强势的」这句贯穿两篇的话,在时间维度上还有下半句:0 的边沿也是唯一可信的边沿。电气规则第三次沉进协议——第一次沉成仲裁,第二次沉成 ACK 表决,这次沉成了同步的节拍来源。
四、心跳的听觉:位填充的真正岗位
现在把 《CAN 总线篇》 那句「位填充是 CAN 版的心跳」放到听诊器上。心脏是用来干嘛的?给全身供节律。位填充在那一篇里像是「为了保证同步顺带存在」的机制,这篇给它转正:它就是同步机制本身。
推一下账:填充规则保证 SOF 到 CRC 之间连续同值位最多 5 个、第 6 个位必是反值——也就是说,翻转边沿永不缺席,上一个边沿之后最坏等到第 6 个位必有下一个。再叠加「只认 1→0」这条铁律,相邻两个可用边沿的间隔还有更坏的情况(动手环节实验二推给你看,答案是约 10 个位)。于是整帧的同步账是这样的:
1
2
3
帧首:SOF 硬同步,全网归零
帧内:每 ≤10 位一次边沿,每次拨表 ≤ SJW
结果:相位差永远被摁在 SJW 量级,永远不会累积成一位
串口每 10 位(一帧)校一次表,所以一帧只能 10 位长;CAN 每约 10 位校一次表,所以一帧可以 111 位长。不是 CAN 的晶振变准了,是对表变勤了——这就是「百位长帧 + 无时钟线」这个不可能组合的成立条件。
五、容差的粗账:晶振允许差多少
这套机制允许两个节点差多少?现场推一遍(这是量级估算,精确公式是博世规范里那两道振荡器容差不等式):
设两个节点的相对偏差是 δ。最坏情况下,两次可用边沿之间隔约 10 个位,这段时间积累的相位差是 δ × 10 个位宽。它必须被一次拨表吞下——拨表上限是 SJW 个 tq,即 SJW/N 个位宽(N 是一个位的 tq 总数):
1
2
δ × 10 ≤ SJW / N
δ ≤ SJW / (10 × N)
取 SJW = 2、N = 16:δ ≲ 1.25%。再算上「漂移期间采样点还得留在本位的安全区」这层更紧的约束,极限压到 1% 出头。厂商应用笔记里常被引用的经典上限约 1.58%(SJW 取满 4、位时间切得更细时的推导结果——量级对得上;具体常数我未当场核对原始出处,欢迎指正)。
对着现实里的时钟源念一遍这个数:
| 时钟源 | 典型偏差 | 判词 |
|---|---|---|
| 晶振 | ±20~100 ppm(千分之 0.2 以内) | 余量百倍,随便跑 |
| 陶瓷谐振器 | 千分之几档 | 掂量着用,账要算 |
| MCU 内部 RC 振荡器(F407 的 HSI) | 手册标称 ±1% 量级(印象中如此,未当场核对) | 踩在生死线上——动手环节实验一验尸 |
到这里机制齐了。但回头看,处处透着别扭:边沿这么好用,为什么不干脆加一根时钟线,把这件事做成明账?答案是——加了那根线,CAN 就不是 CAN 了。
L3:想得透——总线自己就是时钟线
到这一层,问题只剩一个:这个设计拿掉了什么?
本质
去掉所有术语,CAN 的同步到底做了什么?它没有省掉时钟,它把时钟藏进了数据里。每一位的电平翻转本身兼任时钟脉冲,位填充保证脉冲不缺席,SJW 规定拨表步长,SOF 保证每帧有一次彻底归零。一句话说给同事听:串口的时间是「发给你的」,CAN 的时间是「从数据里读出来的」——UART 的时钟藏在每帧开头那一位里,CAN 的时钟摊开在整帧的每一个边沿上。
一个心智模型:一支没有指挥的乐队。每个乐手揣着自己的节拍器,而且个个都不太准——但鼓点是全场共听的声音,鼓一响人人对照自己的表微调,快了就等一等(BS1 延长),慢了就赶一赶(BS2 缩短)。位填充扮演的角色是规定鼓手至少每十拍必须敲一下,不许偷懒。
拆脚手架的时候到了,两处失效要交代。第一,真乐队有个专职鼓手,CAN 里没有专人打拍子——鼓点是所有发送节点合力的边沿,谁在拽线谁就是此刻的鼓手,同一个帧里鼓手可以换人,节拍却不断。第二,真节拍器只管自己的速度,CAN 的拨表是双向的:每位都可能被拉长也可能被掐短,全网像一群互相看齐又互相迁就的人,没有谁在「发号」,只有「看齐」。
三个被否决的方案
被否决的方案一:加一根时钟线(SPI 思路)。 发送方连数据带时钟一起广播,接收方照着采样,多么干净。第一笔账:时钟线是单点广播的——谁的时钟?发送方。主持权在电气层重新回到了中央,和上一篇「把主持权下沉到物理层」的全部努力正面相撞:主机挂了全场哑巴的老问题,连时间都跟着没了。第二笔账:热插拔。新节点挂上线,在它对上别人时钟域之前一个字都听不懂;CAN 的世界里新节点上线即听——下一帧 SOF 的枪声自然收编它。第三笔账最要命:时钟线自己也是跑在整车电磁地狱里的模拟信号,它受了扰谁校它?把「校表」这件事押在另一根同样会坏的线上,等于没押。
被否决的方案二:帧帧硬同步,把帧砍短(UART 思路)。 既然串口靠「一帧 10 位 + 帧首对一次」活得好好的,把 CAN 帧砍到 10 位不就完了?数学上先死:无主机仲裁要求 11 位 ID 逐位在场,光 ID 就比串口整帧还长——「短帧」和「逐位仲裁」不相容,这题没有解。经济上再死一遍:每帧的头尾开销(SOF、控制段、CRC、ACK、EOF)是固定投资,帧越短效率越崩——上一篇驮 8 字节载荷的账就是靠百位长帧才做到近六成效率的。串口能短,因为它是两个人之间的耳语;CAN 不能短,因为它是一场上百位的公开表决。
被否决的方案三:全网分发精确时钟。 给每个节点一个同步的高精度时钟源(车载以太网的 TSN 后来就走了这条路),不就不需要对表了?看两笔账。1986 年的车里没有能干这事的分发机制,这是历史账;更要害的是设计账:分发就是把「时间」变成一种需要中央分配的资源——分配就有分配者,分配者就是单点,单点就有故障域,上一篇的错误三态刚论证完「任何人都可能坏,包括负责判定坏不坏的那个」,这里凭什么例外?还有一笔经济账:追求「绝对准」意味着每个节点都得上 ppm 级时钟;CAN 把问题从「大家都准」改写成「大家跟得上」,时钟需求从 ppm 量级直接降到千分之几——千分之几档的陶瓷谐振器比晶振还便宜。这是整套设计里最划算的一笔交易:用「读边沿」代替「发时钟」,把精度要求降了两个数量级,把单点故障也顺手消灭了。
一个反直觉收尾
回味一下:CAN 的三篇讲到这儿,「没有一根时钟线」的说法其实不准确——整根总线就是时钟线,只是它不专门发时钟,而是把时钟当成了数据的副产品,再用位填充把这个副产品供养成正式节拍器。位填充这个「白吃带宽」的规则(平时每帧多塞几个位,最坏把 111 位撑到一百三十多——上一篇算过最坏账),才是 CAN 真正的时钟线。这笔交易单子是:花几个位的带宽,省一根专门的线、省 ppm 级的时钟、省掉热插拔的对时流程,换来「任何节点随时上线、谁坏了都碍不着对表」。同步在这里不是一项服务,而是数据本身自带的属性——这大概是「把主持权下沉到物理层」这句系列口头禅最彻底的一次执行:连时间都没有主持人。
动手环节:一张纸、一个计算器
这次的实验不需要第二块板子,一个计算器就够。
实验一:HSI 的生死线。 你手头只有一块 F407,板子上没焊晶振(或者懒得等),直接用内部 RC 振荡器 HSI 当 CAN 时钟——手册标称精度 ±1% 量级(印象中如此,未当场核对,欢迎指正)。对面节点是 ±50ppm 的晶振。500kbps,位时间 2µs,HSI 16MHz 配出来是 16 个 tq(tq = 125ns)。先预测:最坏情况下,两次可用边沿之间积累的漂移是多少 tq?SJW = 2 吞得下吗?这块板子的 CAN 能不能跑?
答案:相对偏差 δ ≈ 1%(50ppm 那边忽略不计)。每位漂 1% × 2µs = 20ns,最坏 10 位间隔积累 200ns ≈ 1.6 个 tq——SJW = 2 理论上刚好吞得下,踩线能跑。但别急着高兴,三件事把这个「能」字打成时好时坏:一,±1% 的标称是出厂校准、常温条件下的数,全温度范围更宽(印象中 F4 系列如此,未核实);二,最坏 10 位间隔只是位模式的下限,平时边沿密集得多、漂移被频繁清零,所以实测大概率「基本正常」;三,硬同步每帧兜底归零一次,错不了太多。三者合力的症状是:偶发 CRC 错、偶发位错误,换上晶振立刻好全——「时好时坏」恰好是踩线时钟源最阴险的地方,因为它把你的排查方向引向软件。工程结论不用背:CAN 用晶振,踩过一次就忘不掉。
实验二:最坏位模式。 《CAN 总线篇》 说填充保证「每 6 个位至少一次翻转」,但这篇 L2 又说可用边沿的最坏间隔约 10 个位。先预测:构造一个位串,让相邻两个隐性→显性边沿隔得最远。
答案:从某个 1→0 边沿开始,后面跟五位连续的 0——填充规则强制插一个 1;再造五位连续的 1(加上刚插的那个正好五个 1)——填充规则强制插一个 0。这个 0 的开头就是下一个 1→0 边沿:
1
2
3
4
位序 b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 b10
0 0 0 0 0 [1]填 1 1 1 1 [0]填
↑ ↑
上一个 1→0 边沿 下一个 1→0 边沿:隔了 10 个位
所以对「任何翻转」的最坏间隔是 5 个位(连续同值位上限),对「可用翻转」的最坏间隔是 10 个位——L2 五那笔容差账里的 10 就是从这儿来的。这也顺手解释了 CAN FD 为什么容差骤降:速率提高后位时间缩短,同样 10 个位能攒下的漂移时间少了,账立即变紧(远处还有一个更狠的坑,见收尾)。
实验三:验尸「都是 500k」。 两个厂商的设备组网,都声明 500kbps,都真的在 500kbps 上发。一块板采样点按 CiA 惯例配在 87.5%,另一块抄了别人的老代码,采样点在 50%。先预测:短线实验室里能通吗?线拉长到 40 米会怎样?
答案:短线能通——这正是坑。按 《CAN 总线篇》 实验三那笔路费账:采样点前的预算 − 收发器来回 250ns,付双倍线长的路费(5ns/m)。87.5% 的板子:875 − 250 = 625ns,理论最远 62m;50% 的板子:500 − 250 = 250ns,理论最远 25m——40 米的总线上,最远节点回敬的电平还没跑到,50% 的板子已经采样完事了。症状:桌面联调全通,装机上线偶发仲裁错、ACK 错,抓包还时灵时不灵。「波特率一样」只是合同第一页,这案子每次都要验到第二页才破。顺手记住排查口诀:多厂商组网偶发错误,先对采样点,再查晶振,最后才轮到查你的软件。
三个自测问题,能答上说明这三层你真爬过了:
- 硬同步和重同步各发生在什么时候?各能把相位差纠正到多小?(答案在 L2 二:硬同步在总线空闲时的 SOF 边沿,全网计数器归零,不限纠正量;重同步在帧内每个 1→0 边沿,每次最多拨 SJW 个 tq,一位一次。)
- 为什么重同步只认隐性→显性边沿?(答案在 L2 三:收发器篇的账——显性是拽,边沿陡、时刻确定;隐性是松手后的自然回落,缓而含糊。只信主动的动作。)
- 两块板的预分频、tq 数完全不同,为什么还能逐位对齐?合同上到底写了什么?(答案在 L1 与 L3:位宽、采样点位置、SJW 量级;tq 是各家的内部刻度。对齐的是百分比,不是时钟周期。)
最后留一个我自己没找到干净答案的真问题:SJW 为什么不干脆配到最大? 官方上限 4 个 tq,可几乎所有计算器和教材都默认推荐 1~2,理由多半含糊。我自己的理解是 SJW 大意味着一次拨表猛,遇到毛刺、反射造成的假边沿时被带偏得多,反而放大抖动——但这只是推演,我没见过把「SJW 过大引发故障」讲出具体机理的文献,更没有实测证据。如果你的项目里真见过调大 SJW 惹祸(或者反过来、靠调大救过容差),我很想听。
收尾地图
一张「症状 → 查什么」的速查表收尾:
| 症状/场景 | 查什么 | 一句话理由 |
|---|---|---|
| 多厂商组网、桌面通、装机偶发错 | 两边采样点对齐没有 | 「都是 500k」只是合同第一页 |
| 偶发 CRC/位错误,换晶振就好 | 是不是 HSI/RC 振荡器当 CAN 时钟 | ±1% 踩在容差生死线上(实验一) |
| 长线不通、短线通 | 采样点后移(87.5% 一档) | 传播延迟付双倍路费(CAN 总线篇实验三) |
| 配 BTR 没头绪 | 先定 tq 数与采样点 → 再定预分频 → SJW 给 1~2 | 位时序计算器的三步账(CAN 总线篇实验二) |
| 两块板时钟不同凑不出同样配置 | 放弃「tq 相同」,改对齐位宽和采样点 | 合同签的是百分比(L1) |
| 总线偶发错、又疑软件又疑硬件 | 先采样点 → 再时钟源 → 最后软件 | 时好时坏 = 踩线时钟源的特征(实验一) |
下一站终于要拆收发器篇那颗炸弹了:接收 FIFO 只有 3 级,500kbps 下一帧标准数据帧约 222µs,3 级满打满算 666µs 的余量——把 CAN 接收从轮询改成中断、再塞进 《FreeRTOS 队列》,这个死线就是下篇要拆的雷。再往远处预告一个位时序的续集:CAN FD 把数据段速率拉到 5Mbps 时,信号在 40 米线上跑个来回要几百纳秒——比一个位还长,发送方发完一个位还没等到自己的回声,得开「收发器延迟补偿」自说自话地对表。到时候你会发现今天这篇的账,每一笔都要重算。