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

资讯详情

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

STM32 CAN通信实战:国产TGA1050收发器替代方案与源码实现

STM32 CAN通信实战:国产TGA1050收发器替代方案与源码实现 简介基于TGA1050收发器的STM32之间CAN通信设计源码面向嵌入式开发与汽车电子方向的工程师、学习者解决两个STM32节点之间CAN总线通信的构建难题。项目采用C语言编写基于Keil uVision工程完整覆盖CAN控制器初始化、波特率与滤波器配置、中断服务程序设计以及报文的发送和接收协议栈并给出了错误检测与恢复思路。压缩包共187个文件大小24.82MB包括34个头文件、32个C源文件、33个目标文件、8个汇编源文件以及Keil工程配置、链接映射、列表输出等辅助文件结构分明便于按模块阅读与维护。当前已有348人学习下载。通过这份设计源码使用者能够深入理解TGA1050收发器与STM32的硬件连接和差分信号转换原理掌握CAN通信工程的框架组织方式在实际产品开发中可参考其初始化流程和容错机制提高开发效率降低调试成本。 做嵌入式这些年TGA1050在我眼里就是TJA1050的国产替代方案管脚兼容、价格友好、供货稳定很多STM32开发板上直接焊的就是它。这个项目拆开看是三件事STM32内部自带的CAN控制器负责协议一颗TGA1050收发器负责物理层电平转换再加上一套能直接跑的收发源码。目标是让两块STM32通过CAN总线稳定、可靠地互传数据。这类需求在车载电子、工业现场、设备联网里太常见了尤其是多节点、长距离、强干扰的环境CAN依然是性价比很高的选择。这套设计与源码我已经在两块开发板上实际验证过下面把思路、电路、代码和踩过的坑一起整理出来适合想快速上手CAN通信的嵌入式工程师参考。1. 项目整体思路与方案选型1.1 TGA1050与TJA1050的差异为什么敢用国产料先说说TGA1050。它在封装、引脚定义和功能上和TJA1050基本一致SOP-8封装TXD、RXD接控制器CANH、CANL接总线。当年很多项目不敢用国产收发器主要是怕电磁兼容和ESD性能拉胯。实际测试下来TGA1050在1Mbps速率下表现稳定ESD能力和TJA1050在同一档位复杂工业环境里配合TVS管和共模电感使用没有出现过批量通信异常。选它的另一个原因是成本。消费级和工业级应用里一颗收发器的差价看似不大但量产后算下来很可观而且国产芯片供货周期短不用等海外料号大几周甚至几个月。这个项目是做STM32之间的CAN通信验证收发器本身不是瓶颈用TGA1050既能省钱又能保证功能何乐而不为。这里要说清楚一个分工问题STM32内部集成的bxCAN外设只是CAN控制器负责组帧、仲裁、错误检测这些协议层动作但它输出的TTL电平信号无法直接驱动差分总线。TGA1050的职责就是把STM32发送引脚上的逻辑电平转换成CANH和CANL上的差分信号同时把总线上的差分信号还原成逻辑电平送给接收引脚。控制器加收发器这才是完整的CAN节点。1.2 为什么是CAN不是UART也不是RS-485很多初学者会问两块板子通信用串口不就行了确实UART点对点、全双工、实现简单两块开发板之间传数据完全够用。但一旦变成多节点、线路拉长、现场有电机变频器这类干扰源UART就顶不住了。RS-485虽然支持多点但本质上需要一主多从的轮询机制主节点要挨个点名实时性靠不住。CAN和这两者最大的区别是它天生支持多主通信任何节点都可以主动往总线上发数据靠ID仲裁决定优先级不需要主机分配时间片。这在高实时性场合非常关键比如设备报警、急停信号这类消息必须能够在微秒级抢到总线。再加上CAN的差分传输、CRC校验、错误重发机制物理层和协议层的可靠性都远超普通串口。做个简单对比通信方式拓扑速率实时性错误处理典型场景UART点对点较低一般无协议校验调试、短距离传输RS-485多点主从中等依赖轮询需自定义协议工业仪表、远程采集CAN多主节点最高1Mbps事件驱动硬件自带检错重发车载、工控、设备互联所以这个项目选CAN不是为了炫技而是它确实适合“多个STM32节点之间自主通信”这个场景。1.3 硬件连接STM32与TGA1050之间的电平匹配硬件连接看起来简单实际上有几个细节不能省。STM32F103的CAN_TX和CAN_RX默认映射在PA12和PA11也可以通过重映射到PB8和PB9我建议直接用默认映射少折腾。PA12连接到TGA1050的TXDPA11连接到TGA1050的RXDTGA1050的CANH和CANL接总线。要注意的是收发器的供电电压。TGA1050有些批次设计为5V供电RXD输出高电平时可能接近5V而STM32的GPIO并不是所有引脚都支持5V容忍直接连接存在风险。我的做法是优先选择宽压版本让TGA1050直接用3.3V供电RXD输出也是3.3V和STM32电平完美匹配。如果你手头只有5V版本RXD到STM32之间加一个1kΩ串阻再并联一个3.3V稳压管或者用电阻分压把高电平钳到安全范围。总线两端各放一个120Ω终端电阻这个不能省。CAN总线之所以需要120Ω是因为双绞线的特征阻抗约120Ω终端电阻匹配后能吸收反射信号避免波形振铃影响通信质量。很多新手只接一个甚至不接短距离低速率可能没感觉一旦线长超过几米或速率上到500kbps各种诡异问题就来了。另外TGA1050的电源引脚旁边要放100nF和10μF去耦电容CANH、CANL上建议加TVS管和共模电感。这些物料成本不高却能在做EMC认证或者现场批量部署时省掉大麻烦。2. CAN协议要点报文结构、仲裁与位时序2.1 从5V差分到一帧报文CAN到底怎么说话CAN总线上传输的是差分电压信号。隐性电平对应逻辑1显性电平对应逻辑0。5V供电的收发器在隐性状态下CANH和CANL都保持在2.5V左右差分电压接近0显性状态下CANH被拉高到3.5V左右CANL被拉到1.5V左右差分电压约2V。用一个生活化的类比总线就像一条共享的走廊所有人都可以讲话平时走廊安静这就是隐性有人拉低电平就是显性。CAN协议规定显性优先谁发了显性位总线就呈现显性其他节点的隐性位会被覆盖。这个特性是后面仲裁机制的基础。再说报文帧。CAN标准数据帧由帧起始、仲裁段、控制段、数据段、CRC段、ACK应答段和帧结束构成。标准帧的标识符是11位扩展帧是29位。数据段最长8字节所以一帧CAN报文最多携带8个字节的有效数据这看起来不多但工程上通过合理编码完全够用。帧结束时发送节点会释放总线接收节点如果正确收到帧会在ACK段主动发送一个显性位作为应答。如果总线上没有任何节点应答发送节点就会判定发送失败并重发。这解释了后面排查问题时会遇到的一个现象单块板子自己发数据如果总线上没有其他节点发送是永远不会成功的因为没有人回答它。2.2 ID仲裁与接收过滤小ID为什么优先仲裁是CAN协议里最精彩的部分。多个节点同时发送时它们都在往总线上写ID位同时回读总线电平。因为显性电平覆盖隐性电平写着写着如果有节点的回读结果和自己发出的不一致就说明有更高优先级的节点在竞争这个节点立刻退出转为接收模式让ID更小的帧先走。打个比方就像几个人同时举手发言谁的ID小谁先讲。ID越小优先级越高这是CAN协议的硬规则。在工程实际中通常把急停、报警这类高优先级消息分配小的ID把周期性的状态数据分配大的ID这样关键时刻高优先级消息一定能抢到总线。接收过滤则是接收端的“门卫”。CAN控制器允许设置一组验收码和验收掩码掩码位为1表示该位必须匹配掩码位为0表示该位忽略。SJA1000时代叫ACCCode和ACCMaskSTM32的bxCAN虽然叫过滤器但思路完全一样。如果一个节点只关心特定ID的报文过滤器会帮它直接丢弃不关心的帧避免CPU被无效中断淹没。这也方便实现分布式系统每个节点定义好自己关心哪些ID其他节点发各自的互不打扰。2.3 波特率与SJW两个节点能对上话的前提CAN通信双方必须工作在同一个波特率下这是常识但很多人不知道波特率是怎么来的。CAN的一个位时间由同步段、传播时间段和相位缓冲段构成STM32把后两段合并为BS1和BS2再加上一个同步跳转宽度SJW。位时间计算公式为位时间 1TQ同步段 BS1 BS2波特率 CAN时钟频率 / 预分频系数×位时间TQ数。以STM32F103为例CAN1挂在APB1总线上默认系统时钟72MHz时APB1为36MHz。目标波特率500kbps把预分频设为9BS1设为6TQBS2设为1TQ那么位时间 1 6 1 8TQ波特率 36MHz / (9×8) 500kbps采样点 (16)/8 87.5%。采样点这个参数很多人忽视。采样点太早信号还没稳定就采样了太晚留给线缆传播延迟的时间又不够。推荐采样点在70%到87%之间线上速率500kbps时87.5%是相当稳妥的配置。SJW的作用是重新同步。总线上不同节点的晶振总是有小幅偏差节点在检测到位边沿变化后通过SJW调整采样位置容忍时钟偏差。SJW通常设1~2TQ设得太小抗频偏能力弱设得太大接收窗口摆动过大。对多数应用来说1TQ就够用。3. 源码设计从CubeMX到收发函数3.1 CubeMX一步步配置CAN外设我用STM32CubeMX生成工程省去手写时钟配置的麻烦。选择芯片型号后开启CAN1外设引脚会自动分配为PA11和PA12。注意如果使用重映射引脚需要在GPIO设置里手动配置。关键在于CAN参数配置。把Prescaler设为9Mode选NormalSyncJumpWidth选1TQTimeSeg1选6TQTimeSeg2选1TQ。如果只是为了自测Mode可以选LoopBack回环模式此时CAN控制器在内部自发自收可以不接外部节点。AutoRetransmission建议先关闭这样发送失败会直接返回错误码方便调试阶段定位问题。生成工程后核心初始化代码大致如下hcan1.Instance CAN1; hcan1.Init.Prescaler 9; hcan1.Init.Mode CAN_MODE_NORMAL; hcan1.Init.SyncJumpWidth CAN_SJW_1TQ; hcan1.Init.TimeSeg1 CAN_BS1_6TQ; hcan1.Init.TimeSeg2 CAN_BS2_1TQ; hcan1.Init.TimeTriggeredCommunicationMode DISABLE; hcan1.Init.AutoBusOffManagement DISABLE; hcan1.Init.AutoWakeUp DISABLE; hcan1.Init.AutoRetransmission DISABLE; hcan1.Init.ReceiveFifoLocked DISABLE; hcan1.Init.TransmitFifoPriority DISABLE; HAL_CAN_Init(hcan1);3.2 发送路径从待发数据到mailboxSTM32的CAN外设有3个发送邮箱相当于3个待发通道。调用发送函数时驱动会自动寻找一个空邮箱把报文头和数据拷贝进去然后硬件负责逐位发送。发送一帧报文的代码很简单但有几个字段要理解透彻CAN_TxHeaderTypeDef txHeader; uint32_t mailbox; uint8_t txData[8] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; txHeader.StdId 0x123; // 标准帧ID txHeader.IDE CAN_ID_STD; // 使用标准帧还是扩展帧 txHeader.RTR CAN_RTR_DATA; // 数据帧还是远程帧 txHeader.DLC 8; // 数据长度最多8字节 if (HAL_CAN_AddTxMessage(hcan1, txHeader, txData, mailbox) HAL_OK) { // 报文已加入发送邮箱等待硬件发送 }关于远程帧也就是RTR字段很多教程喜欢讲但实际用得很少。远程帧是请求对方发送数据但设计良好的系统里一般不会主动用远程帧而是直接周期性发数据帧。远程帧处理逻辑复杂一旦用不好容易造成总线风暴我的建议是项目初期一律用数据帧把远程帧当成后续扩展项。如果发送函数返回失败先检查返回值。HAL_BUSY说明三个邮箱全满可能是发送频率太高或者总线上有其他节点阻塞HAL_ERROR说明参数有问题比如DLC超过8。还有种情况是总线处于bus-off状态这时候任何发送都不会成功需要查看错误寄存器确认状态。3.3 接收路径FIFO与中断回调接收报文有两种方式查询和中断。查询方式在主循环里不停调用接收函数缺点是CPU占用高且消息来了不能及时处理。我推荐用中断接收FIFO收到新消息时硬件自动触发中断在回调函数里处理数据速度快且CPU空闲。中断回调代码如下void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef rxHeader; uint8_t rxData[8] {0}; if (hcan-Instance CAN1) { HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, rxHeader, rxData); // 判断ID执行对应处理逻辑 if (rxHeader.StdId 0x123) { // 处理接收到的8字节数据 } } }注意接收FIFO有两个FIFO0和FIFO1一个节点可以用两组过滤器分别把不同ID映射到不同FIFO。如果FIFO满了新来的报文会被丢弃这在调试阶段容易踩坑。我在调试时发现过一种情况接收中断频率太高回调函数处理不过来FIFO溢出丢帧但程序看起来一切正常只有增加一个溢出计数器才发现问题。回调函数里务必不要做耗时操作比如打印日志、延时、复杂计算。CAN一帧500kbps下传输时间约200微秒如果在回调里做太多事下一帧来了处理不过来就会丢帧。我的做法是回调里只做数据拷贝和置标志位数据处理放到主循环。3.4 源码分层与扩展不能只跑通一次工程里的源码不能简单把所有逻辑堆在main.c里否则后续加协议、加节点会越来越乱。我习惯把CAN相关的硬件操作封装成bsp_can.c和bsp_can.h提供初始化、发送、注册接收回调这几个接口上层业务只关心数据内容不关心引脚和寄存器。// bsp_can.h 简要接口设计 void BSP_CAN_Init(void); uint8_t BSP_CAN_SendMessage(uint32_t id, uint8_t *data, uint8_t len); void BSP_CAN_SetReceiveCallback(void (*callback)(uint32_t id, uint8_t *data, uint8_t len));这样后续如果想加协议解析层比如把CAN数据转化成设备控制指令直接在业务层写逻辑就行不需要再动CAN驱动。源码按这个方式组织以后复制到其他项目上也快。4. 实测、常见问题与避坑经验4.1 开机第一件事回环模式自测拿到板子或者新焊接的板卡第一步不要直接连总线先把Mode配成LoopBack用回环模式验证MCU到CAN控制器的通路是否正常。回环模式下CAN控制器的发送数据直接在内部回环到接收路径不需要外部节点应答省去了接线的干扰因素。实测下来回环模式能通过说明STM32的CAN外设、时钟、中断、过滤器配置都没问题剩下的变量只剩外部收发器和物理连接。这一步能筛掉一大批低级问题减少联调时排查范围。4.2 用示波器看CAN波形判断通信质量两块板子联调之前我用示波器先看波形。探头接CANH和CANL用数学通道看差分波形。正常波形下隐性位时CANH和CANL都在2.5V附近差分接近0显性位时CANH到3.5VCANL到1.5V差分约2V。500kbps速率下一个位的时间是2微秒。用示波器的光标功能量一下相邻两个下降沿的间隔可以确认实际波特率是否和预期一致。波形质量怎么看边沿应该陡峭不应该出现圆弧状差分幅度不应该明显衰减在字节间隙处不应该有大幅毛刺。如果波形上升沿很缓多半是终端电阻没接好或者总线分支线太长。波形有回勾可能是阻抗不匹配要检查线缆和终端电阻。有一次我遇到两块板子偶尔通信失败看波形才发现总线在显性位结束时有明显过冲定位到是分支线太长把分支线剪短后问题消失。所以波形是物理层问题最直接的证据。4.3 常见问题排查速查表现象可能原因处理办法发送函数一直超时总线上没有其他节点无ACK应答接上另一块板或CAN分析仪两块板无法通信波特率不一致或CANH/CANL接反核对两侧位时序参数检查接线能发不能收过滤器配置把帧全屏蔽或中断没开检查过滤器和HAL_CAN_ActivateNotification收到大量错误帧位时序偏差大采样点位置不对调整BS1/BS2确保采样点在70%以上偶发通信失败波形异常缺少终端电阻分支线太长总线两端接120Ω缩短分支线Keil报no stm32 target foundSWD接线错误、板子没供电、ST-Link驱动异常检查SWDIO/SWCLK和供电重装驱动虚拟COM口有叹号串口驱动没装好安装对应USB转串口驱动遇到问题先分域排查先看物理层用示波器确认波形再看协议层确认波特率和ID最后才怀疑软件逻辑。这个顺序能省下大量无用功。4.4 几个能救命的小习惯调试CAN通信一年下来我发现几个非常实用的小习惯。第一在程序里加上错误中断的处理监控错误警告和bus-off状态。发送失败时及时读取CAN1-ESR寄存器里面的LEC字段会告诉你上次错误的类型是位错误、CRC错误还是应答错误定位问题事半功倍。第二用CAN分析仪当第三个节点挂在总线上。它能看到所有报文和错误帧还能模拟任意ID发数据联调时非常方便。没有分析仪也可以先用USB转CAN工具十几二十块钱的就行能看原始帧就可以。第三发送失败后的处理逻辑一定要写。我见到很多新手调用发送函数返回错误就丢弃不管了。CAN在强干扰环境下偶尔发送失败是正常的如果开启动AutoRetransmission硬件会自动重发但如果你用的是不自动重传的配置就要在软件里做重试或者记录错误计数否则数据会悄悄丢。第四初始化完成后先读取HAL_CAN_GetState确认状态是READY再启动。如果初始化没完成就调用发送函数会出现莫名其妙的现象。最后再分享一个我自己的习惯新板子到手我一定先用回环模式把收发跑通再接总线。越复杂的项目越要先证明最小系统是好的。CAN通信这种事协议层折腾半天发现是物理层一个电阻没焊是最痛的经历。示波器就是你的眼睛遇到问题不要猜直接看波形一步步缩小范围问题总会浮出水面。本文还有配套的精品资源点击获取
返回列表