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。多算几遍,多测几次,你就能找到感觉了。