STM32F103 CAN 实战:双机通信与 TJA1050 全流程拆解
为什么汽车和工业现场都用 CAN而不是 UART、SPI、I2C本文以 STM32F103 TJA1050 双机通信为例从硬件接线、位时序计算、过滤器配置到三邮箱发送、FIFO 中断接收逐段拆解附 6 条避坑清单。为什么是 CAN一场总线混战做嵌入式这些年你一定用过 UART、SPI、I2C。它们各有绝活但到了汽车和工业现场大家默契地换成了 CAN。原因很直接CAN 是多主机总线。UART 只能点对点SPI 必须有主机I2C 虽支持多设备但仲裁机制复杂。CAN 总线上任意节点都能主动发数据靠 ID 优先级自动仲裁谁重要谁先走。报文本身是广播式的一帧发出去所有节点都能收到收不收由各节点自己的过滤器决定。UART 得一对一拉线SPI 得有个主机指挥CAN 这种「一喊全听见」的模型天然适合节点多的场景。再看物理层。CAN 用差分信号传输CANH 和 CANL 两根线互为参考抗干扰能力远超单端信号。500kbps 下能传 40 米降到 250kbps 能到 100 米这距离 UART 想都不敢想。汽车里几十个 ECU 就是靠 CAN 串成一张网。这套机制最早由博世在 80 年代提出后来成了汽车界的默认总线工业设备也大量沿用。今天我们不聊理论直接上手两块 STM32F103 各接一个 TJA1050 收发器500kbps 双机通信。硬件接线别把芯片直接怼总线上STM32 的 CAN 控制器输出 3.3V 逻辑电平驱动不了总线必须加收发器。TJA1050 是经典之选5V 供电TXD/RXD 直接兼容 3.3V 逻辑不用电平转换外围只要 0.1μF 10μF 去耦电容。接线核心是终端电阻。CAN 总线两端各需一个 120Ω 电阻用来吸收信号反射。怎么验证断电后量 CANH-CANL 之间应该约 60Ω两个 120Ω 并联。缺了一端高速率下波形会振铃、丢帧。双绞线也别省。CANH 和 CANL 是成对出现的差分线走线要绞合在一起绞得越密外界干扰在两根线上留下的痕迹越接近接收端做差后能抵消大半。挂示波器看差分波形CH1 减 CH2隐性时 CANH、CANL 都约 2.5V压差约 0显性时 CANH 拉到约 3.5V、CANL 降到约 1.5V压差超过 2V。只盯单根线容易误判。引脚方面F103 默认 CAN1 复用 PA11(RX)/PA12(TX)也可重映射到 PB8(RX)/PB9(TX)用GPIO_Remap1_CAN1开启。PB9 配复用推挽 50MHzPB8 配浮空或上拉输入。TJA1050 一共 8 个脚逻辑侧 TXD/RXD 接 MCU总线侧 CANH/CANL 接双绞线VCC 接 5VGND 和 MCU 共地。收发器和 MCU 必须共地差分电平才有共同的参考基准不然波形会漂。⚠️F103 的 USB 和 CAN 不能同时用。两者共享 512B SRAM这是硬件限制重映射也救不了。设计时二选一。初始化位时序才是灵魂CAN 挂在APB1 总线 36MHz不是 APB2 的 72MHz。很多人在这里翻车。先开时钟、配引脚。默认引脚 PA11(RX)/PA12(TX)TX 配复用推挽输出RX 配上拉输入RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitStructure.GPIO_PinGPIO_Pin_12;// TXGPIO_InitStructure.GPIO_ModeGPIO_Mode_AF_PP;// 复用推挽GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(GPIOA,GPIO_InitStructure);GPIO_InitStructure.GPIO_PinGPIO_Pin_11;// RXGPIO_InitStructure.GPIO_ModeGPIO_Mode_IPU;// 上拉输入GPIO_Init(GPIOA,GPIO_InitStructure);用重映射 PB8/PB9 时还要额外开 AFIO 时钟再调GPIO_PinRemapConfig(GPIO_Remap1_CAN1, ENABLE)。波特率公式APB1 / Prescaler / (1 BS1 BS2)500kbps 36MHzPrescaler9、BS16tq、BS21tq即 36/9/8 500k采样点 87.5%。常见误区Prescaler9、BS113tq、BS22tq 算出来是 250kbps不是 500k。精确 500k 需要 Prescaler×(1BS1BS2)72。采样点放在 87.5% 也有讲究采样点越靠位末端离波特率跳变沿越远越不容易采到边沿噪声。CAN 波特率是全员必须一致的硬约定SJW 允许每个节点在同步段微调相位弥补晶振偏差。CAN_InitStructure.CAN_SJWCAN_SJW_1tq;CAN_InitStructure.CAN_BS1CAN_BS1_6tq;CAN_InitStructure.CAN_BS2CAN_BS2_1tq;CAN_InitStructure.CAN_Prescaler9;CAN_InitStructure.CAN_ModeCAN_Mode_Normal;// 双机用 NormalCAN_InitStructure.CAN_NARTENABLE;// 禁自动重发CAN_Init(CAN1,CAN_InitStructure);CAN_Init内部会自动处理 INRQ 进入/退出不用手动操作。接下来是过滤器。14 组0-13掩码模式里Mask1 必须匹配0 任意。收 0x123 标准帧ID 要左移 5 位掩码 11 位全匹配FilterActivation必须 ENABLE否则白配。过滤器分两种模式掩码模式IdMask适合范围过滤Mask 全 1 就是精确匹配列表模式IdList只能精确点名一个滤波器能装两个 ID第二个填在FilterMaskId*里。多个滤波器同时命中时编号小的优先级高。日常双机通信一个掩码滤波器就够。CAN_FilterInitStructure.CAN_FilterNumber0;CAN_FilterInitStructure.CAN_FilterModeCAN_FilterMode_IdMask;CAN_FilterInitStructure.CAN_FilterScaleCAN_FilterScale_32bit;CAN_FilterInitStructure.CAN_FilterIdHigh0x1235;CAN_FilterInitStructure.CAN_FilterIdLow0x0000;CAN_FilterInitStructure.CAN_FilterMaskIdHigh0x7FF5;// 11 位全匹配CAN_FilterInitStructure.CAN_FilterMaskIdLow0x0000;CAN_FilterInitStructure.CAN_FilterFIFOAssignmentCAN_FIFO0;CAN_FilterInitStructure.CAN_FilterActivationENABLE;CAN_FilterInit(CAN_FilterInitStructure);掩码全 0 就是收所有帧调试时很实用。发送三个邮箱的江湖F103 有3 个发送邮箱。CAN_Transmit返回邮箱号 0-2如果都占满了返回CAN_TxStatus_NoMailBox0x04。这里有个经典坑邮箱全满时如果 NART 没开硬件会一直自动重发等不到 ACK 就卡死邮箱。双机通信建议直接NARTENABLE禁掉自动重发。也别把NoMailBox当邮箱号传给CAN_TransmitStatus会查无此箱。uint8_tCAN_SendFrame(uint32_tid,uint8_t*buf,uint8_tlen){CanTxMsg TxMsg;TxMsg.StdIdid;TxMsg.IDECAN_Id_Standard;TxMsg.RTRCAN_RTR_Data;TxMsg.DLClen;for(uint8_ti0;ilen;i)TxMsg.Data[i]buf[i];if(CAN_Transmit(CAN1,TxMsg)CAN_TxStatus_NoMailBox)return0;return1;}三个邮箱各有状态位TME 位为 1 表示邮箱空闲可投递。连续发多帧时下一帧会排队进下一个空邮箱全满才返回 NoMailBox。发送完成后可用CAN_TransmitStatus确认返回CAN_TxStatus_Ok才是真正发出去了CAN_TxStatus_Failed说明没等来 ACK多半是总线没接好。顺带说下帧格式。标准帧 ID 是 11 位扩展帧 29 位靠 IDE 标志区分数据场最长 8 字节DLC 表示长度。还有一种 RTR 远程帧不带数据用来向对端要数据发一个 RTR 帧对端收到后把数据帧填回来。日常双机通信用标准帧数据帧就够代码里CAN_Id_StandardCAN_RTR_Data就是标准配置。帧尾还有 ACK 槽接收节点要在 ACK 槽回显一个显性位发送节点才确认对端在场。单机 Normal 模式自测发不出去原因就在这总线上没有第二块板子应答。接收FIFO 与中断接收有 FIFO0 和 FIFO1 两个队列各能存 3 帧。过滤器把匹配的帧送进指定 FIFO双机通信把帧全送 FIFO0 就够。推荐用中断使能CAN_IT_FMP0帧到达时触发挂起中断在 ISR 里用CAN_Receive取走。别忘了 NVICNVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn)并设好优先级不然帧到了中断也进不来。voidUSB_LP_CAN1_RX0_IRQHandler(void){CanRxMsg RxMsg;if(CAN_GetITStatus(CAN1,CAN_IT_FMP0)!RESET){CAN_Receive(CAN1,CAN_FIFO0,RxMsg);// 处理 RxMsg.StdId / RxMsg.DataCAN_ClearITPendingBit(CAN1,CAN_IT_FMP0);}}注意如果 FMP 中断触发后没及时取走新帧可能覆盖旧帧。想锁定旧帧可配置 RFLM 选项。CAN_Receive取走帧后 FIFO 计数自动减一所以 ISR 里先读计数再取帧也行。帧不多、丢一两帧无所谓的场景也可以改成轮询主循环里查 FIFO 计数再取帧代码更简单。双机通信建议中断响应快。调试时先开 LoopBack 模式自测回环帧只在控制器内部绕一圈不上总线不需要任何外部电路就能验证过滤器和收发逻辑。A 发 B 收两边先各自回环自测软件没问题了再切 Normal 上真机。避坑清单6 个血泪教训波特率算错记住 APB1 是 36MHzPrescaler9、BS113tq、BS22tq 是 250k不是 500k。终端电阻缺一端断电量 CANH-CANL约 60Ω 才对缺一端高速率必丢帧。USBCAN 不能同时用共享 512B SRAM硬件限制重映射也不行。过滤器忘 ENABLEFilterActivation必须 ENABLEMask1 是必须匹配标准帧 ID 记得5。发送 NoMailBox 卡死开 NART、别死等、必要时用 TME 中断。回环 vs 正常模式混淆LoopBack 只走内部不上总线双机必须 Normal。工具上带 CAN 解码的逻辑分析仪能直接标出 ID、DLC 和数据比盯着波形数位快得多示波器则用来抓差分波形验证波特率、采样点和 ACK 槽是否正常。先量电阻、再抓波形、最后查代码这个顺序能避开大部分玄学。写在最后CAN 的入门门槛不高但细节极多。位时序、终端电阻、过滤器、邮箱状态每一环都是坑叠出来的。上手时先跑 LoopBack 自测回环通了再上双机能省下不少排查时间。另外提醒一句CAN 调试最坑的往往不是代码是硬件接线松了、电阻缺了、共地没共好代码再对也白搭。先把硬件验证扎实再怀疑软件。你调 CAN 时踩过最深的坑是哪个欢迎在评论区聊聊一起避坑。推荐阅读[STM32F103 SPI 实战W25Q64 Flash 读写全流程拆解]8MB 大容量存储方案[STM32F103 I2C 实战AT24C02 EEPROM 读写全流程拆解]256 字节小存储的取舍

相关新闻