十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

STM32F103 CAN总线双机通讯实战:从CubeMX配置到HAL库应用

STM32F103 CAN总线双机通讯实战:从CubeMX配置到HAL库应用 简介本资源是一套基于STM32F103系列单片机的CAN总线双机通信实战例程面向嵌入式初学者与物联网项目开发者聚焦工业现场常见的多节点数据交互场景帮助用户快速掌握HAL库下CAN外设配置、消息发送/接收、错误处理及双机协同调试等核心能力。压缩包共195个文件含107个头文件定义寄存器映射与接口、69个C源文件涵盖HAL驱动、主逻辑与CAN协议栈实现、14个汇编启动文件及KEIL工程配置文件uvprojx/uvoptx整体大小为1.76MB结构完整、模块清晰便于逐层理解与移植。已有200人学习下载配套代码注释详尽关键引脚定义、时钟配置与CAN滤波器设置均在源码中标明附带重置编译环境的bat脚本显著降低IDE适配门槛内容预览显示工程已集成UART、SPI、I2C、ADC等多外设HAL驱动具备向复杂传感器融合系统扩展的基础支撑能力。1. 项目背景与CAN总线通讯的价值最近在整理一个老项目的代码翻出来一个基于STM32F103的CAN双机通讯例程用的是HAL库。这个项目当时是为了实现两个设备间的实时数据交换比如传感器数据采集和运动控制指令下发。CAN总线在工业控制、汽车电子这些领域用得非常多它的核心优势在于多主、高可靠、抗干扰。简单来说它不像I2C或者UART那样有明确的主从之分总线上任何一个节点都可以主动发消息而且通过一套复杂的错误检测和仲裁机制保证了即使在恶劣的电气环境下通讯也能稳定进行。对于STM32F103这类经典的Cortex-M3内核单片机其内置的bxCAN控制器功能相当完整配合ST提供的HAL库能让我们把更多精力放在应用逻辑上而不是底层的寄存器操作。这个例程虽然基础但涵盖了从CubeMX配置、过滤器设置、中断处理到数据收发的完整链路是理解CAN通讯一个很好的切入点。2. 硬件连接与CubeMX工程配置详解搞CAN通讯第一步永远是硬件。STM32F103的CAN接口通常映射到特定的GPIO上最常见的是PA11(CAN_RX)和PA12(CAN_TX)。你需要确保这两个引脚通过一个CAN收发器比如经典的TJA1050连接到CAN总线上。总线两端必须各接一个120欧姆的终端电阻这个电阻对于消除信号反射、保证信号完整性至关重要很多通讯不稳定的问题都出在这里。软件配置从STM32CubeMX开始。新建一个STM32F103C8Tx或其他对应型号工程后关键步骤如下2.1 时钟与引脚配置首先在Pinout Configuration标签页下找到Connectivity-CAN1。将模式Mode设置为Normal。这时PA11和PA12会自动被配置为CAN1_RX和CAN1_TX。接着去RCC复位和时钟控制设置里将High Speed Clock (HSE)选为Crystal/Ceramic Resonator因为CAN对时钟精度有一定要求通常使用外部晶振更可靠。2.2 CAN参数配置点击进入CAN1的配置页面这里有几个核心参数需要关注Bit Timing Parameters位时序参数这是CAN配置的灵魂直接关系到通讯速率和稳定性。它由Prescaler、Time Quanta in Bit Segment 1、Time Quanta in Bit Segment 2和ReSynchronization Jump Width共同决定。Prescaler预分频器决定了时间单元Time Quantum, tq的长度。tq (Prescaler) / (APB1总线时钟频率)。APB1时钟在STM32F103上最高为36MHz。Bit Segment 1 (BS1)包含传播段和相位缓冲段1用于补偿网络上的物理延迟。Bit Segment 2 (BS2)相位缓冲段2。ReSynchronization Jump Width (SJW)重新同步跳转宽度决定了在一次重新同步中位时间可以被缩短或延长多少个tq通常设置为1或2。一个常见的1Mbps125kbps更常用抗干扰性更好配置示例如下假设APB1时钟为36MHz目标波特率1Mbps。总的时间单元数Time Quanta (tq) 1 / (波特率 * tq时间)。但更直观的方法是使用CubeMX的自动计算功能或者手动计算总tq数 BS1 BS2 1同步段。为了稳定性通常总tq数在8-25之间。例如设置Prescaler3BS113 tqBS22 tqSJW1 tq。则tq 3 / 36MHz ≈ 83.33ns。位时间 (1132)*83.33ns ≈ 1.33us对应波特率约751kbps。需要反复调整Prescaler和段长度直到接近目标值。对于初学者可以先从125kbpsPrescaler12,BS113 tq,BS22 tq开始这是工业上非常常见的速率。Operating Mode操作模式选择Normal即可。Loopback和Silent模式用于自测试和监听非常有用。功能使能务必勾选CAN Interrupts下的FIFO0 message pending interrupt或FIFO1 message pending interrupt或两者都勾选这样收到消息时才能触发中断。通常使用FIFO0就够了。配置完成后生成代码。CubeMX会帮我们初始化好GPIO、时钟和CAN外设的基本参数并生成中断服务函数CAN1_RX0_IRQHandler的框架。3. CAN过滤器配置精准接收的关键CAN总线是广播式的总线上所有消息所有节点都能“听到”。过滤器Filter的作用就是设置一个“关卡”只让符合特定规则的消息进入单片机的接收FIFO从而减轻CPU负担。这是CAN应用开发中必须理解的一环。STM32的bxCAN提供了两种基本的过滤模式标识符列表模式和标识符掩码模式。列表模式过滤器像一个严格的名单只接收ID完全等于预设值的报文。掩码模式过滤器像一个模糊匹配的规则。你设置一个ID值和一个掩码Mask。掩码位为1表示必须严格匹配ID对应位为0表示不关心。例如ID0x123 Mask0x7F0。那么所有ID的高8位0x12必须匹配低4位任意。这常用于接收一组ID连续的报文。在HAL库中我们通过HAL_CAN_ConfigFilter函数来配置。配置通常在CAN启动前进行。一个典型的配置掩码模式过滤器的代码如下CAN_FilterTypeDef sFilterConfig; sFilterConfig.FilterBank 0; // 使用第0组过滤器F103有14组 sFilterConfig.FilterMode CAN_FILTERMODE_IDMASK; // 掩码模式 sFilterConfig.FilterScale CAN_FILTERSCALE_32BIT; // 32位宽模式 sFilterConfig.FilterIdHigh 0x0000; // 期望的ID高16位 sFilterConfig.FilterIdLow 0x0000; // 期望的ID低16位 sFilterConfig.FilterMaskIdHigh 0x0000; // 掩码高16位 sFilterConfig.FilterMaskIdLow 0x0000; // 掩码低16位 sFilterConfig.FilterFIFOAssignment CAN_RX_FIFO0; // 匹配到的报文存到FIFO0 sFilterConfig.FilterActivation ENABLE; // 使能该过滤器 sFilterConfig.SlaveStartFilterBank 14; // 对于单CAN设备此参数无效 if (HAL_CAN_ConfigFilter(hcan1, sFilterConfig) ! HAL_OK) { Error_Handler(); }这段代码将过滤器0配置为32位掩码模式并且ID和掩码都设为0。这意味着不进行任何过滤接收所有报文。这在开发调试初期非常有用可以确保能收到数据。在实际应用中你需要根据通讯协议规划设置具体的ID和掩码。例如如果你只想接收标准ID为0x100到0x10F的报文可以设置期望ID为0x100掩码为0x7F0二进制11111110000这样低4位不关心高7位标准ID共11位必须匹配0x100的高7位。注意过滤器的配置必须在CAN处于初始化模式HAL_CAN_Init之后HAL_CAN_Start之前下进行。一旦CAN启动过滤器配置就被锁定了除非重新进入初始化模式否则无法修改。4. 中断服务函数与报文接收处理流程配置好过滤器并启动CANHAL_CAN_Start后当有匹配的报文到达就会触发我们之前使能的中断。中断服务函数CAN1_RX0_IRQHandler中会自动调用HAL库的回调函数HAL_CAN_RxFifo0MsgPendingCallback。我们的核心接收逻辑就写在这个回调函数里。这个回调函数的工作流程是标准化的检查中断来源回调函数被调用意味着FIFO0有消息挂起。读取报文使用HAL_CAN_GetRxMessage函数从指定的FIFO这里是FIFO0中把报文数据拷贝到一个CAN_RxHeaderTypeDef存放报文头信息如ID、类型、长度DLC和一个数据数组中。处理数据根据报文头中的ID解析数据数组中的内容执行相应的应用逻辑如更新变量、设置标志位、转发数据等。释放FIFO处理完成后HAL库在HAL_CAN_GetRxMessage内部通常会管理FIFO的出队我们无需手动操作。一个典型的接收回调函数实现如下// 定义全局或静态变量用于接收 CAN_RxHeaderTypeDef RxHeader; uint8_t RxData[8]; // CAN一帧最多8字节 void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { if(hcan-Instance CAN1) // 判断是哪个CAN实例触发 { // 1. 读取报文 if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, RxHeader, RxData) HAL_OK) { // 2. 处理数据 uint32_t receivedId RxHeader.StdId; // 获取标准ID uint8_t dataLength RxHeader.DLC; // 获取数据长度 // 示例如果ID是0x101则将数据作为32位整数处理 if (receivedId 0x101 dataLength 4) { uint32_t sensorValue (RxData[3] 24) | (RxData[2] 16) | (RxData[1] 8) | RxData[0]; // ... 使用sensorValue ... } // 可以根据不同的ID添加更多的处理分支 } } }这里的关键是HAL_CAN_GetRxMessage函数它一次性完成了报文的提取。RxHeader结构体包含了帧的所有元信息RxData数组存放了有效数据载荷。5. 报文发送流程与HAL_CAN_AddTxMessage的使用发送报文比接收要主动一些。核心函数是HAL_CAN_AddTxMessage。发送前你需要填充一个CAN_TxHeaderTypeDef结构体来描述要发送的报文并准备好数据缓冲区。发送流程通常如下准备报文头设置ID标准或扩展、帧类型数据帧或远程帧通常用数据帧CAN_RTR_DATA、数据长度DLC0-8。准备数据将待发送的数据填入一个uint8_t数组。选择邮箱并发送CAN控制器有3个发送邮箱。调用HAL_CAN_AddTxMessage函数会找一个空闲的邮箱将报文装载进去并自动启动发送。你可以指定一个uint32_t变量来获取实际使用的邮箱号。检查发送完成可以通过轮询方式检查HAL_CAN_GetTxMailboxesFreeLevel或者使用发送完成中断HAL_CAN_TxMailbox0CompleteCallback来确认发送成功。一个阻塞式轮询等待的发送函数示例uint8_t sendCANMessage(uint32_t id, uint8_t* data, uint8_t len) { CAN_TxHeaderTypeDef TxHeader; uint32_t TxMailbox; // 1. 填充报文头 TxHeader.StdId id; // 标准ID TxHeader.ExtId 0; // 扩展ID标准帧时设为0 TxHeader.IDE CAN_ID_STD; // 标识符类型标准帧 TxHeader.RTR CAN_RTR_DATA; // 帧类型数据帧 TxHeader.DLC len; // 数据长度 TxHeader.TransmitGlobalTime DISABLE; // 2. 发送报文 if (HAL_CAN_AddTxMessage(hcan1, TxHeader, data, TxMailbox) ! HAL_OK) { return 0; // 发送失败 } // 3. 可选简单轮询等待发送完成超时处理 uint32_t tickstart HAL_GetTick(); while(HAL_CAN_GetTxMailboxesFreeLevel(hcan1) ! 3) // 等待3个邮箱都空闲 { if((HAL_GetTick() - tickstart) 100) // 超时100ms { return 0; } } return 1; // 发送成功 }在实际应用中更推荐使用非阻塞的中断方式进行发送。在CubeMX中使能CAN1_TX中断然后在发送函数中不进行轮询等待。发送请求提交后函数立即返回。当硬件真正发送完成时会触发CAN1_TX_IRQHandler进而调用对应的回调函数如HAL_CAN_TxMailbox0CompleteCallback你在回调函数里处理发送成功后的逻辑如释放资源、触发下一个发送等这样不会阻塞主程序。6. 双机通讯例程的完整框架与主循环设计一个完整的双机通讯例程需要两个STM32节点。它们的硬件连接相同软件配置也基本相同波特率、位时序必须完全一致。区别可能在于节点地址/ID规划每个节点应有自己唯一的发送ID以及需要监听的其他节点的ID。例如节点A发送ID为0x101接收ID为0x102节点B则相反。过滤器配置根据上述ID规划配置各自的接收过滤器。主程序的设计通常是一个超级循环while(1)里面包含状态机或定时任务。一个典型的结构是初始化HAL_Init,SystemClock_Config,MX_GPIO_Init,MX_CAN_Init,MX_USART1_Init用于调试打印等。配置过滤器并启动CAN同时使能接收中断HAL_CAN_ActivateNotification(hcan1, CAN_IT_RX_FIFO0_MSG_PENDING)。进入主循环定时发送使用HAL_Delay或硬件定时器周期性地如每100ms组织数据并调用发送函数。处理接收数据接收数据的处理在中断回调函数中完成主循环可以通过检查回调函数中设置的全局标志位来执行一些非实时或复杂的后续逻辑。状态指示与调试根据通讯状态控制LED闪烁或者通过串口打印关键信息如发送/接收计数、错误状态这对于调试至关重要。这里有一个关键点中断回调函数里做的事情要快。只做最必要的拷贝和设置标志位把耗时的处理如复杂的计算、格式化字符串打印放到主循环中基于标志位去执行。否则可能因为中断处理时间过长导致丢失后续报文或系统响应变慢。7. 调试技巧与常见问题排查CAN通讯调试光看代码不行必须借助工具。最常用的就是USB-CAN适配器如周立功、创芯科技等品牌的产品配合上位机软件如CANTest、CANPro、PCAN-View等。它能让你直观地看到总线上流动的所有报文包括ID、数据、帧类型这是定位问题最快的方法。以下是几个最常见的坑和排查思路问题一根本收不到任何报文。检查硬件这是第一步也是最容易出错的一步。用万用表测量CAN_H和CAN_L之间的电阻在总线两端都连接的情况下应该是60欧姆左右两个120欧并联。如果电阻是120欧或无穷大说明终端电阻没接或只接了一端。再检查TJA1050的电源、STM32与TJA1050之间的连接是否正确。检查波特率两个节点的波特率配置必须一字不差。用示波器测量CAN_H和CAN_L的差分信号计算位宽是最直接的方法。或者将两个节点都配置为环回模式CAN_MODE_LOOPBACK自己发自己收如果能通则说明软件配置和驱动电路基本没问题问题很可能出在总线连接或另一个节点上。检查过滤器确认是否因为过滤器设置过于严格把想要的报文过滤掉了。调试初期可以先将过滤器配置为接收所有报文掩码全0确保物理层和数据链路层是通的。检查中断是否使能确认HAL_CAN_ActivateNotification被调用且对应的中断向量表配置正确CubeMX通常已处理好。问题二能收到部分报文但时通时断或者出现错误帧。检查总线负载如果报文发送太频繁可能导致总线拥堵。CAN控制器有错误计数和自动离线管理功能错误过多会进入离线状态。可以通过HAL_CAN_GetError函数获取错误状态。检查电源与地线强烈的共模干扰会导致通讯错误。确保各个节点的电源干净并且共地良好。在干扰大的环境可以考虑使用带隔离的CAN收发器模块。查看错误计数器HAL库提供了HAL_CAN_GetError函数可以读取接收错误计数器REC和发送错误计数器TEC。如果它们持续增长说明总线存在物理问题或严重的仲裁失败。问题三数据内容不对。检查字节序这是最经典的坑。CAN报文数据场是8字节的数组对于多字节数据如int32_t, float发送方和接收方必须约定好字节序大端还是小端。通常的做法是约定小端模式即低字节在前。在上一节的接收示例中sensorValue的拼接方式就是小端。检查DLC确保发送方设置的DLC和实际拷贝的数据长度一致并且接收方按照DLC来解析数据。使用调试工具用USB-CAN适配器抓取总线上的原始报文对比发送的数据和接收的数据一眼就能看出是发送端数据组织错了还是接收端解析错了。8. 从基础例程到实际项目的进阶思考这个双机通讯例程跑通只是万里长征第一步。在实际项目中我们还需要考虑更多1. 协议设计裸的CAN帧只有ID和数据场你需要在上层定义一套应用层协议。例如如何区分命令帧、数据帧、应答帧如何实现多帧传输数据大于8字节如何加入校验如CRC常见的简易做法是利用ID的高位作为“帧类型”数据场的第一个字节作为“命令字”或“数据索引”。2. 错误处理与恢复增加对HAL_CAN_ErrorCallback回调函数的处理监控总线错误、被动错误、离线等状态。当检测到总线关闭时可以尝试执行HAL_CAN_ResetError然后重新初始化CAN外设实现自动恢复。3. 使用FreeRTOS等RTOS在复杂的系统中CAN通讯最好放在一个独立的RTOS任务中。接收中断通过队列Queue或信号量Semaphore将报文传递给处理任务发送也通过队列进行缓冲。这样可以避免在中断服务函数中处理复杂逻辑也使得发送非阻塞化系统架构更清晰。4. 性能优化对于高频发送使用三个发送邮箱并配合DMA如果支持可以显著提高效率。合理规划过滤器组减少软件过滤的开销。如果接收数据量很大确保接收FIFO的溢出中断被使能并妥善处理防止数据丢失。5. 代码封装将CAN的初始化、发送、接收回调处理封装成独立的模块.c/.h文件。对外提供清晰的接口如CAN_Init(),CAN_SendMsg(msg_t* msg),CAN_RegisterRxCallback(callback_func)。这样主程序代码会更简洁模块也更容易移植到其他项目。最后这个基于HAL库的例程最大的好处是跨STM32系列芯片的兼容性比较好。当你从F103切换到F4、F7甚至H7系列时CAN外设的基本操作和HAL API是一致的主要差异可能在于时钟配置和部分高级特性。掌握这个基础框架再去看ST官方更复杂的例程如CAN FD、CANopen等就会容易得多。本文还有配套的精品资源点击获取
返回列表