news 2026/1/31 2:10:24

简单理解:CAN总线通信详解配置流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
简单理解:CAN总线通信详解配置流程

汽车电子和工业控制的"神经系统"


第一部分:CAN总线核心概念

CAN是什么?

CAN(Controller Area Network)就像汽车的"神经系统":

  • 双线差分:CAN_H和CAN_L两根线,抗干扰强

  • 多主架构:任何节点都可以主动发送数据

  • 优先级仲裁:ID小的优先发送,不会冲突

  • 广播通信:一个发,所有节点都能收

CAN数据帧结构

| 帧起始 | ID(11/29位) | 控制场 | 数据场(0-8字节) | CRC | 应答 | 帧结束 |
  • 标准帧:11位ID,最多2032个不同ID

  • 扩展帧:29位ID,最多5.36亿个不同ID

  • 数据长度:0-8字节,短小高效


第二部分:代码分块详细解释

块1:CAN初始化配置

/* CAN初始化函数 - 配置CAN控制器基本参数 */ CAN_HandleTypeDef hcan1; // 定义CAN1的句柄,用于管理所有CAN配置 void CAN1_Init(void) { CAN_FilterTypeDef can_filter; // 定义过滤器结构体,用于筛选接收的消息 // 1. 使能CAN时钟 __HAL_RCC_CAN1_CLK_ENABLE(); // 打开CAN1的时钟,外设需要时钟才能工作 __HAL_RCC_GPIOA_CLK_ENABLE(); // 打开GPIOA时钟,CAN引脚在PA11、PA12 // 2. 配置CAN引脚 GPIO_InitTypeDef gpio_init; gpio_init.Pin = GPIO_PIN_11 | GPIO_PIN_12; // PA11: CAN_RX, PA12: CAN_TX gpio_init.Mode = GPIO_MODE_AF_PP; // 复用推挽输出模式 gpio_init.Pull = GPIO_NOPULL; // 不启用上下拉电阻 gpio_init.Speed = GPIO_SPEED_FREQ_HIGH; // 高速模式 gpio_init.Alternate = GPIO_AF9_CAN1; // 复用功能9对应CAN1 HAL_GPIO_Init(GPIOA, &gpio_init); // 应用GPIO配置 // 3. 配置CAN工作模式 hcan1.Instance = CAN1; // 使用CAN1控制器 hcan1.Init.Mode = CAN_MODE_NORMAL; // 正常工作模式 hcan1.Init.AutoBusOff = DISABLE; // 禁用自动离线恢复 hcan1.Init.AutoWakeUp = DISABLE; // 禁用自动唤醒 hcan1.Init.AutoRetransmission = ENABLE; // 启用自动重传 hcan1.Init.ReceiveFifoLocked = DISABLE; // 不锁定接收FIFO hcan1.Init.TransmitFifoPriority = DISABLE; // 禁用发送邮箱优先级 hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ; // 同步跳转宽度1个时间单元 hcan1.Init.TimeSeg1 = CAN_BS1_9TQ; // 时间段1为9个时间单元 hcan1.Init.TimeSeg2 = CAN_BS2_4TQ; // 时间段2为4个时间单元 hcan1.Init.Prescaler = 6; // 预分频值,决定波特率 // 计算波特率: APB1时钟=36MHz, 36MHz/(6*(1+9+4)) = 36MHz/(6 * 14) = 428.5kHz HAL_CAN_Init(&hcan1); // 应用CAN配置 // 4. 配置CAN过滤器 can_filter.FilterIdHigh = 0x0000; // 过滤器ID高16位 can_filter.FilterIdLow = 0x0000; // 过滤器ID低16位 can_filter.FilterMaskIdHigh = 0x0000; // 掩码高16位 can_filter.FilterMaskIdLow = 0x0000; // 掩码低16位 can_filter.FilterFIFOAssignment = CAN_RX_FIFO0; // 接收到的数据存到FIFO0 can_filter.FilterBank = 0; // 使用过滤器0 can_filter.FilterMode = CAN_FILTERMODE_IDMASK; // 标识符屏蔽位模式 can_filter.FilterScale = CAN_FILTERSCALE_32BIT; // 32位过滤器 can_filter.FilterActivation = ENABLE; // 启用过滤器 can_filter.SlaveStartFilterBank = 14; // 从过滤器库起始编号 HAL_CAN_ConfigFilter(&hcan1, &can_filter); // 应用过滤器配置 // 5. 启动CAN HAL_CAN_Start(&hcan1); // 启动CAN控制器 // 6. 使能接收中断 HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING); }

块2:CAN数据发送

/* 发送CAN数据帧 - 封装要发送的数据 */ uint8_t CAN_Send_Msg(uint32_t id, uint8_t *data, uint8_t len) { uint32_t mailbox; // 邮箱编号,CAN有3个发送邮箱 CAN_TxHeaderTypeDef tx_header; // 定义发送帧头结构体 // 1. 检查数据长度是否合法 if (len > 8) { // CAN帧最多8字节数据 return 1; // 返回1表示错误: 数据过长 } // 2. 配置发送帧头 tx_header.StdId = id; // 标准帧ID(11位),0x000-0x7FF tx_header.ExtId = 0; // 扩展帧ID,标准帧时为0 tx_header.IDE = CAN_ID_STD; // 标识符类型: 标准帧 tx_header.RTR = CAN_RTR_DATA; // 帧类型: 数据帧(不是远程帧) tx_header.DLC = len; // 数据长度码,0-8字节 tx_header.TransmitGlobalTime = DISABLE; // 不发送全局时间 // 3. 将数据加入发送邮箱 // HAL_CAN_AddTxMessage参数: // &hcan1: CAN句柄 // &tx_header: 帧头配置 // data: 要发送的数据数组 // &mailbox: 返回使用的邮箱号(0,1,2) if (HAL_CAN_AddTxMessage(&hcan1, &tx_header, data, &mailbox) != HAL_OK) { return 2; // 返回2表示错误: 加入邮箱失败 } // 4. 等待发送完成 // 轮询检查发送完成标志 while (HAL_CAN_IsTxMessagePending(&hcan1, mailbox)); return 0; // 返回0表示发送成功 }

块3:CAN数据接收(中断方式)

/* CAN接收中断回调函数 - 收到数据时自动调用 */ uint8_t rx_data[8]; // 接收数据缓冲区 uint32_t rx_id; // 接收到的ID uint8_t rx_len; // 接收到的数据长度 CAN_RxHeaderTypeDef rx_header; // 接收帧头 // 中断回调函数 void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { // 1. 从FIFO0读取一帧数据 // HAL_CAN_GetRxMessage参数: // hcan: CAN句柄 // CAN_RX_FIFO0: 从FIFO0读取 // &rx_header: 存储帧头信息 // rx_data: 存储数据 HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data); // 2. 获取接收到的信息 if (rx_header.IDE == CAN_ID_STD) { // 判断是标准帧 rx_id = rx_header.StdId; // 获取标准ID } else { // 扩展帧 rx_id = rx_header.ExtId; // 获取扩展ID } rx_len = rx_header.DLC; // 获取数据长度 // 3. 根据ID处理不同数据(示例) switch (rx_id) { case 0x100: // ID 0x100: 转速信息 Process_Engine_Speed(rx_data); // 处理发动机转速 break; case 0x200: // ID 0x200: 温度信息 Process_Temperature(rx_data); // 处理温度数据 break; case 0x300: // ID 0x300: 开关状态 Process_Switch_Status(rx_data); // 处理开关状态 break; default: // 其他ID Process_Other_Msg(rx_data); // 处理其他消息 break; } }

块4:CAN数据接收(轮询方式)

/* 轮询方式接收CAN数据 - 在主循环中调用 */ uint8_t CAN_Receive_Msg(uint8_t *data, uint32_t *id) { CAN_RxHeaderTypeDef rx_header; // 接收帧头 // 1. 检查FIFO0是否有新消息 if (HAL_CAN_GetRxFifoFillLevel(&hcan1, CAN_RX_FIFO0) > 0) { // 2. 从FIFO0读取消息 if (HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &rx_header, data) == HAL_OK) { // 3. 获取ID if (rx_header.IDE == CAN_ID_STD) { *id = rx_header.StdId; // 标准帧ID } else { *id = rx_header.ExtId; // 扩展帧ID } return rx_header.DLC; // 返回数据长度 } } return 0; // 返回0表示没有数据 }

块5:应用示例 - 汽车转速发送

/* 发送发动机转速到CAN总线 */ void Send_Engine_Speed(uint16_t rpm) { uint8_t can_data[8]; // CAN数据缓冲区 uint8_t data_len = 2; // 数据长度: 2字节 // 1. 准备要发送的数据 // 将16位转速拆分成2个字节 can_data[0] = (rpm >> 8) & 0xFF; // 高字节 can_data[1] = rpm & 0xFF; // 低字节 // 2. 填充其余字节为0(可选) for (uint8_t i = data_len; i < 8; i++) { can_data[i] = 0x00; // 未使用的字节填0 } // 3. 发送CAN消息 // ID: 0x100 表示发动机数据 // 数据: can_data数组 // 长度: data_len if (CAN_Send_Msg(0x100, can_data, data_len) == 0) { // 发送成功 LED_Toggle(); // 可添加LED指示发送成功 } else { // 发送失败处理 Error_Handler(); } }

第三部分:CAN通信完整示例

/* 文件: can_communication.c * 功能: 完整的CAN通信示例,包含发送和接收 * 硬件: STM32F103 + TJA1050 CAN收发器 * 功能: 模拟汽车ECU通信 */ #include "main.h" #include "can.h" #include "stdio.h" // 全局变量 CAN_HandleTypeDef hcan1; uint8_t tx_data[8], rx_data[8]; uint32_t tx_id = 0x100, rx_id = 0; uint8_t tx_len = 8, rx_len = 0; int main(void) { // 1. 初始化系统 HAL_Init(); // 初始化HAL库 SystemClock_Config(); // 配置系统时钟 MX_GPIO_Init(); // 初始化GPIO MX_CAN1_Init(); // 初始化CAN1 // 2. 启动CAN和中断 HAL_CAN_Start(&hcan1); HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING); // 3. 主循环 while (1) { // 3.1 发送引擎转速(示例) static uint16_t engine_rpm = 0; engine_rpm = (engine_rpm + 10) % 8000; // 模拟转速变化 uint8_t data[2]; data[0] = (engine_rpm >> 8) & 0xFF; data[1] = engine_rpm & 0xFF; CAN_Send_Msg(0x100, data, 2); // 发送转速 // 3.2 轮询接收其他ECU数据 rx_len = CAN_Receive_Msg(rx_data, &rx_id); if (rx_len > 0) { // 处理接收到的数据 Process_Received_Data(rx_id, rx_data, rx_len); } // 3.3 延时 HAL_Delay(100); // 100ms发送一次 } } /* CAN接收中断回调函数 */ void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef rx_header; // 读取接收到的消息 HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data); // 获取ID rx_id = (rx_header.IDE == CAN_ID_STD) ? rx_header.StdId : rx_header.ExtId; rx_len = rx_header.DLC; // 根据ID处理(示例) switch (rx_id) { case 0x200: // 车速信息 uint16_t speed = (rx_data[0] << 8) | rx_data[1]; printf("车速: %d km/h\n", speed); break; case 0x201: // 水温信息 uint8_t temp = rx_data[0]; printf("水温: %d C\n", temp); if (temp > 100) { Send_Warning(0x01); // 发送高温警告 } break; default: break; } }

第四部分:CAN通信核心要点总结

1. CAN配置步骤记忆口诀

"时钟引脚先使能,波特率要算精准 模式参数配置好,过滤器是守门人 启动中断别忘了,收发数据靠轮询"

2. 波特率计算公式

CAN波特率 = APB1时钟 / (预分频 * 时间单元总数) 时间单元总数 = 1(同步段) + BS1 + BS2 例如: APB1时钟 = 36MHz 预分频 = 6 BS1 = 9, BS2 = 4 波特率 = 36MHz / (6 * (1+9+4)) = 428.5kbps

3. 过滤器配置模式

模式

作用

应用场景

标识符列表模式

精确匹配几个ID

只接收特定ID的消息

标识符屏蔽位模式

按位过滤ID

接收某一类ID的消息

4. 常见问题排查

现象

可能原因

解决方法

CAN发送失败

波特率不匹配

检查两端波特率设置

接收不到数据

过滤器配置错误

检查过滤器ID和掩码

错误帧太多

终端电阻未接

CAN_H和CAN_L加120Ω电阻

通信不稳定

线路干扰

使用双绞线,远离干扰源

发送邮箱满

发送太快

增加发送间隔或检查发送完成标志

5. 实际应用技巧

// 技巧1: 快速计算DLC对应的数据长度 // CAN的DLC编码特殊,>8有特殊含义 uint8_t get_data_length(uint8_t dlc) { if (dlc <= 8) return dlc; // 对于CAN FD,DLC编码不同 return 8 + (dlc - 8) * 4; } // 技巧2: 扩展帧发送 void send_extended_frame(uint32_t ext_id, uint8_t* data, uint8_t len) { CAN_TxHeaderTypeDef tx_header; tx_header.ExtId = ext_id; // 29位扩展ID tx_header.IDE = CAN_ID_EXT; // 扩展帧 // ... 其他配置相同 } // 技巧3: 远程帧请求 void send_remote_frame(uint32_t id) { CAN_TxHeaderTypeDef tx_header; tx_header.StdId = id; tx_header.RTR = CAN_RTR_REMOTE; // 远程帧 tx_header.DLC = 0; // 远程帧数据长度为0 // ... 发送配置 }

6. 学习建议

  1. 从标准帧开始:先掌握11位ID的标准帧

  2. 用逻辑分析仪:抓取CAN波形,理解帧结构

  3. 分步调试:先调通发送,再调接收

  4. 模拟多节点:用两块开发板互相通信

  5. 参考真实协议:学习CANopen、J1939等上层协议

记住:CAN总线是优先级仲裁的,不是主从式的。任何节点都可以在总线空闲时发送,ID小的优先级高。这是CAN与I2C、SPI最大的区别!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/27 13:56:11

AI分类器性能对比:云端T4 vs 本地RTX3060实测

AI分类器性能对比&#xff1a;云端T4 vs 本地RTX3060实测 1. 引言&#xff1a;为什么要做这个对比测试&#xff1f; 作为一名AI硬件爱好者&#xff0c;我经常被问到一个问题&#xff1a;到底是自己花大价钱买高端显卡划算&#xff0c;还是直接租用云GPU服务更实惠&#xff1f…

作者头像 李华
网站建设 2026/1/27 23:42:21

微服务分布式SpringBoot+Vue+Springcloud的电商用户行为分析系统_

目录微服务架构下的电商用户行为分析系统核心功能模块设计分布式计算与实时分析可视化交互界面系统技术优势开发技术源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;微服务架构下的电商用户行为分析系统 该系统基于SpringBoot、Vue.js和…

作者头像 李华
网站建设 2026/1/28 7:13:19

Rembg抠图在短视频制作中的应用实战

Rembg抠图在短视频制作中的应用实战 1. 引言&#xff1a;智能万能抠图 - Rembg 在短视频内容爆发式增长的今天&#xff0c;高效、高质量的视觉素材处理能力已成为创作者的核心竞争力之一。无论是更换背景、合成特效&#xff0c;还是制作动态贴纸&#xff0c;精准抠图都是不可…

作者头像 李华
网站建设 2026/1/30 5:29:54

Rembg抠图实战:食品包装图片处理

Rembg抠图实战&#xff1a;食品包装图片处理 1. 引言 1.1 业务场景描述 在电商、广告设计和产品展示领域&#xff0c;高质量的图像去背景是提升视觉表现力的关键环节。尤其对于食品包装图片而言&#xff0c;精准抠图不仅能突出产品卖点&#xff0c;还能灵活应用于多平台宣传…

作者头像 李华
网站建设 2026/1/31 4:49:14

集成Cyberpunk风格界面|AI智能实体侦测服务让NER更直观

集成Cyberpunk风格界面&#xff5c;AI智能实体侦测服务让NER更直观 在信息爆炸的时代&#xff0c;非结构化文本中蕴藏着海量关键信息——人名、地名、机构名等命名实体&#xff08;Named Entity&#xff09;是构建知识图谱、舆情分析、智能客服等系统的基石。然而&#xff0c;…

作者头像 李华
网站建设 2026/1/30 11:13:07

医疗数据用Polars处理快又稳

&#x1f4dd; 博客主页&#xff1a;jaxzheng的CSDN主页 医疗数据高效处理新范式&#xff1a;Polars如何实现快与稳 目录 医疗数据高效处理新范式&#xff1a;Polars如何实现快与稳 目录 引言 为什么选择Polars&#xff1f;技术优势与医疗场景的精准匹配 实战案例&#xff1a;电…

作者头像 李华