尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

嵌入式CAN总线从原理到实战:物理层、仲裁机制与STM32配置调试

嵌入式CAN总线从原理到实战:物理层、仲裁机制与STM32配置调试 1. 为什么CAN总线是嵌入式工程师绕不开的一道坎搞嵌入式开发的人迟早会撞上CAN总线。不管你是在做汽车电子、工业控制、医疗器械还是机器人只要涉及多节点通信CAN几乎都是默认选项。我最早接触CAN是在一个车载项目上当时用STM32的bxCAN外设调了整整两天才把通信跑通回头看其实核心问题就出在对协议理解不透彻——以为配好波特率就能收发结果连ACK场是干什么的都没搞清楚。CAN的全称是Controller Area Network中文叫控制器局域网。它最早是博世公司在1980年代为汽车内部ECU通信设计的后来因为可靠性高、实时性强、成本低逐渐扩散到工业自动化、电梯控制、船舶电子等领域。和UART、SPI、I2C这些常见总线相比CAN最大的特点是差分信号传输、非破坏性仲裁、硬件级错误检测和自动重传。这几点决定了它在电磁环境恶劣、节点数量多、实时性要求高的场景下几乎没有对手。这篇文章适合谁看如果你正在学嵌入式课本上CAN那一章翻来覆去就讲帧格式看完还是不知道怎么用如果你已经工作项目里要用CAN但之前只调过UART和SPI如果你是面试准备中的同学CAN几乎是嵌入式岗位的必考题。我会从协议原理讲到寄存器配置从硬件电路讲到调试排错把我在实际项目中踩过的坑和总结的经验都摊开来讲。需要提前说明的是CAN协议本身并不复杂但细节极多。很多人调不通CAN不是代码写错了而是终端电阻没接、波特率算错了、或者收发器选型不对。这些“非代码问题”恰恰是实际工作中最耗时间的部分也是本文重点覆盖的内容。2. CAN总线核心原理拆解从物理层到仲裁机制2.1 差分信号与物理层为什么CAN能抗干扰CAN总线使用两根线CAN_H和CAN_L。显性电平逻辑0时CAN_H约3.5VCAN_L约1.5V压差约2V隐性电平逻辑1时两根线都在2.5V左右压差接近0V。接收端判断的是压差而不是绝对电压这就是差分传输的核心优势——共模干扰会同时耦合到两根线上压差保持不变信号不受影响。实际布线时CAN_H和CAN_L必须用双绞线绞在一起绞距越密抗干扰越好。我见过有人用两根独立导线接CAN短距离低速还能凑合一旦线长超过几米或者旁边有电机、继电器通信立刻出错。这不是代码能解决的问题必须从物理层入手。终端电阻是另一个高频踩坑点。CAN总线两端各需要接一个120Ω电阻并联后等效60Ω。这个电阻的作用是消除信号反射。总线上的信号到达末端时如果阻抗不匹配就会反射回来和原始信号叠加后导致电平判断错误。高速CANISO 11898-2必须接终端电阻低速容错CANISO 11898-3则不需要。很多开发板已经集成了120Ω电阻但如果你是自己画板或者用杜邦线连接多个节点一定要确认总线两端是否有终端电阻。注意终端电阻只接在总线的两个物理端点中间节点不需要接。如果每个节点都焊了120Ω并联后阻抗过低总线驱动能力不够通信距离和稳定性都会大幅下降。2.2 CAN帧格式标准帧与扩展帧的区别CAN协议定义了四种帧类型数据帧、远程帧、错误帧、过载帧。实际开发中99%的场景用的都是数据帧。数据帧又分标准帧11位标识符和扩展帧29位标识符。标准帧的结构依次是帧起始SOF1位显性、仲裁场11位ID RTR位、控制场IDE位 保留位 4位DLC、数据场0~8字节、CRC场15位CRC 1位界定符、ACK场1位ACK槽 1位界定符、帧结束7位隐性。扩展帧在仲裁场多了18位ID同时IDE位为隐性。标准帧和扩展帧可以在同一总线上共存但优先级不同——ID数值越小优先级越高标准帧的11位ID在仲裁时先于扩展帧的IDE位参与比较所以标准帧优先级天然高于扩展帧。DLCData Length Code是4位取值0~8表示数据场字节数。注意CAN经典帧最多只能传8字节CAN FD可以传64字节但那是另一套协议本文主要讲经典CAN。2.3 非破坏性仲裁CAN最精妙的设计CAN总线最核心的机制就是非破坏性仲裁。总线上所有节点同时发送时谁的数据先发完谁就赢输的节点自动退出仲裁等总线空闲后重发。这个过程不会破坏赢家的数据所以叫“非破坏性”。具体原理节点在发送每一位的同时也在监听总线。如果它发送隐性1但监听到显性0说明有更高优先级的节点在发送它立刻退出仲裁转为接收模式。ID越小显性位越多优先级越高。比如ID0x100和ID0x200同时发送0x100在某个高位是0显性0x200是1隐性0x200的节点监听到总线是0但自己发的是1就知道自己输了主动退出。这个机制决定了CAN的实时性——高优先级消息的延迟是确定性的不会被低优先级消息阻塞。在汽车里刹车、气囊这类安全相关消息的ID通常设得很小就是利用这个特性。2.4 错误检测与自动重传CAN的可靠性从哪来CAN有五重错误检测机制位错误、填充错误、CRC错误、格式错误、ACK错误。任何一个节点检测到错误都会立即发送错误帧6个连续显性位通知总线上所有节点丢弃当前帧。每个节点维护两个计数器发送错误计数器TEC和接收错误计数器REC。错误计数增加或减少的规则很细但核心逻辑是正常收发时计数器递减出错时递增。当TEC或REC超过127时节点进入“错误被动”状态发送错误帧的能力受限超过255时节点进入“总线关闭”状态完全停止收发需要软件干预或硬件复位才能恢复。自动重传是硬件行为发送失败的帧会自动重发直到成功或进入总线关闭。这意味着应用层不需要处理重传逻辑但也意味着如果总线上有个节点持续出错它会不断重发占用总线带宽。实际调试时如果发现总线负载异常高先查有没有节点处于错误状态。3. 嵌入式开发中CAN控制器的配置与实操3.1 硬件选型MCU内置CAN还是外挂控制器主流MCU基本都内置CAN控制器。STM32的bxCAN、NXP的FlexCAN、TI的DCAN都是常见选项。内置控制器的优势是成本低、体积小、寄存器直接操作劣势是通道数有限一般1~3路。如果项目需要4路以上CAN或者主控MCU没有CAN外设就需要外挂控制器比如MCP2515SPI接口或SJA1000并行接口。MCP2515在开源硬件圈很流行因为它接线简单SPI最高10MHz配合TJA1050收发器就能工作。但MCP2515的FIFO只有3个缓冲区高负载场景下容易丢帧工业项目里我更倾向用内置CAN的MCU。收发器是另一个必须关注的器件。MCU的CAN_TX和CAN_RX是逻辑电平不能直接接总线必须经过收发器转换成差分信号。常用型号有TJA1050高速CAN、TJA1042带待机模式、SN65HVD2303.3V供电。选型时注意供电电压和速率等级TJA1050是5V供电和3.3V MCU连接时TX引脚可能需要电平匹配。实操心得TJA1050的TX引脚内部有上拉如果MCU的CAN_TX配置为开漏输出可能无法拉低。我遇到过STM32的CAN_TX配置成复用推挽后通信正常换成开漏就发不出去的情况。建议TX用推挽RX用浮空或上拉输入。3.2 波特率计算一个容易算错的细节CAN波特率由位时间决定位时间分为四个段同步段SYNC_SEG、传播段PROP_SEG、相位缓冲段1PHASE_SEG1、相位缓冲段2PHASE_SEG2。总位时间 1 PROP_SEG PHASE_SEG1 PHASE_SEG2单位是时间份额Tq。Tq (BRP 1) / Fpclk其中BRP是波特率预分频器Fpclk是CAN外设时钟。波特率 1 / (总位时间 × Tq)。以STM32F103为例APB1时钟36MHz目标波特率500kbps采样点75%。设BRP4则Tq 5/36MHz ≈ 138.9ns。总位时间 1/500kbps 2000ns需要的时间份额数 2000/138.9 ≈ 14.4取整为14。采样点75%意味着SYNC_SEG PROP_SEG PHASE_SEG1 14 × 0.75 ≈ 10.5取10PHASE_SEG2 4。对应寄存器BS19PROP_SEGPHASE_SEG1BS23PHASE_SEG2减1。实际配置时不用手算STM32CubeMX会自动计算但你要知道采样点的意义。采样点太靠前信号还没稳定就采样容易出错太靠后留给相位缓冲的时间不够。CAN标准推荐采样点在75%~87.5%之间500kbps通常用87.5%125kbps用75%。3.3 STM32 bxCAN初始化代码实战下面是一段我实际项目中用的CAN初始化代码基于STM32F103HAL库CAN_HandleTypeDef hcan; void CAN_Init(void) { hcan.Instance CAN1; hcan.Init.Prescaler 4; hcan.Init.Mode CAN_MODE_NORMAL; hcan.Init.SyncJumpWidth CAN_SJW_1TQ; hcan.Init.TimeSeg1 CAN_BS1_9TQ; hcan.Init.TimeSeg2 CAN_BS2_4TQ; hcan.Init.TimeTriggeredMode DISABLE; hcan.Init.AutoBusOff ENABLE; hcan.Init.AutoWakeUp DISABLE; hcan.Init.AutoRetransmission ENABLE; hcan.Init.ReceiveFifoLocked DISABLE; hcan.Init.TransmitFifoPriority DISABLE; HAL_CAN_Init(hcan); CAN_FilterTypeDef filter; filter.FilterBank 0; filter.FilterMode CAN_FILTERMODE_IDMASK; filter.FilterScale CAN_FILTERSCALE_32BIT; filter.FilterIdHigh 0x0000; filter.FilterIdLow 0x0000; filter.FilterMaskIdHigh 0x0000; filter.FilterMaskIdLow 0x0000; filter.FilterFIFOAssignment CAN_RX_FIFO0; filter.FilterActivation ENABLE; filter.SlaveStartFilterBank 14; HAL_CAN_ConfigFilter(hcan, filter); HAL_CAN_Start(hcan); HAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO0_MSG_PENDING); }这段代码有几个关键点。Prescaler4配合BS19、BS24在36MHz APB1时钟下得到500kbps。AutoBusOffENABLE让硬件在总线关闭后自动恢复省去软件干预。滤波器配置为接收所有IDMask全0实际项目中要根据需要设置掩码避免无关消息占用CPU。发送函数uint8_t CAN_Send(uint32_t id, uint8_t *data, uint8_t len) { CAN_TxHeaderTypeDef txHeader; uint32_t txMailbox; txHeader.StdId id; txHeader.ExtId 0; txHeader.IDE CAN_ID_STD; txHeader.RTR CAN_RTR_DATA; txHeader.DLC len; txHeader.TransmitGlobalTime DISABLE; return HAL_CAN_AddTxMessage(hcan, txHeader, data, txMailbox); }接收用中断回调void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef rxHeader; uint8_t rxData[8]; HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, rxHeader, rxData); // 处理接收到的数据 }3.4 滤波器配置别让无关消息打断CPUCAN滤波器是硬件级的可以在消息进入FIFO之前就过滤掉不相关的ID减少CPU中断负担。STM32的bxCAN有14个滤波器组互联型有28个每个组可以配置为32位掩码模式或16位列表模式。掩码模式的逻辑是接收到的ID与FilterId进行按位与再与FilterMask比较。如果FilterMask某位为1该位必须匹配为0则忽略。比如只想接收ID0x123的标准帧配置FilterId0x1235FilterMask0x7FF5。如果想接收0x120~0x12FFilterMask设为0x7F05。列表模式更直接每个滤波器组可以存两个精确ID只接收这两个ID的消息。适合ID数量少且固定的场景。常见坑STM32的滤波器ID需要左移5位对齐到寄存器高位因为标准帧ID是11位寄存器是32位。很多人直接写0x123进去结果一个消息都收不到。HAL库的FilterIdHigh和FilterIdLow需要手动移位CubeMX生成的代码有时也不对建议自己核对。4. 调试排错与实战经验CAN通信不通怎么查4.1 硬件排查从波形入手CAN通信不通第一步永远是看波形。用示波器同时抓CAN_H和CAN_L正常通信时应该看到差分方波显性电平压差约2V隐性约0V。如果两根线都是2.5V平线说明没有节点在发送或者收发器没工作。常见硬件问题按概率排序终端电阻缺失或过多、CAN_H和CAN_L接反、收发器供电异常、MCU的CAN_TX没有输出。我遇到过最隐蔽的一次是收发器的使能引脚悬空导致芯片处于待机模式波形完全正常但就是不通。后来查数据手册才发现TJA1042的STB引脚必须拉低才能进入正常模式。如果只有一个节点发送时用示波器看CAN_TX引脚应该有脉冲。没有脉冲说明MCU的CAN外设没配置好或者GPIO复用没开。有脉冲但总线无差分信号查收发器。4.2 软件排查错误计数器与状态寄存器STM32的CAN_ESR寄存器记录了错误状态。如果TEC或REC持续增长说明总线上有错误。常见原因波特率不匹配两个节点波特率差一点短帧可能通长帧必错、采样点偏差太大、总线电容过大导致边沿变缓。用CAN分析仪比如周立功的USBCAN或者开源的CANable抓包是最直接的。分析仪能看到每一帧的ID、数据、时间戳还能统计总线负载和错误帧。如果分析仪能收到但你的MCU收不到问题在滤波器或中断配置如果分析仪也收不到问题在物理层或发送方。实操心得调试CAN时先把所有节点的滤波器设为全通确认物理层和基本收发没问题再逐步加滤波器。我见过有人滤波器配错查了半天代码最后发现是掩码位算错了。4.3 常见问题速查表现象可能原因排查方法完全无通信终端电阻缺失、接线反、收发器未使能示波器看差分波形检查收发器STB引脚短帧通长帧不通波特率偏差大、采样点不对重新计算BRP和BS1/BS2用分析仪对比偶发丢帧总线负载高、FIFO溢出分析仪看负载率增大FIFO或降低发送频率进入总线关闭持续错误、TEC255查错误计数器检查是否有节点波特率不匹配只能收不能发ACK错误、无其他节点应答CAN发送需要至少一个节点ACK单节点自发自收需回环模式滤波器不生效ID未左移、掩码模式理解错误核对FilterId和FilterMask的位对齐4.4 回环模式与静默模式自测利器STM32的bxCAN支持回环模式Loopback和静默模式Silent。回环模式下发送的消息不经过总线直接回送到接收FIFO适合没有收发器时验证软件逻辑。静默模式下节点只接收不发送适合监听总线而不干扰通信。我习惯在硬件还没焊好之前先用回环模式跑通收发流程确认代码没问题再上真实总线。这样能把软件问题和硬件问题分开省去很多来回折腾。5. CAN在典型嵌入式项目中的应用与扩展5.1 汽车电子中的CAN网络分层汽车里的CAN网络通常分多条总线动力CAN500kbps、车身CAN125kbps、诊断CAN500kbps。不同总线通过网关连接网关负责路由和协议转换。动力CAN上挂发动机、变速箱、ABS等安全相关节点ID优先级设得很高车身CAN挂车窗、灯光、座椅等舒适性节点实时性要求低。实际开发中OEM会提供DBC文件里面定义了每个消息的ID、周期、信号布局。用Vector CANoe或开源的CANdb解析DBC可以自动生成收发代码。手写解析容易出错尤其是信号跨字节、大小端混合的情况。5.2 工业控制中的CANopen协议CANopen是基于CAN的应用层协议定义了通信对象、对象字典、网络管理等功能。它把CAN的8字节数据场做了标准化封装PDO过程数据对象用于实时数据SDO服务数据对象用于配置参数。CANopen在电梯、伺服驱动器、PLC中很常见。如果你只用裸CAN应用层协议要自己定如果用CANopen可以直接用开源栈如CANopenNode省去大量开发时间。但CANopen有学习成本对象字典的索引和子索引需要查规范。5.3 CAN FD下一代总线的过渡CAN FDFlexible Data Rate把数据场扩展到64字节仲裁段保持经典CAN速率数据段可以提速到5Mbps以上。它解决了经典CAN在大数据量场景下的带宽瓶颈同时兼容现有CAN物理层。目前新车型基本都在往CAN FD迁移但经典CAN在工业领域仍然占主导。从开发角度看CAN FD的控制器和收发器都不同STM32的FDCAN外设支持CAN FD但代码配置比bxCAN复杂。如果你的项目周期长建议直接上CAN FD硬件避免后期迁移。5.4 嵌入式面试中的CAN高频考点CAN几乎是嵌入式面试必问的。常见问题包括CAN为什么用差分信号、非破坏性仲裁的原理、标准帧和扩展帧的区别、错误帧的种类、终端电阻的作用、波特率怎么算。面试官往往不满足于背概念会追问实际调试经验比如“通信不通你怎么查”“滤波器怎么配”。我的建议是面试前用开发板实际跑一遍CAN收发把示波器波形、错误寄存器、滤波器配置都过一遍。有实操经验的人回答这类问题和只背书的完全不一样。6. 个人实操体会与后续学习建议调CAN这些年我最大的体会是协议本身不难难的是把物理层、控制器、应用层串起来理解。很多人卡在“代码看起来没问题但就是不通”根源往往在物理层——终端电阻、接线、收发器状态。所以我的习惯是每次新项目先画一张硬件连接图标清楚每个节点的终端电阻和收发器型号再开始写代码。另一个建议是尽早用CAN分析仪。几百块的投资能省下大量调试时间。分析仪不仅能看数据还能统计总线负载、错误帧、帧间隔这些信息用示波器很难获取。我用的CANable配合candump和can-utils在Linux下调试很方便。后续如果想深入可以看ISO 11898标准文档虽然枯燥但权威。应用层可以学CANopen或J1939这两个在工业和商用车领域用得最多。如果做汽车电子DBC解析和CANoe是必备技能。嵌入式Linux方向的话SocketCAN是重点Linux内核把CAN设备抽象成网络接口用socket编程就能收发和传统字符设备完全不同。最后分享一个小技巧CAN总线的ID分配要有规划不要随手写。按功能模块划分ID段比如0x100~0x1FF给电机控制0x200~0x2FF给传感器0x300~0x3FF给状态上报。这样调试时看ID就知道消息来源后期扩展也不容易冲突。
返回列表