1. 为什么第一课总是最劝退的:环境搭建的真实门槛
带过几期嵌入式实训之后,我发现一个规律:真正在第一天就卡住的人,往往不是被代码难住的,而是被环境劝退的。STM32F103 这块芯片本身并不复杂,Cortex-M3 内核、72MHz 主频、64KB 到 128KB 的 Flash,资料铺天盖地,但一个新手从拿到开发板到点亮第一颗 LED,中间要跨过的坑包括但不限于:Keil MDK 装哪个版本、Pack 包怎么离线安装、ST-Link 驱动为什么在设备管理器里带黄色感叹号、下载时提示 "No ST-Link detected" 到底该查哪一层。这些问题在教程里通常一句话带过,但实际操作时每一个都能耗掉你半天。
这篇内容就是把这半天压缩成半小时。我会按照一个完整实训第一课的逻辑,把 STM32F103 开发环境搭建和开发板认识这两件事讲透,包括工具选型的理由、每一步操作背后的原理、参数怎么算、报错怎么排查。适合刚接触 STM32 的在校学生、转行做嵌入式的开发者,以及需要带实训课的助教参考。你不需要有任何单片机基础,但需要有一台 Windows 电脑、一块 STM32F103 开发板(最小系统板或带外设的整板都行)和一根 ST-Link 下载器。
先说清楚一个前提:环境搭建不是目的,它只是让你能"把代码放进芯片里跑起来"的手段。所以整篇文章的落脚点始终是——怎么用最短路径让开发板和电脑建立可靠的连接,并且这个连接在后续几十节课里都不会再出问题。
2. 开发环境整体设计与工具选型拆解
2.1 为什么是 Keil MDK 而不是其他方案
STM32 的开发环境选择其实不少:Keil MDK、IAR EWARM、STM32CubeIDE、基于 VSCode 加插件的方案,甚至直接用 Makefile 加 arm-none-eabi-gcc。对于实训第一课,我坚持推荐 Keil MDK,理由有三条,都是实际带课踩出来的。
第一是生态成熟度。ST 官方和绝大多数第三方例程、教程、视频都是基于 Keil 工程组织的,你遇到问题时搜索到的答案大概率能直接对应上。第二是调试器集成度高,ST-Link 在 Keil 里的配置几乎是开箱即用,不需要额外折腾 OpenOCD 配置文件。第三是对新手友好,编译、下载、调试三个按钮摆在那里,不需要理解底层构建系统。
IAR 的编译效率确实更好,但授权和界面习惯对新手不友好;STM32CubeIDE 基于 Eclipse,启动慢、索引卡顿是老问题;VSCode 方案灵活但配置成本高,第一课就上这个容易劝退。所以实训场景下,Keil MDK 是性价比最高的选择。
注意:Keil MDK 从 5.37 版本开始不再自带 ARM Compiler 5(AC5),默认只装 AC6。而大量 STM32F103 的老例程是基于 AC5 编译的,直接用 AC6 编译会报一堆语法错误。这是新手最容易踩的坑之一,后面会专门讲怎么处理。
2.2 软件清单与版本对应关系
环境搭建本质上是把几个软件按正确顺序装好,并且让它们互相认识。清单如下:
| 软件/组件 | 作用 | 推荐版本 | 备注 |
|---|---|---|---|
| Keil MDK-ARM | 集成开发环境 | 5.36 或 5.38 | 5.36 自带 AC5,省事 |
| STM32F1xx Device Family Pack | 芯片支持包 | 最新即可 | 提供启动文件、外设寄存器定义 |
| ST-Link 驱动 | 下载器驱动 | 官方最新 | 装完设备管理器无感叹号 |
| ST-Link Utility | 独立下载工具 | 可选 | 用于验证连接、读芯片ID |
| STM32CubeMX | 图形化配置工具 | 可选 | 后续课程生成初始化代码用 |
版本对应关系里最关键的是 Keil 和 Pack 包。Pack 包是 Keil 认识 STM32F103 的前提,没有它,新建工程时器件列表里根本找不到 STM32F103C8T6 这个型号。很多人装完 Keil 发现选不了芯片,就是 Pack 没装。
2.3 硬件认识:开发板上到底有什么
拿到一块 STM32F103 开发板,先别急着插电脑。花五分钟把板子上的东西认全,后面调试会省很多事。
核心是那颗 LQFP48 或 LQFP64 封装的 STM32F103 芯片,常见型号 C8T6(64KB Flash、20KB RAM)或 RCT6(256KB Flash、48KB RAM)。芯片周围是最小系统的三要素:电源电路、晶振电路、复位电路。电源部分通常有一个 5V 转 3.3V 的 LDO,比如 AMS1117-3.3,因为 STM32F103 的工作电压是 2.0V 到 3.6V,典型值 3.3V,而下载器和 USB 供电都是 5V,必须降压。晶振一般有两颗:8MHz 的主晶振给系统时钟用,32.768kHz 的低速晶振给 RTC 用。复位电路是一个按键加电容电阻,按下时把 NRST 引脚拉低。
板子上还会有 BOOT0 和 BOOT1 跳线,这两个引脚决定芯片从哪启动:主 Flash、系统存储器还是 SRAM。正常下载和运行程序时,BOOT0 接低电平(GND),从主 Flash 启动。如果 BOOT0 接高,芯片会进入系统存储器里的 Bootloader,这时候你下载的程序不会运行,很多人"程序下载成功但不跑"就是这个原因。
外设部分因板而异,常见的有 LED、按键、串口、USB 接口、OLED 屏接口等。第一课只需要认准三样:电源指示灯、用户 LED、ST-Link 接口(通常是 4 针的 SWD 接口:3.3V、GND、SWDIO、SWCLK)。
3. 核心细节解析与实操要点
3.1 Keil MDK 安装的关键选择
下载 Keil MDK 安装包后,双击运行,一路 Next 到选择安装路径。这里有个细节:路径不要有中文和空格。我见过太多因为路径里有中文导致编译报错的案例,虽然新版 Keil 对中文路径的容忍度提高了,但没必要冒这个险。建议直接装在D:\Keil_v5这种纯英文短路径下。
安装过程中会弹出 Pack Installer 的安装提示,可以先跳过,装完主程序再单独处理 Pack。安装完成后第一次打开 Keil,会提示你安装 License,实训场景下用评估版就够,代码大小限制是 32KB,STM32F103C8T6 的 Flash 是 64KB,写满一半之前都不会触发限制。如果后续工程超过 32KB,再考虑其他授权方式。
装完之后要确认一件事:打开 Keil,点Project -> Manage -> Pack Installer,看左侧 Devices 列表里有没有 STM32F1 系列。如果没有,说明 Pack 没装,需要手动安装。
3.2 Pack 包离线安装的完整流程
Pack 包在线安装经常因为网络问题失败,尤其是实训机房环境。所以离线安装是必备技能。
首先去 Keil 官网或 ST 官网下载Keil.STM32F1xx_DFP.x.x.x.pack文件。然后有两种安装方式:一是直接双击这个 .pack 文件,它会自动调用 Pack Installer 安装;二是在 Keil 里点Pack Installer -> File -> Import,选择下载好的 pack 文件。
安装成功的标志是:Pack Installer 里 STM32F1 Series 下面的器件变成绿色或显示已安装版本号。这时候新建工程,在器件选择框里输入 STM32F103C8,能搜到就说明 Pack 装好了。
提示:如果 Pack Installer 打开后一直显示 "Offline",检查一下是不是网络问题,或者直接在 Pack Installer 的 Packs 菜单里勾选 "Check for Updates on Launch" 取消掉,避免每次打开都卡在联网检查上。
3.3 ST-Link 驱动安装与验证
ST-Link 驱动是连接电脑和开发板的桥梁。把 ST-Link 插到电脑 USB 口,打开设备管理器,正常情况下会在"通用串行总线设备"或"端口"下看到 STMicroelectronics STLink 设备。如果看到带黄色感叹号的未知设备,说明驱动没装好。
驱动安装有两种途径:一是安装 ST-Link Utility 时会自动装驱动;二是单独下载 ST-Link 驱动包手动安装。装完后拔插一次 ST-Link,确认设备管理器里没有感叹号。
验证驱动是否真正可用,最直接的方法是打开 ST-Link Utility,点Target -> Connect。如果连接成功,下方会显示芯片的型号、Flash 大小、芯片 ID 等信息。这一步能过,说明电脑到下载器这一段是通的。如果报 "No ST-Link detected",问题在驱动或 USB 线;如果报 "Can not connect to target",问题在下载器到开发板这一段,通常是接线或供电问题。
3.4 开发板与 ST-Link 的接线规范
SWD 接口四根线,接错一根就连不上。标准接法:
- ST-Link 的 3.3V 接开发板的 3.3V(有些板子不需要接,因为板子自己供电)
- ST-Link 的 GND 接开发板的 GND
- ST-Link 的 SWDIO 接开发板的 SWDIO(PA13)
- ST-Link 的 SWCLK 接开发板的 SWCLK(PA14)
这里有个常见误区:很多人以为 3.3V 必须接,其实如果开发板已经通过 USB 或外部电源供电,ST-Link 的 3.3V 可以不接,只接 GND、SWDIO、SWCLK 三根线就能下载。但 GND 必须接,否则没有共同参考地,信号无法识别。
接线时注意杜邦线不要插反,SWDIO 和 SWCLK 接反是最常见的错误,表现就是 ST-Link Utility 能识别下载器但连不上芯片。
4. 实操过程与核心环节实现
4.1 从零新建一个 STM32F103 工程
打开 Keil,点Project -> New uVision Project,选择工程保存路径(同样避免中文),给工程起名,比如led_test。弹出器件选择框,在搜索栏输入 STM32F103C8,选中STM32F103C8,点 OK。
接下来会弹出 "Manage Run-Time Environment" 窗口,这是 Keil 的 RTE 框架。对于第一课,我们不需要用 RTE,直接点 Cancel 关掉,采用手动添加文件的方式,这样对工程结构理解更清晰。
工程建好后是空的,需要手动添加启动文件和库文件。启动文件在 Pack 安装目录下,路径类似Keil_v5\ARM\PACK\Keil\STM32F1xx_DFP\x.x.x\Device\Source\ARM\startup_stm32f103xb.s。把这个文件复制到工程目录下,然后在 Keil 里右键 Target,选择Add Group建一个组叫 Startup,再Add Existing Files把启动文件加进去。
标准外设库文件需要从 ST 官网下载的固件包里提取,主要包括stm32f10x.h、system_stm32f10x.c、以及stm32f10x_gpio.c、stm32f10x_rcc.c等外设驱动。把这些文件按功能分组添加到工程里,并在Options for Target -> C/C++ -> Include Paths里把相关头文件目录加进去。
4.2 时钟配置的计算过程
STM32F103 的时钟树是理解这块芯片的第一道坎。外部 8MHz 晶振经过 PLL 倍频到 72MHz 作为系统时钟,这个 72MHz 不是随便来的,它是芯片手册规定的最高主频。
计算过程:8MHz 外部晶振(HSE)先经过一个分频器,通常设为 1 分频,得到 8MHz。然后进入 PLL,PLL 的倍频系数设为 9,得到 8 × 9 = 72MHz。这个 72MHz 再经过 AHB 预分频器(通常设为 1 分频)得到 HCLK = 72MHz,也就是系统时钟。APB1 总线最高 36MHz,所以设 2 分频;APB2 总线最高 72MHz,设 1 分频。
在标准库的system_stm32f10x.c里,这些参数是通过宏定义配置的。默认的SYSCLK_FREQ_72MHz宏就是这套配置。如果你用的是 CubeMX 生成的代码,这些会在SystemClock_Config函数里以 HAL 库的形式体现。
理解时钟配置的意义在于:后面用定时器、串口、延时函数时,所有的时间计算都基于这个 72MHz。比如你要做一个 1ms 的延时,用 SysTick 定时器,重装载值就是 72000 - 1,因为 72MHz 下每个计数周期是 1/72 微秒,数 72000 次正好 1ms。
4.3 编译配置与下载设置
工程文件加好后,点Options for Target进行配置。几个关键项:
在Target标签页,确认晶振频率填 8.0MHz,这是给 Keil 仿真用的,实际硬件用的是板子上的晶振。在Output标签页,勾选Create HEX File,方便后续用其他工具下载。在C/C++标签页,Define框里填入USE_STDPERIPH_DRIVER, STM32F10X_MD,前者告诉编译器使用标准外设库,后者告诉库文件当前芯片是中容量型号(64KB 或 128KB Flash)。
在Debug标签页,选择ST-Link Debugger,然后点旁边的Settings。在Debug子标签里,Port 选 SWD,能识别到芯片 ID 就说明连接正常。在Flash Download子标签里,确认Programming Algorithm里有 STM32F10x Medium-density Flash,起始地址 0x08000000,大小 128KB(C8T6 实际是 64KB,但算法按 128KB 加载不影响)。
配置完成后点编译按钮,如果一切正常,Build Output 窗口会显示0 Error(s), 0 Warning(s)。然后点下载按钮,程序就烧进芯片了。
4.4 第一个程序:点亮 LED
写一个最简单的 LED 闪烁程序,验证整个链路。假设 LED 接在 PC13 引脚,低电平点亮:
#include "stm32f10x.h" void Delay(unsigned int count) { for(; count != 0; count--); } int main(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOC, &GPIO_InitStructure); while(1) { GPIO_SetBits(GPIOC, GPIO_Pin_13); Delay(500000); GPIO_ResetBits(GPIOC, GPIO_Pin_13); Delay(500000); } }这段代码的逻辑:先使能 GPIOC 的时钟(不使能时钟,GPIO 不工作,这是新手最常忘的一步),然后配置 PC13 为推挽输出模式,最后在死循环里翻转电平。下载后如果 LED 开始闪烁,说明环境搭建完全成功。
5. 常见问题与排查技巧实录
5.1 下载失败类问题速查
| 报错信息 | 可能原因 | 排查方法 |
|---|---|---|
| No ST-Link detected | 驱动未装好或 USB 线接触不良 | 查设备管理器,换 USB 口和线 |
| Can not connect to target | 接线错误或板子未供电 | 检查 SWDIO/SWCLK/GND 接线,确认板子电源灯亮 |
| Flash download failed - Target DLL has been cancelled | 芯片被读保护或 Flash 算法选错 | 用 ST-Link Utility 解除读保护,检查 Flash 算法 |
| Error: Flash Download failed | BOOT0 电平不对 | 确认 BOOT0 接 GND |
| 程序下载成功但不运行 | BOOT0 接高或复位电路问题 | 检查 BOOT0 跳线,按复位键 |
5.2 AC5 与 AC6 编译器冲突的处理
前面提到 Keil 5.37 之后不再自带 AC5。如果你的工程是用 AC5 写的,用 AC6 编译会报大量错误,典型的是__align未定义、内联汇编语法不兼容等。
解决办法有两个:一是安装 AC5 编译器,在 Keil 官网下载ARM Compiler 5单独安装包,装完后在Options for Target -> Target标签页的ARM Compiler下拉框里选择Use default compiler version 5。二是把工程迁移到 AC6,修改不兼容的语法,但这对新手来说成本太高。实训场景下推荐第一种,省时省力。
5.3 实操心得与避坑清单
带了几期实训,我整理了一份"第一课必查清单",每次上课前让学生对照检查,能减少八成的问题:
- 路径检查:Keil 安装路径、工程路径、Pack 路径,全部确认无中文无空格
- 驱动检查:设备管理器里 ST-Link 无感叹号,ST-Link Utility 能 Connect
- 接线检查:GND 必接,SWDIO 和 SWCLK 不接反,3.3V 视供电情况决定
- 跳线检查:BOOT0 接 GND,BOOT1 任意(通常也接 GND)
- 配置检查:Define 里有 STM32F10X_MD,Flash 算法选 Medium-density
- 时钟检查:确认板子上的 8MHz 晶振焊接良好,虚焊会导致时钟起不来
还有一个容易被忽略的点:ST-Link 固件版本。有些老版本的 ST-Link 固件和新版 Keil 不兼容,表现为能识别下载器但下载失败。这时候需要用 ST-Link Utility 或 STM32CubeProgrammer 升级固件。升级操作在 ST-Link Utility 的ST-LINK -> Firmware update里,按提示操作即可,注意升级过程中不要拔线。
5.4 关于开发板供电的补充说明
开发板供电方式通常有三种:USB 供电、ST-Link 供电、外部电源供电。实训时推荐用 USB 供电,因为最稳定,电流充足。ST-Link 的 3.3V 输出电流有限,如果板子上外设多(比如接了 OLED 屏、多个 LED),可能出现供电不足导致芯片工作异常。
判断供电是否充足,看板子上的电源指示灯亮度。如果灯明显偏暗,或者芯片发热异常,就要换供电方式。另外,如果同时接了 USB 和 ST-Link 的 3.3V,可能造成电源冲突,建议只保留一种供电来源。
6. 开发板认识的延伸:从最小系统到完整项目
6.1 最小系统板的引脚分布规律
STM32F103C8T6 最小系统板(俗称"蓝板"或"黑板")的引脚分布有规律可循。板子两侧各有一排排针,左侧通常是 PA 口和部分 PB 口,右侧是 PB 口和电源引脚。关键引脚包括:PA13/PA14 是 SWD 调试口,PA9/PA10 是串口1,PA0 是唤醒引脚,PC13 是板载 LED。
认识引脚分布的意义在于:后续做项目时,你需要根据外设需求选择引脚。比如要用串口打印调试信息,就选 PA9/PA10;要用 I2C 接 OLED,就选 PB6/PB7 或 PB8/PB9。这些在芯片的参考手册(RM0008)和数据手册里都有详细说明,第一课不需要全记住,但要知道去哪查。
6.2 从点灯到串口打印的过渡
点亮 LED 之后,下一个里程碑是串口打印。串口是嵌入式开发中最重要的调试手段,没有之一。通过串口,你可以把变量值、程序运行状态打印到电脑上,比点灯调试高效得多。
串口配置的核心是波特率计算。STM32F103 的 USART1 挂在 APB2 总线上,时钟是 72MHz。波特率计算公式是:波特率 = fCK / (16 × USARTDIV)。以 115200 为例,USARTDIV = 72000000 / (16 × 115200) = 39.0625。整数部分是 39,小数部分 0.0625 乘以 16 等于 1,所以 BRR 寄存器的值就是 39 × 16 + 1 = 625,即 0x271。
这个计算过程在标准库里由USART_Init函数自动完成,你只需要传入波特率数值。但理解原理有助于排查串口乱码问题——乱码九成是波特率不匹配。
6.3 后续课程的环境复用建议
第一课搭好的环境,后续几十节课都会复用。所以有几个习惯建议从第一课就养成:
工程目录结构规范化。建议按Project、Libraries、User、Output四个文件夹组织,Project 放 Keil 工程文件,Libraries 放标准库或 HAL 库,User 放自己写的代码,Output 放编译产物。这样工程迁移或备份时不会丢文件。
建立自己的代码模板。把 GPIO 初始化、延时函数、串口初始化这些常用代码整理成模板,新工程直接复制,能省大量时间。
版本管理。哪怕不用 Git,也建议每次重大修改前把工程文件夹复制一份备份,命名带上日期。嵌入式开发中,一个误操作把能跑的工程改崩了,回退成本很高。
7. 我个人在实际操作中的几点体会
环境搭建这件事,看起来是体力活,实际上考验的是对工具链的理解深度。我见过太多人卡在某个报错上,然后开始盲目重装软件、换电脑、换板子,最后问题可能只是一根杜邦线没插紧。所以我的建议是:遇到问题先分层排查,从物理层(接线、供电)到驱动层(设备管理器)到软件层(Keil 配置),一层一层确认,不要跳步。
另外,第一课不要追求把所有功能都跑通。能把 LED 点亮、能用 ST-Link 下载、能看懂工程结构,这三件事做到,第一课的目标就达成了。串口、定时器、中断这些后面慢慢来。嵌入式学习是个螺旋上升的过程,第一课埋下的环境问题,如果不解决,后面每一课都会以不同的形式冒出来烦你。所以宁可第一课多花两小时把环境弄扎实,也不要带着隐患往下走。
最后分享一个我自己的习惯:每搭好一个环境,我会写一份"环境搭建记录",把软件版本、安装路径、关键配置、遇到的报错和解决方法都记下来。下次换电脑或者帮别人搭环境时,直接照着记录走,十分钟搞定。这份记录的价值,在你搭第三个环境的时候就会体现出来。