开发环境:Keil MDK v5.34(µVision 5.34,2021-03-12 发布) | 开发方式:ST 标准外设库 V3.5.0(库函数版)
目标芯片:STM32F103C8T6(Cortex-M3,72MHz,64KB Flash,20KB SRAM,LQFP-48,中容量 MD)
最终效果:下载程序后,开发板上的蓝色 LED 每 500ms 闪烁一次
难度:★☆☆☆☆ 时长:约 1~2 小时 成本:几十元(开发板 + 下载器) 适用:纯新手 / 首次接触 STM32单片机(微控制器)的"Hello World"就是点亮一颗 LED 灯。它虽然简单,却能让你完整走一遍嵌入式开发的全部流程:
本教程使用 Keil v5.34 和 ST 标准外设库(Standard Peripheral Library)V3.5.0,这是 STM32 入门最经典、教程资料最多的一套组合(相比后来主推的 HAL 库,标准库代码直接、易学,国内大量教材都在用它)。
GPIO_Init、RCC_APB2PeriphClockCmd),你只需要调用这些函数,而不用自己计算寄存器每一位的值。学标准库 = 学"调函数",比直接操作寄存器容易得多,又能理解底层原理。只用理解 3 个概念,足够完成本教程:
STM32F103C8T6 有 37 个 GPIO 引脚(PA0~PA15、PB0~PB15、PC13~PC15 等)。每个引脚都可以配置成输入(读外面信号)或输出(向外发信号)。我们要做的,就是把 PC13 配置成输出模式,然后让它在高电平(3.3V)和低电平(0V)之间切换。
GPIO 输出有 8 种模式,点亮 LED 用推挽输出(GPIO_Mode_Out_PP):芯片内部像一个"推"和"挽"两个开关,要么把引脚接到 3.3V(输出高电平),要么把引脚接到 GND(输出低电平),驱动能力最强,最常用。
STM32 为了省电,所有外设(包括 GPIO 端口)默认关着电。用哪个外设,必须先打开它的时钟(想象成"给这个外设通电")。GPIO 挂在 APB2 总线上,所以第一步永远是调用 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE) 给 GPIOC 通电。
蓝板原理图:LED 的阳极(+)接 3.3V,阴极(-)经一个限流电阻接 PC13。因此:
这正是新手最容易踩的坑:PC13 是"输出 0 点亮",和直觉相反。代码里我们约定 LED_ON() = 输出低电平(GPIO_ResetBits)。
| 物品 | 说明 | 价格参考 |
|---|---|---|
| STM32F103C8T6 最小系统板(蓝板) | 板载 8MHz 晶振、复位按键、USB 供电接口、蓝色 LED(PC13) | 约 8~15 元 |
| ST-Link V2 下载器 | 用于把程序烧进芯片,选 SWD 4 针(3.3V/SWDIO/SWCLK/GND)的即可 | 约 10~20 元 |
| USB 线 × 2 | Micro-USB:一根给开发板供电,一根给 ST-Link | 几元 |
| 杜邦线(公对母)× 4 | 连接 ST-Link 与开发板 | 1~3 元 |
| (可选)面包板 + LED + 330Ω 电阻 | 如果想外接 LED 练习,见第 6.5 节 | 几元 |
| ST-Link V2 引脚 | 接到开发板引脚 | 说明 |
|---|---|---|
| 3.3V | 3.3V | 给板子供电(可选,见下方提示) |
| SWDIO | DIO(即 PA13) | 数据线 |
| SWCLK | CLK(即 PA14) | 时钟线 |
| GND | GND | 共地,必须连接 |
蓝板上的丝印一般标着 DIO(PA13)和 CLK(PA14),紧挨着 3.3V 和 GND,对照上图用杜邦线连好即可。有条件可以把 ST-Link 的 RST 也接到板子的 RESET 脚,能提高下载成功率,但不是必须。
1下载:打开 https://www.keil.com/download/product/,找到 MDK-ARM(即 MDK v5),下载 v5.34 安装包(文件名形如 MDK534.exe,约 1GB)。下载前需要免费注册一个 ARM 账号并登录,用邮箱即可。
2运行安装:双击安装包,一路 Next。安装路径建议保持默认 C:\Keil_v5(不要用中文或带空格的路径,否则编译器会出怪问题)。
3注册 License:安装完成后首次打开 µVision,会提示 License。个人学习直接用评估版(Evaluation)即可:编译代码大小限制 32KB,而我们的 LED 程序只有 1~2KB,完全够用,且功能无任何阉割(不弹窗、能下载能调试)。若公司商用,请购买正版 License(软件里 File → License Management → 添加许可证)。
MDK v5 的器件支持靠"器件包"管理。没有 STM32F1 的器件包,新建工程时就选不到 STM32F103C8 这颗芯片。安装方法二选一:
Pack Installer 按钮(一个绿色小箱子图标)→ 左侧搜索框输入 STM32F1xx → 找到 Keil::STM32F1xx_DFP → 点右边的 Install,等待下载完成。Keil.STM32F1xx_DFP.x.x.x.pack,双击即可自动安装到 Keil。这是本教程的"弹药库"——ST 官方写的芯片驱动函数源码。下载方式:
en.stsw-stm32054.zip(约 40MB)。STM32F10x_StdPeriph_Lib_V3.5.0 找镜像仓库下载,内容一致。解压后你会看到如下结构(记住它,下一节要用):
STM32F10x_StdPeriph_Lib_V3.5.0/ ├── Libraries/ ← 我们要用的核心库 │ ├── CMSIS/ │ │ └── CM3/ │ │ ├── CoreSupport/ ← core_cm3.c / core_cm3.h(内核支持) │ │ └── DeviceSupport/ST/STM32F10x/ │ │ ├── stm32f10x.h ← 芯片总头文件 │ │ ├── system_stm32f10x.c ← 系统时钟初始化(默认72MHz) │ │ ├── system_stm32f10x.h │ │ └── startup/arm/ │ │ └── startup_stm32f10x_md.s ← 启动文件(中容量MD!) │ └── STM32F10x_StdPeriph_Driver/ │ ├── inc/ ← 外设头文件(stm32f10x_gpio.h 等) │ └── src/ ← 外设源文件(stm32f10x_gpio.c 等) ├── Project/ ← 官方示例工程(可参考,不直接用) ├── Utilities/ ← 评估板代码(本教程不需要) └── _htmresc/ ← 图片素材(不需要)
startup_stm32f10x_ld.s(低容量,16/32KB)、startup_stm32f10x_md.s(中容量,64/128KB)、startup_stm32f10x_hd.s(大容量,256KB+)。C8T6 = 64KB Flash = 中容量(Medium Density)→ 必须用 startup_stm32f10x_md.s,对应预定义宏 STM32F10X_MD(后面配置工程要用)。在自己电脑上新建一个工程总文件夹(路径不要含中文,例如 D:\STM32_LED_Demo),然后按下面的结构建子文件夹:
STM32_LED_Demo/ ├── Libraries/ ← 从标准库复制(下面细说) │ ├── CMSIS/ │ └── STM32F10x_StdPeriph_Driver/ ├── User/ ← 我们自己写的代码(main.c / led.c / led.h / delay.c / delay.h) ├── Project/ ← 存放 Keil 工程文件(.uvprojx 等) └── Doc/ ← 放资料文档(可选)
从刚才解压的标准库里复制这些文件(只复制需要的,不要整个文件夹复制):
| 目标位置 | 从标准库哪里复制 | 复制什么 |
|---|---|---|
| Libraries\CMSIS\ | Libraries\CMSIS\CM3\CoreSupport\ | core_cm3.c、core_cm3.h |
| Libraries\CMSIS\ | Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\ | stm32f10x.h、system_stm32f10x.c、system_stm32f10x.h |
| Libraries\CMSIS\ | ...\STM32F10x\startup\arm\ | startup_stm32f10x_md.s(中容量启动文件) |
| Libraries\STM32F10x_StdPeriph_Driver\inc\ | Libraries\STM32F10x_StdPeriph_Driver\inc\ | stm32f10x_gpio.h、stm32f10x_rcc.h、stm32f10x_misc.h(可先全拷,后续用到不折腾) |
| Libraries\STM32F10x_StdPeriph_Driver\src\ | Libraries\STM32F10x_StdPeriph_Driver\src\ | stm32f10x_gpio.c、stm32f10x_rcc.c、stm32f10x_misc.c |
说明:标准库外设文件很多(USART、TIM、ADC……),本工程只用到 GPIO 和 RCC,所以只需 gpio、rcc、misc 三个外设源文件;misc.c 是 NVIC 内核中断相关,加进去备用。头文件建议把 inc 整个文件夹复制,省得以后缺头文件。
1打开 Keil µVision 5,菜单 Project → New uVision Project...
2在弹出的保存窗口中,进入刚才建的 STM32_LED_Demo\Project 文件夹,工程名输入 LED_Blink,点保存(会自动生成 LED_Blink.uvprojx)。
3弹出 Select Device for Target 窗口:左侧输入框搜索 STM32F103C8,在结果里选择 STMicroelectronics → STM32F1 Series → STM32F103 → STM32F103C8(注意看清是 C8 不是 C6/CB),点 OK。能看到右侧显示芯片信息:Cortex-M3、72MHz、64KB Flash、20KB SRAM。
4随后会弹出 Manage Run-Time Environment(软件包组件勾选窗口)——标准库开发直接点右上角红色 X 关闭它,不要勾任何组件(那些组件是给 CMSIS-Pack 方式用的,我们用标准库自带文件,避免重复定义)。
5如果弹窗问 "Copy STM32 Startup Code to project folder?",选择 否(我们自己用标准库的 startup_stm32f10x_md.s)。
现在左侧 Project 窗口里出现 Target 1,里面只有一个空的 Source Group 1。接下来把它改造成规范的分组结构。
1右键 Target 1 → Manage Project Items...(或点工具栏第二个图标"项目窗口"),打开项目管理窗口。
2在 Project Targets 里把 Target 1 改名 LED_Blink。
3在 Groups 里删掉 Source Group 1,然后依次点 Add 新建 4 个组,命名为:Startup、CMSIS、FWLIB、User。
4选中某个组,点 Add Files,把对应文件加进去(在文件类型下拉框选"All files"以便能看到 .s 文件):
| 组名 | 添加的文件(路径相对工程根目录) |
|---|---|
| Startup | Libraries\CMSIS\startup_stm32f10x_md.s |
| CMSIS | Libraries\CMSIS\core_cm3.c、Libraries\CMSIS\system_stm32f10x.c |
| FWLIB | Libraries\STM32F10x_StdPeriph_Driver\src\stm32f10x_gpio.c、stm32f10x_rcc.c、stm32f10x_misc.c |
| User | User\main.c、User\led.c、User\led.h、User\delay.c、User\delay.h(文件还没写,先建空的占位,下一节再写内容) |
点击工具栏魔术棒图标(或菜单 Project → Options for Target,快捷键 Alt+F7),逐页配置:
8.0(蓝板外部晶振 8MHz,一般默认就是 8.0)STM32F10X_MD, USE_STDPERIPH_DRIVER... 图标,逐个 Add 以下 4 个路径(对应你建的文件夹):.\Libraries\CMSIS .\Libraries\STM32F10x_StdPeriph_Driver\inc .\User
这 4 个路径让编译器能找到 stm32f10x.h、stm32f10x_gpio.h、stm32f10x_rcc.h、led.h 等头文件。漏掉任何一个,编译都会报 "cannot open source file" 错误。
配置完成,点 OK 关闭。至此工程搭建完毕。先别急着编译——我们的 User 文件夹里还没有真正的代码。
在 User 文件夹里新建 5 个文件,把下面的代码完整复制进去。文件较多,我们用"分层"方式组织:led(硬件驱动)、delay(延时)、main(主程序),这也是以后写复杂工程的标准结构。
把所有与硬件相关的定义集中在这里:以后换板子、换引脚,只改这一个文件。
#ifndef __LED_H #define __LED_H #include "stm32f10x.h" // 标准库总头文件:包含 GPIO、RCC 等所有外设定义 /*---------------- 硬件配置区:换开发板只改这里 ----------------*/ // 板载LED:蓝板 LED 接在 PC13,阴极接PC13、阳极接3.3V,所以"低电平点亮" #define LED_GPIO_PORT GPIOC // LED 所在端口 #define LED_GPIO_PIN GPIO_Pin_13 // LED 所在引脚 #define LED_GPIO_CLK RCC_APB2Periph_GPIOC // 对应端口的时钟(GPIO挂在APB2总线) /*---------------- 操作宏:语义化,主程序里好读 ----------------*/ #define LED_ON() GPIO_ResetBits(LED_GPIO_PORT, LED_GPIO_PIN) // PC13输出低电平 → 灯亮 #define LED_OFF() GPIO_SetBits(LED_GPIO_PORT, LED_GPIO_PIN) // PC13输出高电平 → 灯灭 /* 函数声明 */ void LED_Init(void); #endif // __LED_H
#ifndef / #define / #endif 是"防止头文件重复包含"的经典写法,编译原理是:第一次包含时定义 __LED_H 宏,第二次再包含时直接被跳过,避免重复定义错误。每个头文件都要这样写。
#include "led.h" // 先包含自己的头文件 /** * @brief 初始化 LED 引脚 * @note 三步走:开时钟 → 配结构体 → 调用库函数 */ void LED_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; // 定义"配置结构体"变量 // 第1步:打开 GPIOC 端口的时钟(不打开,寄存器写了也白写) RCC_APB2PeriphClockCmd(LED_GPIO_CLK, ENABLE); // 第2步:往结构体里填配置参数 GPIO_InitStructure.GPIO_Pin = LED_GPIO_PIN; // 选中引脚:PC13 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 输出模式:通用推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输出速度:50MHz(LED够用,选最低的2MHz也行) // 第3步:把结构体配置真正写入寄存器(初始化完成) GPIO_Init(LED_GPIO_PORT, &GPIO_InitStructure); // 初始状态:灯灭(PC13 输出高电平) LED_OFF(); }
逐句讲解:
GPIO_InitTypeDef 是库定义的结构体类型,专门用来装"引脚号、模式、速度"这三个参数。RCC_APB2PeriphClockCmd(外设, ENABLE):打开时钟。GPIOA~GPIOE 都在 APB2 总线上,所以传 RCC_APB2Periph_GPIOC。忘了这步是最常见的错误——现象就是"程序在跑但灯不亮"。GPIO_Mode_Out_PP:推挽输出,芯片既能把引脚拉高也能拉低,驱动 LED 的标准模式。GPIO_Speed_50MHz:数字电路的翻转速度上限,LED 这种慢信号其实无所谓,50MHz 是默认习惯值。GPIO_Init(端口, &结构体):把上面的配置一次性写进芯片寄存器。// delay.h #ifndef __DELAY_H #define __DELAY_H #include "stm32f10x.h" void Delay_ms(uint32_t ms); #endif // __DELAY_H
// delay.c —— 注意:这是"空循环"软件延时,精度不高但简单直观 #include "delay.h" /** * @brief 毫秒级软件延时(近似值) * @note 72MHz主频下,内层循环7200次约耗时1ms(会随编译优化等级略有变化) */ void Delay_ms(uint32_t ms) { uint32_t i, j; for (i = 0; i < ms; i++) { for (j = 0; j < 7200; j++); // 空循环消磨时间 } }
/** * 工程名:LED_Blink * 芯片 :STM32F103C8T6 * 效果 :板载蓝色LED每500ms闪烁一次 */ #include "stm32f10x.h" // 标准库总头文件 #include "led.h" // LED驱动 #include "delay.h" // 延时 int main(void) { LED_Init(); // 第1步:初始化LED引脚(配置成推挽输出) while (1) // 第2步:死循环,程序永远在这里转 { LED_ON(); // 点亮:PC13输出低电平 Delay_ms(500); // 保持500ms LED_OFF(); // 熄灭:PC13输出高电平 Delay_ms(500); // 保持500ms } }
几个关键点:
main 之前不需要自己写 SystemInit 调用——启动文件 startup_stm32f10x_md.s 在芯片复位后会自动调用标准库的 SystemInit() 把主频配到 72MHz,然后才进入我们的 main。while(1) 是嵌入式程序的基本形态:单片机跑完初始化后,主程序必须永远循环(不存在"退出"),否则程序会跑飞。如果手痒想用面包板外接一颗 LED(以 PA0 为例):
PA0 → 330Ω 电阻 → LED 阳极 → LED 阴极 → GND。此时 PA0 输出高电平点亮(和板载相反)。led.h 的硬件配置区即可,主程序一行不用动:// 外接LED接 PA0(阳极接PA0、阴极经电阻接地 → 高电平点亮) #define LED_GPIO_PORT GPIOA #define LED_GPIO_PIN GPIO_Pin_0 #define LED_GPIO_CLK RCC_APB2Periph_GPIOA // 因为极性反了,亮/灭宏也要对调: #define LED_ON() GPIO_SetBits(LED_GPIO_PORT, LED_GPIO_PIN) // 输出高 → 亮 #define LED_OFF() GPIO_ResetBits(LED_GPIO_PORT, LED_GPIO_PIN) // 输出低 → 灭
1点击工具栏 Build 图标(或按 F7,或菜单 Project → Build Target)编译。
2看底部 Build Output 窗口。成功标志:最后一行出现 "LED_Blink.axf" - 0 Error(s), 0 Warning(s)(或 0 Error、少量 Warning 也正常)。同时会在 Project 文件夹下生成 LED_Blink.hex。
3如果报错:双击 Build Output 里的红色错误行,光标会自动跳到出错位置,对照下表排查。
| 报错信息 | 原因 | 解决方法 |
|---|---|---|
| fatal error: stm32f10x.h: No such file or directory | 头文件搜索路径没配 | Options → C/C++ → Include Paths 添加 5.4 节的 4 个路径 |
| undefined symbol: GPIO_Init 等 | 库源文件没加入工程 | 确认 FWLIB 组里加了 stm32f10x_gpio.c、stm32f10x_rcc.c、stm32f10x_misc.c |
| startup_stm32f10x_md.s: error A1163E: Unknown opcode | 启动文件格式/版本不对 | 确认用的是标准库自带的 startup_stm32f10x_md.s,而不是从别处复制的 |
| multiple definition / redefinition | 文件重复添加或宏重复定义 | 检查各组里文件是否有重复;头文件用 #ifndef 保护 |
| #error "Please select first the target STM32F10x device..." | 宏定义少了 STM32F10X_MD | C/C++ → Define 填 STM32F10X_MD, USE_STDPERIPH_DRIVER |
| Warning: #1-D: last line of file ends without a newline | 警告,无害 | 在文件末尾补一个空行即可消除 |
1接线:按第 3.2 节把 ST-Link 与开发板用杜邦线连好(SWDIO→DIO、SWCLK→CLK、GND→GND,3.3V 可选),开发板插 USB 供电,ST-Link 插电脑 USB。
2确认芯片可识别:Options → Debug → Settings,右侧 SW Device 里应显示芯片 IDCODE,说明 ST-Link 和芯片握手成功。
3点击工具栏 LOAD 按钮(Download,图标是一个芯片带向下箭头;也可按 F8 不保证,以点按钮为准)。
4Build Output 窗口滚动输出下载进度,最后出现 Application running ...(因为我们勾了 Reset and Run,下载完自动复位运行)。
5看效果:开发板上 PC13 的蓝色 LED 开始以 500ms 间隔闪烁!🎉
如果灯没亮,按第 9 节 FAQ 排查。下载成功后,把板子拔电再上电,程序依然会运行——因为程序已经烧进了芯片内部的 Flash,断电不丢失。这就是"单片机"和"电脑程序"的本质区别。
| 现象 | 可能原因 / 解决办法 |
|---|---|
| 下载时报 "No target connected" 或 "Cannot connect to target" | ① ST-Link 驱动没装好(重装第 4.3 步驱动);② 接线错了(SWDIO/SWCLK 接反);③ 板子没供电(单独用 USB 给板子供电);④ 杜邦线接触不良(重新插紧)。 |
| 下载后灯不亮,但下载成功 | ① 没勾 Reset and Run,按一下板子 RESET 键;② 检查代码里 LED 引脚是否和你的板子一致(蓝板是 PC13);③ 确认宏定义 STM32F10X_MD 没写错成 HD(HD 会编译过但外设基址可能错乱,极端情况程序跑飞)。 |
| 程序下载过一次后,第二次连不上 ST-Link | 上次程序可能把 SWD 引脚(PA13/PA14)配置成了普通 IO 并锁死。解决:按住板子 RESET 键不放 → 点 Keil LOAD → 松开 RESET,趁"复位窗口期"擦除重烧;或用 ST-Link Utility / STM32CubeProgrammer 做全片擦除(Erase)。 |
| 编译报 "cannot open source file xxx.h" | Include Paths 没配全(第 5.4 节③),把 4 个路径都加上。 |
| 中文注释在 Keil 里显示乱码 | 文件编码问题。菜单 Edit → Configuration → Editor → Encoding 选 Chinese GB2312 (Simplified);或把源码文件另存为 GB2312/ANSI 编码(Notepad++ 可转)。 |
| 灯亮度很低 / 闪烁规律怪异 | 可能程序里用了 SystemInit 后主频不是 72MHz(蓝板晶振虚焊/损坏),先检查板子 8MHz 晶振是否焊好;软件延时随主频变化属正常。 |
| 想删掉整个工程重来 | 直接删除工程文件夹即可。Keil 不写注册表、不残留服务,删文件夹就是卸载工程。 |
| 评估版提示 32KB 限制不够用 | LED 程序编译后仅 1~2KB,远没到限制。以后做大型项目(FreeRTOS 等)超出限制时再考虑购买 License。 |
RCC->APB2ENR、GPIOC->CRH、GPIOC->ODR 寄存器——彻底搞懂库函数背后发生了什么。本教程由 Hermes Agent 依据公开资料整理生成 · 内容核实日期:2026-08-06 · 版权归原作者各自所有,仅供学习使用