4. 位时序与同步:位时间分段、采样点位置计算、同步跳转宽度、波特率配置实战
好,咱们进入CAN协议栈里最硬核、也最容易被忽视的一环——位时序与同步。
说实话,我刚开始做CAN驱动时,觉得这玩意儿不就是配个波特率嘛,有啥难的?结果第一次联调,两台设备就是不通,示波器一抓,波形乱成一团。后来才发现,问题出在采样点位置和同步跳转宽度上。嗯,从那以后,我再也不敢小看位时序了。
4.1 位时间分段:SS、PTS、PBS1、PBS2
一个CAN位时间,说白了就是发一个bit需要多长时间。这个时间被分成了四段,每段都有自己的任务。
| 段名 | 全称 | 作用 | 典型长度 |
|---|---|---|---|
| SS | 同步段 | 用于检测边沿,完成硬同步 | 1 Tq |
| PTS | 传播时间段 | 补偿总线传播延迟 | 1~8 Tq |
| PBS1 | 相位缓冲段1 | 采样点前调整,补偿相位误差 | 1~8 Tq |
| PBS2 | 相位缓冲段2 | 采样点后调整,准备下一个位 | 1~8 Tq |
这里有个关键点:采样点就在PBS1结束、PBS2开始的位置。你想想看,如果采样点偏了,读到的电平值就可能不对,整个通信就乱了。
核心公式:
位时间 = SS + PTS + PBS1 + PBS2
采样点位置 = (SS + PTS + PBS1) / 位时间 × 100%
我个人习惯把SS固定为1个Tq,这样好算。PTS根据总线长度来定,一般1米总线对应1个Tq就够用了。
4.2 采样点位置计算
采样点位置,说白了就是你在位时间的哪个百分比去读电平。这个值选得好不好,直接决定了你的CAN总线能跑多远、能抗多大干扰。
推荐值:
- 高速CAN(1Mbps):采样点87.5%左右
- 中速CAN(500kbps):采样点85%~87%
- 低速CAN(125kbps以下):采样点80%~85%
为什么会这样?高速时位时间短,采样点靠后能留更多时间给信号稳定。低速时位时间长,采样点可以稍微往前挪一点。
举个例子,假设我们要配500kbps,时钟频率40MHz,预分频4,那么Tq = 1 / (40MHz / 4) = 100ns。位时间 = 1 / 500kbps = 2000ns = 20个Tq。
// 配置参数
SS = 1 Tq
PTS = 3 Tq
PBS1 = 13 Tq
PBS2 = 3 Tq
// 总位时间 = 1 + 3 + 13 + 3 = 20 Tq
// 采样点 = (1 + 3 + 13) / 20 = 85%
嗯,85%的采样点,对于500kbps来说中规中矩。我在项目中遇到过采样点配到90%以上的情况,结果总线一长就丢帧,后来降回87%就稳了。
4.3 同步跳转宽度(SJW)
SJW,全称Synchronization Jump Width,同步跳转宽度。它决定了CAN控制器在检测到边沿时,最多能调整多少个Tq来同步。
说白了,SJW就是给同步留的「余量」。总线上的节点时钟不可能完全一致,总会有偏差。SJW越大,能容忍的时钟偏差就越大,但同步速度会变慢。
我的经验:
SJW一般取1~4个Tq。我个人习惯取SJW = PBS1的值,或者取SJW = min(PBS1, PBS2)。
如果总线节点多、时钟精度差,SJW可以设大一点。如果总线短、时钟准,SJW设1个Tq就够了。
SJW的约束条件:
- SJW ≤ PBS1
- SJW ≤ PBS2
- SJW ≥ 1
我曾经在一个项目里,用了便宜的晶振,精度只有±1%。结果SJW设了1个Tq,总线跑着跑着就同步丢了。后来把SJW改成3个Tq,问题就解决了。你想想看,省了晶振的钱,就得在SJW上找补回来。
4.4 波特率配置实战
好,理论说完了,咱们来点实际的。下面是一个基于STM32的CAN波特率配置实战。
需求:配置CAN为500kbps,时钟频率42MHz。
// 第一步:计算预分频
// 目标位时间 = 1 / 500kbps = 2000ns
// Tq = 1 / (42MHz / 预分频)
// 我们需要位时间 = 整数个Tq
// 尝试预分频 = 3
// Tq = 1 / (42MHz / 3) = 71.43ns
// 位时间 = 2000ns / 71.43ns = 28 Tq → 可行
// 第二步:分配段长度
// SS = 1 Tq
// PTS = 5 Tq(假设总线长度10米)
// PBS1 = 12 Tq
// PBS2 = 10 Tq
// 总位时间 = 1 + 5 + 12 + 10 = 28 Tq ✓
// 采样点 = (1 + 5 + 12) / 28 = 64.3% → 太低了!
// 第三步:调整,提高采样点
// 重新分配:
// SS = 1 Tq
// PTS = 3 Tq
// PBS1 = 16 Tq
// PBS2 = 8 Tq
// 总位时间 = 1 + 3 + 16 + 8 = 28 Tq ✓
// 采样点 = (1 + 3 + 16) / 28 = 71.4% → 还是偏低
// 第四步:换预分频
// 尝试预分频 = 2
// Tq = 1 / (42MHz / 2) = 47.62ns
// 位时间 = 2000ns / 47.62ns = 42 Tq
// SS = 1 Tq
// PTS = 5 Tq
// PBS1 = 30 Tq
// PBS2 = 6 Tq
// 总位时间 = 1 + 5 + 30 + 6 = 42 Tq ✓
// 采样点 = (1 + 5 + 30) / 42 = 85.7% ✓ 完美!
注意:
实际配置时,PBS1和PBS2的取值还要考虑SJW。SJW不能超过PBS1和PBS2中的较小值。
另外,不同CAN控制器的寄存器位宽不同,有些只支持到16个Tq,有些支持到25个Tq。配置前先看数据手册。
配置代码示例(基于STM32 HAL库):
CAN_HandleTypeDef hcan;
hcan.Instance = CAN1;
hcan.Init.Prescaler = 2; // 预分频
hcan.Init.Mode = CAN_MODE_NORMAL;
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ; // SJW = 1 Tq
hcan.Init.TimeSeg1 = CAN_BS1_30TQ; // PTS + PBS1 = 30 Tq
hcan.Init.TimeSeg2 = CAN_BS2_6TQ; // PBS2 = 6 Tq
hcan.Init.TimeTriggeredMode = DISABLE;
hcan.Init.AutoBusOff = ENABLE;
hcan.Init.AutoWakeUp = DISABLE;
hcan.Init.AutoRetransmission = ENABLE;
hcan.Init.ReceiveFifoLocked = DISABLE;
hcan.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan) != HAL_OK)
{
Error_Handler();
}
嗯,这里要注意,HAL库里的TimeSeg1其实是PTS + PBS1的总和,不是单独的值。我第一次用的时候没注意,把PTS和PBS1分开设了,结果怎么配都不对。后来看了参考手册才明白。
4.5 避坑指南
最后,分享几个我踩过的坑:
- 采样点不要低于80%:我曾经为了凑整,把采样点配到75%,结果总线长度超过5米就丢帧。后来老老实实算到87%,稳如老狗。
- SJW不要设太大:有次我把SJW设成4个Tq,结果同步跳得太猛,反而引入了抖动。SJW够用就行,不是越大越好。
- 不同节点的采样点要一致:总线上的所有节点,采样点位置必须一样。否则一个节点在85%采样,另一个在80%采样,读到的电平可能不一样。
- 时钟精度影响很大:如果用了内部RC振荡器,时钟精度可能只有±2%。这时候SJW至少要设2个Tq,否则同步会失败。
好了,位时序这块就讲到这里。说白了,就是算好Tq、分好段、定好采样点、配好SJW。多算几遍,多测几次,你就能找到感觉了。