简介:STM32F10x标准库3.6.0最终版是面向STM32F10x系列单片机开发者的官方外设固件库合集,适合刚接触寄存器操作、希望用库函数快速完成外设初始化与工程搭建的嵌入式学习者及工程师,也可作为老项目维护与代码移植的参考资料。压缩包共收录960个文件,约21.25MB,以349个C源文件、277个头文件为核心,配合说明文档、链接脚本、汇编启动文件及少量工程配置与说明文件,覆盖外设驱动、启动代码、工程模板等模块,便于按目录查找和复用。已有2823人学习下载,说明其在STM32入门与工程实践中具有一定参考价值。包里附带操作说明书,能帮助读者理解库函数与底层寄存器的对应关系,减少重复配置工作,提升代码可读性与开发效率;对需要补齐标准库工程结构、查阅外设驱动写法或对照启动流程的读者尤为实用。
1. 从一份 3.6.0 压缩包说起:为什么新板子还要装老库
STM32F10x 标准库 3.6.0 这个包,第一次拿到手的人大概率会愣一下:解压出来没有安装程序,没有 setup.exe,只有一堆.c/.h、几个 startup 汇编文件,外加一个 Project 模板目录。很多人以为拷进工程就能编,结果第一次编译就吃一个#error "Please select first the target STM32F10x device...",或者好不容易编译过了,烧进去板子一动不动,串口还吐乱码。这套库在 F103 系列上出现频率极高,教学板、老项目、外包交接代码几乎都绕不开它;装对了它是稳定的地基,装错了就是一个持续几周的玄学现场。下面按我实际配环境的顺序,从目录认领、宏定义、路径配置一路写到下载和验证,新手能照着走通,熟手可以直接跳到容量宏和编译器取舍那两节对答案。
2. 装前把版本对齐:3.6.0 包里的四个目录与容量宏
标准库不是“装上去”的软件,它是“挂进工程”的源码集合。所以这一步的核心不是双击安装,而是先把包看清楚、把芯片和宏对上,再决定用哪个编译器。这一节没搞明白,后面所有配置都是白费。
2.1 解压后先认目录:CMSIS、StdPeriph_Driver、Project、Utilities
整包解压后大致是四块,作用完全不同,我一般先在资源管理器里逐个点开确认一遍再动手:
STM32F10x_StdPeriph_Lib_V3.6.0/ ├── Libraries/ │ ├── CMSIS/CM3/ │ │ ├── CoreSupport/ # core_cm3.c / core_cm3.h,Cortex-M3 内核访问层 │ │ └── DeviceSupport/…/STM32F10x/ │ │ ├── stm32f10x.h # 全库总头文件,寄存器定义都在这 │ │ ├── system_stm32f10x.c # 时钟初始化,SystemInit 在这里 │ │ ├── system_stm32f10x.h │ │ └── startup/arm|iar|gcc # 三套编译器各一份启动文件 │ └── STM32F10x_StdPeriph_Driver/ │ ├── inc/ # 外设头文件 stm32f10x_gpio.h 等 │ └── src/ # 外设源文件 stm32f10x_gpio.c 等 ├── Project/STM32F10x_StdPeriph_Template/ │ ├── main.c │ ├── stm32f10x_conf.h # 外设头文件的开关总闸 │ ├── stm32f10x_it.c / .h # 中断服务函数 │ └── system_stm32f10x.c # 模板自带的时钟配置副本 └── Utilities/STM32_EVAL/ # 官方评估板驱动,自己的板子基本用不上DeviceSupport下面那层厂商目录名在不同渠道整理的包里写法略有出入,认准里面装着stm32f10x.h和system_stm32f10x.c就对了。有两个地方新手最容易搞混:一是system_stm32f10x.c在 CMSIS 目录和 Project 模板目录里各有一份,工程里只能加一份,加两份就是重复定义;二是Utilities整个目录可以直接无视,它是给官方评估板用的,跟你的最小系统板没关系。我的习惯是先把整包原样留一份在本地不动,再单独复制一份到工程目录里改,改坏了随时能对照原件回滚,这个后悔药成本极低。
2.2 芯片型号与启动文件:LD/MD/HD/XL 怎么对号入座
这是整套环境里第一个真正会让人翻车的地方。标准库用一组宏来区分芯片容量,stm32f10x.h开头会检查你有没有定义其中之一,没定义就直接#error拦下来。对应关系我按实际项目整理成下面这张表:
| 容量宏 | 含义 | 典型 Flash 区间 | 对应启动文件 |
|---|---|---|---|
STM32F10X_LD | 小容量 | 16~32 KB | startup_stm32f10x_ld.s |
STM32F10X_LD_VL | 小容量价值型 | 16~32 KB | startup_stm32f10x_ld_vl.s |
STM32F10X_MD | 中容量 | 64~128 KB | startup_stm32f10x_md.s |
STM32F10X_MD_VL | 中容量价值型 | 64~128 KB | startup_stm32f10x_md_vl.s |
STM32F10X_HD | 大容量 | 256~512 KB | startup_stm32f10x_hd.s |
STM32F10X_HD_VL | 大容量价值型 | 256~512 KB | startup_stm32f10x_hd_vl.s |
STM32F10X_XL | 超大容量 | 512 KB 以上 | startup_stm32f10x_xl.s |
STM32F10X_CL | 互联型 | 按型号 | startup_stm32f10x_cl.s |
宏和启动文件必须同进同退,只改一个就是白改。原因很直接:启动文件里写死了中断向量表的条目数量和顺序,容量越大的型号外设越多、向量表越长。你给大容量芯片配了小容量启动文件,向量表后面那一段就是空的,一旦开到对应中断,CPU 取到的入口地址是无效值,直接 HardFault,表现就是“灯闪了两下就死”。反过来小芯片配大启动文件,链接时 Flash 末尾那段向量区可能压到别的段,能不能跑看运气。
芯片具体属于哪一档,别靠记忆,直接翻型号手册里的 Flash 容量和stm32f10x.h里#ifdef分支的注释对照。我一般会在工程根目录放一个readme.txt,第一行就写清楚“本工程:STM32F103 大容量,宏STM32F10X_HD,启动文件startup_stm32f10x_hd.s”,半年后回来接手也不至于重新猜一遍。这个习惯救过我不止一次。
2.3 编译器版本取舍:ARMCC5、ARMCC6、IAR 与 GCC
标准库 3.6.0 是 ARMCC5 时代的产物,这一点决定了后面一堆兼容性问题。四种常见工具链的取舍我按踩坑频率排一下:
- Keil MDK + ARMCC v5(AC5):最省事,基本开箱即用,模板工程直接能编译。缺点是 AC5 在新版本 MDK 里已经不再是默认,需要单独装 compiler 版本并手动切过去。
- Keil MDK + ARMCC v6(AC6):编译速度和代码质量更好,但会拿老库开刀。
core_cm3.c里的内联汇编写法、部分__weak用法、#pragma arm section在 AC6 下会报错或大量警告。常见做法是把core_cm3.c从工程里移除,只保留core_cm3.h,AC6 下头文件里有对应的内联实现;再不行就给这个文件单独降级编译。 - IAR EWARM:必须换成
startup/iar/下的汇编文件,它和 ARM 语法不是一回事,混用直接编译不过。 - GCC(arm-none-eabi):换成
startup/gcc/下的.s,并且要自己准备链接脚本.ld,标准库包里不提供,这一步工作量最大。
我的建议很明确:工程第一次跑通,先用 AC5。等外设全调完、功能验证过了,再考虑要不要迁 AC6。顺序反过来的话,你会同时面对“库不兼容”和“代码逻辑不对”两个变量,排查时根本分不清是谁的问题,这是最没必要的消耗。
3. 建工程第一步:把库文件挂进 Keil 并写对三个宏
这一节是全篇最值得抄作业的部分。Keil 里建工程本身不难,难的是那些散落在各个对话框里的路径和宏,错一个就是一屏红字。我按自己新建工程的固定顺序写。
3.1 新建工程与分组:把 src 加进来而不是整个目录拖进去
先建一个空工程,选好器件型号(这一步只是让 Keil 知道 Flash 算法和 RAM 大小,不会自动帮你加任何库文件)。然后在 Project 窗口里手动分组建文件,我固定分五组:
Startup -> startup_stm32f10x_hd.s (来自 startup/arm) CMSIS -> system_stm32f10x.c, core_cm3.c StdPeriph -> stm32f10x_gpio.c, stm32f10x_rcc.c, stm32f10x_usart.c … User -> main.c, stm32f10x_it.c Doc -> stm32f10x_conf.h(仅挂进工程便于编辑,不参与编译)StdPeriph组不要图省事把src目录整个拖进来,几十个外设源文件全编译一遍,编译时间翻几倍不说,还会带进一堆用不到的外设。我的做法是用什么加什么:先加stm32f10x_rcc.c(时钟是必须的)、stm32f10x_gpio.c(点灯验证)、stm32f10x_usart.c(串口打印),跑通之后再按需追加。这样每次新增外设都是一次清晰的“我加了哪个文件”的记录,后面出问题也容易定位。
core_cm3.c这个文件按 2.3 节的结论处理:AC5 下加上,AC6 下不加。别两套配置混用,同一个工程里加了一半又删一半,最后自己都记不清。
3.2 Define 里必须写全的三个宏:USE_STDPERIPH_DRIVER 与 STM32F10X_HD
打开 Options for Target → C/C++ → Define,这一栏是整套环境的心脏。我一般写三到四项:
USE_STDPERIPH_DRIVER, STM32F10X_HD, HSE_VALUE=8000000逐项说清楚:
USE_STDPERIPH_DRIVER:stm32f10x.h里有一段#ifdef USE_STDPERIPH_DRIVER才会#include "stm32f10x_conf.h"。不定义它,GPIO_Init、RCC_APB2PeriphClockCmd这些函数的原型全都拿不到,编译直接报implicit declaration,链接报undefined symbol。这是最高频的一个坑。STM32F10X_HD:就是 2.2 节那张表里的容量宏,改成你实际芯片对应的那一个。不写或者写错,stm32f10x.h开头的检查会#error拦下来。HSE_VALUE=8000000:外部晶振频率,单位 Hz。默认值就是 8 MHz,如果你的板子是 12 MHz 或别的值,必须在这里改,否则SystemInit算出来的 PLL 倍频是错的,主频不对,串口波特率全偏,现象是乱码。
如果要开断言检查,再加一项USE_FULL_ASSERT,同时确保stm32f10x_conf.h里的assert_param宏被正确启用。调试阶段开着有用,量产前记得关,assert_param在中断里被触发会拖慢时序。
提示:Define 这一栏是用英文逗号分隔的,用中文逗号或者换行粘贴进去,Keil 会把它当成一个完整的宏名,编译时报“未定义标识符”,很多人第一次栽在这。
3.3 Include Paths 三条路径与 stm32f10x_conf.h 的位置
同一页面的 Include Paths,最少三条,多一条User:
.\Libraries\CMSIS\CM3\CoreSupport .\Libraries\CMSIS\CM3\DeviceSupport\…\STM32F10x .\Libraries\STM32F10x_StdPeriph_Driver\inc .\User第一条让编译器找到core_cm3.h,第二条找到stm32f10x.h和system_stm32f10x.h,第三条是全部外设头文件。第四条是stm32f10x_conf.h和stm32f10x_it.h的所在。src目录不需要加进包含路径,它是.c文件,靠 Group 加进工程参与编译,不走头文件包含。
路径这里我踩过最典型的一次坑是用了绝对路径。工程在自己机器上好好的,拷给同事就全红,原因是D:\Work\...在他那边不存在。从那以后我一律用相对工程文件(.uvprojx)的写法,并且把Libraries、User目录和工程文件放在同一层,整个文件夹打包就能走。这也是我建议新手不要图快、老老实实建目录的原因:省下的十分钟,后面要用两小时还。
编译器行为上还有两个开关值得顺手确认:C/C++ 页勾选C99 Mode(标准库部分示例用到了 C99 的声明位置写法),Target 页勾选Use MicroLIB(如果你打算用printf重定向到串口,不勾的话要额外补半主机相关代码,否则程序一进printf就卡死)。这两个勾选不影响编译通过与否,但直接影响后面能不能跑,属于典型的“编译 0 Error,运行时挂住”。
4. 配置到能跑:SystemInit、时钟树与中断向量表
编译通过只是第一步,能不能跑起来取决于三件事:时钟对不对、中断向量表对不对、下载配置对不对。这一节按这三件事拆开。
4.1 system_stm32f10x.c 里的 72MHz 是怎么来的
打开system_stm32f10x.c,中上部有一组被注释掉的宏:
/* #define SYSCLK_FREQ_HSE HSE_VALUE */ /* #define SYSCLK_FREQ_24MHz 24000000 */ /* #define SYSCLK_FREQ_36MHz 36000000 */ /* #define SYSCLK_FREQ_48MHz 48000000 */ #define SYSCLK_FREQ_56MHz 56000000 /* #define SYSCLK_FREQ_72MHz 72000000 */默认状态通常是打开 72 MHz 或者 56 MHz,具体看你手上这份包的原样。要跑 72 MHz,就把SYSCLK_FREQ_72MHz那一行的注释去掉,其余保留注释。这个宏会决定SetSysClock()到底调用哪个SetSysClockToXX函数。
SetSysClockTo72()里的动作是固定的:开 HSE 并等待就绪 → 配 Flash 等待周期(72 MHz 下是 2 个 wait state,同时开预取缓冲)→ 配 PLL 源为 HSE、倍频系数 9 → 配 AHB 不分频、APB2 不分频、APB1 二分频 → 切 SYSCLK 到 PLL → 更新SystemCoreClock变量。
这里有两个连带关系必须记住。第一,APB1 最高只能跑 36 MHz,所以 72 MHz 主频下 APB1 必须二分频,这一条是硬件限制,不是随便调的。第二,SystemCoreClock这个全局变量是后面算波特率、算 SysTick 的依据,它没更新对,所有基于它的延时和串口分频全是错的。如果你的程序中途改过时钟(比如低功耗切换),改完必须调一次SystemCoreClockUpdate()重新校准这个值。
还有一个更隐蔽的点:SystemInit是由启动文件里的Reset_Handler在进main之前调用的。如果你用的是别人改过的启动文件,或者自己写过一段汇编,一定要确认Reset_Handler里保留了BL SystemInit。少了这一句,程序跑在内部 8 MHz HSI 上,代码逻辑全对,串口就是乱码,示波器一量主频不对,能查半天。
4.2 stm32f10x_conf.h:注释掉的头文件就是 undefined reference 的源头
stm32f10x_conf.h本质上是个开关面板,里面每一行都是#include "stm32f10x_xxx.h",默认可能有一部分被注释掉:
#include "stm32f10x_adc.h" /* #include "stm32f10x_can.h" */ #include "stm32f10x_gpio.h" #include "stm32f10x_rcc.h" #include "stm32f10x_usart.h"规则很简单:要用哪个外设,就取消它头文件的注释,同时把对应的.c加进工程的 StdPeriph 组。这两步必须同时做,只做一半的典型症状是:头文件开着但.c没加,编译通过、链接报undefined symbol GPIO_Init;或者.c加了但头文件注释着,编译就报隐式声明。
底部还有一段断言配置:
#ifdef USE_FULL_ASSERT #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__)) void assert_failed(uint8_t* file, uint32_t line); #else #define assert_param(expr) ((void)0) #endif开了USE_FULL_ASSERT就必须自己实现assert_failed函数(一般放在main.c里,打印文件名和行号后死循环),不实现就是链接错误。这个函数在查“参数传错了”这类问题时极其好用,比一行行打日志快得多。
另外提醒一句:stm32f10x_conf.h只应该有一份。有些人从模板里拷一份到User,又顺手把Project目录里的那份也加进工程,编译器按包含路径顺序取到哪份看运气,改了半天发现改的不是生效的那份,这种黑匣子最耗人。
4.3 下载配置:SWD、Flash 算法与复位方式
编译和运行逻辑都对了,最后一步是把程序送进芯片。Options for Target → Debug 页选你的下载器(ST-Link 或 J-Link),点 Settings:
- Port 选 SW,不要选 JTAG。SWD 只占两根线,接线简单,也不容易和其他外设抢引脚。
- Settings 里能读出 IDCODE,说明连线、供电、芯片都正常。读不到就先别管代码,去查硬件。
- Flash Download 页勾选
Program、Verify、Reset and Run,Programming Algorithm 里添加对应容量的算法,比如大容量就选 High-density 那一档。算法选错,烧写会报 “Flash Download failed”。
一个非常实用的配置项在 Debug → Settings → Reset:默认是Normal,如果你遇到“下载器能连上但一烧写就报错”,把它改成Connect under reset,让下载器在芯片复位期间接管 SWD 引脚,成功率会高很多。尤其是程序里把 SWD 引脚复用成普通 GPIO、或者一上电就进低功耗模式的工程,这一项几乎是必改。
还有一条容易被忽略:Reset and Run勾上之后,烧完自动复位运行。有些人没勾,烧完看着板子没反应,以为程序有问题,实际上是芯片还停在调试器接管的状态,手动按一下复位就好了。这类“假故障”我见过太多次,排查前先确认这一项,能省半小时。
5. 排查清单:编译过了却跑不起来的五种现场
下面这五条是我在 F103 上反复遇到的真实故障形态,按“现象 → 原因 → 解决”记,方便直接对号入座。
第一种:编译 0 Error,烧进去灯不亮,调试器单步能走、全速就跑飞。现象是单步执行正常,一按全速运行程序就停在 HardFault 或者直接复位循环。原因八成是启动文件和容量宏不匹配,向量表长度和芯片实际中断数对不上,开了某个外设中断后取到的入口地址是无效值。解决方式是核对 2.2 节那张表,确认STM32F10X_HD这类宏和startup_stm32f10x_hd.s是同一档,然后 Rebuild 全部重编。别只点 Build,宏变了必须 Rebuild。
第二种:串口能吐数据,但全是乱码,波特率怎么调都不对。现象是接收端能收到字节,就是解不出字符,换个波特率依旧。原因基本锁定在时钟源头:HSE_VALUE写的是 8 MHz,板子上焊的是 12 MHz 晶振,或者system_stm32f10x.c里的SYSCLK_FREQ_72MHz没打开,实际跑在别的频率上。解决方式是先用调试器看SystemCoreClock这个变量的实际值,和你期望的 72000000 对不上,就往时钟配置里查,别在串口初始化参数上折腾。
第三种:链接报一堆undefined symbol xxx_Init,但头文件明明包含了。现象是编译阶段就有隐式声明警告,链接阶段直接失败。原因是stm32f10x_conf.h里对应外设的头文件被注释掉了,或者头文件开着但stm32f10x_xxx.c没加进工程的 StdPeriph 组。解决方式是两头一起查:conf.h 里取消注释,Project 窗口里把.c加进组。加完记得确认加的是src目录下的原件,不是从别处拷来的旧版本。
第四种:换到 AC6 编译器后,core_cm3.c报一屏错误。现象是 ARMCC v6 下编译core_cm3.c出现内联汇编语法错误和大量告警,AC5 下同样代码完全正常。原因是这个文件里的寄存器访问用的是老版本编译器的内联汇编写法。解决方式有两种:一是把core_cm3.c从工程移除,只保留core_cm3.h(AC6 下头文件里提供了内联实现);二是整个工程切回 AC5 编译。我一般选后者,先把功能跑通再谈工具链升级。
第五种:下载器能识别芯片,但一烧写就报 Flash Download failed。现象是 Settings 里 IDCODE 读得到,点下载就失败,偶尔提示目标电压或算法问题。原因可能是 Flash 算法选错容量档、芯片处于低功耗或 Stop 模式导致 SWD 被关、程序把 SWD 引脚复用成了普通 IO。解决方式是把 Reset 模式改成Connect under reset,核对 Programming Algorithm 是否和容量匹配,硬件上确认 BOOT0 是接地而不是被拉高。如果程序里确实复用了 SWD 引脚,就在初始化最开始加一段延时,给下载器留出接管窗口。
这五条覆盖了我八成以上的现场。共同点是:都不是代码逻辑问题,全是环境配置问题。所以出故障时先怀疑配置,别急着改业务代码,这个顺序能省下大量时间。
6. 装完怎么验:三层自检与一条必走的收尾流程
配完环境,最危险的心态是“能烧进去就算装好了”。我习惯按三层自检验收,每层都有明确的期望值和出错时的排查方向:
| 层次 | 验证动作 | 期望结果 | 不对时先看哪里 |
|---|---|---|---|
| 编译层 | Rebuild All | 0 Error,警告逐条看完 | Define 宏、Include Paths、conf.h 开关 |
| 时钟层 | 调试器看SystemCoreClock | 等于 72000000 | HSE_VALUE、SYSCLK_FREQ 宏、Reset_Handler |
| 外设层 | GPIO 翻转 + 串口打印 + 定时器中断计数 | 灯按秒闪、串口有稳定输出、计数递增 | 时钟使能、引脚配置、NVIC 优先级分组 |
时钟层最容易跳过,但它恰恰是最该验的。我的做法是在main开头加三行:
/* 时钟自检:把实际主频打印出来,和预期值对不上就别往下调外设 */ SystemCoreClockUpdate(); printf("SYSCLK = %u Hz\r\n", (unsigned int)SystemCoreClock);SystemCoreClockUpdate()会重新从寄存器反算当前主频并写入SystemCoreClock,比单纯相信SystemInit更可靠。串口打印需要重定向,AC5 + MicroLIB 下的标准写法:
/* printf 重定向到 USART1,前提是 Target 页勾了 Use MicroLIB */ int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); USART_SendData(USART1, (uint8_t)ch); return ch; }while那一行是在等发送寄存器空,不加的话第一个字节都发不出去;USART_FLAG_TXE表示数据已经从发送寄存器搬进移位寄存器,用它判断比查 TC 标志更及时。这两处是printf卡死的高频原因,很多人以为是 MicroLIB 没勾,实际是忘了等标志位。
外设层我固定用“灯 + 串口 + 定时器中断”三件套一起验,因为它们分别覆盖了 GPIO、时钟与外设复用、中断向量表三套机制。定时器中断里每进一次就把一个全局计数器加一,主循环里打印这个计数器:能稳定递增,说明向量表和 NVIC 都对;数值卡住不动,就是中断没进来或者优先级配置有问题,这时回头查 2.2 节的启动文件和NVIC_PriorityGroupConfig有没有漏。
收尾时我有一条雷打不动的流程:把整个工程目录压缩一份存档,文件名带上芯片型号、容量宏、编译器和验证日期,同时在根目录留一个readme.txt写清楚这四项。这条习惯是被坑出来的——有一次接手一个半年前的工程,宏和启动文件都是别人配的,我照着旧记忆换了个容量档,Rebuild 之后所有外设集体失灵,查了整整一个下午才发现是启动文件没跟着换。从那以后我每次新建或迁移工程,都强制走一遍“编译层 → 时钟层 → 外设层”三层自检,并且把结果写在 readme 第一行,宁可多花十分钟,也不给自己留黑匣子。这份 3.6.0 的完整包建议原样留一份在本地别动,工程里用复制出来的那份改,改废了随时能回到干净状态。希望这些步骤能帮你把环境一次装对,少走几段弯路。
本文还有配套的精品资源,点击获取