ARTICLE DETAIL

资讯详情

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

嵌入式CAN总线实战指南:从原理到STM32配置与问题排查

嵌入式CAN总线实战指南:从原理到STM32配置与问题排查 1. 为什么CAN总线是嵌入式工程师绕不开的一道坎搞嵌入式这行你可以不会写设备树可以没碰过RTOS但只要你的产品涉及板间通信、汽车电子、工业控制CAN总线就是那道你迟早要迈过去的门槛。我见过太多人STM32的GPIO玩得飞起串口收发也熟得不行一到CAN就卡壳——要么初始化配错波特率要么发不出数据要么总线一挂上就报错误帧折腾半天找不到北。这份“报告6686”其实是一份典型的嵌入式CAN总线知识梳理核心就是帮嵌入式开发者把CAN从“听说过”变成“能用起来”。它解决的不是什么高深问题而是最实际的痛点CAN初始化怎么配、报文怎么收发、过滤器怎么设置、总线出问题了怎么排查。适合谁看刚入行的嵌入式新人、从纯MCU转向汽车电子或工业控制的开发者、以及面试前需要快速复习CAN八股文的同学。我自己的经历是第一次接触CAN是在一个车载控制器项目上当时用STM32F103的CAN外设波特率500K结果两块板子怎么都通不上。示波器一测波形乱七八糟后来才发现是终端电阻没接。这种坑文档里不会写但实际项目中一踩一个准。所以这篇博文我打算把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的仲裁机制就是靠显性电平覆盖隐性电平来实现的——谁先发出显性位谁就抢到总线。终端电阻是必须的标准是120欧姆接在总线两端。我见过有人只在一端接电阻短距离低速还能凑合一旦速率上到500K或者线缆拉长到几十米通信就时好时坏。为什么是120欧姆因为CAN总线的特性阻抗大约是120欧姆终端电阻用来匹配阻抗消除信号反射。你可以把它理解成水管末端的堵头不堵住的话水会反弹回来形成驻波。注意终端电阻不是随便接的必须接在总线的两个物理端点。如果节点是手拉手串联的电阻接在首尾两个节点上如果是星型连接那问题就大了CAN不推荐星型拓扑。2.2 CAN帧结构一张表看懂标准帧和扩展帧CAN协议有标准帧11位ID和扩展帧29位ID两种格式。标准帧用在大多数常规场景扩展帧用在需要更多ID空间的场合比如J1939协议就是29位ID。字段标准帧扩展帧说明帧起始SOF1位1位显性位同步用仲裁段11位ID RTR29位ID SRR IDE RTR决定优先级控制段IDE r0 DLCIDE r1 r0 DLCDLC表示数据长度0-8数据段0-8字节0-8字节实际载荷CRC段15位CRC 界定符同左校验ACK段ACK槽 界定符同左接收方拉低确认帧结束7位隐性同左EOF仲裁段是CAN最精妙的设计。总线上多个节点同时发送时谁发的ID数值小显性位多谁就赢。输的那个节点会自动退让下一轮再发。这就像一群人同时说话谁声音大谁先说但CAN是靠ID优先级来定不是靠音量。DLC是数据长度码4位取值0-8。注意经典CAN一帧最多8字节数据CAN FD可以到64字节但那是另一个话题了。很多新手会问“为什么只能发8字节”这是协议设计时的权衡8字节对于大多数控制指令足够了而且帧短意味着实时性好总线占用时间短。2.3 CAN的位定时与波特率计算波特率配置是CAN初始化最容易出错的地方。CAN的位时间分成四段同步段Sync_Seg、传播段Prop_Seg、相位缓冲段1Phase_Seg1、相位缓冲段2Phase_Seg2。以STM32为例CAN时钟来自APB1假设APB1是36MHz目标波特率500K那么位时间总长度是36M/500K72个时钟周期。这72个周期要分配到上述四段中。通常Sync_Seg固定1个周期Prop_Seg Phase_Seg1 Phase_Seg2 71。采样点位置很关键一般在75%左右比较稳。计算公式是(Sync_Seg Prop_Seg Phase_Seg1) / 总位时间。如果Prop_Seg1Phase_Seg152Phase_Seg218那么采样点在(1152)/7275%比较合理。实际配置时STM32的CAN_BTR寄存器里BRP是预分频TS1和TS2分别对应Phase_Seg1和Phase_Seg2SJW是同步跳转宽度。我一般用下面的参数BRP4TS115TS24SJW1这样位时间4*(1154)80个时钟周期36M/80450K不太对。重新算要500K位时间72个时钟周期BRP4的话TQ总数72/418TS1TS2118取TS113TS24采样点(113)/1877.8%可以。提示如果你懒得算可以用STM32CubeMX自动生成但建议至少手动算一遍面试常问而且出了问题你能知道从哪查。3. 嵌入式CAN开发实操从初始化到收发3.1 硬件连接与电平匹配先确认硬件。CAN收发器常用的是TJA1050、SN65HVD230、MCP2551等。MCU的CAN_TX和CAN_RX接到收发器的TXD和RXD收发器的CAN_H和CAN_L接到总线。注意TJA1050是5V供电SN65HVD230是3.3V别搞混了。如果两块板子通信最简单的方式是CAN_H接CAN_HCAN_L接CAN_L两端各接一个120欧姆电阻。如果只有一块板子想自测可以进入回环模式LoopbackMCU自己发自己收不需要外部收发器。我踩过的坑有一次用3.3V的MCU直接接5V的TJA1050结果CAN_TX电平不够收发器识别不到。后来加了电平转换芯片才解决。所以选型时一定要看收发器的VIL/VIH参数确保和MCU电平匹配。3.2 STM32 CAN初始化代码逐行解析下面以STM32 HAL库为例给出一段可复用的CAN初始化代码CAN_HandleTypeDef hcan; void CAN_Init(void) { hcan.Instance CAN1; hcan.Init.Prescaler 4; // 预分频APB136MTQ4/36M hcan.Init.Mode CAN_MODE_NORMAL; // 正常模式 hcan.Init.SyncJumpWidth CAN_SJW_1TQ; hcan.Init.TimeSeg1 CAN_BS1_13TQ; // Phase_Seg1 Prop_Seg hcan.Init.TimeSeg2 CAN_BS2_4TQ; // Phase_Seg2 hcan.Init.TimeTriggeredMode DISABLE; hcan.Init.AutoBusOff DISABLE; 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(); } // 配置过滤器 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; if (HAL_CAN_ConfigFilter(hcan, filter) ! HAL_OK) { Error_Handler(); } HAL_CAN_Start(hcan); HAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO0_MSG_PENDING); }这段代码里Prescaler、TimeSeg1、TimeSeg2决定了波特率。AutoRetransmission我一般开ENABLE因为CAN总线仲裁失败或错误时会自动重发省得应用层处理。过滤器配置成掩码模式Mask全0表示不关心任何位所有报文都收进FIFO0。3.3 发送与接收中断和轮询怎么选发送用HAL_CAN_AddTxMessage接收可以用轮询HAL_CAN_GetRxMessage也可以用中断。我推荐中断方式因为CAN报文到达是异步的轮询会浪费CPU。// 发送 CAN_TxHeaderTypeDef txHeader; uint8_t txData[8] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; uint32_t txMailbox; txHeader.StdId 0x123; txHeader.ExtId 0; txHeader.IDE CAN_ID_STD; txHeader.RTR CAN_RTR_DATA; txHeader.DLC 8; txHeader.TransmitGlobalTime DISABLE; HAL_CAN_AddTxMessage(hcan, txHeader, txData, txMailbox); // 接收回调 void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef rxHeader; uint8_t rxData[8]; HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, rxHeader, rxData); // 处理rxData }中断回调里不要做耗时操作把数据拷到缓冲区置个标志位主循环里处理。我见过有人在回调里直接跑PID算法结果CAN中断频繁触发系统直接卡死。注意CAN发送邮箱只有3个如果连续发送大量报文AddTxMessage会返回HAL_ERROR。这时候要么等邮箱空要么用发送完成回调来排队。4. CAN总线常见问题与排查技巧实录4.1 通信不上从硬件到软件的排查顺序CAN通信不上是最常见的问题我一般按以下顺序排查测终端电阻断电万用表测CAN_H和CAN_L之间电阻应该是60欧姆左右两个120欧姆并联。如果是120欧姆说明只接了一个如果是无穷大说明一个都没接。测电平上电测CAN_H和CAN_L对地电压隐性时都应该在2.5V左右。如果偏差大检查收发器供电。看波形示波器差分探头看CAN_H-CAN_L应该有明显的差分方波。如果没有波形检查MCU的CAN_TX是否有输出。查波特率两块板子的波特率必须完全一致采样点也要接近。我遇到过一块板子采样点80%另一块60%短距离能通长距离就丢帧。查过滤器如果发送正常但收不到大概率是过滤器配置问题。先把过滤器设成全通过确认能收到再慢慢调。4.2 错误帧与总线关闭Bus-Off怎么恢复CAN节点有错误计数器发送错误超过255时进入Bus-Off状态节点自动脱离总线。这时候需要软件干预恢复。STM32的HAL库可以开启AutoBusOff自动恢复。但自动恢复有个问题如果总线短路导致持续错误节点会不断尝试恢复又不断Bus-Off影响总线。所以我一般用自动恢复但在应用层加个计数频繁Bus-Off就报警。手动恢复的代码if (HAL_CAN_GetError(hcan) HAL_CAN_ERROR_BOF) { HAL_CAN_Stop(hcan); HAL_CAN_Start(hcan); }4.3 常见问题速查表现象可能原因排查方法完全无通信终端电阻缺失、接线反了测电阻、查CAN_H/L偶发丢帧波特率偏差、采样点不对示波器测位时间收不到特定ID过滤器配置错误改全通过测试发送失败邮箱满、总线Bus-Off查错误码、加延时距离短就出错线缆阻抗不匹配、分支太长换双绞线、缩短分支上电就Bus-OffCAN_H/L短路、收发器损坏断电测短路4.4 实操心得那些文档不会告诉你的细节第一个心得CAN总线的地线一定要接。虽然CAN是差分信号但共模电压范围有限如果两个节点地电位差太大收发器会损坏。我见过一个项目两块板子分别供电地没连结果通信时好时坏后来把地连上就稳了。第二个心得双绞线不是随便扭两下就行节距要均匀一般20-30 twists per meter。线缆阻抗要120欧姆别拿普通杜邦线凑合短距离低速还行一上速度就完蛋。第三个心得CAN分析仪是个好东西。早期我调试CAN全靠示波器和打印效率极低。后来买了个USB-CAN分析仪能直接看报文、统计错误帧、发模拟数据排查问题快十倍。推荐入门级的就行几百块钱比浪费的时间值。第四个心得面试常问的CAN八股文其实就那几个CAN为什么用差分、仲裁机制怎么工作、标准帧和扩展帧区别、波特率怎么算、Bus-Off怎么恢复。把这几条吃透基本够用。5. CAN在嵌入式项目中的典型应用场景5.1 汽车电子ECU之间的通信骨架汽车上CAN总线是标配发动机ECU、变速箱ECU、ABS、仪表盘都挂在CAN上。车速、转速、水温这些信号通过CAN广播谁需要谁收。诊断接口OBD-II也是CAN读故障码、刷ECU都走CAN。汽车CAN通常是500K波特率ID分配有规范比如0x000-0x0FF是高优先级安全相关0x100-0x4FF是车身控制。做汽车嵌入式开发不懂CAN基本没法干活。5.2 工业控制PLC与远程IO的可靠连接工业现场PLC和远程IO模块之间常用CANopen协议底层就是CAN。CANopen定义了对象字典、PDO、SDO这些概念但底层还是CAN帧。工业环境干扰大CAN的差分传输和CRC校验保证了可靠性。我做过一个项目PLC通过CAN控制20个远程IO节点线缆拉了80米波特率250K跑了两年没出过通信故障。关键就是终端电阻接对、线缆用屏蔽双绞线、屏蔽层单端接地。5.3 储能与BMS电池管理系统的通信总线储能BMS里主控和从控之间用CAN通信从控采集电池电压温度通过CAN上报主控。BMS的CAN通常用扩展帧因为节点多11位ID不够分。波特率一般250K或500K要求高实时性和高可靠性。这个场景对CAN的错误处理要求高因为电池数据丢了可能导致过充过放。所以BMS的CAN驱动一般会做应用层重传和超时检测不能只依赖CAN底层的自动重传。6. 进阶方向从经典CAN到CAN FD与时间触发6.1 CAN FD更大数据量和更高波特率经典CAN最多8字节500K波特率在汽车OTA升级、大数据量传输场景下不够用了。CAN FDFlexible Data-rate把数据段扩展到64字节数据段波特率可以到5M甚至更高仲裁段还是保持低速以保证兼容性。CAN FD的帧格式和经典CAN不同多了FDF、BRS、ESI这几个位。如果你用STM32G4或H7系列硬件支持CAN FD配置时注意数据段波特率要单独设置。6.2 时间触发CAN确定性通信的尝试TTCANTime-Triggered CAN在CAN基础上加了时间调度每个节点按时间槽发送避免仲裁带来的不确定性。这个在航空和高端汽车里有用但普及度不高因为CAN FD和以太网已经覆盖了大部分需求。6.3 嵌入式Linux下的CAN开发如果你做嵌入式Linux项目CAN设备在/dev下是can0、can1这样的网络接口。用ip命令配置ip link set can0 type can bitrate 500000 ip link set can0 up收发用socketcan和UDP socket类似。candump看报文cansend发报文。Linux下的CAN开发比裸机简单因为内核帮你处理了大部分底层细节但调试时还是要懂位定时和错误处理。7. 我个人在CAN开发中的几点体会CAN总线这东西入门不难精通不易。协议本身不复杂但实际项目中的问题往往出在硬件和细节上。我的建议是先拿两块开发板和两个收发器把通信跑通然后故意制造一些错误——拔掉终端电阻、改错波特率、短接CAN_H和CAN_L——看看现象是什么这样印象最深。另外别只盯着MCU的CAN外设收发器的选型和电路设计同样重要。我见过太多项目软件调了半天最后发现是收发器选错了型号。还有CAN分析仪真的值得买省下的调试时间远超它的价格。最后说个面试技巧如果面试官问你CAN别只背协议结合你实际做过的项目讲比如“我在XX项目里用CAN做了XX遇到了XX问题最后怎么解决的”这样比干巴巴背八股文强得多。
返回列表