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

资讯详情

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

STM32F407 CAN总线通信实战:从硬件设计到HAL库驱动开发

STM32F407 CAN总线通信实战:从硬件设计到HAL库驱动开发 简介本资源是一份面向嵌入式初学者与STM32进阶开发者的HAL库实战项目聚焦STM32F407VET6芯片的CAN总线通信基础应用解决CAN收发功能验证、波特率配置及中断处理等典型开发痛点适用于智能车、工业通信模块等需要可靠双节点通信的场景。压缩包共89个文件含58个头文件.h用于外设定义与函数声明、23个源文件.c实现HAL驱动、中断服务、用户逻辑及系统初始化另有工程配置文件.ioc/.uvprojx、启动代码.s和说明文档.txt结构完整符合STM32CubeMXMDK-ARM标准开发范式。目前已有3143人学习下载。读者可直接导入Keil MDK环境编译运行无需额外配置代码已按功能模块分层组织如User_CAN.c/h封装收发逻辑can.c/h专注底层寄存器操作并内置500kbps波特率校准、1秒周期发送与ID回显接收机制附带详细注释与错误处理分支便于理解CAN协议帧结构与HAL回调机制。1. 项目概述从零构建一个可靠的CAN通信节点最近在做一个工业数据采集的项目核心需求是把分布在产线不同位置的传感器数据汇总到一个主控板上。传感器和主控板之间距离从几米到几十米不等现场电磁环境也比较复杂。一开始考虑过串口但多节点、长距离和抗干扰的需求让我直接放弃了它。最终我选择了CAN总线。它天生就是为这种分布式、强干扰的工业环境设计的双绞线差分信号带来的抗共模干扰能力以及基于优先级的非破坏性仲裁机制让它在多节点通信时既稳定又高效。这次分享的就是基于STM32F407和HAL库实现CAN通信收发功能的完整实验。我会从最基础的硬件连接、CubeMX配置讲起一直深入到邮箱滤波、中断处理、数据收发等核心代码的实现并附上经过实际项目验证的程序源码。无论你是刚开始接触CAN总线的新手还是想从标准库迁移到HAL库的开发者这篇文章都能给你提供一个清晰、可复现的参考路径。STM32F407自带了两个功能强大的CAN控制器CAN1和CAN2配合HAL库的抽象层能让我们更专注于应用逻辑而不是底层的寄存器操作。2. 硬件设计与核心电路解析2.1 CAN节点硬件架构拆解一个完整的STM32 CAN节点其硬件部分可以清晰地分为三块微控制器MCU、CAN收发器和物理总线。STM32F407内部集成了CAN控制器它负责处理CAN协议中数据链路层的所有工作比如报文封装、校验、仲裁、错误处理等。但控制器产生的信号是数字逻辑电平0-3.3V无法直接驱动长达数十米的总线。这就需要CAN收发器作为“翻译官”和“驱动器”将控制器的逻辑电平转换为符合ISO 11898标准的差分信号CAN_H和CAN_L并具备一定的抗干扰和总线保护能力。注意千万不要试图将STM32的CAN_Tx/Rx引脚直接连接到总线上这绝对会损坏芯片。收发器是必须的。对于STM32F407CAN1和CAN2的引脚是固定的CAN1: RX - PA11, TX - PA12CAN2: RX - PB12, TX - PB13 当然部分引脚有重映射功能但PA11/PA12和PB12/PB13是最常用的默认配置。在原理图设计和PCB布线时需要确保这些引脚连接到你选用的CAN收发器芯片的对应引脚上。2.2 CAN收发器选型与关键外围电路市面上常见的CAN收发器有NXP的TJA1050、TI的SN65HVD230等。它们功能类似但有些细节差异。以经典的TJA1050为例它支持最高1Mbps的通信速率具有待机模式并且斜率控制有助于降低EMI。这里重点讲一下它的外围电路这是稳定通信的基础。电源与去耦TJA1050的VCC引脚通常是引脚8需要接5V或3.3V具体看型号TJA1050是5V。必须在靠近芯片VCC和GND引脚的地方放置一个100nF的陶瓷电容用于滤除高频噪声。这是保证芯片稳定工作的第一步布局不当会引入通信误码。终端电阻配置这是新手最容易出错的地方。CAN总线要求在总线的两个最远端各并联一个120欧姆的终端电阻用以匹配电缆的特性阻抗通常为120欧姆消除信号反射。如果你的电路板只是总线上的一个中间节点那么板上不应该焊接这个120欧姆电阻R1。只有当你的节点确定是总线的一端时才需要焊接。很多开发板为了测试方便会通过跳帽来选择是否接入终端电阻。// 典型TJA1050连接电路作为非终端节点 STM32F407 CAN_TX ---- TJA1050 TXD (Pin1) STM32F407 CAN_RX ---- TJA1050 RXD (Pin4) TJA1050 CAN_H (Pin7) ---- 总线CAN_H TJA1050 CAN_L (Pin6) ---- 总线CAN_L TJA1050 VCC (Pin8) ---- 5V 并联100nF电容到GND TJA1050 GND (Pin5) ---- 电源地 TJA1050 S (Pin3) ---- 接GND选择高速模式。若接高电平则进入静音模式。 // 120欧姆电阻R1不焊接在本板上。总线保护在工业环境中建议在CAN_H和CAN_L对地之间各加一个ESD保护二极管如SM712并在总线入口处串联一个共模电感可以显著提升抗浪涌和抗静电能力。对于实验室环境这部分可以简化。2.3 通信速率计算与配置依据CAN通信的波特率配置是个关键点总线上所有节点的波特率必须严格一致。波特率由一系列分频系数决定。STM32的CAN外设时钟来源于APB1总线在STM32F407中APB1的频率通常是42MHz系统时钟168MHz经过4分频。波特率计算公式为波特率 APB1时钟 / (Prescaler * (TimeSegment1 TimeSegment2 1))。其中Prescaler波特率预分频器范围1-1024。TimeSegment1 (BS1)包含同步段固定1个时间单位和传播时间段。用于补偿网络内的物理延迟。TimeSegment2 (BS2)相位缓冲段2。用于在采样点后进行微调。采样点的位置通常建议在75%-80%之间这对于提高噪声容限有好处。采样点计算公式为采样点 (1 BS1) / (1 BS1 BS2)。举个例子我们要配置500Kbps的波特率APB1时钟为42MHz。先确定一个合适的预分频器Prescaler。假设我们设为3。计算时间单位Time Quanta的频率42MHz / 3 14MHz。计算一个位时间包含的时间单位数14MHz / 500Kbps 28 Time Quanta。分配BS1和BS2。为了获得约80%的采样点可以设BS121BS26。验证总时间单位 1(同步段) BS1 BS2 121628符合。采样点 (121)/28 78.6%符合要求。因此配置为Prescaler3 BS121 BS26。在CubeMX中这些参数对应NominalBitRate的配置。TimeSeg1 BS1TimeSeg2 BS2。SynchronizationJumpWidth通常设置为和BS2相同或略小这里可以设为6。3. 软件工程创建与CubeMX深度配置3.1 工程初始化与外设引脚分配打开STM32CubeMX选择STM32F407系列对应的型号。首先配置时钟树Clock Configuration确保系统时钟正确通常使用外部晶振HSE倍频到168MHz并确认APB1总线时钟为42MHz这是CAN外设的时钟源。在Pinout Configuration标签页下找到Connectivity-CAN1。工作模式选择Normal模式。Loopback和Silent模式用于自测试我们最终需要正常通信。引脚自动分配CubeMX会自动将PA11和PA12分配为CAN1_RX和CAN1_TX。检查确认即可。如果你需要使用CAN2则激活CAN2引脚会是PB12和PB13。参数配置切换到Parameter Settings子标签页这是配置的核心。3.2 CAN参数详细配置与滤波初设在Parameter Settings中我们需要仔细设置之前计算好的通信参数Prescaler (for Nominal Bit Rate): 设置为3。Nominal Time Seg1: 设置为21 Time Quanta。Nominal Time Seg2: 设置为6 Time Quanta。Nominal Synchronization Jump Width: 设置为6 Time Quanta。Mode: 保持Normal。Auto Bus Off Management: 建议使能。当节点发送错误累积到一定数量会自动进入总线关闭状态并在一定时间后自动恢复提高了系统的鲁棒性。Auto Wake Up Mode: 禁用。我们不需要总线唤醒功能。Auto Retransmission:务必使能。这是保证可靠性的关键。当发送失败如仲裁失败时硬件会自动重发无需软件干预。Receive Fifo Locked Mode: 禁用。FIFO溢出时新报文会覆盖旧报文。Transmit Fifo Priority: 可以选择By identifier报文ID优先级或By request order请求顺序。根据应用需求定通常用默认的ID优先级。接下来是滤波器Filter配置这是CAN的“守门员”决定了哪些报文能进入MCU的接收邮箱FIFO极大减轻CPU负担。STM32F407提供了28个滤波器组可以分配给CAN1或CAN2或两者共享。我们先做一个最简单的配置接收所有报文。在Filter Configuration中点击Add添加一个滤波器。Filter Activate: 勾选启用它。Filter Mode: 选择Mask mode掩码模式。另一种是List mode列表模式掩码模式更灵活常用。Filter Scale: 选择32-bit。32位模式可以一次性检查标准ID11位或扩展ID29位。Filter Bank Assignment: 选择分配给CAN1或CAN2。Filter FIFO Assignment: 选择FIFO0。CAN控制器有两个接收FIFOFIFO0和FIFO1用于缓存接收到的报文。Filter ID High/Low Mask ID High/Low这是关键。为了接收所有标准ID报文将Filter ID High/Low都设为0Mask ID High/Low也都设为0。掩码为0表示不检查任何位所有ID都通过。为了接收所有扩展ID报文同样将ID和Mask都设为0。实操心得在项目初期调试阶段强烈建议先将滤波器设为“接收所有”确保通信链路畅通。待通信正常后再根据实际应用需求配置精确的过滤规则这样可以排除是滤波器配置错误导致的收不到数据问题。3.3 中断与DMA配置策略为了高效处理接收到的数据我们通常使用中断方式。在NVIC Settings标签页下找到CAN1 RX0 interrupts或CAN1 RX1 interrupts取决于你之前FIFO分配给了哪个。使能这个中断。为什么是RX0因为我们之前把滤波器分配给了FIFO0所以使能FIFO0的中断。同时也可以使能CAN1 SCE interrupt状态改变中断用于处理错误、总线离线等状态变化便于调试。对于发送HAL库提供了阻塞和非阻塞中断/DMA模式。对于简单的轮询发送可以不开启发送中断。但如果发送数据量大或者不想在发送函数里等待可以启用CAN1 TX interrupts。不过CAN的发送有硬件自动重传通常阻塞模式HAL_CAN_AddTxMessage配合轮询标志在简单应用中也能接受。DMA配置对于超高频率或确定性要求极高的收发可以考虑DMA。在DMA Settings中添加将CAN1_TX或CAN1_RX关联到某个DMA流。但HAL库的CAN DMA发送接收函数相对复杂且对于大多数应用中断方式已完全足够。我个人的经验是除非有明确的性能瓶颈否则优先使用中断方式代码更清晰易懂。最后生成工程代码选择你熟悉的IDEMDK-ARM或IAR等。在生成代码时建议将外设初始化代码生成为.c/.h文件对这样结构更清晰。4. HAL库CAN驱动核心代码实现4.1 CAN初始化与启动流程剖析CubeMX生成的代码在can.c中完成了外设的初始化MX_CAN1_Init。我们需要在自己的应用代码中如main.c启动CAN外设并激活配置好的滤波器。// 在main函数初始化部分 after MX_CAN1_Init() CAN_FilterTypeDef sFilterConfig; /* 启动CAN外设 */ if (HAL_CAN_Start(hcan1) ! HAL_OK) { Error_Handler(); } /* 配置并启动滤波器 (示例接收所有标准ID报文) */ sFilterConfig.FilterBank 0; // 使用滤波器组0 sFilterConfig.FilterMode CAN_FILTERMODE_IDMASK; sFilterConfig.FilterScale CAN_FILTERSCALE_32BIT; sFilterConfig.FilterIdHigh 0x0000; sFilterConfig.FilterIdLow 0x0000; sFilterConfig.FilterMaskIdHigh 0x0000; sFilterConfig.FilterMaskIdLow 0x0000; sFilterConfig.FilterFIFOAssignment CAN_RX_FIFO0; // 存入FIFO0 sFilterConfig.FilterActivation ENABLE; sFilterConfig.SlaveStartFilterBank 14; // 如果使用双CAN这个参数用于分配滤波器组CAN1用0-13CAN2用14-27。 if (HAL_CAN_ConfigFilter(hcan1, sFilterConfig) ! HAL_OK) { Error_Handler(); } /* 激活CAN接收中断针对FIFO0 */ if (HAL_CAN_ActivateNotification(hcan1, CAN_IT_RX_FIFO0_MSG_PENDING) ! HAL_OK) { Error_Handler(); }这段代码是CAN通信的“点火开关”。HAL_CAN_Start让控制器进入工作状态HAL_CAN_ConfigFilter设置了接收规则HAL_CAN_ActivateNotification打开了接收中断的阀门。缺一不可。4.2 报文发送函数封装与实战发送报文的核心是填充一个CAN_TxHeaderTypeDef结构体然后调用HAL_CAN_AddTxMessage。这个函数是非阻塞的它会将报文放入发送邮箱由硬件自动发送。/** * brief 发送一帧CAN标准数据帧 * param id: 标准ID (11位) * param data: 数据数组指针 * param len: 数据长度 (0-8) * retval HAL status */ HAL_StatusTypeDef CAN_Send_Msg(uint32_t id, uint8_t *data, uint8_t len) { CAN_TxHeaderTypeDef TxHeader; uint32_t TxMailbox; if (len 8) len 8; // CAN帧数据场最大8字节 TxHeader.StdId id; // 使用标准ID TxHeader.ExtId 0x00; // 扩展ID设为0当使用标准ID时此字段忽略 TxHeader.IDE CAN_ID_STD; // 标识符类型标准帧 TxHeader.RTR CAN_RTR_DATA; // 帧类型数据帧 TxHeader.DLC len; // 数据长度码 TxHeader.TransmitGlobalTime DISABLE; // 不捕获时间戳 // 将报文添加到第一个空闲的发送邮箱并返回邮箱编号 if (HAL_CAN_AddTxMessage(hcan1, TxHeader, data, TxMailbox) ! HAL_OK) { return HAL_ERROR; } // 可选等待发送完成阻塞方式。对于有自动重传的CAN通常不需要。 // while(HAL_CAN_GetTxMailboxesStatusLevel(hcan1) ! 0); // 等待所有邮箱空 return HAL_OK; }关键点解析IDE位CAN_ID_STD表示标准帧11位IDCAN_ID_EXT表示扩展帧29位ID。必须与你要发送的ID类型匹配。RTR位CAN_RTR_DATA表示数据帧CAN_RTR_REMOTE表示远程帧。远程帧用于请求特定ID的数据对方收到后会回复一个数据帧。我们大部分应用都是数据帧。DLC范围0-8表示数据字节数。即使数据数组长度大于8也只会发送前8个字节。TxMailbox这是一个输出参数函数会告诉你报文被放入了哪个硬件发送邮箱0,1,2。你可以通过这个值来查询特定邮箱的发送状态但在自动重传使能的情况下通常不关心。注意事项HAL_CAN_AddTxMessage只是将报文放入硬件发送队列。如果三个发送邮箱都满了它会返回HAL_ERROR。在实际应用中你需要根据HAL_CAN_GetTxMailboxesStatusLevel函数的返回值来判断是否有空闲邮箱或者实现一个简单的发送缓冲队列来应对突发数据。4.3 中断接收与数据处理实战接收我们采用中断方式。当FIFO0中有新报文时会触发中断进入回调函数HAL_CAN_RxFifo0MsgPendingCallback。我们需要在这个函数里读取报文。首先在stm32f4xx_it.c中中断服务函数CAN1_RX0_IRQHandler已经由CubeMX生成它会调用HAL库的中断处理函数HAL_CAN_IRQHandler最终调用到我们的回调函数。我们在main.c或自定义的CAN处理文件中重写这个回调函数// 定义接收缓冲区 uint8_t RxData[8]; CAN_RxHeaderTypeDef RxHeader; /** * brief CAN FIFO0消息挂起回调函数 * param hcan: CAN句柄指针 * retval None */ void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { if (hcan-Instance CAN1) // 判断是哪个CAN触发的 { // 从FIFO0中读取一帧报文 if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, RxHeader, RxData) HAL_OK) { // 成功接收到一帧数据在这里进行处理 Process_CAN_Message(RxHeader, RxData); } } } /** * brief 处理接收到的CAN报文 * param pHeader: 报文头指针 * param pData: 数据指针 * retval None */ static void Process_CAN_Message(CAN_RxHeaderTypeDef *pHeader, uint8_t *pData) { // 1. 判断帧类型 if (pHeader-IDE CAN_ID_STD) { printf(收到标准帧 ID: 0x%03X, , pHeader-StdId); } else { printf(收到扩展帧 ID: 0x%08lX, , pHeader-ExtId); } // 2. 判断是否是远程帧 if (pHeader-RTR CAN_RTR_REMOTE) { printf(这是一帧远程请求帧。\r\n); // 通常需要根据StdId/ExtId回复对应的数据帧 // Reply_To_Remote_Request(pHeader-StdId); return; } // 3. 处理数据帧 printf(数据长度: %d, 数据: , pHeader-DLC); for (int i 0; i pHeader-DLC; i) { printf(%02X , pData[i]); } printf(\r\n); // 4. 根据ID进行具体的应用层解析 switch (pHeader-StdId) // 以标准ID为例 { case 0x100: // 处理ID为0x100的报文 // 例如 motor_speed (pData[0] 8) | pData[1]; break; case 0x200: // 处理ID为0x200的报文 break; default: // 其他ID的报文 break; } }中断处理要点快速处理中断回调函数中应尽快读取数据然后退出。复杂的处理如浮点运算、打印最好放到主循环或任务中可以通过设置标志位、将数据拷贝到环形缓冲区等方式。FIFO溢出如果报文接收太快而中断处理太慢可能导致FIFO溢出。可以启用HAL_CAN_ActivateNotification(hcan1, CAN_IT_RX_FIFO0_OVERRUN)来监控溢出中断并在回调函数HAL_CAN_RxFifo0FullCallback中处理。双FIFOCAN控制器有两个接收FIFO。你可以用滤波器将不同ID范围的报文分配到FIFO0和FIFO1并分别使能它们的中断实现简单的报文分类处理。5. 高级应用滤波器配置精讲与实战5.1 掩码模式与列表模式深度解析前面我们用了掩码模式并设置掩码为0来接收所有报文。这在实际产品中是不安全的会浪费CPU资源。我们需要精确过滤。掩码模式Mask Mode它像是一个“通配符”过滤器。FilterId是你想要匹配的IDFilterMaskId决定ID中的哪些位需要被严格匹配掩码位为1哪些位不关心掩码位为0。例如我们只想接收标准ID为0x100到0x10F的报文。标准ID是11位但在32位模式下我们需要将其左对齐到寄存器中。规则是STID[10:0]放在FilterIdHigh的[28:18]位。我们希望匹配ID的高8位即0x10低3位任意。0x100的二进制是001 0000 000011位。FilterIdHigh应设置为0x100 5左移5位对齐到[28:18] 0x2000。FilterMaskIdHigh需要设置高8位bit28-bit21必须匹配所以这些位掩码设为1低3位bit20-bit18不关心设为0。即掩码为1111 1111 1000二进制换算到FilterMaskIdHigh的[28:18]位其值为0xFF80。配置代码如下sFilterConfig.FilterIdHigh 0x2000; // (0x100 5) sFilterConfig.FilterIdLow 0x0000; sFilterConfig.FilterMaskIdHigh 0xFF80; // 高8位必须匹配低3位任意 sFilterConfig.FilterMaskIdLow 0x0000; // 低16位不用于标准ID设为0 sFilterConfig.FilterMode CAN_FILTERMODE_IDMASK; sFilterConfig.FilterScale CAN_FILTERSCALE_32BIT;这样所有ID在0x100到0x10F之间的标准帧都会被接收。列表模式List Mode它更像一个“白名单”。在32位尺度下一个滤波器可以存放两个标准ID或一个扩展ID。只有ID完全等于列表中某一个的报文才会被接收。例如只接收标准ID为0x123和0x456的报文。计算FilterIdHigh和FilterIdLow。每个ID需要左移5位对齐。0x123 5 0x24600x456 5 0x8AC0在列表模式下FilterIdHigh存放第一个IDFilterIdLow存放第二个ID。sFilterConfig.FilterIdHigh 0x2460; // ID 0x123 sFilterConfig.FilterIdLow 0x8AC0; // ID 0x456 sFilterConfig.FilterMaskIdHigh 0x0000; // 列表模式下掩码无效 sFilterConfig.FilterMaskIdLow 0x0000; sFilterConfig.FilterMode CAN_FILTERMODE_IDLIST; sFilterConfig.FilterScale CAN_FILTERSCALE_32BIT;5.2 扩展ID过滤与双CAN滤波器分配实战对于扩展ID29位配置原理类似但ID的摆放位置不同。扩展IDExtId[28:0]被分成三部分存放ExtId[28:21]放在FilterIdHigh的[7:0]ExtId[20:13]放在FilterIdHigh的[15:8]ExtId[12:0]放在FilterIdLow的[15:3]左移3位例如要过滤扩展ID0x18DAF110假设这是一个汽车诊断常用的ID。将其分解为29位二进制0x18DAF110是29位ID的十六进制表示实际需要确认其完整29位值这里假设它就是29位值。更简单的方法是使用HAL库提供的宏((ext_id 3) 16) 0xFFFF得到FilterIdHigh(ext_id 3) 0xFFFF得到FilterIdLow。掩码的计算同样遵循位对应关系。双CAN滤波器分配STM32F407的28个滤波器组是CAN1和CAN2共享的。通过SlaveStartFilterBank参数来划分。例如设置SlaveStartFilterBank 14则表示滤波器组 0 到 13 (共14个) 分配给CAN1。滤波器组 14 到 27 (共14个) 分配给CAN2。 在配置CAN2的滤波器时FilterBank参数必须从14开始。这个分配必须在初始化阶段完成并且一旦分配两个CAN控制器应使用各自范围内的滤波器组。6. 调试技巧、常见问题与实战心得6.1 硬件链路诊断与软件调试方法问题一根本收不到也发不出任何报文。这是最令人头疼的情况。请按以下步骤排查检查物理连接确保CAN_H和CAN_L没有接反总线两端是否有120欧姆终端电阻用万用表测量CAN_H和CAN_L之间的电阻应在60欧姆左右两个120欧姆并联。测量CAN_H或CAN_L对地电压在静默状态下两者电压大约都在2.5V左右差值接近0V。如果有数据通信你会看到电压波动。检查芯片供电与引脚确认CAN收发器如TJA1050的VCC电压是否正确5V或3.3VSTM32的CAN_TX/RX引脚是否与收发器正确连接。检查初始化代码确认HAL_CAN_Start和HAL_CAN_ActivateNotification是否被成功调用。可以在这些函数后添加打印语句。使用回环模式自检在CubeMX中将CAN模式改为Loopback。在这个模式下芯片自己发送自己接收不经过收发器。写一个发送函数然后在接收中断里打印数据。如果回环模式能通证明软件和STM32的CAN控制器本身没问题问题出在收发器或物理总线上。检查波特率这是最隐蔽的错误。确保总线上所有节点的波特率、BS1、BS2、SJW设置完全一致。哪怕有一个参数不同通信都无法建立。可以用逻辑分析仪或带CAN解码功能的示波器抓取总线波形测量位时间反推实际波特率。问题二能发送但收不到对方的报文或者反之。检查滤波器这是最常见的原因。特别是从“接收所有”切换到特定过滤后收不到了。请暂时将滤波器配置为接收所有ID掩码全0看是否能收到。如果能说明物理链路和对方发送是好的问题就在滤波器配置上。仔细核对ID计算和掩码设置。检查ID类型对方发送的是标准帧还是扩展帧你的滤波器配置是否与之匹配例如对方发扩展帧你的滤波器只过滤标准帧肯定收不到。检查FIFO分配你的中断使能的是CAN_IT_RX_FIFO0_MSG_PENDING但滤波器配置里FilterFIFOAssignment是否分配给了FIFO0如果分配给了FIFO1则需要使能CAN_IT_RX_FIFO1_MSG_PENDING中断。6.2 稳定性提升与抗干扰实践增加错误处理除了接收中断务必使能状态改变中断CAN_IT_ERROR和CAN_IT_BUSOFF。在回调函数HAL_CAN_ErrorCallback中可以读取错误计数器并做相应处理如尝试恢复。void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan) { uint32_t error HAL_CAN_GetError(hcan); if(error HAL_CAN_ERROR_BUSOFF) { printf(CAN总线进入离线状态\r\n); // 在自动总线关闭管理使能的情况下硬件会尝试恢复。 // 也可以在这里手动调用 HAL_CAN_ResetErrorState 等函数。 } // 可以读取发送错误计数器和接收错误计数器 // uint8_t tsr hcan-Instance-TSR; // uint8_t rec (hcan-Instance-ESR 24) 0xFF; // 接收错误计数 // uint8_t tec (hcan-Instance-ESR 16) 0xFF; // 发送错误计数 }软件超时与重发机制虽然硬件有自动重传但在应用层对于重要的指令或数据可以实现一个“发送-确认-重发”机制。发送后启动一个定时器如果在规定时间内没有收到对方的确认帧则应用层软件重发。这提升了应用级的可靠性。总线负载管理避免在短时间内爆发式发送大量报文。CAN总线虽然有仲裁但高负载会导致延迟增加甚至丢帧。合理安排报文发送周期对于非实时数据可以适当降低发送频率。PCB布局与隔离对于强干扰环境考虑使用带隔离的CAN收发器模块如ADM3052并将隔离部分的电源如芯片的VCC和总线侧的VCC用隔离DC-DC分开信号用光耦或磁耦隔离。PCB上CAN收发器尽量靠近接口端子CAN信号线走差分线等长且平行远离高速数字信号和电源线。6.3 项目源码结构建议一个健壮的CAN通信模块建议将代码分层组织/Drivers /BSP bsp_can.c/.h // 硬件抽象层引脚初始化、收发器控制如待机模式 /Modules /CAN can_driver.c/.h // 驱动层HAL库封装、初始化、发送接收基础API can_protocol.c/.h // 协议层报文ID定义、数据打包解包、应用层协议解析 can_task.c/.h // 任务层如果使用RTOS创建CAN收发任务、消息队列 /Application main.c // 调用BSP和Module进行初始化启动任务或主循环在can_driver.h中提供清晰的接口如CAN_Init(),CAN_SendStdData(),CAN_RegisterRxCallback()。在can_protocol.h中用枚举定义所有的报文ID用结构体定义每个报文的数据格式。这样当需要增加新的报文类型时只需要修改协议层驱动层和应用层几乎不用动代码可维护性大大增强。最后分享一个我调试时的小技巧在发送函数里除了发送应用数据可以定期比如每秒一次发送一个固定的“心跳”帧ID自定。然后用一个USB-CAN适配器连接到总线上使用上位机软件如CANTest、PCAN-View来监听。只要能看到心跳帧就证明你的节点发送功能基本正常发送链路是通的。然后再用这个上位机软件向你的STM32发送一帧数据看能否在串口调试助手上打印出来以此验证接收链路。这种“一发一收”的交叉验证法能快速定位问题是出在发送端还是接收端。本文还有配套的精品资源点击获取
返回列表