1. CAN总线通信基础与项目背景CAN总线Controller Area Network是工业控制领域最常用的现场总线之一特别适合汽车电子和工业自动化场景。它采用差分信号传输抗干扰能力强最多可支持110个节点同时通信。在实际项目中我们经常需要让STM32通过CAN总线与上位机进行双向数据交互比如远程控制机械臂时上位机发送指令给STM32同时STM32需要实时反馈传感器数据。我最近完成的一个AGV小车项目就采用了这种架构。STM32F407作为下位机通过CAN接收上位机的运动指令同时将编码器数据和红外避障信号上传给上位机做路径规划。刚开始开发时我在HAL库的配置和中断处理上踩了不少坑后来摸索出一套稳定可靠的实现方案。下面就把关键代码和配置经验分享给大家。2. 硬件准备与CubeMX配置2.1 硬件连接要点我用的是STM32F407 Discovery开发板自带CAN收发器。如果使用其他开发板需要外接TJA1050之类的CAN收发芯片。硬件连接要注意CAN_H和CAN_L需使用双绞线终端接120Ω电阻确保上位机端通常是USB-CAN适配器的波特率与STM32设置一致调试阶段建议同时连接串口方便打印调试信息2.2 CubeMX关键配置步骤打开CubeMX新建工程后按顺序配置时钟树确保APB1时钟为42MHzCAN时钟源CAN配置工作模式选择Normal波特率分频设置为500Kbps36MHz/(952)开启接收FIFO0中断GPIO配置配置用户按键PE4用于触发发送配置LED灯PE5发送指示灯串口配置启用USART1异步模式波特率115200用于打印接收数据配置完成后生成代码时记得勾选Generate peripheral initialization as a pair of .c/.h files选项这样CAN的初始化代码会单独放在can.c文件中方便后续维护。3. CAN发送功能实现3.1 发送数据结构体封装在can.h中定义发送结构体和函数原型typedef struct { uint32_t StdId; // 标准ID uint32_t ExtId; // 扩展ID uint32_t IDE; // 标识符类型 uint32_t RTR; // 帧类型 uint8_t DLC; // 数据长度 uint8_t Data[8]; // 数据域 } CAN_TxMsg; void CAN_SendMessage(CAN_TxMsg *msg);在can.c中实现发送函数void CAN_SendMessage(CAN_TxMsg *msg) { CAN_TxHeaderTypeDef txHeader; uint32_t txMailbox; txHeader.StdId msg-StdId; txHeader.ExtId msg-ExtId; txHeader.IDE msg-IDE; txHeader.RTR msg-RTR; txHeader.DLC msg-DLC; txHeader.TransmitGlobalTime DISABLE; if(HAL_CAN_AddTxMessage(hcan, txHeader, msg-Data, txMailbox) ! HAL_OK) { Error_Handler(); } }3.2 按键触发发送实现在main.c的while循环中添加按键检测逻辑while (1) { if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) GPIO_PIN_RESET) { HAL_Delay(50); // 消抖 if(HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) GPIO_PIN_RESET) { CAN_TxMsg txMsg { .StdId 0x123, .ExtId 0x028900F0, .IDE CAN_ID_EXT, .RTR CAN_RTR_DATA, .DLC 8, .Data {0x00,0x04,0x93,0xE0,0x00,0x00,0x27,0x10} }; HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET); CAN_SendMessage(txMsg); HAL_Delay(100); HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); } } }4. CAN接收功能实现4.1 过滤器配置技巧CAN总线上的数据帧很多需要通过过滤器筛选需要的帧。在can.c中添加过滤器配置函数void CAN_Filter_Config(void) { CAN_FilterTypeDef filter; filter.FilterBank 0; filter.FilterMode CAN_FILTERMODE_IDMASK; filter.FilterScale CAN_FILTERSCALE_32BIT; filter.FilterIdHigh 0x0000; filter.FilterIdLow 0x0000; filter.FilterMaskIdHigh 0x0000; filter.FilterMaskIdLow 0x0000; filter.FilterFIFOAssignment CAN_FilterFIFO0; filter.FilterActivation ENABLE; HAL_CAN_ConfigFilter(hcan, filter); }4.2 中断接收回调函数在stm32f4xx_it.c中重写接收中断回调函数void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef rxHeader; uint8_t rxData[8]; if(HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, rxHeader, rxData) HAL_OK) { // 通过串口打印接收到的数据 char buf[64]; sprintf(buf, ID:0x%08X DLC:%d Data:%02X %02X %02X %02X %02X %02X %02X %02X\r\n, rxHeader.ExtId, rxHeader.DLC, rxData[0], rxData[1], rxData[2], rxData[3], rxData[4], rxData[5], rxData[6], rxData[7]); HAL_UART_Transmit(huart1, (uint8_t*)buf, strlen(buf), HAL_MAX_DELAY); } }4.3 主函数初始化在main函数中完成CAN初始化和启动int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_CAN1_Init(); MX_USART1_UART_Init(); CAN_Filter_Config(); HAL_CAN_Start(hcan); HAL_CAN_ActivateNotification(hcan, CAN_IT_RX_FIFO0_MSG_PENDING); while (1) { // 主循环 } }5. 调试技巧与常见问题5.1 使用逻辑分析仪抓包当通信不正常时建议用逻辑分析仪抓取CAN总线波形检查波形幅度是否正常显性电平约2V隐性电平约2.5V波特率是否准确数据帧结构是否正确5.2 常见错误排查无法接收到数据检查过滤器配置是否过于严格确认终端电阻是否连接测量CAN总线差分电压发送失败检查HAL_CAN_GetTxMailboxesFreeLevel返回值确认CAN控制器是否进入Bus-Off状态查看CAN错误计数器HAL_CAN_GetError数据错乱检查发送和接收端的字节序是否一致确认DLC设置与实际数据长度匹配5.3 性能优化建议对于高频率通信场景如100Hz以上在接收回调函数中避免耗时操作使用DMA传输代替查询方式适当提高CAN总线波特率最高1Mbps合理设置过滤器减少不必要的中断我在实际项目中发现当发送频率超过500Hz时需要在接收回调函数中使用环形缓冲区暂存数据然后在主循环中处理否则会出现数据丢失现象。具体实现可以参考FreeRTOS的消息队列机制。