--- tags: [source-summary] type: source source: "尚硅谷嵌入式技术之STM32单片机(高级篇)V2.0.1 — 第1章:CAN + 配套代码01~04" author: "尚硅谷研究院" date: 2026-07-15 created: 2026-07-15 --- # CAN通信协议与bxCAN外设 > **用生活理解**:CAN 总线就像一场没有主持人的圆桌会议——谁都可以发言(多主),但发言时先报自己的"优先级数字"(ID)。数字小的优先级高,即使两个人同时开口,优先级低的那位会自动闭嘴让优先级高的先说。这就是 CAN 的**无损仲裁**——不会因为冲突而浪费任何数据。 --- ## CAN 协议基础 CAN = Controller Area Network(控制器局域网),由 Bosch 公司 1986 年发明,主要用于汽车电子通信。 | 特性 | 说明 | | -------- | -------------------------------------- | | 总线类型 | **多主从、广播式**(所有节点同时接收) | | 物理层 | 差分信号(CAN_H / CAN_L),抗干扰强 | | 速率 | 最高 1Mbps(40m 以内) | | 距离 | 5Kbps 时可达 10km | | 仲裁 | 无损位仲裁(ID 小的优先级高) | | 可靠性 | CRC 校验 + 错误重传 + 错误节点自动脱离 | > **参考**:https://www.kvaser.cn/about-can/can-protocol-tutorial/(CAN 协议教程中文版) ### 物理层 — 差分信号 ``` CAN_H = 3.5V CAN_L = 1.5V → 显性 = 逻辑 0 CAN_H = 2.5V CAN_L = 2.5V → 隐性 = 逻辑 1 显性电平会"覆盖"隐性电平(仲裁机制的基础) ``` 总线两端需要各接一个 **120Ω 终端电阻**(匹配阻抗,防止信号反射)。 ### 协议层 — 5 种帧类型 | 帧类型 | 用途 | 软件控制? | | ---------- | ------------------------------ | ----------- | | **数据帧** | 发送数据 | ✅ | | **远程帧** | 请求具有相同 ID 的节点发送数据 | ✅ | | 错误帧 | 检测到错误时通知所有节点 | ❌ 硬件自动 | | 过载帧 | 接收方未准备好时延迟发送 | ❌ 硬件自动 | | 帧间隔 | 分隔数据帧/远程帧 | ❌ 硬件自动 | ### 数据帧结构(标准格式 11 位 ID) ``` ┌──┬──────────┬───┬───┬──┬────┬──────────┬─────────────┬───────┬──┬──────────┐ │SOF│ ARBITRATION │CTRL│DATA(0~64)│ CRC │ACK│ EOF │ │ 1 │ ID 11+RTR1│ 6 │ 0~8B │ 15+1(del)│ 2 │ 7 │ └──┴──────────┴───┴───┴──┴────┴──────────┴─────────────┴───────┴──┴──────────┘ ``` **逐段详解**: | 字段 | 位数 | 说明 | | -------- | ---- | ------------------------------------------------------------------ | | **SOF** | 1 | 帧起始,显性电平(0),同步所有节点 | | **ID** | 11 | 标识符,决定优先级(值越小优先级越高) | | **RTR** | 1 | 0=数据帧,1=远程帧 | | **IDE** | 1 | 0=标准格式(11位ID),1=扩展格式(29位ID) | | **r0** | 1 | 保留位,显性 | | **DLC** | 4 | 数据长度(0~8),单位字节 | | **DATA** | 0~64 | 实际数据(0~8 字节,DLC 指定长度) | | **CRC** | 15 | 循环冗余校验(含 CRC 界定符 1 位) | | **ACK** | 2 | 应答:ACK 槽 + ACK 界定符。接收节点在 ACK 槽拉低(显性)= 正常收到 | | **EOF** | 7 | 帧结束,全部隐性 | **扩展格式**:29 位 ID(11 位基本 ID + 18 位扩展 ID),IDE=1。 ### CAN 总线仲裁 **仲裁原则**:谁先发 SOF → 谁先发 ID → ID 小的持续发显性(0) → ID 大的在隐性位(1)时丢失仲裁 → 自动转为接收。 **示例(两个节点同时发送)**: ``` 节点A: ID = 0x066 (二进制 000 0110 0110) 节点B: ID = 0x077 (二进制 000 0111 0111) 仲裁过程: 位1-5: A=00001, B=00001 → 相同,继续 位6: A=1(隐性), B=1(隐性) → 相同,继续 位7: A=0(显性), B=1(隐性) → A 输出显性,B 输出隐性 总线被 A 拉为显性 → B 读到总线和自身不同 → B 失去仲裁 立即停止发送,转为接收 结果: 节点A 胜出,继续发送。节点B 等总线空闲自动重试。 ``` ### CAN 的位时序 ``` 1 位 = Tq × (SYNC_SEG + BS1 + BS2) = Tq × (1 + TS1[3:0] + TS2[2:0]) ``` STM32 把传播时间段合并到 BS1: ``` ┌─────── SYNC_SEG ──────┐ │ BS1(传播+相位缓冲1) │ │ BS2 │ └───────────────────────┘ ``` **波特率计算**: ``` Baud = 1 / 位时间 = fAPB / BRP / (1 + BS1 + BS2) fAPB = 36MHz (CAN1 在 APB1) BRP = 35 → Tq = (35+1) / 36MHz = 1us BS1 = 3 Tq, BS2 = 5 Tq → 位 = (1+3+5) × 1us = 9us Baud = 1/9us ≈ 111Kbps ``` --- ## STM32 bxCAN 外设 bxCAN = Basic Extended CAN,支持 CAN 2.0A(标准帧)和 2.0B Active(标准+扩展帧)。 ### 主要特性 | 特性 | 值 | | ------------- | -------------------------------------- | | 支持标准 | CAN 2.0A + 2.0B Active | | 最高速率 | 1Mbps | | **发送邮箱** | **3 个**(每个可缓存 1 帧) | | **接收 FIFO** | **2 个**(每个 3 级深度,共 6 帧缓存) | | **过滤器组** | **14 个**(0~13) | | DMA | ❌ 不支持 | | 自动重发 | ✅ 可配置 | ### bxCAN 功能框图 ``` 主动内核(配置/控制/状态寄存器) ↓ 发送邮箱[0~2] → CAN 内核(发送调度) → Tx 引脚 ↓ 接收滤波器(14组) ← CAN 内核(接收) ← Rx 引脚 ↓ 接收 FIFO[0~1] (各3级深度) → CPU 通过寄存器读取 ``` ### 3 种工作模式 | 模式 | 如何进入 | 特点 | | ---------- | --------------------------- | ----------------------------------- | | **初始化** | MCR.INRQ=1, 等待 MSR.INAK=1 | 可配置寄存器(波特率、过滤器等) | | **正常** | MCR.INRQ=0, 等待 MSR.INAK=0 | 正常收发 | | **睡眠** | MCR.SLEEP=1 | 低功耗,检测到 CAN 总线活动自动唤醒 | ### 3 种测试模式 | 模式 | BTR 配置 | 用途 | | ------------ | --------------- | -------------------------------------------------- | | **静默** | SILM=1 | Rx 正常 → 可监听总线但不发送("只听不说的第三者") | | **环回** | LBKM=1 | Tx 内部回连到 Rx → 自收发不影响总线 | | **环回静默** | SILM=1 + LBKM=1 | 自收发且不影响总线(推荐调试用) | ### 接收过滤器 14 个过滤器组(0~13),每组由 2 个 32 位寄存器(FR1, FR2)组成。 | 模式 | 说明 | 类比 | | -------------------- | ---------------------------------------------- | -------------- | | **掩码模式**(32位) | FR1=ID, FR2=掩码。掩码位=1 必须匹配,=0 不关心 | 模糊搜索 | | **列表模式**(32位) | FR1 和 FR2 各存一个 ID,精确匹配 | 白名单 | | **掩码模式**(16位) | FR1 存 2 个 ID,FR2 存 2 个掩码 | 双通道模糊搜索 | | **列表模式**(16位) | FR1 和 FR2 各存 2 个 ID,共 4 个精确匹配 | 4 个白名单 | > **参考**:参考手册 §22(bxCAN 寄存器描述) --- ## 软件设计(寄存器版 — 环回静默测试) **项目路径**:`stm32/01_can_test_register` **文件:`stm32/01_can_test_register/Hardware/CAN/can.h`** ```c /* * @Author: wushengran * @Date: 2024-12-21 16:52:20 * @Description: * * Copyright (c) 2024 by atguigu, All Rights Reserved. */ #ifndef __CAN_H #define __CAN_H #include "stm32f10x.h" // 接收数据结构,保存接收到的报文信息 typedef struct { uint16_t stdID; uint8_t data[8]; uint8_t len; } RxMsg; // 初始化 void CAN_Init(void); // 发送报文 void CAN_SendMsg(uint16_t stdID, uint8_t * data, uint8_t len); // 接收报文 void CAN_ReceiveMsg(RxMsg rxMsg[], uint8_t * msgCount); #endif ``` **文件:`stm32/01_can_test_register/Hardware/CAN/can.c`** ```c /* * @Author: wushengran * @Date: 2024-12-21 16:52:16 * @Description: * * Copyright (c) 2024 by atguigu, All Rights Reserved. */ #include "can.h" static void CAN_FilterConfig(void); // 初始化 void CAN_Init(void) { // 1. 开启时钟:CAN、GPIO和AFIO RCC->APB1ENR |= RCC_APB1ENR_CAN1EN; RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; // 2. 重映射PB8和PB9 AFIO->MAPR |= AFIO_MAPR_CAN_REMAP_1; AFIO->MAPR &= ~AFIO_MAPR_CAN_REMAP_0; // 3. GPIO配置模式: // PB8 - 浮空输入,MODE 00,CNF 01 GPIOB->CRH &= ~GPIO_CRH_MODE8; GPIOB->CRH &= ~GPIO_CRH_CNF8_1; GPIOB->CRH |= GPIO_CRH_CNF8_0; // PB9 - 复用推挽输出,MODE 11,CNF 10 GPIOB->CRH |= GPIO_CRH_MODE9; GPIOB->CRH |= GPIO_CRH_CNF9_1; GPIOB->CRH &= ~GPIO_CRH_CNF9_0; // 4. CAN 初始化配置 // 4.1 进入初始化模式 CAN1->MCR |= CAN_MCR_INRQ; while ((CAN1->MSR & CAN_MSR_INAK) == 0) {} // 4.2 退出睡眠模式 CAN1->MCR &= ~CAN_MCR_SLEEP; while ((CAN1->MSR & CAN_MSR_SLAK) != 0) {} // 4.3 自动离线管理 CAN1->MCR |= CAN_MCR_ABOM; // 4.4 自动唤醒管理 CAN1->MCR |= CAN_MCR_AWUM; // 4.5 设置环回静默模式 CAN1->BTR |= CAN_BTR_SILM; CAN1->BTR |= CAN_BTR_LBKM; // 4.6 设置位时序 // 4.6.1 设置波特率分频系数:35,Tq = 1us CAN1->BTR &= ~CAN_BTR_BRP; CAN1->BTR |= (35 << 0); // 4.6.2 设置BS1和BS2时间长度 CAN1->BTR &= ~CAN_BTR_TS1; CAN1->BTR |= (2 << 16); CAN1->BTR &= ~CAN_BTR_TS2; CAN1->BTR |= (5 << 20); // 4.6.3 配置同步跳转宽度 CAN1->BTR &= ~CAN_BTR_SJW; CAN1->BTR |= (1 << 24); // 4.7 退出初始化模式 CAN1->MCR &= ~CAN_MCR_INRQ; while ((CAN1->MSR & CAN_MSR_INAK) != 0) {} // 5. CAN过滤器配置 CAN_FilterConfig(); } // 定义静态过滤器配置函数 static void CAN_FilterConfig(void) { // 1. 进入初始化模式 CAN1->FMR |= CAN_FMR_FINIT; // 2. 设置过滤器组工作模式:0 - 掩码位模式 CAN1->FM1R &= ~CAN_FM1R_FBM0; // 3. 设置位宽:1 - 32位 CAN1->FS1R |= CAN_FS1R_FSC0; // 4. 设置过滤器关联FIFO:FIFO0 CAN1->FFA1R &= ~CAN_FFA1R_FFA0; // 5. 设置过滤器组0的ID寄存器:FR1 CAN1->sFilterRegister[0].FR1 = 0x06e << 21; // 6. 设置过滤器组0的掩码寄存器:FR2 = 0,所有位都匹配 CAN1->sFilterRegister[0].FR2 = 0x7f1 << 21; // 7. 激活过滤器组0 CAN1->FA1R |= CAN_FA1R_FACT0; // 8. 退出初始化模式 CAN1->FMR &= ~CAN_FMR_FINIT; } // 发送报文:标准ID、数据、长度,使用发送邮箱0 void CAN_SendMsg(uint16_t stdID, uint8_t * data, uint8_t len) { // 1. 等待发送邮箱0为空 while ( (CAN1->TSR & CAN_TSR_TME0) == 0) { } // 2. 组装要发送的数据帧 // 2.1 设置标准ID CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_STID; CAN1->sTxMailBox[0].TIR |= stdID << 21; // 2.2 设为标准帧 CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_IDE; // 2.3 设为数据帧 CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_RTR; // 2.4 设置数据长度 CAN1->sTxMailBox[0].TDTR &= ~CAN_TDT0R_DLC; CAN1->sTxMailBox[0].TDTR |= len << 0; // 2.5 写入数据 // 清空寄存器 CAN1->sTxMailBox[0].TDLR = 0; CAN1->sTxMailBox[0].TDHR = 0; // 循环写入每一个字节 for (uint8_t i = 0; i < len; i++) { // 判断前4个字节,放到TDLR if (i < 4) { CAN1->sTxMailBox[0].TDLR |= data[i] << (i * 8); } // 后4个字节,放到TDHR else { CAN1->sTxMailBox[0].TDHR |= data[i] << ((i - 4) * 8); } } // 3. 请求发送该帧 CAN1->sTxMailBox[0].TIR |= CAN_TI0R_TXRQ; // 4. 等待发送完成 while ((CAN1->TSR & CAN_TSR_TXOK0) == 0) { } } // 接收报文:结构体数组,数组长度,从FIFO0读取 void CAN_ReceiveMsg(RxMsg rxMsg[], uint8_t * msgCount) { // 1. 获取FIFO0的报文个数,通过指针返回 * msgCount = (CAN1->RF0R & CAN_RF0R_FMP0) >> 0; // 2. 循环取出每一个报文 for (uint8_t i = 0; i < *msgCount; i++) { // 定义指针,指向当前保存报文的数据对象 RxMsg * msg = &rxMsg[i]; // 2.1 获取ID msg->stdID = (CAN1->sFIFOMailBox[0].RIR >> 21) & 0x7ff; // 2.2 获取数据长度 msg->len = (CAN1->sFIFOMailBox[0].RDTR >> 0) & 0x0f; // 2.3 获取数据 uint32_t low = CAN1->sFIFOMailBox[0].RDLR; uint32_t high = CAN1->sFIFOMailBox[0].RDHR; for (uint8_t j = 0; j < msg->len; j++) { // 如果是前4个字节,就从RDLR读取 if (j < 4) { msg->data[j] = (low >> (8 * j)) & 0xff; } // 如果是后4个字节,就从RDHR读取 else { msg->data[j] = (high >> (8 * (j - 4))) & 0xff; } } // 2.4 释放FIFO0的邮箱,以便读取下一个报文 CAN1->RF0R |= CAN_RF0R_RFOM0; } } ``` **文件:`stm32/01_can_test_register/User/main.c`** ```c /* * @Author: wushengran * @Date: 2024-09-13 16:29:39 * @Description: * * Copyright (c) 2024 by atguigu, All Rights Reserved. */ #include "usart.h" #include "can.h" #include int main(void) { // 初始化 USART_Init(); CAN_Init(); printf("尚硅谷CAN通讯实验:环回静默模式测试,寄存器版...\n"); // 1. 发送三帧数据 uint16_t stdID = 0x066; uint8_t * data = "abcdefg"; CAN_SendMsg(stdID, data, strlen((char *)data)); stdID = 0x068; data = "123"; CAN_SendMsg(stdID, data, strlen((char *)data)); stdID = 0x067; data = "xyz"; CAN_SendMsg(stdID, data, strlen((char *)data)); // 2. 开始接收 RxMsg rxMsg[3]; uint8_t msgCount; CAN_ReceiveMsg(rxMsg, &msgCount); printf("报文接收完毕,count = %d\n", msgCount); // 3. 打印接收到的数据 for (uint8_t i = 0; i < msgCount; i++) { printf("stdID = %#X, len = %d, data = %.*s\n", rxMsg[i].stdID, rxMsg[i].len, rxMsg[i].len, rxMsg[i].data); } while (1) { } } ``` --- ## 软件设计(寄存器版 — 双机收发通信) **项目路径**:`stm32/03_can_tx_register`(发送节点)、`stm32/03_can_rx_register`(接收节点) 与 01 项目相比,关键差别: - **不设置环回静默模式**(SILM=0, LBKM=0 → 正常模式) - **过滤器掩码 FR2=0x00**(接受所有报文,而非只匹配 0x06e) **can.c 差异(vs 01)**: ```c // 03 项目注释掉了环回静默配置(can.c:52-54) // CAN1->BTR |= CAN_BTR_SILM; // CAN1->BTR |= CAN_BTR_LBKM; // 03 项目过滤器掩码设为 0(接受所有 ID) CAN1->sFilterRegister[0].FR2 = 0x00; // 01 项目为 0x7f1 << 21 ``` **文件:`stm32/03_can_tx_register/User/main.c`** — 发送节点,每 1s 发一帧 ```c #include "delay.h" #include "usart.h" #include "can.h" #include int main(void) { USART_Init(); CAN_Init(); printf("尚硅谷CAN通讯实验:双机收发测试 - 发送节点,寄存器版...\n"); uint16_t stdID = 0x066; uint8_t * data = "abc"; uint8_t buffer[10]; uint32_t i = 0; while (1) { sprintf((char *)buffer, "%s %d", data, ++i); CAN_SendMsg(stdID, buffer, strlen((char *)buffer)); printf("报文发送完毕...\n"); Delay_ms(1000); } } ``` **文件:`stm32/03_can_rx_register/User/main.c`** — 接收节点,持续轮询 ```c #include "usart.h" #include "can.h" #include int main(void) { USART_Init(); CAN_Init(); printf("尚硅谷CAN通讯实验:双机收发测试 - 接收节点,寄存器版...\n"); RxMsg rxMsg[3]; uint8_t msgCount; while (1) { CAN_ReceiveMsg(rxMsg, &msgCount); for (uint8_t i = 0; i < msgCount; i++) { printf("stdID = %#X, len = %d, data = %.*s\n", rxMsg[i].stdID, rxMsg[i].len, rxMsg[i].len, rxMsg[i].data); } } } ``` --- ## 软件设计(HAL 库版 — 环回静默测试) **项目路径**:`stm32/02_can_test_hal` CubeMX 生成 MX_CAN_Init,用户在 can.c 的 `USER CODE` 区添加过滤器和收发函数。 **文件:`stm32/02_can_test_hal/Core/Src/can.c`** — HAL 初始化与收发 ```c CAN_HandleTypeDef hcan; void MX_CAN_Init(void) { hcan.Instance = CAN1; hcan.Init.Prescaler = 36; // 与寄存器版 BRP=35 对应 hcan.Init.Mode = CAN_MODE_SILENT_LOOPBACK; // 环回静默模式 hcan.Init.SyncJumpWidth = CAN_SJW_2TQ; hcan.Init.TimeSeg1 = CAN_BS1_3TQ; hcan.Init.TimeSeg2 = CAN_BS2_6TQ; hcan.Init.TimeTriggeredMode = DISABLE; hcan.Init.AutoBusOff = ENABLE; hcan.Init.AutoWakeUp = ENABLE; hcan.Init.AutoRetransmission = DISABLE; hcan.Init.ReceiveFifoLocked = DISABLE; hcan.Init.TransmitFifoPriority = DISABLE; if (HAL_CAN_Init(&hcan) != HAL_OK) Error_Handler(); } // CAN 过滤器配置 void CAN_FilterConfig(void) { CAN_FilterTypeDef filterConfig; filterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; filterConfig.FilterBank = 0; filterConfig.FilterMode = CAN_FILTERMODE_IDMASK; filterConfig.FilterScale = CAN_FILTERSCALE_32BIT; filterConfig.FilterIdHigh = 0x0000; filterConfig.FilterIdLow = 0x0000; filterConfig.FilterMaskIdHigh = 0x0000; filterConfig.FilterMaskIdLow = 0x0000; filterConfig.FilterActivation = ENABLE; HAL_CAN_ConfigFilter(&hcan, &filterConfig); } // 发送报文 void CAN_SendMsg(uint16_t stdID, uint8_t * data, uint8_t len) { while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0) {} CAN_TxHeaderTypeDef txHeader; txHeader.StdId = stdID; txHeader.IDE = CAN_ID_STD; txHeader.RTR = CAN_RTR_DATA; txHeader.DLC = len; uint32_t txMailBox; HAL_CAN_AddTxMessage(&hcan, &txHeader, data, &txMailBox); while (__HAL_CAN_GET_FLAG(&hcan, CAN_FLAG_TXOK0) == 0) {} } // 接收报文 void CAN_ReceiveMsg(RxMsg rxMsg[], uint8_t * msgCount) { *msgCount = HAL_CAN_GetRxFifoFillLevel(&hcan, CAN_RX_FIFO0); CAN_RxHeaderTypeDef rxHeader; for (uint8_t i = 0; i < *msgCount; i++) { HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &rxHeader, rxMsg[i].data); rxMsg[i].stdID = rxHeader.StdId; rxMsg[i].len = rxHeader.DLC; } } ``` > HAL 库的 `CAN_TxHeaderTypeDef` / `CAN_RxHeaderTypeDef` 等效于寄存器版的手动拼装 TIR/TDTR/TDLR/TDHR。`CAN_FilterTypeDef` 等效于操作 sFilterRegister[].FR1/FR2。 **文件:`stm32/02_can_test_hal/Core/Src/main.c`** — 环回测试逻辑(与 01 寄存器版效果相同) ```c #include "main.h" #include "can.h" #include "usart.h" #include "gpio.h" #include int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_CAN_Init(); MX_USART1_UART_Init(); HAL_CAN_Start(&hcan); // 启动 CAN 模块 CAN_FilterConfig(); // 配置过滤器 printf("尚硅谷CAN通讯实验:环回静默模式测试,HAL版...\n"); // 发送三帧 uint16_t stdID = 0x066; uint8_t *data = "abcdefg"; CAN_SendMsg(stdID, data, strlen((char *)data)); stdID = 0x068; data = "123"; CAN_SendMsg(stdID, data, strlen((char *)data)); stdID = 0x067; data = "xyz"; CAN_SendMsg(stdID, data, strlen((char *)data)); // 接收 RxMsg rxMsg[3]; uint8_t msgCount; CAN_ReceiveMsg(rxMsg, &msgCount); printf("报文接收完毕,count = %d\n", msgCount); for (uint8_t i = 0; i < msgCount; i++) { printf("stdID = %#X, len = %d, data = %.*s\n", rxMsg[i].stdID, rxMsg[i].len, rxMsg[i].len, rxMsg[i].data); } while (1) {} } ``` --- ## 软件设计(HAL 库版 — 双机收发通信) **项目路径**:`stm32/04_can_tx_hal`(发送节点)、`stm32/04_can_rx_hal`(接收节点) 与 02 项目的唯一区别是 `MX_CAN_Init` 中 `Mode = CAN_MODE_NORMAL`(而非 `SILENT_LOOPBACK`),其余 can.c 完全一致。 **can.c 差异(vs 02)**: ```c hcan.Init.Mode = CAN_MODE_NORMAL; // 02 项目为 CAN_MODE_SILENT_LOOPBACK ``` **文件:`stm32/04_can_tx_hal/Core/Src/main.c`** — HAL 发送节点 ```c #include "main.h" #include "can.h" #include "usart.h" #include "gpio.h" #include int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_CAN_Init(); MX_USART1_UART_Init(); HAL_CAN_Start(&hcan); CAN_FilterConfig(); printf("尚硅谷CAN通讯实验:双机收发测试 - 发送节点,HAL版...\n"); uint16_t stdID = 0x066; uint8_t * data = "abc"; uint8_t buffer[10]; uint32_t i = 0; while (1) { sprintf((char *)buffer, "%s %d", data, ++i); CAN_SendMsg(stdID, buffer, strlen((char *)buffer)); printf("报文发送完毕...\n"); HAL_Delay(1000); } } ``` **文件:`stm32/04_can_rx_hal/Core/Src/main.c`** — HAL 接收节点 ```c #include "main.h" #include "can.h" #include "usart.h" #include "gpio.h" #include int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_CAN_Init(); MX_USART1_UART_Init(); HAL_CAN_Start(&hcan); CAN_FilterConfig(); printf("尚硅谷CAN通讯实验:双机收发测试 - 接收节点,HAL版...\n"); RxMsg rxMsg[3]; uint8_t msgCount; while (1) { CAN_ReceiveMsg(rxMsg, &msgCount); for (uint8_t i = 0; i < msgCount; i++) { printf("stdID = %#X, len = %d, data = %.*s\n", rxMsg[i].stdID, rxMsg[i].len, rxMsg[i].len, rxMsg[i].data); } } } ``` > 双机实验需两块开发板,一块烧 TX(04_can_tx_hal),一块烧 RX(04_can_rx_hal),通过 CAN_H/CAN_L 对接并各接 120Ω 终端电阻。 --- ## HAL 库版 CAN HAL 库使用 `CAN_HandleTypeDef` 管理 CAN 外设,CubeMX 图形化配置波特率/过滤器/工作模式。 ```c // CubeMX 生成: MX_CAN1_Init() + 过滤器配置 CAN_HandleTypeDef hcan1; // HAL 库发送 CAN_TxHeaderTypeDef tx_header; uint8_t tx_data[8] = "Hello"; uint32_t tx_mailbox; tx_header.StdId = 0x066; // 标准 ID tx_header.ExtId = 0; // 扩展 ID(标准帧不用) tx_header.IDE = CAN_ID_STD; // 标准帧 tx_header.RTR = CAN_RTR_DATA; // 数据帧 tx_header.DLC = 5; // 数据长度 // 添加报文到发送邮箱(非阻塞) HAL_CAN_AddTxMessage(&hcan1, &tx_header, tx_data, &tx_mailbox); // HAL 库接收(轮询) CAN_RxHeaderTypeDef rx_header; uint8_t rx_data[8]; if (HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &rx_header, rx_data) == HAL_OK) { printf("Rx ID=0x%03X, Data=%s\r\n", rx_header.StdId, rx_data); } // HAL 库中断方式:使能接收中断并实现回调 HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING); // 中断回调 void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CAN_RxHeaderTypeDef rx_header; uint8_t rx_data[8]; HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data); // 处理收到的报文 } ``` ## 核心速查表 | 操作 | 寄存器操作 | HAL 库 | | ---------- | ----------------------------------------------- | ------------------------------------- | | 初始化模式 | `MCR | = INRQ; while(!MSR.INAK)` | CubeMX 自动处理 | | 设置波特率 | `BTR = BRP + (TS1<<16) + (TS2<<20) + (SJW<<24)` | 结构体 `CAN_InitTypeDef` | | 环回静默 | `BTR | = SILM + LBKM` | `CAN_MODE_LOOPBACK` | | 发送 | 邮箱 TIR/TDTxR/TDxR + TXRQ | `HAL_CAN_AddTxMessage()` | | 接收 | 读 FIFO 邮箱寄存器 | `HAL_CAN_GetRxMessage()` | | 配置过滤器 | `FINIT=1` → 设 FR1/FR2 → `FINIT=0` | `CAN_FilterTypeDef` 结构体 | | 中断接收 | EXTI + NVIC | `HAL_CAN_ActivateNotification()` | | 回调 | — | `HAL_CAN_RxFifo0MsgPendingCallback()` | ## 常见问题与避坑 1. **CAN 初始化卡死在 INAK 等待** → 检查 CAN1 时钟是否开启(APB1ENR.CAN1EN)、引脚配置是否正确 2. **环回正常但双机不通信** → 检查终端电阻(120Ω×2)、波特率一致、退出环回模式(清除 SILM/LBKM) 3. **过滤器不生效** → 必须在 FINIT=1 模式下配置过滤器,激活后 FINIT=0 4. **发送不出去** → 3 个邮箱全满(检查 TSR.TME[2:0])、未请求发送(TXRQ=0) 5. **接收不到数据** → FIFO 溢出(RFOMR.FOVR)、过滤器未匹配、中断未使能 6. **CAN 错误状态** → 查询 ESR(错误状态寄存器),REC/TEC 计数(>127 进入错误被动,>255 脱离总线)