从物理层到STM32实战:CAN总线调试避坑指南

发布时间:2026/10/11 1:40:37
从物理层到STM32实战:CAN总线调试避坑指南 调试CAN总线的时候你是不是也遇到过这种场景明明按手册配置好了波特率、收发器也接了示波器探针搭上去却死活看不到一个波形或者数据发得出去了对端就是收不到。这种问题在CAN项目里太常见了。CANController Area Network总线从诞生到现在几乎每一颗车规级MCU都内置了CAN控制器汽车电子、工业控制、仪器仪表、储能BMS里到处都有它的身影。搞懂CAN不只是会调库、会发几帧数据而是得理解它为什么这么设计、物理层的信号到底长什么样、帧仲裁为什么不会破坏数据。这篇文章就从物理层讲到协议层再从STM32外设配置讲到完整收发Demo最后把我这几年调CAN攒下的排查经验和避坑技巧全部摊开讲适合刚接触CAN的学生、刚转行做嵌入式的开发也适合那些已经把CAN跑起来但一直没琢磨透底层细节的朋友。1. CAN 总线到底是什么——先搞定物理层那点事1.1 差分信号CAN 为什么抗干扰这么强CAN总线通常就用两根线CANH和CANL走的是差分信号。什么叫差分就是节点发送数据时不是输出一个对地绝对电平而是让两根线之间存在一个电压差值接收端只认这个差值不认单根线对地的绝对电压。用个生活类比两个人并排走路一个负责说话一个负责录周围环境噪声回来把两段录音相减环境噪声被抵消语音反而更清晰。CAN就是这个思路——共模噪声同时叠加在CANH和CANL上差分相减之后噪声被消除真正的差分电压留了下来。这就是CAN能在发动机点火、电机驱动这种电磁环境极其恶劣的地方稳定通信的根本原因。实际电平关系是这样的隐性位逻辑1CANH和CANL都停在2.5V附近差分电压约为0V。显性位逻辑0CANH被拉高到约3.5VCANL被拉到约1.5V差分电压约2V。注意一个关键点CAN总线的“显性”和“隐性”并不是简单的TTL高电平对应1而是显性位0会覆盖隐性位1。这个特性直接决定了后面要讲的仲裁机制也是理解CAN协议的基础。1.2 终端电阻为什么是首尾各一个120欧姆CAN总线两端必须各接一个120Ω终端电阻这是很多初学朋友最容易忽略的地方也是最容易出问题的点。原因并不复杂信号在导线末端如果遇到阻抗不连续会发生反射反射波叠加在原始信号上轻则让边沿变圆、抖动重则导致接收端误判电平。120Ω是为了匹配双绞线的特征阻抗让信号走到总线末尾时被吸收而不是反射回去。这里有个非常常见的误区网上不少教程写“每个节点都接120Ω终端电阻”这绝对是错的。CAN总线上只能有两个终端电阻物理位置在总线最远的两端。如果多个节点都接了120Ω并联等效阻抗会明显小于120Ω总线上的差分信号幅值会被拉低严重时接收端完全无法识别显性位。我调试过一块多板级联的设备一开始每块板子都带了120Ω跳线帽全部默认焊上结果总线怎么调都是错误帧满天飞。后来把中间所有节点的电阻都断开只在首尾保留信号瞬间就正常了。1.3 波特率与位时序到底怎么算CAN的波特率不是拍脑袋定的它是通过控制器的分频器和位时间参数配出来的。STM32的bxCAN里需要关注几个参数预分频BRP、同步段、BS1段、BS2段还有SJW同步跳转宽度。最核心的公式是波特率 时钟频率 / (BRP × (1 BS1 BS2))其中括号里是总共的时间量子个数1是固定的同步段所占的1个时间量子。举个例子STM32F103的CAN外设挂在APB1总线上APB1时钟是36MHz目标是配置出500kbps。那么36MHz / 500kbps 72也就是说 BRP × (1 BS1 BS2) 72。常见取法BRP4(1BS1BS2)18。如果BS114BS23则采样点 (114)/18 83.3%如果BS113BS24采样点 77.8%如果BS19BS28采样点只有55.6%。采样点这个参数特别容易被忽视。CAN协议要求在位的后半段采样工程上一般推荐75%~87.5%越接近87.5%对总线长度和节点时钟误差的容忍度越高。默认配置BS19、BS28采样点55.6%也能跑但网络拓扑一变、线一长或者某个节点晶振精度差一点就开始出现随机错误帧。所以我现在做项目只要波特率余量允许一律把采样点往87.5%靠。下面给出STM32F103在APB136MHz时的几组常用配置供直接参考目标波特率BRPBS1BS2实际波特率采样点125kbps8132125k82.4%250kbps4132250k82.4%500kbps4132500k82.4%1Mbps21321M82.4%这份表格是我自己项目里常用的组合实测在工业现场环境下稳定性不错。如果你的板子上CAN时钟来自其他频率套用上面公式自己算一遍就行关键是理解原理而不是背参数。2. 协议层的几个核心机制看懂才算入门2.1 帧结构拆解标准帧和扩展帧差在哪CAN协议里最常用的报文是数据帧另外还有远程帧、错误帧和过载帧。数据帧又分标准帧CAN 2.0A11位标识符和扩展帧CAN 2.0B29位标识符。一帧标准数据帧从前往后依次是SOF起始帧1个显性位标志一帧开始。仲裁段11位ID RTR位远程发送请求位数据帧为0。控制段IDE位、保留位和DLC数据长度码DLC指明后面带几个数据字节最多8字节。数据段0~8字节的实际数据。CRC段15位CRC校验位和CRC界定符。ACK段接收节点在ACK槽发送显性位表示“我收到了”。EOF7个隐性位帧结束。扩展帧的区别在仲裁段和控制段IDE位为1ID从11位扩展到29位。实际项目里大多数场景用标准帧就够了比如工业设备之间点对点通信、传感器数据上报。汽车OBD诊断报文多是扩展帧但那是特定协议的要求。一个容易混淆的地方DLC虽然是4位但它最大只能表示8因为经典CAN的数据段上限就是8字节。有人说DLC可以配成9~15表示特殊用途但在标准CAN协议里设了不生效发送时实际还是不会超过8字节。CAN FD灵活数据速率协议出现后才有更长的数据段那是另一套玩法你手上不是支持CAN FD的控制器就不要乱设。2.2 非破坏性仲裁为什么CAN不用CSMA/CD那套以太网收发数据时遇到冲突会停止发送然后随机退避一段时间再重发这种方式叫CSMA/CD碰撞本身会毁掉报文。CAN不一样它用的是非破坏性逐位仲裁。原理是多个节点同时发送时在仲裁段逐位比较ID。显性位0在物理上会覆盖隐性位1所以发送隐性位的节点如果监测到总线上出现了和自己发送不一样的显性位就立刻知道自己仲裁失败马上退出发送变成接收者等总线空闲后再自动重发。整个过程没有一个数据位被破坏仲裁失败的节点不需要像以太网那样随机退避总线上最终留下的是优先级最高的那帧报文。基于这个机制CAN报文里ID越小、优先级越高。在设计系统时ID的规划很重要紧急报文比如安全相关的状态帧的ID要尽量小周期性慢速报文的ID可以大一些。2.3 错误处理机制和节点状态机CAN协议定义了5种错误类型位错误发送节点发送一个位同时在总线上监听到不同的电平。填充错误CAN规定每连续5个相同位后必须插入一个反相填充位如果违反了这个规则就报错。CRC错误接收节点计算的CRC和发送帧里的CRC不一致。形式错误固定格式的位比如CRC界定符、EOF电平不正确。应答错误发送节点在ACK槽没有检测到显性位说明总线上没有其他节点成功接收。一旦检测到错误节点会发送错误帧同时内部的错误计数器会进行加减。按错误计数器的值节点状态分为错误主动、错误被动、总线关闭三个等级错误主动正常参与通信出错时发送主动错误帧。错误被动仍然能收发但只能发送被动错误帧而且发送前必须等待总线空闲。总线关闭彻底和总线隔离不再收发任何帧需要软件复位或者等硬件恢复机制重新加入总线。对应用开发来说最需要关注的是“为什么我的节点进总线关闭了”。常见原因就是总线短路、缺少终端电阻、波特率偏差太大。STM32的bxCAN里有个ABOM自动总线关闭管理位开启后节点在被总线关闭后会自动恢复对可靠性要求高的系统建议打开不然节点离线后需要软件干预才能重新上线。3. STM32 bxCAN 外设配置实操3.1 引脚、时钟和初始化标志位STM32的bxCAN控制器使用起来非常套路化先配时钟再配引脚复用最后配置CAN模式和位时序。以STM32F103为例CAN1可以映射到PA11/PA12也可以通过重映射放到PB8/PB9。我用PB8/PB9比较多因为PA11/PA12经常被USB占掉通道冲突。配置代码用标准外设库写出来是这样GPIO_InitTypeDef GPIO_InitStructure; CAN_InitTypeDef CAN_InitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); GPIO_PinRemapConfig(GPIO_Remap1_CAN1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); CAN_InitStructure.CAN_TTCM DISABLE; CAN_InitStructure.CAN_ABOM ENABLE; CAN_InitStructure.CAN_AWUM ENABLE; CAN_InitStructure.CAN_NART DISABLE; CAN_InitStructure.CAN_RFLM DISABLE; CAN_InitStructure.CAN_TXFP DISABLE; CAN_InitStructure.CAN_Mode CAN_Mode_Normal; CAN_InitStructure.CAN_SJW CAN_SJW_1tq; CAN_InitStructure.CAN_BS1 CAN_BS1_13tq; CAN_InitStructure.CAN_BS2 CAN_BS2_2tq; CAN_InitStructure.CAN_Prescaler 4; CAN_Init(CAN1, CAN_InitStructure);几个标志位的含义值得展开说说CAN_ABOM自动离线恢复。总线关闭后硬件会自动等待恢复时序重新参与总线通信省掉软件干预推荐打开。CAN_AWUM自动唤醒。节点从睡眠模式收到总线活动时自动唤醒多用于低功耗设计。CAN_NART禁止自动重传。如果配置成ENABLE发送失败后硬件不会自动重试我习惯保持DISABLE让硬件自动重传这样应用层只需要把报文交给控制器剩下的交给硬件。CAN_RFLMFIFO锁定模式。如果FIFO满了RFLMENABLE时新报文直接被丢弃保持旧报文DISABLE时新报文覆盖旧报文。具体用哪种取决于你的业务状态类报文覆盖旧的没问题诊断记录类报文建议锁定旧的。3.2 过滤器配置接收报文的闸门bxCAN最大的特色就是有滤波器组STM32F103有28个过滤器组。它的作用是在硬件层先过滤一遍报文匹配的才进FIFO不匹配的直接丢弃完全不进中断、不占CPU。过滤器有两种工作模式列表模式和屏蔽位模式。列表模式要求报文ID必须和预设ID完全一致屏蔽位模式则是通过掩码决定ID的哪些位必须匹配、哪些位不关心。调试阶段最简单的方式是让所有报文都通过掩码全部清零就行CAN_FilterInitTypeDef CAN_FilterInitStructure; CAN_FilterInitStructure.CAN_FilterNumber 0; CAN_FilterInitStructure.CAN_FilterMode CAN_FilterMode_IdMask; CAN_FilterInitStructure.CAN_FilterScale CAN_FilterScale_32bit; CAN_FilterInitStructure.CAN_FilterIdHigh 0x0000; CAN_FilterInitStructure.CAN_FilterIdLow 0x0000; CAN_FilterInitStructure.CAN_FilterMaskIdHigh 0x0000; CAN_FilterInitStructure.CAN_FilterMaskIdLow 0x0000; CAN_FilterInitStructure.CAN_FilterFIFOAssignment CAN_FIFO0; CAN_FilterInitStructure.CAN_FilterActivation ENABLE; CAN_FilterInit(CAN_FilterInitStructure);掩码全0意味着不检查任何位所有ID都能进。等系统功能跑通后再把掩码收紧比如只接收0x1xx范围内的标准帧就把掩码的ID高位部分保留。这里有个经验FIFO0和FIFO1可以配合使用把高优先级报文分配进FIFO0接收中断也分开处理这样紧急报文不会排在普通报文后面等着处理。这个用好了对系统实时性提升很明显。3.3 报文的发送邮箱机制和后期的坑bxCAN的发送端有3个发送邮箱。发送流程是把报文填进一个空闲邮箱然后请求发送硬件在总线空闲时自动把帧发出去。标准外设库的发送代码CanTxMsg TxMessage; TxMessage.StdId 0x123; TxMessage.RTR CAN_RTR_Data; TxMessage.IDE CAN_Id_Standard; TxMessage.DLC 8; for (i 0; i 8; i) { TxMessage.Data[i] tx_buffer[i]; } CAN_Transmit(CAN1, TxMessage);注意CAN_Transmit本身只是把报文放进邮箱并触发发送它不等发送完成。如果3个邮箱都满了CAN_Transmit会返回发送失败。所以发送前最好查一下邮箱空闲数量或者用发送完成中断来做下一帧的排队。HAL库的版本略有不同它用CAN_TxHeaderTypeDef结构体CAN_TxHeaderTypeDef TxHeader; uint8_t txData[8]; uint32_t txMailbox; TxHeader.StdId 0x123; TxHeader.IDE CAN_ID_STD; TxHeader.RTR CAN_RTR_DATA; TxHeader.DLC 8; HAL_CAN_AddTxMessage(hcan, TxHeader, txData, txMailbox);HAL的AddTxMessage只是把报文加入邮箱实际发送状态通过HAL_CAN_GetTxMailboxesFreeLevel查询或者注册HAL_CAN_TxMailbox0CompleteCallback回调函数。我踩过的坑在初始化CAN后马上调HAL_CAN_Start这是必须的不然过滤器配置好了但CAN外设根本没有启动总线上一帧都发不出去。另外如果开启了CAN中断却没写对应中断服务函数或者开启了发送邮箱空中断但不处理程序会不停进入中断表现就是主循环卡住、看上去像死机。3.4 报文的接收中断里尽量别干重活接收的核心是FIFOFIFO0和FIFO1各能存3帧报文。推荐用中断方式接收报文到达后硬件把帧存进FIFO然后触发中断。标准外设库的中断服务函数可以这样写void USB_LP_CAN1_RX0_IRQHandler(void) { CanRxMsg RxMessage; CAN_Receive(CAN1, CAN_FIFO0, RxMessage); rx_flag 1; rx_id RxMessage.StdId; rx_len RxMessage.DLC; memcpy(rx_data, RxMessage.Data, 8); }HAL库则是通过回调函数void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef RxHeader; uint8_t rxData[8]; HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, RxHeader, rxData); // 拷贝数据置标志位 }最重要的习惯是中断服务函数里只做数据拷贝、置标志位真正的业务解析放到主循环里处理。因为CAN的中断优先级一般设置得比较高如果中断里做复杂的协议解析、浮点运算会导致其他低优先级中断长时间得不到响应而且FIFO可能在处理过程中溢出丢帧。4. 完整应用 DemoSTM32 双机 CAN 通信实战4.1 硬件准备和连接这个Demo我建议用两块STM32F103最小系统板来做成本低好复现。需要准备的东西有两块STM32F103开发板。两个CAN收发器模块最常见的是TJA1050或者MCP2551模块。双绞线或者普通杜邦线建议用双绞线抗干扰好一些。两个120Ω电阻。接线方式也很关键板子A的CAN_TX接收发器A的TXDCAN_RX接RXD。板子B同样的接法。收发器A的CANH接收发器B的CANHCANL接CANL。总线首尾各接一个120Ω电阻即收发器A的CANH和CANL之间接一个收发器B的CANH和CANL之间接一个。两块板子的GND必须连在一起。这里必须强调共地问题。CAN是差分信号确实不太依赖共地但收发器本身是单端供电器件它的信号参考地必须和控制器一致。如果不共地TXD/RXD的电平就不在正常范围内通信必然失败。我在实际调试中吃过这个亏用两个独立USB供电的开发板直接怼结果TJA1050模块一直输出错误帧接上共地线立刻好了。如果项目最终需要电气隔离应该使用带隔离的CAN收发器模块比如ADM3053这类在隔离电源支持下才能实现真正的不共地通信。4.2 双机收发代码思路整个Demo分两个角色A机周期性发送数据帧B机收到后回发应答帧。这样一套流程同时验证了发送、接收、中断、过滤器和回帧逻辑。A机主循环里每500ms发送一帧uint8_t tx_buf[8]; uint16_t counter 0; while (1) { tx_buf[0] counter 8; tx_buf[1] counter 0xFF; tx_buf[2] 0x01; tx_buf[3] 0x02; tx_buf[4] 0x03; tx_buf[5] 0x04; tx_buf[6] 0x05; tx_buf[7] 0x06; CanTxMsg TxMessage; TxMessage.StdId 0x101; TxMessage.RTR CAN_RTR_Data; TxMessage.IDE CAN_Id_Standard; TxMessage.DLC 8; memcpy(TxMessage.Data, tx_buf, 8); CAN_Transmit(CAN1, TxMessage); counter; delay_ms(500); }B机在接收中断里解析如果ID是0x101就回发一个应答帧ID用0x201数据长度8字节内容记录自己的状态和接收到的计数器值。这样做的好处是一旦链路有问题你能立刻判断方向A机有没有发、B机有没有回。上位机或者示波器一看就清楚。4.3 用 USB-CAN 分析仪验证链路在实际项目中数据最终是给到上位机的所以我强烈建议手头常备一个USB-CAN分析仪。它可以把总线上的所有报文抓出来带时间戳、带ID、带数据还能统计错误帧数量是CAN调试的“照妖镜”。调试流程一般是这样的先把分析仪的CANH、CANL接上总线共地。上位机软件设置波特率和分析仪一致比如500kbps。开始捕获观察报文的ID、DLC和数据是否符合预期。如果看到错误帧优先检查终端电阻、波特率和共地。用软件自带的“发送单帧”功能把测试帧直接发到总线上验证下位机的接收逻辑。上位机软件的界面通常都能显示CAN报文失败计数、错误帧计数这些指标比正常报文本身更能反映链路质量。5. 常见问题与排查技巧实录5.1 调试中的高频问题速查下面这张表是我这几年做CAN项目遇到最多的问题合集按现象、可能原因、解决办法列出来遇到问题先对照这个表排查一遍现象可能原因解决办法示波器看不到任何波形收发器没供电、接线错误、CAN外设没有启动检查收发器VCC/GND检查CANH/CANL是否接反确认配置后调HAL_CAN_Start上位机大量错误帧终端电阻缺失或数量不对、共地问题、波特率不一致首尾各接120Ω统一接地确认所有节点波特率一致能发送但接收端收不到过滤器配置错误、掩码挡住了报文先配置全通过滤器逐位收紧掩码测试发送一直失败/邮箱全满总线上没有ACK应答、总线短路检查对端收发器是否供电、是否只有单节点在总线上节点运行一会儿离线总线关闭打开ABOM自动恢复同时排查根本原因大概率还是波特率或电阻问题接收中断频繁但FIFO溢出中断处理时间太长、FIFO太小中断里只拷贝置位业务放主循环必要时用DMA或者提高中断优先级5.2 示波器实测波形怎么看示波器测CAN有一个方便之处总线活跃时CANH和CANL之间有比较明显的差分方波。最直观的观测方式是交流耦合、两个通道相减直接看CANH减CANL的结果。总线空闲时CANH和CANL都稳定在2.5V附近差分输出是一条平线。一旦有节点发送差分波形会先出现一个显性位SOF位往下掉的边沿非常明显。示波器触发方式设置为下降沿触发触发电平设置在1V左右就能稳定抓到完整帧。如果总线上两个终端电阻都在显性位差分电平应该在2V左右如果电阻数量不对这个幅值会明显偏低。很多人波形看不到或者幅值只有几百毫伏排查了一圈最后发现就是终端电阻接多了或者接少了。强调一下用示波器看CAN的电压一定不要只探单根线。单看CANH对地你只会看到一条在2.5V上下抖动的线根本分不清是信号还是噪声必须看差分。5.3 几个能保命的经验和习惯第一ID分配要有规划。别随手给每个报文一个ID最后发现紧急报文ID不是最小。建议先列一张表格把所有要传输的报文语义、周期、优先级整理好再给ID编号。系统大了以后这张表就是整个团队的通信协议。第二采样点别用默认值迷信。我见过太多项目初期默认配置能跑量产之后批量组装却偶发通信错误。把采样点调到82%以上配合终端电阻能显著提升批量产品的通信一致性。第三错误帧一定要保留统计信息。很多芯片的CAN外设都有错误计数寄存器把这些值记录下来在对外沟通时是非常有力的证据。你说“CAN通信有时候不稳定”别人没法判断你说“发送错误计数在10秒内从0涨到96接收错误计数稳定在0”对方立刻知道问题大概率在发送方向或者总线上。第四调试初期别着急写业务逻辑先用一个最简单的“回环”测试单节点把发送邮箱连到接收过滤器自发自收。如果自发自收都失败那就是配置问题如果自发自收成功但双机失败才需要考虑物理层和对方节点的问题。这个二分法定位效率极高。我个人在实际操作中有个习惯每写一个CAN相关项目第一件事是把CAN错误中断使能打开在错误中断里把错误计数打出来。很多人只开了收发中断等出了问题一片茫然。多花一分钟开错误中断能帮你省下后面几小时的抓瞎时间。另外把总线上所有节点的波特率误差算一遍速率为500kbps时晶振误差超过0.5%就要警惕哪怕是便宜的晶振批量采购时也容易有这个坑。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询