STM32零基础入门教程:从环境搭建到实战项目(点亮LED + 串口打印 + 呼吸灯)

文章目录

    • 摘要
    • [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 安装

  1. 访问 Keil 官网 下载 MDK-ARM
  2. 安装时选择默认路径(避免中文路径)
  3. 安装完成后,下载 STM32F1 的 Pack 包Keil.STM32F1xx_DFP

重要:Keil 未注册时有 32KB 代码限制,F103C8T6 的 Flash 为 64KB,入门阶段够用。如需解除限制,请购买正版 License。

3.3 STM32CubeMX 安装

  1. 访问 STM32CubeMX 官方页面 下载
  2. 安装完成后,打开软件 → HelpManage embedded software packages → 勾选 STM32F1 → 安装
  3. 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 配置步骤

  1. 新建工程FileNew Project → 搜索 STM32F103C8
  2. 配置调试接口Pinout & ConfigurationSYSDebug → 选择 Serial Wire
  3. 配置 GPIO :点击 PA1 → 选择 GPIO_Output
  4. 时钟配置Clock Configuration 标签页 → HCLK 输入 72 → 回车自动配置
  5. 项目设置Project Manager → 填入项目名 LED_Blink → Toolchain 选 MDK-ARMGenerate 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 编译与烧录

  1. Keil 中点击 Build(F7),确认 0 Error 0 Warning
  2. 连接 ST-Link:SWDIO ↔ SWIOSWCLK ↔ SWCLKGND ↔ GND
  3. 点击 Load(F8)烧录
  4. 观察 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 配置

  1. 打开之前的工程,在 CubeMX 中启用 USART1:Pinout → 点击 PA9(TX)/ PA10(RX)→ 选择 USART1
  2. ConfigurationUSART1 → Mode 选 Asynchronous,波特率 115200,其余默认
  3. 重新生成代码

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 配置外部中断

  1. 点击 PB12 → 选择 GPIO_EXTI12
  2. GPIO → PB12 → GPIO modeExternal Interrupt Mode with Falling edge trigger
  3. NVIC 标签页 → 勾选 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)

  1. TimersTIM2Clock SourceInternal Clock
  2. Channel 1PWM Generation CH1
  3. 参数设置:Prescaler = 71Counter 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. 进阶学习路线

学完本文后,按以下路径继续深入:

  1. 基础外设系统学习

    • I2C:驱动 OLED 屏幕(0.96寸 SSD1306
    • SPI:驱动 TFT 彩屏或 Flash 存储
    • ADC:采集电位器/光敏电阻/温度传感器
    • DMA:数据搬运,释放 CPU
  2. RTOS 入门

    • FreeRTOS 任务创建与调度
    • 消息队列与信号量
    • 软件定时器
  3. 实战项目

    • 基于 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

下一步行动建议:不要只看不练,把文中每个代码块都亲自敲一遍、烧录验证。嵌入式开发没有捷径,动手是唯一的路。


参考资料

  1. STM32F103C8T6 官方数据手册
  2. STM32 HAL 库用户手册 UM1850
  3. STM32CubeMX 官方下载
  4. Keil MDK-ARM 下载
  5. 野火 STM32 HAL 库教程(开源)
  6. 正点原子 STM32 开发指南
  7. ARM Cortex-M3 技术参考手册
  8. CSDN STM32 专栏优质文章合集

相关推荐
云泽8084 小时前
深入浅出 STM32(十一):从寄存器映射到 HAL 库位操作源码详解
stm32·单片机·嵌入式硬件
哥不想学算法4 小时前
STM32零基础教程(六):ADC、I2C、SPI与DMA
stm32·单片机·嵌入式硬件
szxinmai主板定制专家5 小时前
基于 RK3588 + Xilinx Kintex-7 FPGA 异构工业主控板设计方案
arm开发·人工智能·嵌入式硬件·fpga开发·zynq
我是一棵无人问荆的小草5 小时前
LM393DT比较器输出
单片机·嵌入式硬件
云泽8085 小时前
深入浅出 STM32(十三):从施密特触发器到四种输入配置的底层逻辑
stm32·单片机·嵌入式硬件
Rambo.xia6 小时前
RFSoC高速采集卡设计项目:一块板卡串联综合优化5大核心技术
单片机·嵌入式硬件·fpga开发
XMAIPC_Robot7 小时前
RK3588+STM32:高性能机器人运动控制解决方案,兼顾实时性与AI算力
人工智能·stm32·嵌入式硬件·算法·fpga开发·机器人·arm+fpga
天吾cc7 小时前
RTT-MQTT
网络·单片机·嵌入式硬件·算法
国科安芯7 小时前
星上能源管家:AS32S601辐射加固MCU在卫星电源管理中的性能解析
单片机·嵌入式硬件·能源·抗辐射加固