文章目录
-
- 摘要
- [1. 什么是 STM32](#1. 什么是 STM32)
-
- [STM32 常见系列对比](#STM32 常见系列对比)
- [2. 为什么选择 STM32 入门](#2. 为什么选择 STM32 入门)
- [3. 开发环境搭建](#3. 开发环境搭建)
-
- [3.1 物料清单](#3.1 物料清单)
- [3.2 Keil MDK 安装](#3.2 Keil MDK 安装)
- [3.3 STM32CubeMX 安装](#3.3 STM32CubeMX 安装)
- [4. 第一个工程:点亮 LED(GPIO 入门)](#4. 第一个工程:点亮 LED(GPIO 入门))
-
- [4.1 硬件原理](#4.1 硬件原理)
- [4.2 CubeMX 配置步骤](#4.2 CubeMX 配置步骤)
- [4.3 核心代码](#4.3 核心代码)
- [4.4 编译与烧录](#4.4 编译与烧录)
- [5. 时钟系统初探](#5. 时钟系统初探)
-
- [5.1 STM32F103 时钟树(简化版)](#5.1 STM32F103 时钟树(简化版))
- [5.2 时钟配置验证代码](#5.2 时钟配置验证代码)
- [6. 串口通信:打印 "Hello World"](#6. 串口通信:打印 "Hello World")
-
- [6.1 CubeMX 配置](#6.1 CubeMX 配置)
- [6.2 重定向 printf 到串口](#6.2 重定向 printf 到串口)
- [6.3 在 main() 中调用](#6.3 在 main() 中调用)
- [7. 中断入门:按键控制 LED](#7. 中断入门:按键控制 LED)
-
- [7.1 硬件接线](#7.1 硬件接线)
- [7.2 CubeMX 配置外部中断](#7.2 CubeMX 配置外部中断)
- [7.3 中断回调函数](#7.3 中断回调函数)
- [7.4 主循环响应](#7.4 主循环响应)
- [8. 实战项目:PWM 呼吸灯 + 串口控制](#8. 实战项目:PWM 呼吸灯 + 串口控制)
-
- [8.1 项目需求](#8.1 项目需求)
- [8.2 PWM 配置(CubeMX)](#8.2 PWM 配置(CubeMX))
- [8.3 PWM 呼吸灯代码](#8.3 PWM 呼吸灯代码)
- [8.4 串口命令解析](#8.4 串口命令解析)
- [8.5 主循环整合](#8.5 主循环整合)
- [9. 常见踩坑与解决](#9. 常见踩坑与解决)
- [10. 进阶学习路线](#10. 进阶学习路线)
- [11. 总结](#11. 总结)
- 参考资料
摘要
本文面向零基础嵌入式新手,以 STM32F103C8T6(Blue Pill 最小系统板) 为硬件平台,STM32CubeMX + Keil MDK + HAL 库 为软件栈,手把手带你完成环境搭建、GPIO 点灯、串口通信、外部中断与 PWM 呼吸灯四个递进式实战项目。全文包含 12 个可运行代码块、5 张架构图、完整的踩坑记录及进阶学习路线,跟着走一遍即可独立上手 STM32 开发。
1. 什么是 STM32
STM32 是意法半导体(STMicroelectronics)推出的基于 ARM Cortex-M 内核的 32 位微控制器(MCU)家族。你可以把它理解为一块功能强大的"可编程芯片大脑",广泛应用于:
- 智能家居(温控器、智能灯)
- 无人机 / 机器人(飞控、电机驱动)
- 工业控制(PLC、传感器采集)
- IoT 设备(联网传感器节点)
一句话理解:STM32 = 高性能计算核心 + 丰富的外设接口(GPIO/串口/I2C/SPI/PWM/ADC)+ 极低功耗,是嵌入式开发入门的首选平台。
STM32 常见系列对比
| 系列 | 内核 | 主频 | Flash 典型值 | 定位 | 典型型号 |
|---|---|---|---|---|---|
| F0 | Cortex-M0 | 48 MHz | 16-256 KB | 低成本入门 | STM32F030 |
| F1 | Cortex-M3 | 72 MHz | 16-512 KB | 经典入门(本文使用) | STM32F103C8T6 |
| F4 | Cortex-M4 | 180 MHz | 256-2048 KB | 高性能 + DSP | STM32F407 |
| G0 | Cortex-M0+ | 64 MHz | 16-512 KB | 新一代性价比 | STM32G030 |
| H7 | Cortex-M7 | 480+ MHz | 1-2 MB | 旗舰性能 | STM32H743 |
新手推荐 F103C8T6:资料最多、生态最成熟、价格低廉(约 ¥10),遇到问题几乎都能在网上找到答案。
2. 为什么选择 STM32 入门
相比 Arduino,STM32 的学习曲线更陡峭,但收益也更大:
| 维度 | Arduino | STM32(HAL 库) |
|---|---|---|
| 开发方式 | 图形化封装,隐藏底层 | CubeMX 图形化 + 代码自由控制 |
| 性能 | 8 位 AVR,16 MHz | 32 位 ARM,72 MHz |
| 外设控制 | 函数封装简单但灵活性差 | HAL 库 API 统一,底层可深入 |
| 就业价值 | 玩具级 | 企业级,招聘硬通货 |
| 学习资源 | 丰富 | 极其丰富 |
Arduino 让你知道"能做什么",STM32 让你理解"为什么这么做"。
3. 开发环境搭建
本节完成三大件的安装,预计耗时 30 分钟。
3.1 物料清单
| 物料 | 型号 / 版本 | 用途 | 参考价格 |
|---|---|---|---|
| 开发板 | STM32F103C8T6 最小系统板 | 主控 | ~¥10 |
| 下载器 | ST-Link V2 | 烧录程序 | ~¥15 |
| 杜邦线 | 母对母 × 4 | 接线 | ~¥2 |
| LED + 电阻 | 5mm LED + 220Ω | 点灯实验 | ~¥1 |
| USB-TTL | CH340 模块(可选) | 串口打印 | ~¥5 |
3.2 Keil MDK 安装
- 访问 Keil 官网 下载 MDK-ARM
- 安装时选择默认路径(避免中文路径)
- 安装完成后,下载 STM32F1 的 Pack 包 :Keil.STM32F1xx_DFP
重要:Keil 未注册时有 32KB 代码限制,F103C8T6 的 Flash 为 64KB,入门阶段够用。如需解除限制,请购买正版 License。
3.3 STM32CubeMX 安装
- 访问 STM32CubeMX 官方页面 下载
- 安装完成后,打开软件 →
Help→Manage embedded software packages→ 勾选STM32F1→ 安装 - CubeMX 的核心功能:图形化配置引脚、时钟树和外设,自动生成初始化代码
#mermaid-svg-XKOmglRGtq42xv8a{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XKOmglRGtq42xv8a .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XKOmglRGtq42xv8a .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XKOmglRGtq42xv8a .error-icon{fill:#552222;}#mermaid-svg-XKOmglRGtq42xv8a .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XKOmglRGtq42xv8a .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XKOmglRGtq42xv8a .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XKOmglRGtq42xv8a .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XKOmglRGtq42xv8a .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XKOmglRGtq42xv8a .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XKOmglRGtq42xv8a .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XKOmglRGtq42xv8a .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XKOmglRGtq42xv8a .marker.cross{stroke:#333333;}#mermaid-svg-XKOmglRGtq42xv8a svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XKOmglRGtq42xv8a p{margin:0;}#mermaid-svg-XKOmglRGtq42xv8a .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XKOmglRGtq42xv8a .cluster-label text{fill:#333;}#mermaid-svg-XKOmglRGtq42xv8a .cluster-label span{color:#333;}#mermaid-svg-XKOmglRGtq42xv8a .cluster-label span p{background-color:transparent;}#mermaid-svg-XKOmglRGtq42xv8a .label text,#mermaid-svg-XKOmglRGtq42xv8a span{fill:#333;color:#333;}#mermaid-svg-XKOmglRGtq42xv8a .node rect,#mermaid-svg-XKOmglRGtq42xv8a .node circle,#mermaid-svg-XKOmglRGtq42xv8a .node ellipse,#mermaid-svg-XKOmglRGtq42xv8a .node polygon,#mermaid-svg-XKOmglRGtq42xv8a .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XKOmglRGtq42xv8a .rough-node .label text,#mermaid-svg-XKOmglRGtq42xv8a .node .label text,#mermaid-svg-XKOmglRGtq42xv8a .image-shape .label,#mermaid-svg-XKOmglRGtq42xv8a .icon-shape .label{text-anchor:middle;}#mermaid-svg-XKOmglRGtq42xv8a .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XKOmglRGtq42xv8a .rough-node .label,#mermaid-svg-XKOmglRGtq42xv8a .node .label,#mermaid-svg-XKOmglRGtq42xv8a .image-shape .label,#mermaid-svg-XKOmglRGtq42xv8a .icon-shape .label{text-align:center;}#mermaid-svg-XKOmglRGtq42xv8a .node.clickable{cursor:pointer;}#mermaid-svg-XKOmglRGtq42xv8a .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XKOmglRGtq42xv8a .arrowheadPath{fill:#333333;}#mermaid-svg-XKOmglRGtq42xv8a .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XKOmglRGtq42xv8a .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XKOmglRGtq42xv8a .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XKOmglRGtq42xv8a .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XKOmglRGtq42xv8a .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XKOmglRGtq42xv8a .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XKOmglRGtq42xv8a .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XKOmglRGtq42xv8a .cluster text{fill:#333;}#mermaid-svg-XKOmglRGtq42xv8a .cluster span{color:#333;}#mermaid-svg-XKOmglRGtq42xv8a div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XKOmglRGtq42xv8a .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XKOmglRGtq42xv8a rect.text{fill:none;stroke-width:0;}#mermaid-svg-XKOmglRGtq42xv8a .icon-shape,#mermaid-svg-XKOmglRGtq42xv8a .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XKOmglRGtq42xv8a .icon-shape p,#mermaid-svg-XKOmglRGtq42xv8a .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XKOmglRGtq42xv8a .icon-shape .label rect,#mermaid-svg-XKOmglRGtq42xv8a .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XKOmglRGtq42xv8a .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XKOmglRGtq42xv8a .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XKOmglRGtq42xv8a :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} STM32CubeMX
图形化配置
生成 HAL 库
初始化代码
Keil MDK
编写业务逻辑
ST-Link
烧录到开发板
开发板运行
图 1:STM32 标准开发流程
4. 第一个工程:点亮 LED(GPIO 入门)
嵌入式世界的 "Hello World"------点亮一颗 LED。本节涵盖从 0 到点亮的完整流程。
4.1 硬件原理
LED 正极通过 220Ω 限流电阻接 PA1 引脚,负极接 GND。当 PA1 输出**高电平(3.3V)**时 LED 点亮。
PA1 ──[220Ω]──▶|── GND
LED
4.2 CubeMX 配置步骤
- 新建工程 :
File→New Project→ 搜索STM32F103C8 - 配置调试接口 :
Pinout & Configuration→SYS→Debug→ 选择Serial Wire - 配置 GPIO :点击 PA1 → 选择
GPIO_Output - 时钟配置 :
Clock Configuration标签页 → HCLK 输入 72 → 回车自动配置 - 项目设置 :
Project Manager→ 填入项目名LED_Blink→ Toolchain 选MDK-ARM→Generate Code
4.3 核心代码
生成代码后在 Core/Src/main.c 中找到 while(1) 循环,写入以下代码:
c
/* USER CODE BEGIN 3 */
// 点亮 LED
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
HAL_Delay(500); // 延时 500ms
// 熄灭 LED
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
HAL_Delay(500); // 延时 500ms
/* USER CODE END 3 */
关键代码解读:
| 行 | 代码 | 作用 |
|---|---|---|
| 1 | HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET) |
PA1 输出高电平,LED 亮 |
| 2 | HAL_Delay(500) |
延时 500 毫秒(阻塞式) |
| 3 | HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET) |
PA1 输出低电平,LED 灭 |
HAL 库命名规则 :
HAL_外设名_操作(参数),统一且易读。
4.4 编译与烧录
- Keil 中点击
Build(F7),确认 0 Error 0 Warning - 连接 ST-Link:
SWDIO ↔ SWIO,SWCLK ↔ SWCLK,GND ↔ GND - 点击
Load(F8)烧录 - 观察 LED 以 1Hz 频率闪烁------恭喜,你已成功入门 STM32!
5. 时钟系统初探
理解时钟是 STM32 进阶的第一步。时钟就是 MCU 的心跳,决定了外设的工作频率。
5.1 STM32F103 时钟树(简化版)
#mermaid-svg-DffmjdBx7ey0Wrjw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DffmjdBx7ey0Wrjw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DffmjdBx7ey0Wrjw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DffmjdBx7ey0Wrjw .error-icon{fill:#552222;}#mermaid-svg-DffmjdBx7ey0Wrjw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DffmjdBx7ey0Wrjw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DffmjdBx7ey0Wrjw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DffmjdBx7ey0Wrjw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DffmjdBx7ey0Wrjw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DffmjdBx7ey0Wrjw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DffmjdBx7ey0Wrjw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DffmjdBx7ey0Wrjw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DffmjdBx7ey0Wrjw .marker.cross{stroke:#333333;}#mermaid-svg-DffmjdBx7ey0Wrjw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DffmjdBx7ey0Wrjw p{margin:0;}#mermaid-svg-DffmjdBx7ey0Wrjw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DffmjdBx7ey0Wrjw .cluster-label text{fill:#333;}#mermaid-svg-DffmjdBx7ey0Wrjw .cluster-label span{color:#333;}#mermaid-svg-DffmjdBx7ey0Wrjw .cluster-label span p{background-color:transparent;}#mermaid-svg-DffmjdBx7ey0Wrjw .label text,#mermaid-svg-DffmjdBx7ey0Wrjw span{fill:#333;color:#333;}#mermaid-svg-DffmjdBx7ey0Wrjw .node rect,#mermaid-svg-DffmjdBx7ey0Wrjw .node circle,#mermaid-svg-DffmjdBx7ey0Wrjw .node ellipse,#mermaid-svg-DffmjdBx7ey0Wrjw .node polygon,#mermaid-svg-DffmjdBx7ey0Wrjw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DffmjdBx7ey0Wrjw .rough-node .label text,#mermaid-svg-DffmjdBx7ey0Wrjw .node .label text,#mermaid-svg-DffmjdBx7ey0Wrjw .image-shape .label,#mermaid-svg-DffmjdBx7ey0Wrjw .icon-shape .label{text-anchor:middle;}#mermaid-svg-DffmjdBx7ey0Wrjw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DffmjdBx7ey0Wrjw .rough-node .label,#mermaid-svg-DffmjdBx7ey0Wrjw .node .label,#mermaid-svg-DffmjdBx7ey0Wrjw .image-shape .label,#mermaid-svg-DffmjdBx7ey0Wrjw .icon-shape .label{text-align:center;}#mermaid-svg-DffmjdBx7ey0Wrjw .node.clickable{cursor:pointer;}#mermaid-svg-DffmjdBx7ey0Wrjw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DffmjdBx7ey0Wrjw .arrowheadPath{fill:#333333;}#mermaid-svg-DffmjdBx7ey0Wrjw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DffmjdBx7ey0Wrjw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DffmjdBx7ey0Wrjw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DffmjdBx7ey0Wrjw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DffmjdBx7ey0Wrjw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DffmjdBx7ey0Wrjw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DffmjdBx7ey0Wrjw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DffmjdBx7ey0Wrjw .cluster text{fill:#333;}#mermaid-svg-DffmjdBx7ey0Wrjw .cluster span{color:#333;}#mermaid-svg-DffmjdBx7ey0Wrjw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-DffmjdBx7ey0Wrjw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DffmjdBx7ey0Wrjw rect.text{fill:none;stroke-width:0;}#mermaid-svg-DffmjdBx7ey0Wrjw .icon-shape,#mermaid-svg-DffmjdBx7ey0Wrjw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DffmjdBx7ey0Wrjw .icon-shape p,#mermaid-svg-DffmjdBx7ey0Wrjw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DffmjdBx7ey0Wrjw .icon-shape .label rect,#mermaid-svg-DffmjdBx7ey0Wrjw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DffmjdBx7ey0Wrjw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DffmjdBx7ey0Wrjw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DffmjdBx7ey0Wrjw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 外部晶振 HSE
8 MHz
PLL 倍频器
×9
SYSCLK
72 MHz
AHB 总线
72 MHz
APB1
36 MHz
APB2
72 MHz
CPU / DMA / Flash
USART2/3, SPI2, I2C
GPIO, USART1, SPI1, TIM1
图 4:STM32F103C8T6 时钟树简化图,SYSCLK = HSE × 9 = 72 MHz
5.2 时钟配置验证代码
c
/* 获取并打印系统时钟频率 */
uint32_t sys_clk = HAL_RCC_GetSysClockFreq();
uint32_t hclk = HAL_RCC_GetHCLKFreq();
uint32_t pclk1 = HAL_RCC_GetPCLK1Freq();
uint32_t pclk2 = HAL_RCC_GetPCLK2Freq();
printf("SYSCLK: %lu Hz\r\n", sys_clk); // 输出 72000000
printf("HCLK: %lu Hz\r\n", hclk); // 输出 72000000
printf("PCLK1: %lu Hz\r\n", pclk1); // 输出 36000000
printf("PCLK2: %lu Hz\r\n", pclk2); // 输出 72000000
各外设挂在不同的总线上,配置外设前务必确认它所属的时钟域。比如 USART1 在 APB2(72 MHz),USART2 在 APB1(36 MHz)。
6. 串口通信:打印 "Hello World"
6.1 CubeMX 配置
- 打开之前的工程,在 CubeMX 中启用 USART1:
Pinout→ 点击 PA9(TX)/ PA10(RX)→ 选择USART1 Configuration→USART1→ Mode 选Asynchronous,波特率115200,其余默认- 重新生成代码
6.2 重定向 printf 到串口
在 Core/Src/main.c 的 /* USER CODE BEGIN 0 */ 区域添加:
c
/* USER CODE BEGIN 0 */
#include <stdio.h>
/* 重定向 printf 到 USART1 */
int __io_putchar(int ch) {
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
/* USER CODE END 0 */
6.3 在 main() 中调用
c
/* USER CODE BEGIN 2 */
printf("=====================\r\n");
printf("STM32F103 启动成功!\r\n");
printf("SYSCLK: %lu Hz\r\n", HAL_RCC_GetSysClockFreq());
printf("Hello, STM32 World!\r\n");
printf("=====================\r\n");
/* USER CODE END 2 */
使用 USB-TTL 模块连接 PA9(RX)、PA10(TX)、GND,打开串口助手(波特率 115200),即可看到打印输出。
7. 中断入门:按键控制 LED
轮询(Polling)会阻塞 CPU,中断(Interrupt)才是嵌入式开发的正确姿势。
7.1 硬件接线
按键一端接 PB12 ,另一端接 GND。配置 PB12 为 GPIO_Input,内部上拉(Pull-up)。
7.2 CubeMX 配置外部中断
- 点击 PB12 → 选择
GPIO_EXTI12 GPIO→ PB12 →GPIO mode选External Interrupt Mode with Falling edge triggerNVIC标签页 → 勾选EXTI line[15:10] interrupts→ 设置优先级
7.3 中断回调函数
c
/* 按键标志位(在 main.c 顶部 USER CODE BEGIN PV 区域声明) */
volatile uint8_t button_pressed = 0;
/* 外部中断回调(放在 USER CODE BEGIN 4 区域) */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == GPIO_PIN_12) {
button_pressed = 1; // 置标志位,不在中断里做耗时操作
}
}
7.4 主循环响应
c
/* USER CODE BEGIN 3 */
while (1) {
if (button_pressed) {
button_pressed = 0;
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1); // 翻转 LED
printf("按键按下,LED 已翻转\r\n");
}
HAL_Delay(10); // 简单消抖
}
/* USER CODE END 3 */
中断编程黄金法则 :中断回调中只做标志位置位 / 极轻量操作,耗时的处理放到主循环。中断内调用
HAL_Delay()或printf()会导致不可预知的问题。
8. 实战项目:PWM 呼吸灯 + 串口控制
学完以上基础,现在把它们整合成一个完整项目。
8.1 项目需求
| 功能 | 描述 |
|---|---|
| 呼吸灯 | LED 通过 PWM 实现渐亮渐灭,周期 3 秒 |
| 串口控制 | 通过串口发送 ON / OFF / BREATH 切换模式 |
| 按键控制 | 短按切换模式,长按 1 秒恢复默认 |
8.2 PWM 配置(CubeMX)
Timers→TIM2→Clock Source选Internal ClockChannel 1选PWM Generation CH1- 参数设置:
Prescaler = 71,Counter Period = 999- PWM 频率 = 72 MHz / (71+1) / (999+1) = 1 kHz
- 占空比范围:0 ~ 999
8.3 PWM 呼吸灯代码
c
/* 呼吸灯函数 */
void breath_led(void) {
static uint16_t pwm_val = 0;
static int8_t direction = 1;
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pwm_val);
pwm_val += direction;
if (pwm_val >= 999) direction = -1;
if (pwm_val == 0) direction = 1;
HAL_Delay(3); // 3ms × 1000 步 = 3 秒一个周期
}
8.4 串口命令解析
c
/* 串口接收缓冲区 */
char uart_buf[32] = {0};
uint8_t uart_idx = 0;
/* 模式枚举 */
typedef enum {
MODE_OFF = 0,
MODE_ON,
MODE_BREATH
} led_mode_t;
led_mode_t current_mode = MODE_BREATH;
/* 串口命令处理 */
void process_uart_cmd(void) {
if (strstr(uart_buf, "ON")) {
current_mode = MODE_ON;
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);
printf(">>> 模式切换: 常亮\r\n");
} else if (strstr(uart_buf, "OFF")) {
current_mode = MODE_OFF;
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
printf(">>> 模式切换: 熄灭\r\n");
} else if (strstr(uart_buf, "BREATH")) {
current_mode = MODE_BREATH;
printf(">>> 模式切换: 呼吸灯\r\n");
}
memset(uart_buf, 0, sizeof(uart_buf));
uart_idx = 0;
}
8.5 主循环整合
c
while (1) {
switch (current_mode) {
case MODE_BREATH:
breath_led();
break;
case MODE_ON:
TIM2->CCR1 = 999; // 100% 占空比
HAL_Delay(100);
break;
case MODE_OFF:
TIM2->CCR1 = 0; // 0% 占空比
HAL_Delay(100);
break;
}
/* 串口接收处理(实际项目中建议用中断接收,此处简化为示意) */
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) {
char ch = (char)(huart1.Instance->DR & 0xFF);
if (ch == '\r' || ch == '\n') {
process_uart_cmd();
} else {
uart_buf[uart_idx++] = ch;
}
}
}
9. 常见踩坑与解决
| 问题 | 原因 | 解决方案 |
|---|---|---|
Keil 编译报错 core_cm3.h 找不到 |
Pack 未安装 | 重装 STM32F1 Pack |
| ST-Link 连接不上 | 接线错误或驱动缺失 | 检查 SWDIO/SWCLK/GND 三根线,安装 ST-Link 驱动 |
| LED 不亮 | 引脚配置错误 / 限流电阻过大 | 确认 CubeMX 中 PA1 为 Output,电阻换 220Ω |
| printf 无输出 | 未重定向 / 串口线接反 | 确认 TX-RX 交叉连接,确认 __io_putchar 已添加 |
| 中断不触发 | NVIC 未使能 / 优先级配置问题 | CubeMX 中确认勾选对应中断线 |
| PWM 无波形 | TIM 时钟未使能 / 通道未配置 | CubeMX 时钟树确认 TIM 时钟源 |
10. 进阶学习路线
学完本文后,按以下路径继续深入:
-
基础外设系统学习
- I2C:驱动 OLED 屏幕(
0.96寸 SSD1306) - SPI:驱动 TFT 彩屏或 Flash 存储
- ADC:采集电位器/光敏电阻/温度传感器
- DMA:数据搬运,释放 CPU
- I2C:驱动 OLED 屏幕(
-
RTOS 入门
- FreeRTOS 任务创建与调度
- 消息队列与信号量
- 软件定时器
-
实战项目
- 基于 STM32 的温湿度监测仪(DHT11 + OLED + 串口上报)
- 四轴无人机飞控入门(MPU6050 + 电机驱动 + PID)
- 物联网节点(ESP8266 + MQTT + 云平台)
推荐配套资料:STM32 HAL 库开发实战指南(开源社区维护)、ST 官方 HAL 库文档
11. 总结
本文从零开始覆盖了 STM32 入门的五个核心模块:
| 模块 | 掌握技能 | 对应外设 |
|---|---|---|
| 环境搭建 | Keil + CubeMX + ST-Link 烧录 | --- |
| GPIO | 点灯、引脚控制 | GPIO |
| 串口 | printf 调试输出 | USART |
| 外部中断 | 按键事件响应 | EXTI + NVIC |
| PWM | 呼吸灯、电机调速 | TIM |
下一步行动建议:不要只看不练,把文中每个代码块都亲自敲一遍、烧录验证。嵌入式开发没有捷径,动手是唯一的路。
参考资料
- STM32F103C8T6 官方数据手册
- STM32 HAL 库用户手册 UM1850
- STM32CubeMX 官方下载
- Keil MDK-ARM 下载
- 野火 STM32 HAL 库教程(开源)
- 正点原子 STM32 开发指南
- ARM Cortex-M3 技术参考手册
- CSDN STM32 专栏优质文章合集