ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CAN总线自动波特率检测:NXP1778从原理到工程实现

CAN总线自动波特率检测:NXP1778从原理到工程实现 1. 为什么总线一上电就“疯了”自动波特率检测要解决的问题1.1 波特率不匹配的真实后果先把话说透CAN总线上所有节点的波特率必须完全一致这是通信成立的前提没有商量余地。哪家把500 kbps的节点塞进一条125 kbps的总线里等待它的不是“偶尔收不到数据”而是整条总线被错误帧淹没。我见过不少刚接触CAN的工程师在联调时被这个坑整得怀疑人生。现象很典型示波器一夹上去CAN_H和CAN_L之间的差分波形看着也正常矩形波、边沿干净可主控就是收不到任何报文。这时候用CAN分析仪抓包满屏都是错误帧和Bus Off提示。原因就是某个从站的波特率配成了250 kbps而主站跑的是500 kbps。波特率不匹配的本质是节点对“一个位到底有多长”的判断产生了偏差。CAN采用的是NRZ编码加位填充机制接收节点靠每一位的采样点去读取显性0和隐性1电平。如果你按500 kbps的节奏去采样一条实际为125 kbps的总线等于把1个位当成了4个位来读采样点全程落在错误的位置CRC校验几乎不可能通过。接收错误计数器会快速累加到256节点随之进入Bus Off状态CAN控制器自动断开与总线的连接直到恢复条件满足才重新参与通信。这就引出了自动波特率检测的意义设备上电后自己不预设波特率而是通过观察总线信号和接收合法帧主动识别出当前总线正在使用的通信速率再完成CAN控制器初始化。这个功能在汽车诊断仪、CAN转串口模块、工业网关、总线节点批量升级工具里几乎是刚需。1.2 自动检测的典型应用场景不是所有项目都需要自动波特率检测。如果整个系统由你一个人说了算波特率定死烧进固件就完事那根本不需要这么复杂。需要自动检测的场合通常有这几个共同特点。第一个场景是“盲插型设备”。比如工程师拿一个USB-CAN适配器去分析一台来路不明的设备你不知道对方的CAN波特率是多少仪表盘上也没有标识。如果这个适配器支持自动波特率检测插上线就能自动适配几分钟出报文。如果不支持你就只能靠猜125K、250K、500K、1M挨个试运气不好试半天。第二个场景是“总线诊断与逆向”。做汽车售后诊断时OBD接口上挂着的ECU可能来自不同供应商通信速率各不相同。诊断仪必须在上电后迅速判断当前总线的波特率才能发起后续诊断会话。这里德尔塔时间是硬指标检测太慢会导致诊断仪软件超时。第三个场景是“免配置网关”。有些工业CAN网关要同时接入多条波特率不同的总线或者对端设备可能被现场工程师改过配置。如果网关必须手动改拨码开关或重新烧固件来匹配波特率调试效率会低得让人抓狂。自动检测功能可以让这类产品真正做到“即插即用”。所以你看自动波特率检测不是炫技而是一个很实在的工程需求。下面我直接进入正题讲讲在NXP1778上怎么把它落地。2. 三条技术路线我为什么选了“先粗测、后锁定”的组合方案2.1 常见实现路线横向对比自动波特率检测行业内大致有三条路线。第一条是“暴力扫描法”。把常见波特率做成一张表125K、250K、500K、800K、1M逐个写入CAN控制器每配置一次就进入接收模式等一段时间如果等到了一帧校验通过的报文就认为波特率匹配。这个方法实现最简单代码量也小很多低端CAN转串口模块就是这么干的。缺点是慢一条候选表跑一遍可能要一两秒而且如果总线上当前没有报文在跑你等再久也是白等。第二条是“外部示波器法”。用示波器抓取CAN_H对地或差分波形人工测量最小位时间再换算成波特率。这个方法准确但显然不适合做成产品功能只能作为实验室调试手段。而且CAN空闲时总线是隐性电平波形上只有一堆连续的“1”根本看不出位时间必须等总线上有实际通信时才能抓到。第三条是“定时器捕获法”。硬件上把CAN收发器的RXD信号同时引到MCU一个定时器输入捕获引脚通过捕获跳变沿的时间差直接测量总线上的位时间再反推波特率。这个方案快一个帧头就能出结果但需要额外占一个定时器通道和引脚而且测量结果容易受到总线干扰和位填充规则的影响必须配合软件滤波。我在NXP1778上最终采用的是“定时器捕获粗测 候选波特率锁定”的组合方案。先利用捕获快速缩小波特率范围再通过CAN控制器自身的报文接收校验做最终确认兼顾了检测速度和可靠性。2.2 NXP1778片上资源的取舍理由选NXP1778做这个功能首先是因为这颗芯片的CAN控制器资源比较规整。它内部集成两个独立CAN控制器符合CAN 2.0B规范支持11位标准帧和29位扩展帧收发缓冲区和验收滤波器都够用。也就是说你完全可以一个CAN口干活另一个CAN口留着做扩展不用在资源上捉襟见肘。定时器资源也够。NXP1778内部有多个32位定时器每个定时器又带多个捕获通道。我用的是定时器1的捕获通道把CAN收发器输出的RXD信号引过去配置成双边沿捕获就能记录每个跳变沿发生时的定时器计数值。这个计数值之间的差再除以定时器时钟频率就是实际的位时间。还有一个考虑是芯片的成熟度。NXP1778是Cortex-M3内核主频最高能跑到120 MHzCAN控制器挂载在APB总线上外设时钟可以灵活分频。做波特率匹配时BRP预分频系数和位段配置的自由度很大基本覆盖了工业CAN领域常用的所有波特率。注意NXP1778的CAN控制器是传统CAN 2.0不支持CAN FD。如果你的现场总线已经升级到CAN FD那这套自动检测逻辑需要重新设计因为FD帧的仲裁段和数据段的位速率不同检测复杂度完全是另一回事。3. 动手前必须吃透的CAN位时序与寄存器计算3.1 一个CAN位时间里到底发生了什么在写代码之前我建议你把CAN位时序彻底搞明白。所谓波特率就是每秒传输的位数而每一位在总线上持续的时间叫做“位时间”。一个完整的位时间在CAN协议里被划分成四段同步段、传播段、相位缓冲段1、相位缓冲段2。同步段固定为1个时间量子Time Quantum用于跳变沿的同步。传播段用来补偿总线上信号传播延迟和物理层延迟。相位缓冲段1和相位缓冲段2用于吸收时钟偏差配合SJW同步跳转宽度实现再同步。采样点就落在相位缓冲段1和相位缓冲段2之间。采样点的位置对通信质量影响非常大配得太靠前或太靠后在总线长度较长、节点数较多的时候都容易出现误码。业内一般推荐采样点设置在75%到87.5%之间具体取多少要看总线拓扑和延迟。这里有一个比较容易混淆的地方NXP1778的CAN控制器寄存器里并没有独立的“传播段”而是把传播段和相位缓冲段1合并成了一个参数TSEG1相位缓冲段2对应TSEG2同步段在硬件上固定为1个时间量子。所以一个位时间可以表示为位时间 1个同步段 TSEG1 TSEG2而波特率的计算公式是波特率 CAN外设时钟 / ((BRP 1) × (1 TSEG1 TSEG2))其中BRP是波特率预分频寄存器值CAN外设时钟就是NXP1778的APB外设时钟PCLK。注意公式里BRP要加1TSEG1和TSEG2也要加1因为寄存器的值是从0开始计的。3.2 由波特率反推BRP与TSEG的完整计算假设NXP1778的PCLK配成了30 MHz我们要配置500 kbps的波特率采样点放在75%左右。先计算总的时间量子数。位时间等于1/500k 2000 ns一个时间量子的长度等于(1 BRP) / PCLK。如果BRP取0时间量子就是33.3 ns2000 ns大概需要60个时间量子显然TSEG加起来不够用。所以要把BRP调大一些。常见的做法是先定采样点再定分频系数。我习惯让总时间量子数落在8到25之间太小采样点调整粒度太粗太大则对时钟精度要求高。这里把BRP设为2时间量子长度就是3/30 MHz 100 ns位时间2000 ns对应20个时间量子。总时间量子数 1 (TSEG1 1) (TSEG2 1)也就是位时间量子总数 1 TSEG1寄存器值 1 TSEG2寄存器值 1 TSEG1寄存器值 TSEG2寄存器值 3。要使总和等于20可以取TSEG1 13、TSEG2 4这样总数为1 14 5 20采样点位置 (1 14) / 20 75%。SJW的取值一般不超过4这里取1到4都可以常见配置是SJW TSEG2 4。最终寄存器配置就是BRP 2、TSEG1 13、TSEG2 4、SJW 4。我把常用波特率在30 MHz PCLK下的配参整理成了表格调试时可以直接抄目标波特率BRP寄存器值TSEG1寄存器值TSEG2寄存器值采样点实际波特率1 Mbps013475%1.000 Mbps800 kbps113475%0.833 Mbps500 kbps213475%0.500 Mbps250 kbps513475%0.250 Mbps125 kbps1113475%0.125 Mbps要注意800 kbps那组算出来实际是833 kbps不是标准800 kbps说明30 MHz PCLK对800 kbps的整数分频支持不够理想。这类非标速率建议直接换PCLK频率或者接受一定误差前提是误差必须在容忍范围内。CAN协议的位时间误差容限一般在1%以内833 kbps和800 kbps之间差了4%大概率会出问题现场最好避免这种组合。4. NXP1778上的自动波特率检测工程实现4.1 硬件连接一根“偷听线”与最小电路硬件上我把CAN收发器的RXD输出同时接了两路一路到NXP1778的CAN接收引脚另一路到定时器捕获输入引脚。这样做的目的是在不影响正常CAN通信的前提下用定时器直接观察总线上的电平跳变。以我手头的板子为例CAN收发器用的是TJA1050RXD引脚是推挽输出逻辑电平与MCU的IO电平兼容。我把它分出一根走线串联一个100欧姆的电阻后接到定时器捕获引脚。串联电阻是为了在捕获引脚配置错误时不至于把收发器输出拉垮算是一道保护。需要强调一下这根“偷听线”不能随便接。首先捕获引脚必须支持定时器输入捕获功能具体是哪个引脚要看数据手册的引脚复用表。其次走线尽量短不要绕远避免引入额外电容导致边沿变缓。最后如果板上没有预留这个信号也可以把CAN收发器的RXD焊一根飞线出来实测在低速波特率下问题不大但1 Mbps时信号质量会受影响。上电之后先把CAN控制器设为复位模式不参与总线通信让定时器捕获通道先跑起来。此时总线上的任何报文都会在RXD引脚产生跳变定时器捕获模块就能记录下这些跳变的时间戳。4.2 定时器捕获粗测位时间的代码实现我用的是定时器1捕获通道0配置成上升沿和下降沿都捕获并开启捕获中断。核心思路是记录相邻两次跳变之间的时间差也就是脉冲宽度然后在一小段时间内统计这些脉冲宽度的最小值。为什么关注最小脉冲宽度因为CAN采用的是NRZ编码在位填充规则下连续相同电平的位数最多不超过5位。也就是说总线上电平跳变之间的间隔正常情况下最小正好等于1个位时间最大不会超过5个位时间。只要抓到了足够多的跳变边沿取其中最小的脉冲宽度就可以估算出位时间的上限。代码实现大致是这个样子void TIMER1_IRQHandler(void) { uint32_t curr_cap; uint32_t diff; static uint32_t last_cap 0; if (TIMER_GetIntCapture(TIMER1, TIMER_CAP_CH0)) { curr_cap TIMER_GetCaptureValue(TIMER1, TIMER_CAP_CH0); if (last_cap ! 0) { diff curr_cap - last_cap; if (diff min_pulse_width) { min_pulse_width diff; } } last_cap curr_cap; TIMER_ClearIntCapture(TIMER1, TIMER_CAP_CH0); } }定时器时钟我配成了30 MHz所以一个计数值对应33.3 ns。测到最小脉冲宽度之后用30 MHz除以这个计数值就得到了位时间再取倒数就是波特率。比如测到最小脉冲宽度是30个计数值对应1000 ns位时间波特率就是1 Mbps。有一个细节要提醒边沿捕获测量到的脉冲宽度在总线同时存在多个节点发送时会混入仲裁造成的额外跳变最小脉宽可能比真实的1个位时间更窄。所以粗测结果只能作为候选范围不能直接当最终结论必须走下一步的CAN控制器校验。4.3 波特率候选遍历与合法性校验粗测得到了一个波特率估值接下来要把它换成CAN控制器的实际寄存器配置并验证是否能正常通信。做法是生成一张候选波特率表把粗测值附近的几个常见波特率都放进去依次尝试。候选表不能拍脑袋定。我一般以粗测值为中心上下浮动20%作为搜索区间然后把区间内的标准波特率全部列进去。比如粗测结果是520 kbps那就把500 kbps和1 Mbps都列为候选优先试500 kbps。每个候选波特率下的流程如下先把CAN控制器退出复位模式清空错误计数器配置验收滤波器为接收所有帧然后启动一个200毫秒的接收超时定时器。如果在这段时间内收到了一帧RTR位为0的数据帧并且错误计数器没有超过阈值就认为当前波特率匹配成功。如果接收错误计数器持续累加或者收到了错误帧中断就立即切换到下一个候选波特率并把CAN控制器重新复位。为什么一定要等数据帧而不是任意帧因为远程帧只有ID和DLC负载区为空用来判断报文是否可信的维度太少了。数据帧则带CRC校验只要CRC能通过就说明从物理层到数据链路层都匹配上了可靠性很高。校验通过后把当前的波特率值固化到Flash存储里下次上电直接加载不用重新检测。如果现场总线环境变化也可以通过外部命令强制重新触发检测。4.4 锁存波特率后的正式初始化参数检测成功后设备不能直接用“扫描阶段”的临时参数跑业务因为扫描阶段为了快速切换采样点设置可能不是最优。锁定波特率之后我会再做一次正式的CAN参数初始化重新计算BRP、TSEG1、TSEG2和SJW。以500 kbps为例正式参数就是前面计算的那组BRP 2、TSEG1 13、TSEG2 4、SJW 4采样点落在75%。如果是1 Mbps则BRP 0、TSEG1 13、TSEG2 4。如果总线上有多个节点且线缆较长我倾向于把采样点往后调比如80%牺牲一点对时钟偏差的容忍度换取更靠后的采样时机。初始化代码我封装成了一个函数void can_init_with_baud(uint32_t baud) { CAN_CFG_T can_cfg; CAN_Init(CAN1, can_cfg); switch (baud) { case 1000000: CAN_SetBitrate(CAN1, 0, 13, 4, 4); break; case 500000: CAN_SetBitrate(CAN1, 2, 13, 4, 4); break; case 250000: CAN_SetBitrate(CAN1, 5, 13, 4, 4); break; case 125000: CAN_SetBitrate(CAN1, 11, 13, 4, 4); break; default: break; } }初始化完成后把验收滤波器按业务需求重新配置再开启接收中断整个自动波特率检测流程才算彻底走完。从设备上电到正式收发报文实测最快能压到100毫秒以内慢也不会超过500毫秒。5. 实测结果与踩坑现场5.1 三组实测数据对比我在两种硬件环境上做了验证一种是我自己的开发板CAN收发器到MCU走线很短大概3厘米另一种是模拟现场的长走线环境人为串了1米左右的杜邦线。每组环境分别测了125 kbps、250 kbps和500 kbps三个速率。短走线环境下的结果非常理想最小脉冲宽度测量稳定候选表几乎第一次就命中检测耗时在80到120毫秒之间。长走线环境下捕获到的脉冲宽度噪声明显变大出现过一次粗测值偏离真实值15%的情况好在候选表覆盖了正确速率最终都成功锁定。还有一个意料之中的现象125 kbps的位时间长达8微秒检测最容易几乎不会误判。反而是1 Mbps这种高速率位时间只有1微秒定时器捕获如果中断响应不及时容易丢掉边沿导致最小脉宽测出来偏大把1 Mbps误判成500 kbps。针对这个情况我最后把定时器捕获优先级调到最高并且把捕获中断服务函数里的操作精简到极致只做减法运算和最小值更新其他判断全部挪到主循环里处理。优化之后1 Mbps的误判率基本降到了零。5.2 调试中遇到的四个坑第一个坑是“第一个下降沿的假脉冲”。上电瞬间CAN收发器输出可能有一段不确定电平定时器捕获模块会记录到非常窄的毛刺最小脉冲宽度直接变成几十纳秒整个换算逻辑瞬间失效。处理办法是在软件上设置一个最小脉冲宽度的有效范围比如小于10个定时器计数值的直接丢弃不参与计算。第二个坑是“总线空闲时捕获不到边沿”。如果设备上电后总线上正好没有节点发报文定时器捕获一直等不到跳变检测就会卡死。我加了一个超时逻辑超过500毫秒没有捕获到边沿就自动切换到纯扫描模式把候选波特率表整个跑一遍同时周期性地发出一个请求帧诱使对端节点回复。第三个坑是“验收过滤器把合法帧丢了”。扫描阶段配置成接收所有帧后有时反而收不到原因是我忘了禁用验收过滤器的工作模式默认的过滤规则把帧全滤掉了。这个问题排查了很久最后发现是初始化顺序不对必须先把过滤器设为Bypass模式再启用CAN控制器。第四个坑是“波特率表里漏了非标速率”。现场总线可能是500 kbps之外的特殊速率比如333.33 kbps或666.66 kbps候选表里没有粗测值又恰好落在两个标准速率之间导致校准失败。后来我在候选表生成逻辑里加了一步如果粗测值与两个相邻标准速率的偏差都超过5%就把粗测值对应的波特率四舍五入后也加入候选表保证不遗漏。6. 高频问题速查与一点心得体会6.1 CAN自动波特率排查速查表现象可能原因排查思路检测超时无结果总线上没有节点发报文检查硬件接线确认对端波特率增加诱探帧锁定波特率后偶发错误帧采样点配置不优用示波器观察眼图调整TSEG1/TSEG2位置粗测值明显偏离真实值捕获中断响应慢或毛刺干扰提高中断优先级增加最小脉宽过滤某些波特率始终检测不到候选表覆盖不全以粗测值为中心动态生成候选表检测成功后重启又不识别Flash存储异常或对端换速率检查存储逻辑增加二次在线重检测总线Bus Off反复波特率不匹配或有多个冲突节点查看错误计数器用CAN分析仪抓错误帧类型这张表里的问题我几乎都在调试过程中实际遇到过。最值得记下的一条经验是如果你的设备在锁定波特率之后依然频繁出现错误帧先别急着怀疑软件用示波器对比一下CAN_H和CAN_L的差分波形看采样点附近信号是否已经稳定。很多时候问题出在终端电阻缺失或者接了两端120欧姆电阻导致信号反射跟波特率检测本身没关系。6.2 最后想说的几点经验这一步做完自动波特率检测的基本功就算扎实了。最后聊几点我个人的感受。定时器捕获加候选表这套方案最大的价值在于把“盲试”变成了“有方向的试”。纯扫描法也不是不能用但在1 Mbps这种高速率下面对一堆未知节点一遍扫下来如果是非标速率基本就废了。有了粗测环节整个检测过程smart了很多鲁棒性也明显提升。另一个体会是自动波特率检测的上限取决于物理层质量。总线线缆过长、分支过多、没有终端电阻都会让波形边沿变缓直接干扰捕获测量。工程上如果总线拓扑很恶劣我建议在检测阶段把CAN收发器的工作模式切换到斜率控制模式牺牲一点速率换取信号完整性检测完成后再切换回高速模式。如果你后续想在这个基础上扩展可以考虑把自动检测和整车/现场的波特率白名单结合起来。比如某些安全敏感的总线虽然支持自动检测但只允许在预设的白名单速率范围内切换超出范围直接报错并切断通信。这样既保留了便利性又不至于因为误配波特率把一条正在运行的总线拖垮。项目做到后面你会发现真正考验功力的不是检测算法本身而是各种边界情况的处理——总线空闲、节点丢帧、信号毛刺、非标速率。把这些边角料处理干净系统才算真正稳定。这套代码我后来移植到好几款不同MCU上核心算法没怎么改只换了寄存器和中断处理充分说明思路对了平台差异只是时间问题。
返回列表