资讯动态

CAN总线入门:从协议帧到STM32实战

发布时间:2026/8/16 22:13:00 来源:尧图企业网站定制
假设你在做一个电机控制项目STM32作为主控需要向电机驱动器发送转速指令电机驱动器还要返回当前转速和温度。电机驱动器在2米外的设备末端周围有大量电磁干扰。用I2C距离太远抗不住干扰。用UART只能点对点挂不了多个设备。这时候就需要CAN总线——汽车和工业控制的标准通信协议。一、CAN是什么CANController Area Network控制器局域网是德国博世Bosch公司开发的多主串行通信协议专为汽车和工业环境设计。博世Bosch是德国的一家大型工程技术公司CAN协议就是他们为汽车内部各电子模块之间通信而发明的。1.1 CAN和你熟悉的I2C/SPI/UART有什么不同对比项UARTI2CSPICAN线数2线(TX/RX)2线(SDA/SCL)4线(MOSI/MISO/SCK/CS)2线(CAN_H/CAN_L)距离几十厘米几十厘米几十厘米可达1000米(低速)抗干扰弱弱弱强(差分信号)多设备点对点多设备(地址区分)多设备(CS片选)多设备(ID区分)速率最高几Mbps100kHz-1MHz几十MHz最高1MbpsCAN最大的优势两根线差分传输抗干扰极强能在恶劣的电磁环境汽车发动机舱中稳定通信且一条总线可以挂多个设备。1.2 应用场景汽车发动机控制、仪表盘、ABS刹车系统之间的通信你车里的OBD接口就是CAN电机驱动主控与电机驱动器通信发送转速指令、接收状态反馈工业控制PLC与传感器/执行器通信机器人机械臂各关节电机与主控通信二、CAN物理层硬件怎么接线2.1 接线方式所有设备并联在两根线上CAN_H和CAN_LSTM32主控 电机驱动器 传感器节点 ┌──────┐ ┌──────┐ ┌──────┐ │ CAN_H├──┬─────┤CAN_H ├┬───────┤CAN_H │ │ CAN_L├──┴─────┤CAN_L ├┴───────┤CAN_L │ └──┬───┘ └──────┘ └──┬───┘ │ │ 120Ω电阻 120Ω电阻 (终端电阻) (终端电阻)终端电阻总线两端各加一个120Ω电阻用于匹配线路阻抗防止信号反射就像水流到管子末端如果没有缓冲会反弹回来。忘了加终端电阻是CAN不通的最常见原因2.2 差分信号是什么意思CAN不用单根线的0V/3.3V表示0和1而是用两根线的电压差来表示显性(逻辑0)CAN_H ≈ 3.5V, CAN_L ≈ 1.5V → 压差2V → 表示0 隐性(逻辑1)CAN_H ≈ 2.5V, CAN_L ≈ 2.5V → 压差0V → 表示1为什么抗干扰强因为干扰会同时作用在两根线上共模干扰但差分信号只看两根线的差值干扰被抵消了。这就像两个人同时被推了一下但他们之间的距离不变。2.3 线与原理仲裁的基础CAN总线的物理设计决定了只要有一个节点发送显性(0)总线就是显性(0)。只有所有节点都发隐性(1)总线才是隐性(1)。这叫线与——就像并联的开关任何一个按下(0)输出就是0。这就是CAN仲裁的物理基础见第四节。三、CAN帧结构CAN标准帧由以下部分组成┌──────┬────────┬─────┬────────┬──────┬─────┬───────┬──────┐ │ 帧起始│ 仲裁段 │控制段│ 数据段 │CRC段 │ACK段│ 帧结束 │ 间隔 │ │ 1位 │ 12/32位 │ 6位 │0-64位 │ 16位 │ 2位 │ 7位 │ 3位 │ └──────┴────────┴─────┴────────┴──────┴─────┴───────┴──────┘字段内容解释仲裁段11位ID(标准帧) RTRID决定优先级RTR区分数据帧(0)和远程帧(1)控制段IDE DLCIDE区分标准帧(0)/扩展帧(1)DLC数据长度(0-8字节)数据段0-8字节数据实际要传的数据CRC段15位CRC 1位界定符自动校验不用你管ACK段2位发送方发1接收方回0表示我收到了术语解释RTRRemote Transmission Request远程传输请求RTR0是数据帧我要发数据RTR1是远程帧我请求别人发数据给我。实际项目中远程帧很少用大部分都是数据帧。IDEIdentifier Extension标识符扩展IDE0用11位标准IDIDE1用29位扩展ID。一般用标准帧就够了。DLCData Length Code数据长度码表示数据段有几个字节范围0-8。CAN一帧最多传8字节。重点CAN一帧最多传8字节。要传更多数据比如一个结构体有20字节需要自己分包发送。四、仲裁机制CAN的核心4.1 具体场景理解假设你的项目里有3个设备挂在CAN总线上主控STM32ID0x100最高优先级发转速指令电机驱动器ID0x200返回状态数据温度传感器ID0x300定期上报温度如果3个设备同时想发数据谁的ID小谁先发这就是仲裁。4.2 仲裁过程多个节点同时发送时ID越小优先级越高。仲裁通过线与实现电机驱动器发送 ID0x200 0010 0000 0000 温度传感器发送 ID0x300 0011 0000 0000 逐位比较从高位开始 电机 0 0 1 0 0 0 0 0 0 0 0 0 温度 0 0 1 1 0 0 0 0 0 0 0 0 ↑ 第3位电机发0(显性)温度发1(隐性) 由于线与机制总线值0(显性) 温度传感器发现自己发的1变成了0 → 仲裁失败停止发送 电机驱动器继续发送完整帧 结果ID小的电机驱动器赢了温度传感器等总线空闲后重发4.3 为什么叫非破坏性仲裁因为优先级高的帧完整无损地发送成功优先级低的帧只是等待重发数据不会被破坏。这比以太网的冲突就全部重发高效得多。五、STM32 bxCAN配置实战5.1 bxCAN是什么STM32F1内部集成的CAN控制器叫bxCANBasic Extended CAN基本扩展CAN。基本是因为它比高端CAN控制器如CAN-FD功能少一些但对新手项目完全够用。它支持标准帧和扩展帧有14个过滤器F1系列。5.2 CubeMX配置Connectivity → CAN → 勾选 在 Parameter Settings 里配置 Bit Timing: Prescaler: 9 ← 分频系数 Time Quanta in Bit Segment 1 (BS1): 6 TQ Time Quanta in Bit Segment 2 (BS2): 1 TQ Resync Jump Width (SJW): 1 TQ → 波特率 36MHz / (9 × (161)) 500kbps5.3 波特率参数详解新手必看这个公式新手最容易看不懂我来逐个解释BaudRate APB1时钟 / (Prescaler × (1 BS1 BS2))每个参数是什么意思CAN把一个bit的时间分成若干个TQTime Quanta时间量子。你可以把TQ理解为CAN时钟的最小时间单位。一个bit 1个同步段(SYNC) BS1段 BS2段其中SYNC固定1个TQ一个bit的时间组成 ├── SYNC (1 TQ) ──┤── BS1 (6 TQ) ──┤── BS2 (1 TQ) ──│ ↑ 采样点在这里读取总线电平Prescaler分频系数APB1时钟(36MHz)除以Prescaler得到TQ的频率。Prescaler9 → TQ频率36MHz/94MHz → 1个TQ0.25微秒BS1时间段1采样点之前的时间用来等待信号稳定。BS16表示6个TQBS2时间段2采样点之后的时间用来处理采样结果。BS21表示1个TQSJW重新同步跳跃宽度时钟有误差时允许调整的最大TQ数一般设1计算500kbps1个bit (1 6 1) 8个TQ 1个TQ 1/(36MHz/9) 0.25微秒 1个bit 8 × 0.25 2微秒 波特率 1/2微秒 500kbps ✓采样点位置建议在75%-87.5%处采样点 (1BS1)/(1BS1BS2) 7/8 87.5%合理。5.4 发送代码电机控制场景/* 场景主控STM32向电机驱动器发送转速指令 * 协议约定ID0x200数据[0-1]目标转速(rpm)数据[2]方向(0正转/1反转) */CAN_TxHeaderTypeDef tx_header;uint8_ttx_data[8]{0};uint32_ttx_mailbox;/* 组装数据 */int16_ttarget_rpm3000;/* 目标转速3000rpm */tx_data[0]target_rpm0xFF;/* 转速低8位 */tx_data[1](target_rpm8)0xFF;/* 转速高8位 */tx_data[2]0;/* 0正转 *//* 填充帧头 */tx_header.StdId0x200;/* 标准ID0x200 */tx_header.IDECAN_ID_STD;/* 使用标准帧(11位ID) */tx_header.RTRCAN_RTR_DATA;/* 数据帧(不是远程帧) */tx_header.DLC3;/* 数据长度3字节 */tx_header.TransmitGlobalTimeDISABLE;/* 发送 */HAL_CAN_AddTxMessage(hcan,tx_header,tx_data,tx_mailbox);5.5 接收代码中断方式CAN_RxHeaderTypeDef rx_header;uint8_trx_data[8];/* 1. 配置过滤器先设为接收所有帧调试阶段 */CAN_FilterTypeDef filter;filter.FilterIdHigh0;filter.FilterIdLow0;filter.FilterMaskIdHigh0;filter.FilterMaskIdLow0;filter.FilterFIFOAssignmentCAN_RX_FIFO0;filter.FilterBank0;filter.FilterModeCAN_FILTERMODE_IDMASK;filter.FilterScaleCAN_FILTERSCALE_32BIT;filter.FilterActivationENABLE;HAL_CAN_ConfigFilter(hcan,filter);/* 2. 启动CAN并开启接收中断 */HAL_CAN_Start(hcan);HAL_CAN_ActivateNotification(hcan,CAN_IT_RX_FIFO0_MSG_PENDING);/* 3. 接收回调函数CAN收到帧时HAL库自动调用 */voidHAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef*hcan){HAL_CAN_GetRxMessage(hcan,CAN_RX_FIFO0,rx_header,rx_data);/* 场景接收电机驱动器返回的状态数据 * 协议约定ID0x181数据[0-1]当前转速数据[2]温度 */if(rx_header.StdId0x181){int16_tcurrent_rpmrx_data[0]|(rx_data[1]8);uint8_ttemprx_data[2];printf(电机转速: %d rpm, 温度: %d°C\r\n,current_rpm,temp);}}六、过滤器配置6.1 过滤器是什么为什么需要STM32的CAN控制器有14个过滤器F1系列。如果你只关心ID0x181的帧电机状态不关心其他帧可以用过滤器把不需要的帧挡在CPU之外减少中断次数。6.2 过滤器模式列表模式只接收指定ID的帧精确匹配掩码模式接收符合某种规律的ID用mask过滤6.3 掩码模式配置详解/* 只接收ID0x180~0x1FF的帧 */filter.FilterIdHigh0x1805;/* ID左移5位对齐 */filter.FilterIdLow0;filter.FilterMaskIdHigh0x7005;/* mask也左移5位 */filter.FilterMaskIdLow0;filter.FilterModeCAN_FILTERMODE_IDMASK;filter.FilterScaleCAN_FILTERSCALE_32BIT;mask规则mask为1的位必须匹配为0的位忽略。mask0x700二进制111 0000 0000表示只看ID的高3位0x180的高3位0010x1FF的高3位001所以0x180~0x1FF都能通过0x200的高3位010不匹配被过滤为什么ID要左移5位因为STM32的32位过滤器中11位标准ID存放在高16位的高11位低5位是其他信息RTR、IDE等。所以标准ID要左移5位才能对齐到正确位置。这个你不用记知道标准ID左移5位这个规则就行。七、常见踩坑坑1CAN不通——终端电阻最常见现象CAN发送函数一直返回超时或者完全收不到数据。原因CAN总线两端必须各加一个120Ω电阻。只有一个电阻或没有电阻信号反射导致通信失败。解决用万用表量CAN_H和CAN_L之间的电阻如果有两个120Ω并联应该量到60Ω。量不到就说明缺电阻。坑2只有一个节点CAN发不出去新手必踩现象STM32发CAN帧发送函数返回超时错误帧根本没发出去。原因CAN协议规定发送方发出帧后必须有至少一个其他节点回复ACK应答信号。如果你只有一个STM32板子没有第二个CAN节点没有人回复ACK发送方认为通信失败一直重发直到超时。解决方案1接两个CAN节点两个STM32或一个STM32一个CAN分析仪方案2STM32的CAN有个Loopback模式回环模式自发自收不需要外部节点。调试阶段可以用这个模式测试代码是否正确。CubeMX里Operating Mode选Loopback即可。这是新手最困惑的问题明明代码是对的接线是对的就是发不出去。因为CAN不像UART可以单节点发CAN必须有应答。坑3波特率不匹配现象两个节点都接好了但互相收不到数据。原因两个节点的波特率必须完全一致差1%都可能通信失败。解决用上面的公式仔细算两边的波特率是否一致。常见错误一个节点APB136MHz(F1)另一个APB142MHz(F4)同样的Prescaler和BS1/BS2值波特率不同。坑4过滤器配错收不到数据现象发数据的一方发了但接收方收不到。原因过滤器设太严格把想要的帧也过滤掉了。解决调试阶段先把过滤器设为全接收ID0, mask0表示不过滤任何帧确认通信正常后再设过滤规则。坑5CAN收发器没接现象STM32的CAN_TX/CAN_RX引脚直接连到另一块的CAN_TX/CAN_RX不通。原因STM32的CAN引脚输出的是TTL电平0/3.3V不是CAN总线的差分信号。中间需要一个CAN收发器芯片如TJA1050、SN65HVD230把TTL电平转成差分信号。STM32 CAN_TX ──→ [TJA1050] ──→ CAN_H STM32 CAN_RX ←── [TJA1050] ←── CAN_L买开发板时注意有没有带CAN收发器。有些便宜的板子只有STM32的CAN引脚引出没有收发器芯片直接连是通不了的。八、总结要点内容CAN特点差分两线、抗干扰强、多主、仲裁、CRC帧结构IDDLC数据(0-8字节)CRCACK仲裁ID越小优先级越高非破坏性仲裁终端电阻总线两端各120Ω波特率APB1/(Prescaler×(1BS1BS2))必须两节点单节点发不出去(没ACK)调试用Loopback模式收发器STM32需要外接CAN收发器(TJA1050等)一句话总结CAN的核心是差分传输抗干扰 ID仲裁定优先级 终端电阻120Ω×2 至少两个节点才能通信。相关文章推荐UART深入DMA接收空闲中断实现不定长数据收发I2C协议详解SHT30实战作者嵌入式阿蔡 | 一枚准大四的嵌入式菜鸟正在打牢基础记录成长

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价