第3章 单片机编程
3 第 3 章 单片机编程¶
本章系统讲解 STM32 微控制器的硬件基础与编程实践,从芯片选型、最小开发板到 GPIO、时钟、电路基础和外设接口,并通过 CubeMX 开发典型项目巩固所学。
3.1 STM32 芯片简介¶
STM32 是意法半导体(STMicroelectronics,简称 ST)推出的一系列基于 ARM Cortex-M 内核的 32 位微控制器(MCU)产品线,广泛应用于工业控制、消费电子、机器人及物联网等领域。
3.1.1 命名规则¶
STM32 的型号名称遵循一套规律性命名约定,以常见型号 STM32F103C8T6 为例:
表 3-1 命名规则
| 字段 | 含义 | 示例说明 |
|---|---|---|
STM32 |
产品系列,32位 ARM 微控制器 | — |
F |
产品子系列(F=通用型,H=高性能,L=低功耗,G=主流型等) | F 通用型 |
103 |
具体产品线编号(103为基础型,407为高性能型等) | 基础型 |
C |
引脚数量(C=48脚,R=64脚,V=100脚,Z=144脚) | 48 引脚 |
8 |
Flash 容量(6=32KB,8=64KB,B=128KB,C=256KB,E=512KB) | 64 KB |
T |
封装类型(T=LQFP,H=BGA,U=VFQFPN) | LQFP 封装 |
6 |
工作温度范围(6=−40~85°C,7=−40~105°C) | 工业级 |
上表对命名规则中各方案的特性进行了横向对比,便于读者根据实际需求选择最合适的技术路线。
3.1.2 主要产品系列¶
ST 根据性能与应用场景,将 STM32 划分为多个子系列:
表 3-2 主要产品系列
| 系列 | 内核 | 主频 | 特点 | 典型型号 |
|---|---|---|---|---|
| STM32F1(基础型) | Cortex-M3 | 72 MHz | 性价比高,入门首选 | STM32F103C8T6 |
| STM32F4(高性能型) | Cortex-M4(带FPU) | 168 MHz | 浮点运算,适合复杂控制算法 | STM32F407ZGT6 |
| STM32F7(高级型) | Cortex-M7 | 216 MHz | 双精度FPU,高速外设 | STM32F767IGT6 |
| STM32H7(旗舰型) | Cortex-M7 | 480 MHz | 最高性能,双核可选 | STM32H743IIT6 |
| STM32L(低功耗型) | Cortex-M0/M3/M4 | 32~80 MHz | 超低功耗,适合电池供电设备 | STM32L476RGT6 |
| STM32G(主流型) | Cortex-M0+/M4 | 64~170 MHz | 替代 F1/F3,高集成度 | STM32G431CBU6 |
在机器人控制课程中,以 STM32F103 系列作为核心学习平台,其 72 MHz 主频和丰富外设足以满足绝大多数底层控制任务。
3.1.3 核心特性¶
STM32 相较于传统 8 位单片机(如 51 系列、AVR)具有显著优势:
- 32 位处理能力:一条指令可处理 32 位数据,运算效率远高于 8/16 位 MCU,适合执行 PID、矩阵运算等控制算法
- 丰富的片上外设:集成 GPIO、USART、SPI、I2C、CAN、USB、ADC、DAC、定时器/PWM 等,减少外部芯片需求
- 中断系统(NVIC):嵌套向量中断控制器支持多达 240 个外部中断,优先级可灵活配置,实现强实时响应
- DMA 控制器:直接内存访问,无需 CPU 介入即可完成数据搬运,显著降低 CPU 占用率
- 低功耗模式:支持多种睡眠/待机模式,适用于对功耗敏感的移动机器人应用
- 完善的生态:官方 HAL/LL 库、STM32CubeMX 图形化配置工具及大量开源例程,学习资源丰富
3.1.4 开发生态¶
表 3-3 开发生态
| 工具/资源 | 说明 |
|---|---|
| STM32CubeMX | ST 官方图形化初始化代码生成工具,可自动配置时钟、外设,生成 HAL 初始化代码 |
| STM32CubeIDE | 基于 Eclipse 的官方集成开发环境,集成编译、调试、烧录功能 |
| Keil MDK | 业界主流的 ARM 开发工具,广泛用于嵌入式项目 |
| HAL 库 | 硬件抽象层库,屏蔽寄存器细节,提供统一 API,便于跨型号移植 |
| LL 库 | 底层驱动库,接近寄存器操作,效率更高,适合对性能要求严苛的场景 |
| ST-Link | ST 官方调试/烧录器,支持 SWD 和 JTAG 调试接口 |
3.2 Blue Pill 最小开发板¶
Blue Pill 是目前最流行的入门级 STM32 开发板之一,因其极低的成本(通常不足 10 元人民币)、完整的最小系统设计和紧凑的体积,成为学习嵌入式开发的首选硬件平台。它将上一节介绍的 STM32 最小系统全部集成在一块约 53 mm × 23 mm 的 PCB 上,开箱即用。
3.2.1 硬件规格¶
表 3-4 硬件规格
| 参数 | 规格 |
|---|---|
| 核心芯片 | STM32F103C8T6 |
| CPU 内核 | ARM Cortex-M3 |
| 主频 | 最高 72 MHz |
| Flash | 64 KB(程序存储) |
| SRAM | 20 KB(运行内存) |
| 工作电压 | 3.3V(板载 AMS1117-3.3 稳压) |
| 供电输入 | Micro-USB 5V 或引脚直接 3.3V/5V |
| 晶振 | 8 MHz HSE + 32.768 kHz LSE |
| GPIO 数量 | 最多 32 个(PA0-PA15, PB0-PB15, PC13-PC15) |
| 调试接口 | SWD(4 针排针:GND / SWCLK / SWDIO / 3.3V) |
| 板载 LED | PC13 引脚连接一颗用户 LED(低电平点亮) |
| 复位按键 | 1 个(NRST) |
| USB 接口 | Micro-USB(供电或 USB-FS 通信) |
| 尺寸 | 53 mm × 23 mm |
上述参数配置是硬件规格的典型推荐值,实际工程中可根据硬件条件和性能需求进行适当调整。
3.2.2 引脚布局图¶
图 3-1 上图以框图形式描绘了引脚布局图的系统架构,清晰呈现了各模块之间的连接关系与信号流向。
3.2.3 板载资源说明¶
板载 LED(PC13)
Blue Pill 在 PC13 引脚上连接了一颗 LED,是学习 GPIO 控制的第一个实验对象。注意该 LED 为低电平点亮(输出 0 亮、输出 1 灭),与常规逻辑相反,原因是 PC13 的驱动能力较弱(最大 3 mA),电路采用了共阳极接法。
Micro-USB 接口
板上的 Micro-USB 有两个作用: - 供电:通过板载 AMS1117 稳压后为全板提供 3.3V - USB 通信:STM32F103 支持全速 USB(USB-FS 12Mbps),可将 Blue Pill 模拟为 CDC 虚拟串口、HID 设备等,但需要软件配置并在 USB D+ 线上额外接一颗 1.5 kΩ 上拉电阻
SWD 调试口
板子一侧引出了 4 针 SWD 排针(GND / SWCLK / SWDIO / 3.3V),连接 ST-Link 调试器后即可在 Keil 或 STM32CubeIDE 中完成程序烧录与断点调试。
BOOT 跳线
板上有两个跳线帽控制 BOOT0 和 BOOT1:
- 正常运行:BOOT0 = 0,BOOT1 = 0 → 从 Flash 启动用户程序
- 串口烧录:BOOT0 = 1,BOOT1 = 0 → 进入系统 Bootloader,可通过 USART1 用串口烧录程序(无需 ST-Link)
3.2.4 Blue Pill 与最小系统的对应关系¶
图 3-2 该框图展示了 Blue Pill 与最小系统的对应关系的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。
小结:Blue Pill 开发板本质上就是一块 STM32F103 最小系统板,在掌握最小系统的原理后,便能看懂并读懂 Blue Pill 的原理图,进而自行设计定制化的嵌入式硬件。
3.3 STM32CubeMX 介绍¶
3.3.1 工具概述¶
STM32CubeMX 是 ST(意法半导体)官方推出的图形化初始化代码生成工具,是 STM32 生态的核心组成部分。它将繁琐的芯片初始化配置(时钟树、引脚模式、外设参数)转变为可视化的鼠标点选操作,并自动生成完整的 HAL 库初始化代码框架,极大地降低了 STM32 开发的入门门槛。
图 3-3 上图直观呈现了工具概述的组成要素与数据通路,有助于理解系统整体的工作机理。
STM32CubeMX 可独立安装使用,也可作为插件集成进 STM32CubeIDE(ST 官方 IDE)中。两者配合使用是当前 STM32 开发的主流工作流。
3.3.2 关键特色¶
1.8.2.1 图形化芯片引脚配置¶
CubeMX 提供直观的芯片俯视图(Pin View),开发者可在图形界面上逐一点击每个引脚,从下拉菜单中选择功能,无需查阅数百页的数据手册。
图 3-4 上图以框图形式描绘了图形化芯片引脚配置的系统架构,清晰呈现了各模块之间的连接关系与信号流向。
主要配置项:
表 3-5
| 配置项 | 说明 |
|---|---|
| GPIO mode | 选择引脚工作模式(推挽输出、开漏输出、上拉输入等8种) |
| GPIO speed | 选择输出速率(Low / Medium / High,影响信号边沿陡峭程度) |
| GPIO Pull-up/Pull-down | 内部上/下拉配置 |
| Initial output level | 初始电平(High 或 Low) |
| User Label | 为引脚起别名(如 LED),生成代码时自动替换为宏定义 |
设置 User Label 后,CubeMX 生成的代码中会自动创建
#define LED_Pin GPIO_PIN_13和#define LED_GPIO_Port GPIOC宏,程序可读性更强。
1.8.2.2 可视化时钟树配置¶
时钟树配置是 STM32 开发中最容易出错的环节。CubeMX 提供时钟树图形界面(Clock Configuration),开发者直接填写目标主频,工具自动计算 PLL 倍频系数、总线分频比,并实时校验是否超出芯片规格。
图 3-5 该框图展示了可视化时钟树配置的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。
1.8.2.3 外设图形化配置¶
对于每一个使用的外设(USART、SPI、I2C、ADC、定时器、USB 等),CubeMX 均提供独立的配置面板,以 USART1 为例:
图 3-6 上图直观呈现了外设图形化配置的组成要素与数据通路,有助于理解系统整体的工作机理。
所有外设配置完成后,引脚冲突检测会自动高亮冲突引脚(红色),帮助开发者在写代码前就发现硬件分配错误。
1.8.2.4 自动生成完整工程框架¶
点击 "Generate Code" 按钮后,CubeMX 生成一个完整的可编译工程,其结构如下:
图 3-7 上图以框图形式描绘了自动生成完整工程框架的系统架构,清晰呈现了各模块之间的连接关系与信号流向。
关键设计:生成的 main.c 中大量使用 /* USER CODE BEGIN xxx */ 和 /* USER CODE END xxx */ 注释对:
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
/* USER CODE BEGIN 2 */
// ← 开发者在此添加初始化后的自定义代码
/* USER CODE END 2 */
while (1)
{
/* USER CODE BEGIN WHILE */
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // ← 开发者填写的业务逻辑
HAL_Delay(500);
/* USER CODE END WHILE */
}
}
重要机制:用户代码只能写在
USER CODE BEGIN/END之间。这样当需要修改硬件配置、重新用 CubeMX 生成代码时,工具会保留注释块内的代码,不会覆盖开发者已写的业务逻辑。
1.8.2.5 中间件与 RTOS 支持¶
CubeMX 的 Middleware(中间件) 配置页面,支持一键集成多种软件组件:
图 3-8 该框图展示了中间件与 RTOS 支持的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。
FreeRTOS 集成特别说明:
FreeRTOS 是嵌入式领域使用最广泛的开源实时操作系统(RTOS),支持多任务、信号量、消息队列、软件定时器等特性。在 CubeMX 中启用 FreeRTOS 后:
- CubeMX 自动将 FreeRTOS 源码加入工程
- 提供图形界面配置任务(Task)、堆栈大小、优先级
- 自动生成
freertos.c,包含任务创建代码框架 - 自动处理 SysTick 冲突(FreeRTOS 占用 SysTick,HAL 改用 TIM)
/* CubeMX 为 FreeRTOS 生成的任务框架示例 */
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
for(;;)
{
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
osDelay(500); /* FreeRTOS 延时,让出 CPU 给其他任务 */
}
/* USER CODE END StartDefaultTask */
}
使用 FreeRTOS 后,LED 闪烁变成了一个独立的任务(Task),系统可同时运行多个任务(如同时控制 LED 和读取传感器),这正是机器人控制系统所必需的多任务能力。
3.3.3 CubeMX 与手写代码的对比¶
表 3-6 CubeMX 与手写代码的对比
| 维度 | 手写 HAL 代码 | CubeMX 生成代码 |
|---|---|---|
| 初始化代码 | 需手动查手册编写 | 图形配置,自动生成 |
| 引脚冲突检测 | 依赖开发者经验 | 自动高亮冲突 |
| 时钟配置 | 手动计算 PLL 参数 | 填目标频率自动计算 |
| 重新配置 | 全部重写 | 修改 .ioc 文件,重新生成 |
| FreeRTOS 集成 | 手动移植,步骤复杂 | 勾选即可,自动配置 |
| 适合阶段 | 深入理解底层原理 | 快速搭建项目,工程实践 |
本课程建议:使用 STM32CubeIDE + CubeMX 作为主要开发环境,借助图形化工具快速完成外设初始化,将精力集中在控制算法和机器人业务逻辑的编写上。
3.4 第一个嵌入式程序:LED 闪烁¶
LED 闪烁(Blink) 是嵌入式开发的 "Hello World",其本质是让连接 LED 的 GPIO 引脚交替输出高低电平,从而驱动 LED 周期性亮灭。
在 Blue Pill 上,LED 连接在 PC13 引脚,采用低电平点亮(输出 0 → 亮,输出 1 → 灭)。
3.4.1 程序基本结构¶
一个完整的 STM32 HAL 库 LED 闪烁程序由以下几个部分组成:
图 3-9 上图直观呈现了程序基本结构的组成要素与数据通路,有助于理解系统整体的工作机理。
各部分职责说明:
表 3-7
| 部分 | 职责 |
|---|---|
| 头文件包含 | 引入 HAL 库定义,使代码能使用 HAL_GPIO_WritePin 等 API |
| 函数声明 | C 语言规范要求:函数在调用前需先声明(或定义在调用处之前) |
| main() 主函数 | 程序入口,依次完成初始化,然后进入永不退出的主循环 |
| SystemClock_Config() | 嵌入式程序必须自行配置时钟,此函数将主频从默认 8 MHz 提升至 72 MHz |
| MX_GPIO_Init() | 专门负责 GPIO 初始化,将外设配置逻辑与业务逻辑分离,结构清晰 |
嵌入式 C 程序的典型模式:初始化阶段(运行一次)+
while(1)主循环(永续运行)。所有嵌入式程序几乎都遵循此结构。
3.4.2 从电路图确定 LED 的引脚编号¶
编写程序之前,必须先从电路图(原理图,Schematic)中查找 LED 连接的是哪一个引脚。以 Blue Pill 为例,查找步骤如下:
第一步:在电路图中找到 LED 符号
电路图中 LED 用如下符号表示(三角形加竖线,旁有箭头表示发光):
图 3-10 上图以框图形式描绘了从电路图确定 LED 的引脚编号的系统架构,清晰呈现了各模块之间的连接关系与信号流向。
第二步:顺着连线追踪到芯片引脚
图 3-11 该框图展示了从电路图确定 LED 的引脚编号的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。
第三步:读取引脚标号
在 Blue Pill 原理图中,LED 阴极一侧的连线上标有网络标号 PC13,由此确认:
-
LED 连接在 GPIOC 端口的第 13 号引脚
-
在程序中写作
GPIOC+GPIO_PIN_13 - 因为 LED 阳极接 VCC、阴极接 PC13,所以 PC13 输出低电平(0)时 LED 亮,输出高电平(1)时 LED 灭
图 3-12 上图直观呈现了从电路图确定 LED 的引脚编号的组成要素与数据通路,有助于理解系统整体的工作机理。
3.5 GPIO 管脚工作模式¶
确定了引脚编号之后,程序还必须配置该引脚的工作模式(通过 MX_GPIO_Init() 函数中的 GPIO_InitStruct.Mode 参数)。工作模式决定了引脚的电气行为:是作为输入还是输出?内部是什么电路结构?
STM32 GPIO 共有 8 种工作模式,分为输入类和输出类两大类:
图 3-13 上图以框图形式描绘了 GPIO 管脚工作模式的系统架构,清晰呈现了各模块之间的连接关系与信号流向。
推挽输出与开漏输出的内部电路对比:
图 3-14 该框图展示了 GPIO 管脚工作模式的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。
表 3-8 各模式的典型应用场景:
| 工作模式 | HAL 库常量 | 典型应用 |
|---|---|---|
| 浮空输入 | GPIO_MODE_INPUT + GPIO_NOPULL |
外部已有确定电平的信号输入 |
| 上拉输入 | GPIO_MODE_INPUT + GPIO_PULLUP |
按键检测(按下拉低,松开默认高) |
| 下拉输入 | GPIO_MODE_INPUT + GPIO_PULLDOWN |
需要默认低电平的信号输入 |
| 模拟输入 | GPIO_MODE_ANALOG |
ADC 采样、DAC 输出 |
| 推挽输出 | GPIO_MODE_OUTPUT_PP |
驱动 LED、继电器、蜂鸣器 |
| 开漏输出 | GPIO_MODE_OUTPUT_OD |
I2C 总线、多设备共享总线 |
| 复用推挽输出 | GPIO_MODE_AF_PP |
UART TX、SPI MOSI/SCK |
| 复用开漏输出 | GPIO_MODE_AF_OD |
I2C SDA/SCL |
上表对 GPIO 管脚工作模式中各方案的特性进行了横向对比,便于读者根据实际需求选择最合适的技术路线。
3.5.1 浮空输入(Float)、上拉输入(PU)、下拉输入(PD)的使用场景¶
这三种输入模式的核心区别在于引脚默认电平由谁决定(外部电路/内部上拉/内部下拉),选择的核心原则是:让引脚在“无外部有效输入”时拥有确定的电平,避免电平飘忽不定导致误判。下面结合实际场景讲清楚每种模式的使用时机:
3.5.2 浮空输入(Float):外部有明确电平驱动时用¶
核心特性:引脚内部无上/下拉电阻,处于高阻态,电平完全由外部电路的驱动信号决定;若外部无驱动,引脚电平会受电磁干扰“飘忽不定”(0/1 乱跳)。
适用场景 & 实际例子: - 场景 1:通信类外设的输入引脚(外部有主动电平驱动) 例:USART 的 RX 引脚、SPI 的 MISO 引脚、I2C 的 SDA/SCL 引脚(I2C 总线通常外接上拉电阻,内部只需浮空)。 ✅ 原因:这些引脚的电平由通信对端(如串口模块、传感器、另一块 MCU)主动输出高/低电平,内部无需上/下拉,避免干扰外部驱动。 - 场景 2:ADC 模拟输入引脚 例:读取电压传感器的 ADC 通道引脚。
✅ 原因:上/下拉电阻会分流,导致 ADC 采样精度下降;浮空输入无内部电阻,能精准采集外部模拟电压。 - 场景 3:外部电路已自带上/下拉的引脚 例:外部电路已经接了 10kΩ上拉到 VCC 的按键引脚(此时内部选浮空,避免双重上拉)。
CubeMX 配置示例: 在「Pinout & Configuration」中选择引脚→「GPIO mode」→「Input Floating」。
3.5.3 上拉输入(PU):外部低电平有效、默认需高电平时用¶
核心特性:引脚内部通过约 40kΩ电阻上拉到 VCC,无外部输入时默认电平为高;当外部电路将引脚拉到 GND 时,电平变为低。
适用场景 & 实际例子: - 场景 1:轻触按键检测(最典型) 例:按键一端接 GND,另一端接 MCU 引脚(无外部上拉)。 ✅ 原因: - 按键松开:内部上拉使引脚为高电平(无操作); - 按键按下:引脚被拉到 GND,电平变低(检测到按键触发); - 避免浮空时按键松开的电平飘忽,导致误触发。 - 场景 2:外部低电平触发的中断引脚
例:传感器的“报警引脚”(正常时悬空/高,报警时拉到 GND)。 ✅ 原因:默认高电平(无报警),报警时外部拉低,触发中断,逻辑清晰且无误触发。
- 场景 3:总线类引脚(无外部上拉时) 例:简单的单总线通信(如 DS18B20),内部上拉替代外部电阻(低速率场景)。
CubeMX 配置示例: 引脚→「GPIO mode」→「Input Pull-Up」;代码层面会自动生成:
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); // 启用上拉(部分系列需手动置1)
1.10.3.1 下拉输入(PD):外部高电平有效、默认需低电平时用¶
核心特性:引脚内部通过约 40kΩ电阻下拉到 GND,无外部输入时默认电平为低;当外部电路将引脚拉到 VCC 时,电平变为高。
适用场景 & 实际例子: - 场景 1:高电平触发的外部中断/检测 例:传感器的“数据就绪引脚”(无数据时悬空,有数据时拉到 VCC)。 ✅ 原因:默认低电平(无数据),有数据时外部拉高,触发检测,避免浮空误判。 - 场景 2:工业场景的高电平有效输入 例:外部继电器触点(闭合时接 VCC,断开时悬空),内部下拉使默认低电平,闭合时高电平。 - 场景 3:避免上电瞬间高电平误触发 例:某些外设上电瞬间可能出现电平抖动,下拉使默认低电平,仅当外部主动拉高时才响应。
CubeMX 配置示例: 引脚→「GPIO mode」→「Input Pull-Down」;代码层面:
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET); // 启用下拉
1.10.3.2 关键注意事项(新手避坑)¶
- 上拉/下拉的电阻是“弱上拉/下拉”(约 40kΩ):
- 优点:功耗低,无需外部电阻;
- 缺点:驱动能力弱(如 I2C 总线高速场景需外接 1k~4.7kΩ强上拉,仅靠内部不够)。
- 浮空输入≠“不用管”:
- 仅当外部有明确电平驱动时用,无外部驱动时(如悬空按键)绝对不能用,否则电平乱跳。
- ADC 输入必须用浮空:上/下拉电阻会分流,导致采样值偏离实际电压。
1.10.3.3 总结¶
- 浮空输入(Float):外部有主动电平驱动(如通信 RX、ADC 采样)时用,依赖外部确定电平;
- 上拉输入(PU):外部低电平有效(如接地按键),需要默认高电平时用;
- 下拉输入(PD):外部高电平有效,需要默认低电平时用; 核心原则:让引脚在“无有效输入”时拥有确定的默认电平,避免电平飘忽导致误判。
3.5.4 开漏输出(OD)、复用推挽输出(AF_PP)、复用开漏输出(AF_OD)的使用场景¶
选择的核心逻辑: - 是否需要多设备共享总线(线与逻辑)→ 选开漏(OD/AF_OD); - 是否需要外设复用引脚(非普通 GPIO 控制)→ 选复用(AF_PP/AF_OD); - 是否需要主动拉高电平(无需外部上拉)→ 选推挽(AF_PP)。
1.10.4.1 开漏输出(GPIO_MODE_OUTPUT_OD):普通 GPIO 的开漏模式¶
核心特性: - 内部仅能“拉低”电平(输出 0 时引脚接 GND),输出 1 时引脚处于高阻态(无法主动拉高),必须外接上拉电阻(1k~10kΩ到 VCC)才能输出高电平; - 支持线与逻辑:多个开漏引脚接同一条总线,只要有一个引脚拉低,总线就为低;全部释放(高阻)时,上拉电阻使总线为高(避免推挽输出短接烧引脚)。
适用场景 & 实际例子: - 场景 1:多 MCU 共享的状态引脚(如“忙”“报警”信号) 例:3 个 MCU 的 GPIO 引脚接同一条总线,任意一个 MCU 拉低引脚表示“忙”,全部释放则为“空闲”。 - 场景 2:普通 GPIO 模拟 I2C(软件 I2C) 例:用 PA0(SDA)、PA1(SCL)模拟 I2C,配置为开漏输出,外接 4.7kΩ上拉电阻(模拟 I2C 的线与逻辑)。 - 场景 3:电平转换(3.3V MCU 驱动 5V 外设) 例:3.3V MCU 的开漏引脚外接上拉到 5V,输出高电平时为 5V,低电平时为 0V,实现 3.3V→5V 电平兼容。
CubeMX & 代码示例: - CubeMX:引脚 → GPIO mode → Output Open Drain; - 代码(控制电平):
// PA0配置为开漏输出,外接上拉电阻
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET); // 输出1(高阻态,上拉电阻拉高)
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET); // 输出0(拉低到GND)
1.10.4.2 复用推挽输出(GPIO_MODE_AF_PP):外设复用+推挽输出¶
核心特性: - “复用(AF)”:引脚不再由普通 GPIO 控制器管理,交给片内外设(UART、SPI、TIM 等)控制; - “推挽(PP)”:内部同时有拉高/拉低电路,能主动输出高(接 VCC)/低(接 GND)电平,驱动能力强(mA 级),无需外部上拉; - 不支持线与逻辑(多个推挽引脚短接会导致 VCC→GND 短路)。
适用场景 & 实际例子: - 场景 1:单设备独占的通信外设输出引脚 例:UART 的 TX 引脚(USART1_TX)、SPI 的 MOSI/SCK 引脚、TIM 的 PWM 输出引脚。 ✅ 原因:这些引脚仅由一个外设独占,需要稳定、主动的高/低电平输出,推挽的驱动能力和稳定性更优。 - 场景 2:需要强驱动能力的外设输出 例:SPI 主设备的 SCK 引脚,需带动多个从设备,推挽输出能提供足够的驱动电流。
CubeMX & 代码示例: - CubeMX:引脚 → GPIO mode → Alternate Function Push Pull → 选择对应外设功能(如 USART1_TX); - 代码(由外设驱动,无需手动控制引脚):
// 初始化UART(TX为AF_PP)
HAL_UART_Transmit(&huart1, (uint8_t*)"Hello", 5, 100); // TX引脚由USART1自动输出电平
1.10.4.3 复用开漏输出(GPIO_MODE_AF_OD):外设复用+开漏输出¶
核心特性: - 结合“外设复用”和“开漏”优点:引脚由外设直接控制,同时保持开漏特性(需外部上拉),支持多设备共享总线; - 比普通 GPIO 开漏更高效(硬件外设驱动,无需软件模拟)。
适用场景 & 实际例子: - 场景 1:硬件 I2C 总线(最典型) 例:STM32 的 I2C1_SDA(PB7)、I2C1_SCL(PB6)配置为 AF_OD,外接 4.7kΩ上拉电阻。 ✅ 原因:I2C 允许多主/从设备共享总线,必须依赖线与逻辑,硬件 I2C 外设直接驱动开漏引脚,比软件模拟更稳定。 - 场景 2:SMBus/CAN 总线 例:SMBus 的 Alert 引脚、CAN 总线的 TX 引脚,配置为 AF_OD,支持多节点共享总线。
CubeMX & 代码示例: - CubeMX:引脚 → GPIO mode → Alternate Function Open Drain → 选择对应外设功能(如 I2C1_SDA); - 代码(硬件 I2C 初始化):
// 初始化I2C(SDA/SCL为AF_OD)
HAL_I2C_Init(&hi2c1);
// 发送数据,引脚由I2C外设自动控制电平
HAL_I2C_Master_Transmit(&hi2c1, 0xA0, (uint8_t*)"Data", 4, 100);
1.10.4.4 新手避坑关键¶
- 开漏输出(OD/AF_OD)必须外接上拉电阻:无外部上拉时,输出 1(高阻)的引脚电平会飘忽不定,外设无法识别;
- 推挽输出(AF_PP)禁止多设备共享:多个推挽引脚短接会形成短路,烧毁引脚;
- 复用模式引脚不可普通 GPIO 控制:配置为 AF_PP/AF_OD 后,
HAL_GPIO_WritePin无法修改引脚电平,需通过外设驱动。
3.5.5 总结¶
- 开漏输出(OD):普通 GPIO 使用,需多设备共享/电平转换,外接上拉(如软件 I2C、多 MCU 共享引脚);
- 复用推挽输出(AF_PP):外设独占引脚,需主动高/低电平输出,无需上拉(如 UART TX、SPI SCK);
- 复用开漏输出(AF_OD):外设复用+多设备共享总线,需外接上拉(如硬件 I2C、CAN 总线); 核心选择逻辑:先判断是否需要外设复用(选 AF),再判断是否需要总线共享(选 OD),否则选 PP。
3.6 GPIO 工作模式的命名解释¶
要让学生通过字面意思+生活比喻秒记复用、推挽、开漏,核心是把专业术语拆成“好懂的名字逻辑”,不用死记硬背,具体拆解如下:
3.6.1 一、复用(Alternate Function,AF)——“引脚换主人,功能能复用”¶
1.11.1.1 字面拆解¶
“复用”=重复使用、换着用,不是只能干一件事。
1.11.1.2 通俗比喻¶
STM32 的引脚就像教室的公用插座: - 默认状态是“GPIO 模式”(当普通照明插座用); - 但还能插 UART、SPI、I2C 等外设(当通信/控制插座用)。
插座没换,只是换了“功能主人”——从 GPIO 换成外设用,这就是复用。
1.11.1.3 核心记忆¶
引脚不换,功能复用:不是只能当 GPIO,还能被外设“借去用”。
3.6.2 二、推挽(Push-Pull,PP)——“能推高、能拉低,主动输出高低”¶
1.11.2.1 字面拆解¶
“推”=往上推,“挽”=往下拉,两个动作对应两种电平。
1.11.2.2 通俗比喻¶
引脚的输出电路里有两个“大力士”: - 一个往上推:直接接电源 VCC,把引脚“顶成高电平”(输出 1); - 一个往下拉:直接接地 GND,把引脚“拽成低电平”(输出 0)。
两个大力士轮流干活,主动把电平定死,不用靠外部帮忙,这就是推挽。
1.11.2.3 核心记忆¶
双大力士,推高拉低:能主动输出高低电平,驱动能力强,不用外接上拉。
3.6.3 三、开漏(Open-Drain,OD)——“只能拉低,拉高靠外接”¶
1.11.3.1 字面拆解¶
“开漏”=漏极开路,简化记:“开”=开路(不直接接 VCC),“漏”=只留接 GND 的通路。
1.11.3.2 通俗比喻¶
引脚的输出电路里只有一个“往下拉的大力士”(接 GND),没有往上推的大力士: - 想输出 0:大力士拉低引脚(接 GND); - 想输出 1:引脚变成“高阻态”(悬空),必须靠外部接一个上拉电阻往上“拉”,才能变高电平。
就像“只有拉低的手,没有拉高的手,拉高得靠别人帮忙”,这就是开漏。
1.11.3.3 核心记忆¶
单拉低,拉高靠外接:只能输出 0,1 必须靠上拉电阻,适合多设备共享总线。
3.6.4 四、终极记忆口诀(学生直接背)¶
- 复用:引脚换功能,外设来复用;
- 推挽:双大力士,推高拉低强;
- 开漏:单拉低,拉高靠外接。
3.6.5 五、组合场景再强化(秒懂)¶
- 复用推挽(AF_PP):外设用+主动高低(比如 UART 的 TX、SPI 的 SCK,独占引脚,不用上拉);
- 复用开漏(AF_OD):外设用+只能拉低(比如硬件 I2C,多设备共享,必须上拉);
- 普通开漏(GPIO_OD):GPIO 用+只能拉低(比如软件 I2C,自己模拟,外接上拉)。
本程序使用的工作模式:推挽输出(GPIO_MODE_OUTPUT_PP)
LED 闪烁程序需要引脚主动输出高电平(LED 灭)和低电平(LED 亮),因此选择推挽输出模式。推挽输出具有较强的驱动能力(最大 25 mA),足以点亮 LED。
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 ← LED 驱动必选 */
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; /* 低速(2 MHz),驱动 LED 足够 */
为什么不用开漏输出? 开漏模式在引脚输出"1"时只是断开 N 管,引脚电平由外部上拉决定;而 Blue Pill 的 PC13 没有外接上拉电阻,因此无法靠开漏输出高电平,LED 将永远亮着。
1.11.5.1 程序执行流程¶
图 3-15 上图直观呈现了程序执行流程的组成要素与数据通路,有助于理解系统整体的工作机理。
1.11.5.2 STM32 CubeMX 时钟树最简默认设置方法¶
时钟树配置的核心是选择时钟源和分配分频/倍频系数,对新手来说,无需外部硬件依赖、CubeMX 自动计算参数的方式就是最简单的,具体步骤如下:
1.11.5.3 核心原则¶
优先使用 HSI(内部高速时钟) 作为系统时钟源,无需外接晶振/时钟元件,CubeMX 自动完成分频/倍频计算,零手动修改成本。
1.11.5.4 具体操作步骤¶
① 打开 CubeMX,选择目标 STM32 芯片(如 STM32F103C8T6),进入工程配置界面; ② 进入「Pinout & Configuration」→「System Core」→「RCC」: - 「High Speed Clock (HSE)」选择 Disabled(关闭外部高速时钟); - 「High Speed Clock (HSI)」保持 Enabled(默认已开启,内部 8MHz/16MHz 时钟,依芯片型号而定); - 低速时钟(LSI/LSE)默认关闭即可,无需修改; ③ 进入「Clock Configuration」(时钟配置核心页面): - 点击页面左上角「Reset Clock Configuration」(重置时钟配置),CubeMX 会恢复芯片默认时钟树; - 此时系统时钟(SYSCLK)默认由 HSI 提供(如 F103 为 8MHz),HCLK(AHB 总线)、PCLK1(APB1)、PCLK2(APB2)均为默认分频(通常 1:1),无需手动调整; ④ 直接生成代码即可,无需额外修改时钟相关配置。
1.11.5.5 最简配置的效果¶
- 硬件上:无需焊接外部晶振、谐振电容,减少硬件接线和故障点;
- 软件上:CubeMX 自动生成
SystemClock_Config()函数,无需手动编写时钟初始化代码; - 性能上:满足基础外设(GPIO、UART、SPI 等)的低速使用需求(如 UART 波特率 9600/115200)。
1.11.5.6 需要特殊设置时钟树的场景¶
当最简配置无法满足功能/性能需求时,需针对性调整时钟树,常见场景如下:
1.11.5.7 需求更高的系统主频¶
- 原因:HSI 主频上限低(如 F103 最大 8MHz,F407 最大 16MHz),而多数 STM32 支持更高主频(F103 最大 72MHz、F407 最大 168MHz、H7 系列可达 400MHz),需通过 HSE+PLL 倍频实现;
- 配置要点:
- 启用 HSE(选择「Crystal/Ceramic Resonator」,需硬件焊接 8MHz/12MHz 晶振+2 个 22pF 电容);
- 在「Clock Configuration」中设置 PLL 倍频系数(如 F103:HSE 8MHz → PLL 倍频 9 → SYSCLK=72MHz);
- 调整分频系数:确保 AHB/APB 总线时钟不超过芯片手册上限(如 F103 的 PCLK1 最大 36MHz,需分频 2)。
1.11.5.8 外设需要高精度/精准时钟¶
- 场景 1:USB 外设 USB 协议要求必须提供 精准 48MHz 时钟,仅靠 HSI 无法满足(精度±1% 以上),需配置:
- 启用 HSE(8MHz 晶振),通过 PLL 分频/倍频生成 48MHz(如 F103:PLLCLK=72MHz → 分频 1.5 → 48MHz);
- 场景 2:RTC(实时时钟)
RTC 需低功耗、高精度计时,需启用 LSE(外部低速晶振,32.768kHz)(硬件焊接 32.768kHz 晶振+2 个 12.5pF 电容),而非 LSI(内部低速,精度±5%); - 场景 3:以太网(ETH)/I2S ETH 需 25MHz 外部时钟或 PLL 生成精准时钟,I2S 需音频级精准时钟,均需调整 PLL 分频系数。
1.11.5.9 低功耗应用场景¶
-
原因:HSI/HSE 主频高、功耗大,低功耗模式(Stop/Standby)需切换到低速时钟源;
-
配置要点:
- 启用 LSI(内部低速 128kHz)或 LSE(32.768kHz)作为低功耗时钟源;
- 关闭 HSE/PLL,降低 SYSCLK 频率(如切换到 32kHz),减少功耗。
1.11.5.10 时钟安全/冗余需求¶
- 场景:工业控制、汽车电子等对时钟稳定性要求高的场景,需防止时钟故障导致系统崩溃;
- 配置要点:启用「Clock Security System (CSS)」(时钟安全系统),当 HSE 故障时,硬件自动切换到 HSI,保证系统持续运行。
1.11.5.11 外部时钟输入(HSE Bypass 模式)¶
- 场景:硬件上使用外部设备(如其他 MCU、时钟芯片)提供的时钟信号(而非晶振);
- 配置要点:RCC 中 HSE 选择「BYPASS Mode」,适配外部时钟输入的硬件接线(仅接时钟信号线,无需晶振)。
1.11.5.12 总结¶
- 最简配置核心:启用 HSI(内部时钟)+ 重置时钟配置,无需外接晶振,CubeMX 自动完成参数计算,满足基础低速需求;
- 特殊配置触发条件:需高主频、USB/ETH/RTC 等高精度外设、低功耗、时钟安全或外部时钟输入时,需针对性启用 HSE/LSE + 调整 PLL/分频系数;
- 关键注意事项:特殊配置时需确保各总线/外设时钟不超过芯片手册上限(如 APB1 最大 36MHz for F103),外设时钟满足功能刚需(如 USB 必须 48MHz)。
3.7 为什么需要外部时钟源¶
3.7.1 一、STM32 不同系列 HSI 的最高工作频率¶
HSI(内部高速时钟)的基础频率和最大倍频能力因 STM32 芯片系列不同而差异显著,核心结论先明确:
表 3-9 一、STM32 不同系列 HSI 的最高工作频率
| STM32系列 | HSI基础频率 | 理论最大倍频后主频 | 工程实际可用主频 |
|---|---|---|---|
| F1系列(如F103) | 8MHz(±1%~±4%温漂) | 8MHz × 9(PLL倍频)= 72MHz | ≤ 36MHz(稳定性可接受) |
| F4系列(如F407) | 16MHz(±1%温漂) | 16MHz × 10.5 = 168MHz | ≤ 84MHz(仅低速外设场景) |
| H7系列(如H743) | 64MHz(±0.5%温漂) | 64MHz × 6 = 384MHz | ≤ 192MHz(极少用) |
通过上表的对比可以看出,不同方案在 STM32 系列、HSI 基础频率、理论最大倍频后主频等等方面各有优劣,实际选型时应结合具体应用场景综合权衡。
1.12.1.1 关键补充(以最常用的 F103 为例):¶
F103 的 HSI 是内部 RC 振荡器,而非精准晶振,存在两个核心问题: 1. 精度差:出厂校准后误差±1%,温漂/电压变化会导致误差扩大到±4%; 2. 稳定性弱:温度、电压波动会让时钟频率漂移,无法保证高频下的时序稳定性。
因此,即便 F103 的 PLL 理论上能将 HSI(8MHz)倍频到 72MHz(8×9),官方手册和工程实践中均不推荐,仅建议 HSI 用于≤36MHz 的低速场景(如仅 GPIO、低波特率 UART)。
3.7.2 二、要工作到 72MHz,是否必须用 HSE?¶
1.12.2.1 理论层面:可以不用(但完全不推荐)¶
F103 的时钟树逻辑上支持 HSI(8MHz) → PLL 倍频 9 → SYSCLK=72MHz,CubeMX 中也能手动配置该参数:
- 步骤:RCC 中启用 HSI → 时钟配置页面将 PLLMUL 设为 9 → 确认 AHB/APB 分频(HCLK=72MHz,PCLK1=36MHz,PCLK2=72MHz)。
但这种配置仅能让芯片“跑起来”,实际会出现: - 外设不稳定:UART 波特率误差超标、SPI/I2C 通信丢包; - USB 功能失效:USB 要求 48MHz 精准时钟(误差≤0.25%),HSI 的±4% 误差完全不满足; - 长期可靠性差:温度变化会导致主频漂移,甚至程序跑飞。
1.12.2.2 工程实践层面:必须用 HSE¶
72MHz 是 F103 的额定最高主频,要稳定运行该频率,唯一可靠方案是 HSE(外部高速晶振)+ PLL,原因: - HSE(通常 8MHz 晶振)精度极高:商用晶振误差仅±50ppm(0.005%),温漂可忽略; - 符合官方设计规范:STM32F1 参考手册明确推荐“HSE 作为 PLL 输入源实现最高主频”; - 满足外设时序要求:高频下的 GPIO、定时器、通信外设(UART/SPI/I2C)时序精准,无丢包/错码风险。
3.7.3 HSE 配置 72MHz 的核心步骤(CubeMX):¶
① 硬件:焊接 8MHz 晶振 + 2 个 22pF 谐振电容到 STM32 的 OSC_IN/OSC_OUT 引脚; ② CubeMX:RCC 中 HSE 选择「Crystal/Ceramic Resonator」(启用外部晶振); ③ 时钟配置:PLLSRC 选择 HSE → PLLMUL 设为 9 → 自动计算得 SYSCLK=8×9=72MHz; ④ 确认分频:PCLK1(APB1)分频 2(36MHz,符合 F103 最大限制),PCLK2(APB2)分频 1(72MHz)。
3.7.4 总结¶
- HSI 最高频率:F1 系列理论 8×9=72MHz(不可用),工程中≤36MHz;F4 系列 HSI 16MHz,理论倍频到 168MHz,实际仅≤84MHz;
- 72MHz 的核心结论:理论上可通过 HSI+PLL 实现,但工程中必须用 HSE,否则外设不稳定、可靠性差;
- 核心原则:STM32 高主频(如 F1 的 72MHz、F4 的 168MHz)场景,优先选择 HSE 作为时钟源,HSI 仅用于无外部晶振的临时调试/低速场景。
1.12.4.1 方法一:使用 HAL 库(推荐,STM32CubeIDE)¶
HAL(Hardware Abstraction Layer)是 ST 官方提供的硬件抽象库,屏蔽了底层寄存器细节,代码可读性高、跨型号移植方便,是当前主流开发方式。
/* main.c - Blue Pill LED 闪烁(HAL 库版本)
* 开发环境: STM32CubeIDE
* 目标板: Blue Pill (STM32F103C8T6)
* LED 引脚: PC13,低电平点亮
*/
#include "stm32f1xx_hal.h" /* HAL 库头文件 */
/* 函数声明 */
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
int main(void)
{
/* 1. HAL 库初始化(配置 SysTick 定时器,提供 HAL_Delay 的时间基准) */
HAL_Init();
/* 2. 配置系统时钟:外部 8MHz 晶振 → PLL 倍频 → 72MHz */
SystemClock_Config();
/* 3. 初始化 GPIO(使能 GPIOC 时钟,配置 PC13 为推挽输出) */
MX_GPIO_Init();
/* 4. 主循环:LED 每 500ms 翻转一次状态 */
while (1)
{
/* PC13 输出低电平 → LED 亮 */
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);
HAL_Delay(500); /* 延时 500 毫秒 */
/* PC13 输出高电平 → LED 灭 */
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
HAL_Delay(500); /* 延时 500 毫秒 */
/* 也可以用一句话实现翻转,效果相同:
* HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
* HAL_Delay(500);
*/
}
}
/* GPIO 初始化函数 */
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 使能 GPIOC 外设时钟(STM32 外设上电后默认关闭,必须先开启才能使用) */
__HAL_RCC_GPIOC_CLK_ENABLE();
/* 配置 PC13 引脚参数 */
GPIO_InitStruct.Pin = GPIO_PIN_13; /* 选择 PC13 引脚 */
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出模式 */
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; /* 低速输出(驱动 LED 足够) */
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/* 初始状态:PC13 输出高电平 → LED 默认熄灭 */
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET);
}
/* 系统时钟配置:HSE 8MHz → PLL × 9 → SYSCLK 72MHz */
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/* 启用 HSE 外部高速晶振,并配置 PLL 倍频 */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; /* 8MHz × 9 = 72MHz */
HAL_RCC_OscConfig(&RCC_OscInitStruct);
/* 配置总线分频:SYSCLK=72MHz, AHB=72MHz, APB1=36MHz, APB2=72MHz */
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
}
1.12.4.2 方法二:直接操作寄存器(理解底层原理)¶
直接操作寄存器可以帮助深刻理解 HAL 库背后的硬件机制,也是嵌入式进阶学习的必经之路。
/* main.c - Blue Pill LED 闪烁(寄存器直接操作版本)
* 不依赖任何库,直接读写硬件寄存器
* 适合理解 STM32 底层工作原理
*/
#include "stm32f103xb.h" /* 包含 STM32F103 寄存器地址定义 */
/* 简易软件延时(循环空转,不精确,仅用于教学演示) */
void delay(uint32_t count)
{
while (count--);
}
int main(void)
{
/*
* 步骤 1:使能 GPIOC 时钟
* RCC_APB2ENR 寄存器第 4 位(IOPCEN)控制 GPIOC 时钟
* 置 1 → 开启时钟,GPIOC 寄存器才可以正常读写
*/
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
/*
* 步骤 2:配置 PC13 为通用推挽输出,最高速率 2MHz
*
* GPIOC_CRH 寄存器控制 PC8~PC15 的引脚模式
* PC13 对应 CRH 中的 CNF13[1:0] 和 MODE13[1:0](第 20~23 位)
*
* MODE13 = 10(输出,最大速率 2MHz)
* CNF13 = 00(通用推挽输出)
*
* 先清零再置位,避免影响其他引脚配置
*/
GPIOC->CRH &= ~(0xF << 20); /* 清除 PC13 的配置位 */
GPIOC->CRH |= (0x2 << 20); /* MODE13=10, CNF13=00 → 推挽输出 2MHz */
/* 步骤 3:主循环,交替拉高拉低 PC13 实现 LED 闪烁 */
while (1)
{
/*
* 使用 BSRR 寄存器原子操作控制引脚电平(推荐方式,无需关中断)
* BSRR 高 16 位 = BRR(置低),低 16 位 = BSR(置高)
*/
/* PC13 置低 → LED 亮 */
GPIOC->BSRR = GPIO_BSRR_BR13; /* BR13 位置 1 → PC13 输出 0 */
delay(500000);
/* PC13 置高 → LED 灭 */
GPIOC->BSRR = GPIO_BSRR_BS13; /* BS13 位置 1 → PC13 输出 1 */
delay(500000);
}
}
1.12.4.3 关键概念解析¶
为什么要先开启时钟?
STM32 采用时钟门控技术:每个外设(GPIO、UART、SPI 等)上电后默认关闭时钟,以降低功耗。使用任何外设前,必须先通过 RCC(复位与时钟控制)寄存器将其时钟打开,否则对外设寄存器的读写操作无效甚至死机。
推挽输出 vs 开漏输出
表 3-10 关键概念解析
| 模式 | 原理 | 适用场景 |
|---|---|---|
| 推挽输出(PP) | 可主动输出高电平(接 VCC)和低电平(接 GND),驱动能力强 | 驱动 LED、继电器等负载 |
| 开漏输出(OD) | 只能主动输出低电平,高电平需外接上拉电阻 | I2C 总线、电平转换 |
LED 闪烁使用推挽输出,因为需要主动驱动 LED 的通断。
HAL 库 vs 寄存器操作对比
表 3-11 关键概念解析
| 维度 | HAL 库 | 寄存器直接操作 |
|---|---|---|
| 代码量 | 较多(含初始化结构体) | 少而精 |
| 可读性 | 高,接近自然语言 | 低,需查手册 |
| 执行效率 | 略低(有封装开销) | 最高 |
| 移植性 | 好,跨 STM32 型号 | 差,型号相关 |
| 学习曲线 | 平缓,入门推荐 | 陡峭,进阶必备 |
建议:初学阶段使用 HAL 库快速上手,理解程序逻辑后再研读寄存器版本,深入掌握硬件工作机制。
3.8 电路图解读基础¶
读懂电路图(原理图,Schematic)是嵌入式开发的基本技能。电路图描述的是电气连接关系,而非物理布局,它回答的核心问题是:谁连接了谁,通过什么方式连接。
3.8.1 LED 电路局部图¶
Blue Pill 上的 LED 电路极为简洁,是理解电路图的最佳入口:
图 3-16 上图以框图形式描绘了 LED 电路局部图的系统架构,清晰呈现了各模块之间的连接关系与信号流向。
读图要点一:追踪网络标号(Net Label)
原理图中,PC13 是一个网络标号(Net Label),凡是标有同一名称的节点,在电气上都是相连的,无论它们在图纸上距离多远。因此:
- 芯片引脚旁的 PC13 标号
- LED 电路一端的 PC13 标号
→ 这两个点在 PCB 上通过铜箔走线直接相连。
3.8.2 从芯片引脚到板上针脚的追踪路径¶
阅读电路图时,需要在三个层次之间建立对应关系:
图 3-17 该框图展示了从芯片引脚到板上针脚的追踪路径的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。
3.8.3 Blue Pill 完整电路图(简化原理图)¶
图 3-18 上图直观呈现了 Blue Pill 完整电路图(简化原理图)的组成要素与数据通路,有助于理解系统整体的工作机理。
3.8.7 读图基本要点¶
要点 1:识别符号(元器件)¶
电路图使用标准符号表示元器件,掌握常见符号是读图的基础:
图 3-19 上图以框图形式描绘了要点 1:识别符号(元器件)的系统架构,清晰呈现了各模块之间的连接关系与信号流向。
要点 2:理解网络标号(Net Label)¶
图 3-20 该框图展示了要点 2:理解网络标号(Net Label)的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。
要点 3:区分电源轨与信号线¶
图 3-21 上图直观呈现了要点 3:区分电源轨与信号线的组成要素与数据通路,有助于理解系统整体的工作机理。
要点 4:读懂芯片引脚图(Pin Diagram)¶
图 3-22 上图以框图形式描绘了要点 4:读懂芯片引脚图(Pin Diagram)的系统架构,清晰呈现了各模块之间的连接关系与信号流向。
要点 5:电流方向与 LED 极性¶
图 3-23 该框图展示了要点 5:电流方向与 LED 极性的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。
要点 6:从原理图到实物排针的对应方法¶
图 3-24 上图直观呈现了要点 6:从原理图到实物排针的对应方法的组成要素与数据通路,有助于理解系统整体的工作机理。
总结:读电路图的核心思路是"追踪信号流"——从信号源(MCU 引脚)出发,沿着同名网络标号,经过元器件符号,找到最终的负载(LED、电机、传感器等)。掌握网络标号、元器件符号和电源轨三个基本规则,就能读懂绝大多数嵌入式开发板的原理图。
在硬件层内部,以 STM32 微控制器(如 STM32F103 系列)的最小系统和一般嵌入式系统为例,其主要组成部分可以详细划分为以下几个核心模块:
- 核心微处理器 (CPU/MCU):整个硬件系统的"大脑",负责执行指令和数据处理。例如基于 ARM Cortex-M3 内核的 STM32 单片机,其内部还集成了 DMA 控制器(用于直接内存存取,减轻 CPU 负担)、NVIC(嵌套向量中断控制器)等核心组件。
- 基本支撑电路 (最小系统要素):使微处理器能够正常工作的最基本物理条件。
- 电源 (Power):为整个系统提供稳定的电压(如 3.3V 稳压电源)。
- 时钟电路 (Clock/Oscillator):包括高速外部时钟(HSE,如 8MHz 晶振)和低速外部时钟(LSE),为 CPU 及外设提供工作的心跳节拍。
- 复位电路 (Reset):用于在系统上电或出现异常时,将系统状态初始化到默认的安全起点。
- 存储器 (Memory):分为非易失性存储器(如用于存放程序代码的 Flash)和易失性存储器(如用于存放运行数据的 SRAM)。
- 外部设备与 I/O 接口 (Peripherals & I/O):微处理器与外部世界进行交互的通道。
- 通用 I/O 端口 (GPIO):用于最基本的信号输入输出,如驱动 LED 灯、读取按键状态等。
- 通信接口:用于数据传输的串口(TTL 电平的 USART/UART)、I2C、SPI、USB、CAN 等通信总线。
- 功能外设:包含模拟数字转换器(ADC)、数字模拟转换器(DAC)、各类定时器(用于延时或输出 PWM 信号控制电机等)。
根据上述构成详细分解,硬件层组成图如下:
图 3-25 上图以框图形式描绘了要点 6:从原理图到实物排针的对应方法的系统架构,清晰呈现了各模块之间的连接关系与信号流向。
依据上述层次,嵌入式系统的构成图如下:
图 3-26 该框图展示了要点 6:从原理图到实物排针的对应方法的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。
3.9 嵌入式电路基础知识¶
在动手搭建或调试嵌入式系统之前,需要掌握一组核心电气概念。这些知识决定了能不能接、能不能驱动、会不会烧,是实践中避免硬件损坏的安全底线。
3.9.1 芯片工作电压¶
表 3-12 不同时代的芯片采用不同的工作电压,混接会导致芯片损坏。
| 芯片类别 | 典型工作电压 | 代表芯片 |
|---|---|---|
| 传统 5V 单片机 | 5V | 51 单片机(AT89C51)、ATmega328(Arduino UNO) |
| 现代 3.3V ARM | 3.3V | STM32 全系列、ESP32、树莓派 GPIO |
| 低功耗 MCU | 1.8V ~ 3.3V | STM32L 系列、nRF52 |
| 高性能 SoC | 1.0V ~ 1.8V(内核)+ 3.3V(I/O) | 树莓派 CPU 内核、i.MX 系列 |
STM32 工作电压详情
图 3-27 上图直观呈现了芯片工作电压的组成要素与数据通路,有助于理解系统整体的工作机理。
注意:部分 STM32 引脚标注了 FT(5V Tolerant),表示该引脚可以容忍 5V 输入,但仍需查手册确认,不可一概而论。
3.9.2 电平标准:高电平与低电平¶
数字电路用电压范围而非精确值来定义"1"和"0",这是因为实际信号总有噪声和波动。
1.15.2.1 TTL 电平(5V 系统,如 Arduino UNO / 51 单片机)¶
图 3-28 上图以框图形式描绘了 TTL 电平(5V 系统,如 Arduino UNO / 51 单片机)的系统架构,清晰呈现了各模块之间的连接关系与信号流向。
1.15.2.2 LVTTL / LVCMOS 电平(3.3V 系统,如 STM32)¶
图 3-29 该框图展示了 LVTTL / LVCMOS 电平(3.3V 系统,如 STM32)的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。
1.15.2.3 两种电平直接互连的风险¶
图 3-30 上图直观呈现了两种电平直接互连的风险的组成要素与数据通路,有助于理解系统整体的工作机理。
3.9.3 三、串口电平(UART / RS-232 / RS-485)¶
串口是嵌入式系统中最常用的通信接口,但"串口"本身只定义了协议,不同场合下使用的电平标准差异极大:
图 3-31 上图以框图形式描绘了三、串口电平(UART / RS-232 / RS-485)的系统架构,清晰呈现了各模块之间的连接关系与信号流向。
STM32 连接 PC 的正确方式:
图 3-32 该框图展示了三、串口电平(UART / RS-232 / RS-485)的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。
3.9.4 四、GPIO 驱动能力与输出功率¶
STM32 的 GPIO 是弱驱动接口,设计用于数字信号控制,不能直接驱动大功率负载。
1.15.4.1 STM32F103 GPIO 电气参数¶
图 3-33 上图直观呈现了 STM32F103 GPIO 电气参数的组成要素与数据通路,有助于理解系统整体的工作机理。
1.15.4.2 GPIO 能直接驱动的器件¶
图 3-34 上图以框图形式描绘了 GPIO 能直接驱动的器件的系统架构,清晰呈现了各模块之间的连接关系与信号流向。
1.15.4.3 三极管驱动电路(GPIO 扩流的基本方法)¶
图 3-35 该框图展示了三极管驱动电路(GPIO 扩流的基本方法)的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。
3.9.5 五、发光二极管(LED)的电压与电流¶
LED 是电流驱动器件(不是电压驱动),必须限流使用,否则会因电流过大烧毁。
1.15.5.1 常见 LED 参数¶
图 3-36 上图直观呈现了常见 LED 参数的组成要素与数据通路,有助于理解系统整体的工作机理。
1.15.5.2 限流电阻计算公式¶
图 3-37 上图以框图形式描绘了限流电阻计算公式的系统架构,清晰呈现了各模块之间的连接关系与信号流向。
3.9.6 六、上拉电阻与下拉电阻¶
GPIO 引脚悬空(未连接任何信号)时,电压值不确定,会随机变化,导致程序逻辑错乱。上下拉电阻用于将悬空引脚钳位到确定的电平。
图 3-38 该框图展示了六、上拉电阻与下拉电阻的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。
STM32 内部上下拉:STM32 GPIO 在配置为输入模式时,可以通过寄存器开启片内上拉(40kΩ)或下拉(40kΩ),无需外接电阻,简化硬件设计。
3.9.7 七、去耦电容(Bypass Capacitor)¶
去耦电容是每个数字芯片电源引脚旁必不可少的小电容,功能是滤除电源噪声、稳定瞬态供电。
图 3-39 上图直观呈现了七、去耦电容(Bypass Capacitor)的组成要素与数据通路,有助于理解系统整体的工作机理。
3.9.8 八、常用元器件电气参数速查¶
图 3-40 上图以框图形式描绘了八、常用元器件电气参数速查的系统架构,清晰呈现了各模块之间的连接关系与信号流向。
安全原则:在嵌入式硬件调试中,遵循"先算后接"的原则——连接任何器件前,先估算工作电流是否在驱动芯片的安全范围内。若电流超出,必须加驱动芯片或三极管,而不是"试试看"。
3.10 STM32 核心接口与外设¶
STM32 芯片集成了丰富的片上外设,每个引脚除了可作为通用 GPIO 外,大多还具备一种或多种复用功能(Alternate Function)。掌握这些接口的特性与应用场景,是读懂数据手册和芯片引脚图的关键。
3.10.1 一、主要接口总览表¶
表 3-13 一、主要接口总览表
| 接口缩写 | 英文全称 | 中文名称 | 主要作用 | 典型应用 |
|---|---|---|---|---|
| GPIO | General Purpose Input/Output | 通用输入输出 | 可软件配置为输入或输出,读取/控制数字信号 | LED 控制、按键检测、继电器驱动 |
| USART | Universal Synchronous/Asynchronous Receiver/Transmitter | 通用同步/异步收发器 | 串行数据收发,全双工,最常用通信接口 | 调试输出、GPS 模块、蓝牙模块、串口屏 |
| SPI | Serial Peripheral Interface | 串行外设接口 | 高速同步全双工串行总线,主从结构 | SD 卡、Flash 存储、LCD 屏幕、ADC/DAC 芯片 |
| I2C | Inter-Integrated Circuit | 集成电路间总线 | 两线制同步串行总线,支持多设备挂载 | OLED 屏、MPU-6050 陀螺仪、温湿度传感器、EEPROM |
| CAN | Controller Area Network | 控制器局域网 | 差分总线,高抗干扰,多节点广播 | 汽车电子、工业机器人关节控制、电机驱动器 |
| USB | Universal Serial Bus | 通用串行总线 | 高速设备与主机通信 | 模拟虚拟串口(CDC)、HID 设备、大容量存储 |
| ADC | Analog-to-Digital Converter | 模数转换器 | 将模拟电压值转为数字量 | 电位器读值、电池电压监测、传感器模拟量采集 |
| DAC | Digital-to-Analog Converter | 数模转换器 | 将数字量输出为模拟电压 | 音频输出、模拟信号发生、电机平滑控制 |
| TIM | Timer | 定时器 | 精确计时、产生 PWM、捕获输入信号 | 电机 PWM 调速、舵机控制、超声波测距、编码器读值 |
| IWDG | Independent Watchdog | 独立看门狗 | 防止程序跑飞,自动复位系统 | 所有需要高可靠性的嵌入式产品 |
| WWDG | Window Watchdog | 窗口看门狗 | 比 IWDG 更严格,必须在时间窗口内喂狗 | 实时性要求严格的控制系统 |
| RTC | Real-Time Clock | 实时时钟 | 低功耗运行,维持日期和时间 | 数据记录时间戳、定时唤醒、闹钟功能 |
| DMA | Direct Memory Access | 直接内存访问 | 不经过 CPU,外设与内存直接传输数据 | ADC 连续采样、串口大数据收发、SPI 批量传输 |
| NVIC | Nested Vectored Interrupt Controller | 嵌套向量中断控制器 | 管理所有中断的优先级与响应 | 所有需要实时响应的事件处理 |
| EXTI | External Interrupt/Event Controller | 外部中断/事件控制器 | 检测 GPIO 引脚电平变化并触发中断 | 按键中断、编码器计数、传感器触发信号 |
| SWD | Serial Wire Debug | 串行线调试接口 | 程序烧录与在线调试 | Keil/CubeIDE 连接 ST-Link 调试器 |
3.10.2 二、重点接口详解¶
1.16.2.1 GPIO — 通用输入输出¶
GPIO 是最基础也是使用最频繁的外设,每个引脚均可独立配置工作模式:
图 3-41 该框图展示了 GPIO — 通用输入输出的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。
1.16.2.2 TIM — 定时器¶
定时器是 STM32 中功能最强大、使用最复杂的外设之一,远不止"计时"那么简单:
图 3-42 上图直观呈现了 TIM — 定时器的组成要素与数据通路,有助于理解系统整体的工作机理。
1.16.2.3 ADC — 模数转换器¶
图 3-43 上图以框图形式描绘了 ADC — 模数转换器的系统架构,清晰呈现了各模块之间的连接关系与信号流向。
3.10.3 三、串行通信接口横向对比¶
STM32 支持多种串行通信协议,选择哪种取决于距离、速率、节点数和抗干扰要求:
1.16.3.1 接口特性速查¶
表 3-14 接口特性速查
| 特性 | UART/USART | SPI | I2C | CAN |
|---|---|---|---|---|
| 信号线数 | 2(TX/RX) | 4(MOSI/MISO/SCK/CS) | 2(SDA/SCL) | 2(CAN_H/CAN_L) |
| 通信方式 | 全双工异步 | 全双工同步 | 半双工同步 | 半双工差分 |
| 最高速率 | ~5 Mbps | ~45 Mbps | 100k/400k/3.4M bps | 1 Mbps |
| 最大距离 | 数十米(TTL) | <1 m(板级) | <1 m(板级) | 40m@1Mbps / 1km@50kbps |
| 节点数 | 点对点 | 1主多从(CS片选) | 1主多从(地址寻址,最多127) | 多主多从(最多110节点) |
| 典型用途 | 调试、模块通信 | 高速板级外设 | 短距离低速传感器 | 汽车/工业总线 |
上表对接口特性速查中各方案的特性进行了横向对比,便于读者根据实际需求选择最合适的技术路线。
1.16.3.2 UART / RS-232 / RS-485 / CAN 深度对比¶
这四种协议在日常工作中最容易混淆,需从物理层与协议层两个维度理解它们的本质区别:
图 3-44 该框图展示了 UART / RS-232 / RS-485 / CAN 深度对比的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。
1.16.3.3 关键区别可视化¶
图 3-45 上图直观呈现了关键区别可视化的组成要素与数据通路,有助于理解系统整体的工作机理。
1.16.3.4 协议选型指南¶
图 3-46 上图以框图形式描绘了协议选型指南的系统架构,清晰呈现了各模块之间的连接关系与信号流向。
3.10.4 CAN 总线编程实战¶
CAN(Controller Area Network)是机器人系统中最重要的工业通信总线——从汽车 ECU 到工业机器人多关节控制,几乎所有需要可靠多节点通信的场景都使用 CAN。STM32 片上集成了 bxCAN 控制器,原生支持 CAN 2.0A/B 协议。
CAN 2.0 协议基础¶
CAN 帧结构(标准帧 CAN 2.0A):
表 3-15 CAN 2.0 协议基础
| 字段 | 位数 | 说明 |
|---|---|---|
| SOF | 1 | 帧起始(显性位) |
| 仲裁段 | 12 | 11 位 ID + 1 位 RTR(远程帧标志) |
| 控制段 | 6 | IDE + r0 + 4 位 DLC(数据长度 0-8) |
| 数据段 | 0-64 | 0~8 字节有效数据 |
| CRC 段 | 16 | 15 位 CRC + 1 位定界符 |
| ACK 段 | 2 | 接收方应答 |
| EOF | 7 | 帧结束 |
通过上表的对比可以看出,不同方案在字段、位数、说明等方面各有优劣,实际选型时应结合具体应用场景综合权衡。
图 3-47 该框图展示了 CAN 2.0 协议基础的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。
CAN 总线仲裁机制:
CAN 采用非破坏性位仲裁——多个节点同时发送时,ID 最小(最高优先级)的报文获得总线控制权,其余节点自动退让并在稍后重试:
- 显性位(Dominant, 逻辑 0):CAN_H - CAN_L ≈ +2V
-
隐性位(Recessive, 逻辑 1):CAN_H - CAN_L ≈ 0V
-
当显性位与隐性位同时出现时,总线呈现显性("线与"逻辑)
图 3-48 上图直观呈现了 CAN 2.0 协议基础的组成要素与数据通路,有助于理解系统整体的工作机理。
CAN 错误检测机制(五重保护):
表 3-16
| 检测方式 | 原理 |
|---|---|
| CRC 校验 | 15 位 CRC,检测数据传输错误 |
| 位填充 | 连续 5 个相同位后插入反转位,防止时钟偏移 |
| 帧格式检查 | 检查固定格式字段(SOF/EOF/ACK)是否正确 |
| ACK 检查 | 发送方检查是否收到接收方的应答 |
| 位监控 | 发送方监控总线状态是否与发送值一致 |
上表对 CAN 2.0 协议基础的核心信息进行了结构化整理,读者可根据需要快速查阅相关内容。
STM32 bxCAN 外设架构¶
STM32F103 集成 bxCAN 控制器,支持 CAN 2.0A/B,具有 3 个发送邮箱和 2 个接收 FIFO(各含 3 级深度缓冲):
图 3-49 上图以框图形式描绘了 STM32 bxCAN 外设架构的系统架构,清晰呈现了各模块之间的连接关系与信号流向。
CubeMX 配置 CAN¶
在 STM32CubeMX 中配置 CAN 外设的关键步骤:
- 启用 CAN:Connectivity → CAN → Mode: Master Mode
- 波特率配置:
CAN 波特率 = APB1 时钟 / (Prescaler × (BS1 + BS2 + 1))
对于 36MHz APB1 时钟,常用配置:
| 目标波特率 | Prescaler | BS1 | BS2 | 采样点位置 |
|---|---|---|---|---|
| 1 Mbps | 4 | 6 TQ | 2 TQ | 77.8% |
| 500 kbps | 8 | 6 TQ | 2 TQ | 77.8% |
| 250 kbps | 16 | 6 TQ | 2 TQ | 77.8% |
| 125 kbps | 32 | 6 TQ | 2 TQ | 77.8% |
- 引脚分配:CAN_TX → PA12,CAN_RX → PA11(或重映射至 PB9/PB8)
- 中断配置:启用 CAN RX0 中断(接收 FIFO 0)
HAL 库 CAN 编程¶
初始化与滤波器配置:
// can_init.c
#include "main.h"
CAN_HandleTypeDef hcan;
void CAN_Init(void) {
hcan.Instance = CAN1;
hcan.Init.Prescaler = 4; // 36MHz / 4 = 9MHz
hcan.Init.Mode = CAN_MODE_NORMAL;
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan.Init.TimeSeg1 = CAN_BS1_6TQ; // BS1 = 6
hcan.Init.TimeSeg2 = CAN_BS2_2TQ; // BS2 = 2
// 波特率 = 9MHz / (1+6+2) = 1 Mbps
hcan.Init.TimeTriggeredMode = DISABLE;
hcan.Init.AutoBusOff = ENABLE; // 自动恢复
hcan.Init.AutoWakeUp = DISABLE;
hcan.Init.AutoRetransmission = ENABLE; // 自动重传
hcan.Init.ReceiveFifoLocked = DISABLE;
hcan.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan) != HAL_OK) {
Error_Handler();
}
// 配置接收滤波器(接收所有报文)
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; // 掩码全0=接收所有
filter.FilterMaskIdLow = 0x0000;
filter.FilterFIFOAssignment = CAN_RX_FIFO0;
filter.FilterActivation = ENABLE;
if (HAL_CAN_ConfigFilter(&hcan, &filter) != HAL_OK) {
Error_Handler();
}
// 启动 CAN 和接收中断
HAL_CAN_Start(&hcan);
HAL_CAN_ActivateNotification(&hcan,
CAN_IT_RX_FIFO0_MSG_PENDING);
}
发送 CAN 报文:
// can_tx.c — 发送电机速度指令
void CAN_SendMotorCommand(uint16_t motor_id,
int16_t target_rpm) {
CAN_TxHeaderTypeDef tx_header;
uint8_t tx_data[4];
uint32_t tx_mailbox;
// 设置报文头
tx_header.StdId = 0x200 + motor_id; // 标准 ID
tx_header.ExtId = 0;
tx_header.RTR = CAN_RTR_DATA; // 数据帧
tx_header.IDE = CAN_ID_STD; // 标准帧
tx_header.DLC = 4; // 4 字节数据
tx_header.TransmitGlobalTime = DISABLE;
// 数据段:目标转速(大端序)
tx_data[0] = (target_rpm >> 8) & 0xFF; // RPM 高字节
tx_data[1] = target_rpm & 0xFF; // RPM 低字节
tx_data[2] = 0x01; // 使能标志
tx_data[3] = 0x00; // 保留
// 发送到空闲邮箱
if (HAL_CAN_AddTxMessage(&hcan, &tx_header,
tx_data, &tx_mailbox) != HAL_OK) {
// 所有邮箱满,处理拥塞
Error_Handler();
}
}
接收 CAN 报文(中断模式):
// can_rx.c — 中断回调接收电机反馈
typedef struct {
int16_t rpm; // 实际转速
int16_t current; // 实际电流 (mA)
uint16_t position; // 编码器位置
} MotorFeedback_t;
volatile MotorFeedback_t motor_fb[4]; // 4 个电机的反馈
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);
// 根据 ID 解析不同电机的反馈
if (rx_header.StdId >= 0x201 && rx_header.StdId <= 0x204) {
uint8_t idx = rx_header.StdId - 0x201;
motor_fb[idx].rpm =
(int16_t)((rx_data[0] << 8) | rx_data[1]);
motor_fb[idx].current =
(int16_t)((rx_data[2] << 8) | rx_data[3]);
motor_fb[idx].position =
(uint16_t)((rx_data[4] << 8) | rx_data[5]);
}
}
接收滤波器高级配置¶
在多节点 CAN 网络中,通过滤波器只接收关心的报文,减轻 CPU 负担:
// 滤波器示例:只接收 ID 范围 0x201~0x204 的报文
void CAN_ConfigMotorFilter(void) {
CAN_FilterTypeDef filter;
filter.FilterBank = 1;
filter.FilterMode = CAN_FILTERMODE_IDLIST; // ID 列表模式
filter.FilterScale = CAN_FILTERSCALE_16BIT; // 16 位
// 两个 16 位 ID 列表(每个 Bank 可放 4 个 16 位 ID)
filter.FilterIdHigh = 0x201 << 5; // ID 0x201
filter.FilterIdLow = 0x202 << 5; // ID 0x202
filter.FilterMaskIdHigh = 0x203 << 5; // ID 0x203
filter.FilterMaskIdLow = 0x204 << 5; // ID 0x204
filter.FilterFIFOAssignment = CAN_RX_FIFO0;
filter.FilterActivation = ENABLE;
HAL_CAN_ConfigFilter(&hcan, &filter);
}
滤波器模式对比:
表 3-17 接收滤波器高级配置
| 模式 | 原理 | 适用场景 |
|---|---|---|
| 掩码模式(IDMASK) | ID & Mask == FilterID & Mask | 接收一个 ID 范围(如 0x200~0x20F) |
| 列表模式(IDLIST) | ID == FilterID1 或 ID == FilterID2 | 只接收指定的几个 ID |
通过上表的对比可以看出,不同方案在模式、原理、适用场景等方面各有优劣,实际选型时应结合具体应用场景综合权衡。
CAN 总线在机器人系统中的应用¶
图 3-50 该框图展示了 CAN 总线在机器人系统中的应用的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。
CAN 在机器人领域的典型应用:
表 3-18
| 应用场景 | CAN ID 分配策略 | 波特率 | 说明 |
|---|---|---|---|
| 多关节机械臂 | 每关节一个 ID 段 | 1 Mbps | 各关节电机驱动器通过 CAN 接收位置/速度指令 |
| AGV 底盘 | 功能分组 | 500 kbps | 驱动电机、转向、传感器分组通信 |
| RoboMaster 机器人 | C620 电调协议 | 1 Mbps | 大疆 C620 电调使用 CAN 接收电流指令和反馈转速 |
| 汽车电子 | SAE J1939 | 250 kbps | 发动机、变速箱、仪表盘通过 CAN 互联 |
3.11 本章小结¶
表 3-19 本章系统介绍了 STM32 微控制器开发的核心基础:
| 主题 | 关键内容 |
|---|---|
| 芯片与开发板 | STM32F103C8T6 架构、Blue Pill 硬件资源 |
| 开发工具 | CubeMX 图形化配置、HAL 库编程模型 |
| GPIO | 推挽/开漏/浮空/上拉等 8 种工作模式 |
| 时钟系统 | HSE/HSI/PLL 时钟树、72MHz 主频配置 |
| 电路读图 | 原理图符号、网络标号、去耦电容、上拉电阻 |
| 电气基础 | 工作电压、电平兼容、限流电阻、分压电路 |
| 通信接口 | UART/SPI/I2C/CAN 特性对比与选型 |
📌 实践项目请参见 附录 C:STM32 实践项目集,包含 5 个由浅入深的实战项目(流水灯、呼吸灯、电压表、串口控制台、智能温控风扇)。
3.12 本章测验¶
Quiz results are saved to your browser's local storage and will persist between sessions.
1) STM32F103C8T6 的 "C8" 表示什么?
2) 在 STM32CubeMX 中配置 GPIO 为推挽输出模式时,以下哪项描述是正确的?
3) 以下哪些是 SPI 通信相比 I2C 的优势?
4) 当 STM32 的 GPIO 需要连接 5 V 器件时,以下哪项措施是正确的?
Quiz Progress
0 / 0 questions answered (0%)
0 correct