tags: [source-summary] type: source source: "尚硅谷嵌入式技术之STM32单片机(高级篇)V2.0.1 — 第1章:CAN + 配套代码01~04" author: "尚硅谷研究院" date: 2026-07-15
用生活理解:CAN 总线就像一场没有主持人的圆桌会议——谁都可以发言(多主),但发言时先报自己的"优先级数字"(ID)。数字小的优先级高,即使两个人同时开口,优先级低的那位会自动闭嘴让优先级高的先说。这就是 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Ω 终端电阻(匹配阻抗,防止信号反射)。
| 帧类型 | 用途 | 软件控制? |
|---|---|---|
| 数据帧 | 发送数据 | ✅ |
| 远程帧 | 请求具有相同 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。
仲裁原则:谁先发 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 等总线空闲自动重试。
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
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 | ❌ 不支持 |
| 自动重发 | ✅ 可配置 |
主动内核(配置/控制/状态寄存器)
↓
发送邮箱[0~2] → CAN 内核(发送调度) → Tx 引脚
↓
接收滤波器(14组) ← CAN 内核(接收) ← Rx 引脚
↓
接收 FIFO[0~1] (各3级深度) → CPU 通过寄存器读取
| 模式 | 如何进入 | 特点 |
|---|---|---|
| 初始化 | MCR.INRQ=1, 等待 MSR.INAK=1 | 可配置寄存器(波特率、过滤器等) |
| 正常 | MCR.INRQ=0, 等待 MSR.INAK=0 | 正常收发 |
| 睡眠 | MCR.SLEEP=1 | 低功耗,检测到 CAN 总线活动自动唤醒 |
| 模式 | 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/stm32/01_can_test_register
文件:stm32/stm32/01_can_test_register/Hardware/CAN/can.h
#ifndef __CAN_H
#define __CAN_H
#include "stm32f10x.h"
// 接收数据结构
typedef struct {
uint16_t stdID; // 标准 ID
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/stm32/01_can_test_register/Hardware/CAN/can.c
#include "can.h"
// 过滤器配置(内部函数)
static void CAN_FilterConfig(void);
// CAN 初始化(环回静默模式,125Kbps)
void CAN_Init(void)
{
/* ========== 1. 开启时钟 ========== */
// CAN1 挂载在 APB1 总线上(36MHz)
RCC->APB1ENR |= RCC_APB1ENR_CAN1EN; // |=: CAN1时钟使能
RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // |=: GPIOB时钟
RCC->APB2ENR |= RCC_APB2ENR_AFIOEN; // |=: AFIO时钟(重映射用)
/* ========== 2. 引脚重映射(PB8=RX, PB9=TX) ========== */
// CAN 默认引脚是 PA11(RX)/PA12(TX)
// 开发板使用重映射到 PB8(RX)/PB9(TX)
// MAPR[25:24] = 10 → CAN 重映射到 PB8/PB9
AFIO->MAPR |= AFIO_MAPR_CAN_REMAP_1; // |=: 置位REMAP[1]
AFIO->MAPR &= ~AFIO_MAPR_CAN_REMAP_0; // &=~: 清零REMAP[0]
/* ========== 3. GPIO 模式 ========== */
// PB8(CAN_RX): 浮空输入 MODE=00, CNF=01
GPIOB->CRH &= ~GPIO_CRH_MODE8; // MODE8=00(输入)
GPIOB->CRH &= ~GPIO_CRH_CNF8_1; // CNF8[1]=0
GPIOB->CRH |= GPIO_CRH_CNF8_0; // CNF8[0]=1 → 浮空输入
// PB9(CAN_TX): 复用推挽输出 MODE=11, CNF=10
GPIOB->CRH |= GPIO_CRH_MODE9; // MODE9=11(50MHz输出)
GPIOB->CRH |= GPIO_CRH_CNF9_1; // CNF9[1]=1
GPIOB->CRH &= ~GPIO_CRH_CNF9_0; // CNF9[0]=0 → 复用推挽
/* ========== 4. CAN 初始化 ========== */
// 4.1 进入初始化模式(INRQ=1, 等待 INAK=1)
CAN1->MCR |= CAN_MCR_INRQ; // |=: INRQ位置1
while ((CAN1->MSR & CAN_MSR_INAK) == 0) // 等待INAK=1确认
{}
// 4.2 退出睡眠模式(SLEEP=0, 等待 SLAK=0)
CAN1->MCR &= ~CAN_MCR_SLEEP; // &=~: SLEEP位清0
while ((CAN1->MSR & CAN_MSR_SLAK) != 0) // 等待SLAK=0
{}
// 4.3 自动离线管理
CAN1->MCR |= CAN_MCR_ABOM; // ABOM=1: 错误节点自动恢复
// 4.4 自动唤醒管理
CAN1->MCR |= CAN_MCR_AWUM; // AWUM=1: 检测总线活动自动唤醒
/* ========== 5. 配置位时序 ========== */
// 5.1 测试模式:环回 + 静默
CAN1->BTR |= CAN_BTR_SILM; // SILM=1: 静默模式
CAN1->BTR |= CAN_BTR_LBKM; // LBKM=1: 环回模式
// 5.2 波特率: APB1=36MHz, BRP=35 → Tq=1us
// BS1=3, BS2=5 → 位时间=1+3+5=9us → Baud≈111Kbps
CAN1->BTR &= ~CAN_BTR_BRP; // 清除BRP位域
CAN1->BTR |= (35 << 0); // BRP[9:0]=35
CAN1->BTR &= ~CAN_BTR_TS1; // 清除TS1位域
CAN1->BTR |= (3 << 16); // TS1[3:0]=3 (BS1=4Tq)
CAN1->BTR &= ~CAN_BTR_TS2; // 清除TS2位域
CAN1->BTR |= (5 << 20); // TS2[2:0]=5 (BS2=6Tq)
CAN1->BTR &= ~CAN_BTR_SJW; // 清除SJW位域
CAN1->BTR |= (1 << 24); // SJW[1:0]=1
// 5.3 退出初始化模式,进入正常模式
CAN1->MCR &= ~CAN_MCR_INRQ; // &=~: INRQ位清0
while ((CAN1->MSR & CAN_MSR_INAK) != 0) // 等待INAK=0确认
{}
/* ========== 6. 过滤器配置 ========== */
CAN_FilterConfig();
}
static void CAN_FilterConfig(void)
{
// 1. 进入过滤器初始化模式
CAN1->FMR |= CAN_FMR_FINIT; // FINIT=1: 允许配置过滤器
// 2. 过滤器0: 掩码模式(32位)
CAN1->FM1R &= ~CAN_FM1R_FBM0; // FBM0=0: 掩码模式
// 3. 过滤器位宽: 32位
CAN1->FS1R |= CAN_FS1R_FSC0; // FSC0=1: 32位宽
// 4. 关联 FIFO: FIFO0
CAN1->FFA1R &= ~CAN_FFA1R_FFA0; // FFA0=0: 匹配帧→FIFO0
// 5. 过滤器ID (FR1) = 0x06e ← 只接收 ID=0x06e 的帧
CAN1->sFilterRegister[0].FR1 = 0x06e << 21;
// 6. 过滤器掩码 (FR2) = 0x7f1 ← 高11位全部必须匹配
CAN1->sFilterRegister[0].FR2 = 0x7f1 << 21;
// 7. 激活过滤器0
CAN1->FA1R |= CAN_FA1R_FACT0; // FACT0=1: 激活
// 8. 退出过滤器初始化模式
CAN1->FMR &= ~CAN_FMR_FINIT;
}
// 发送报文(使用发送邮箱0)
void CAN_SendMsg(uint16_t stdID, uint8_t *data, uint8_t len)
{
// 1. 等待邮箱0为空
while ((CAN1->TSR & CAN_TSR_TME0) == 0) // TME0=1 表示空
{}
// 2. 写入 ID
CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_STID; // 清除STID位域
CAN1->sTxMailBox[0].TIR |= (uint32_t)stdID << 21; // 写入11位标准ID
// 3. 写入 DLC(数据长度)
CAN1->sTxMailBox[0].TDTR &= ~CAN_TDT0R_DLC; // 清除DLC
CAN1->sTxMailBox[0].TDTR |= len; // 写入长度
// 4. 写入数据(最多8字节)
for (uint8_t i = 0; i < len && i < 8; i++)
{
CAN1->sTxMailBox[0].TDLR = data[0]; // 低32位寄存器存data[0~3]
if (len > 4) // 超过4字节的存入高32位
CAN1->sTxMailBox[0].TDHR = *(uint32_t *)&data[4];
}
// 5. 请求发送(TXRQ=1)
CAN1->sTxMailBox[0].TIR |= CAN_TI0R_TXRQ;
}
| 操作 | 寄存器操作 |
|---|---|
| 初始化模式 | `MCR |
| 退出初始化 | MCR &= ~INRQ; while(MSR.INAK); |
| 设置波特率 | BTR = BRP + (TS1<<16) + (TS2<<20) + (SJW<<24) |
| 环回静默 | `BTR |
| 发送 | TIR = ID<<21 + TXRQ + TDTxR = DLC + TDxR = data |
| 接收 | 读 sFIFOMailBox[0] 的 TIR/TDTxR/TDxR |
| 配置过滤器 | FINIT=1 → 设 FR1/FR2 → FINIT=0 |