简介:《野火 EmbedFire STM32 HAL 库开发实战指南—基于 F103》PDF 教程,面向零基础或刚接触 ARM Cortex-M 内核的嵌入式学习者,围绕意法半导体 HAL 库的标准化 API 展开,帮助读者从开发环境搭建走到外设驱动与项目实践,降低 STM32 入门门槛。全书按学习路径编排:先说明本书用法与配套硬件,再依次讲解 KEIL5 安装与 STM32 芯片包配置、DAP 仿真器的硬件连接与 Debug、Utilities 参数设置、程序下载,随后进入 STM32 基础概念与 HAL 库对 GPIO、中断、定时器、串口、ADC 等外设的配置方法,并以 LED 闪烁、串口通信、数据采集等实战项目收束,附带技术论坛交流渠道。资源为单个 PDF 文件,压缩包约 43.41MB,章节编号与目录完整,便于按模块检索或打印查阅。目前已有 1881 人学习下载,适合配合野火 F103-MINI 开发板边读边练,逐步建立可移植的 HAL 库编程思路与调试习惯。
1. 从一块 F103-MINI 说起:HAL 库环境装不对,例程一行都跑不起来
很多人入门 stm32 arm hal库 的第一块板子就是 F103-MINI:Keil5 装上、CubeMX 一点、例程编译一下,看着像十分钟能收工的活。真实情况是,卡住人的地方从来不在 HAL 库的 API 上,而在装环境这一步——器件包没装、宏写成了STM32F103xB却拿着 256KB 板子的启动文件、DAP 仿真器在 Debug 页选成了别的下载器,于是编译报错、下载失败、灯不亮,一条条全冒出来。
这套环境本身不复杂:Keil MDK5 加 STM32F1 器件支持包、STM32CubeMX 加 Java 运行环境、一个 CMSIS-DAP 仿真器,三样对齐就能跑通。难的是版本和参数对不上时没人告诉你错在哪,报错信息还经常指错方向。下面按我实际装机的顺序走一遍,从装包到点灯,每一步都留了能回退的检查点。
2. 装 Keil MDK5 与 F1 器件包:安装路径和器件支持决定能不能编译
2.1 安装包版本、路径和权限
先明确要装的是两样东西:MDK-ARM 5.x 主程序,和 STM32F1 系列的器件支持包(DFP)。版本上不用追最新,Keil5 各小版本对 F1 系列的支持差异很小,反倒是新版本默认把编译器换成了 ARM Compiler 6,而野火那批例程是按 AC5 写的,直接编译会冒出一堆语法层面的报错。装 MDK5.2x 这一档,默认带 AC5,最省事;如果机器上只有 5.36 之后的版本,得单独补装 Legacy 支持包。
路径上有两个硬要求:不要装在C:\Program Files或Program Files (x86)下面,UAC 会拦住器件包解压和 Flash 算法的写入;不要带中文和空格。我一般直接装D:\Keil_v5,工程目录也一并放在英文路径下。
# 安装前先把目录建好,别让安装器一半装 C 盘一半装 D 盘 mkdir -p /d/Keil_v5 mkdir -p /d/STM32Cube/Repository第一行是主程序和器件包的根目录,第二行是 CubeMX 存固件包的仓库,第 3 章会用到。Windows 下用 Git Bash 就写/d/这种形式,用 cmd 就写D:\Keil_v5。安装器里会问 Core Components 和 Pack 两个路径,都指到D:\Keil_v5下面。分着装的后患是重装系统时器件包全丢,工程打开一片红,还得重新对一遍版本。
2.2 主程序安装与授权:L250 这道坎绕不过去
安装过程基本是一路下一步,中间只在那两个路径处停一下。装完第一次打开会弹 License Management 窗口,这一步必须处理掉。
Keil 评估版有 32KB 代码限制。HAL 库工程空着就有十几 K,加上 GPIO、串口和 printf 重定向,很容易顶到上限,链接阶段直接给你一句:
*** FATAL ERROR L250: CODE SIZE LIMIT IN RESTRICTED VERSION EXCEEDED这个报错跟代码质量没关系,是授权状态不对,改代码改到天亮也没用。常见做法是通过官方渠道获取正式授权,或者确认自己手上是合法授权的版本。这一步别想着绕过去,省下来的时间后面会加倍还回去——我见过有人在链接选项里反复调优化等级、删 printf,折腾一下午,最后发现问题根本不在代码里。
装完之后顺手确认一下编译器:Project → Manage → Project Items → Folders/Extensions 里能看到 ARM Compiler 的版本,5.x 那一档是我们要的。
2.3 装 STM32F1 器件支持包
没有器件包,Options for Target 的 Device 页里根本搜不到 STM32F103,工程都建不起来。两种装法:
在线装。打开 Pack Installer,切到 Packs 标签,左边展开 Keil,找到Keil::STM32F1xx_DFP,点右边的 Install,等右下角进度条走完。网络不稳的时候会卡在 Downloading 半天不动,重开一次 Pack Installer 就行。
离线装。拿到.pack文件直接双击,安装器会自己找 Keil 的安装路径;也可以在 Pack Installer 里 File → Import 导入。
装完验证两点:D:\Keil_v5\ARM\PACK\Keil\STM32F1xx_DFP\下面应该出现一个2.x.x版本的文件夹;进去之后能看到Device和Flash两个子目录。Flash里面那些.FLM文件就是下载算法,后面第 4 章挂 Flash 算法时用的就是它们,很多人下载失败,根子在这一步没装全。
2.4 装完先自检:空工程编译过再碰例程
顺序是这样:Project → New μVision Project → 选一个英文目录 → Device 页输入STM32F103RC选中对应型号 → 弹出 Run-Time Environment 窗口,勾上 CMSIS 分组里的 CORE 和 Device 分组里的 Startup → OK → 直接 Build。
目标很明确:Build Output 里出现0 Error(s), 0 Warning(s),再往下走。这一步看着多余,作用是先把“装环境的问题”和“例程的问题”隔开。空工程都编不过,说明是 Keil 本体或者器件包的问题,别去例程里翻;空工程能过、例程编不过,问题范围就缩到宏定义和头文件路径上,直接对应第 4 章那几行配置。装环境最怕的就是一上来拿例程试,报错几十条,分不清哪条是真问题。
3. STM32CubeMX 与 Java 环境:从选型到生成一份能编译的 HAL 工程
3.1 Java 运行环境:先确认 JRE,再装 CubeMX
老版 CubeMX 本质是个 Java 程序,启动时找不到 JVM 就一声不响——双击图标转两圈,没了,连报错窗口都不给。这种黑匣子现象最浪费时间,因为你连往哪个方向查都不知道。
java -version # 期望看到类似输出,重点是 1.8 这一档 # java version "1.8.0_xxx" # Java(TM) SE Runtime Environment (build 1.8.0_xxx-bxx) # Java HotSpot(TM) 64-Bit Server VM要的是 Java 8 这一档(1.8 就是 Java 8)。装 11 或 17 上去,老版本 CubeMX 可能起不来。新版 6.x 的安装包已经自带 JRE,先直接装 CubeMX,能起来就不用管 Java;起不来再回头补 JRE 8。补装之后要重开一个终端再执行一次java -version,环境变量没生效的话,检查系统变量 Path 里有没有%JAVA_HOME%\bin,改完重启终端,别在旧窗口里反复试。
3.2 新建工程与选型:型号、封装、宏三者必须对上
打开 CubeMX → New Project → 在 Commercial Part Number 里输入型号。以板子丝印为准,F103-MINI 常见的是STM32F103RCTx,LQFP64 封装。选中之后右侧会显示 Flash 大小和引脚数,这两个数字要跟丝印对得上再往下走。
型号不只是个名字,它决定了后面两件事:代码里写哪个宏,Keil 里挂哪个启动文件。搞错了会掉进最难查的那类问题——能编译、能下载、一运行就跑飞。
| 型号 | Flash | 容量等级 | 工程宏 | 启动文件 |
|---|---|---|---|---|
| STM32F103C8T6 | 64KB | 中容量 | STM32F103xB | startup_stm32f103xb.s |
| STM32F103RCT6 | 256KB | 大容量 | STM32F103xE | startup_stm32f103xe.s |
| STM32F103ZET6 | 512KB | 大容量 | STM32F103xE | startup_stm32f103xe.s |
表里的宏不是随手写的。HAL 库的寄存器定义文件靠它挑对应型号的寄存器映射和外设数量,写小了(256KB 的板子写成STM32F103xB),编译器认定的 Flash 和 RAM 都比实际小,越过边界的那部分变量会在链接时被裁掉,运行起来就是随机异常。启动文件同理,它决定了中断向量表有多长。
3.3 时钟树:HSE 8MHz 走 PLL 到 72MHz
要改两处。Pinout & Configuration → System Core → RCC → High Speed Clock (HSE) 选Crystal/Ceramic Resonator,对应板载那颗 8MHz 无源晶振。
然后切到 Clock Configuration 页:Input frequency 填 8,PLL Source Mux 选 HSE,PLLMul 选 ×9,System Clock Mux 选 PLLCLK,AHB Prescaler 保持 /1,得到 72MHz;APB1 Prescaler 选 /2,得到 36MHz;APB2 Prescaler 保持 /1,也是 72MHz。
注意 APB1 的上限是 36MHz,超了 CubeMX 会把那个框标红。这条限制跟串口直接相关:USART1 挂在 APB2 上,USART2/3 挂在 APB1 上,波特率算出来不对、串口助手收一堆乱码,十有八九是这两条总线频率和你以为的不一样。
板子上要是没焊外部晶振,就把 HSE 换成 HSI。内部 RC 也是 8MHz,走 PLL 最高只能到 64MHz,而且精度差,高波特率下容易出错。用内部时钟先把程序跑起来可以,长期用还是老老实实接晶振。
3.4 工程属性与代码生成选项
Project Manager 页有几项要手动确认:
Project Name 和 Project Location 全英文、无空格;Toolchain/IDE 选MDK-ARM,Version 选V5,选成 V4 生成的工程 Keil5 打不开,选成别的 IDE 后面第 4 章的配置项名字都不一样。
Code Generator 标签里勾两个:
Copy only the necessary library files——固件包只复制用到的.c,工程体积小,也不会在 Keil 里把一堆没用到外设的文件全编进去。Generate peripheral initialization as a pair of .c/.h files per peripheral——每个外设的初始化单独成文件,后面手动改参数不用在 main.c 里翻。
改完点 GENERATE CODE,弹窗里点 Open Project,Keil 会自己把工程拉起来。
3.5 生成出来的工程:Core 和 Drivers 两层
打开之后工程树大概是这个结构:
Core/ Inc/ main.h、stm32f1xx_hal_conf.h、外设头文件 Src/ main.c、stm32f1xx_it.c、stm32f1xx_hal_msp.c、时钟配置 Drivers/ STM32F1xx_HAL_Driver/ HAL 库源码 CMSIS/ 内核与设备头文件 MDK-ARM/ .uvprojx 工程文件main.c里凡是夹在/* USER CODE BEGIN */和/* USER CODE END */之间的代码,重新生成不会被覆盖;写在这两行外面的,下次点 GENERATE CODE 就没了。这条规矩从第一天就得当回事,我在第 6 章写点灯代码时会严格放在用户区里。
另外注意stm32f1xx_hal_conf.h,HAL 库的各个外设模块靠里面的宏开关控制编不编进来。工程里明明加了串口代码却报HAL_UART_Init未定义,就是这里的HAL_UART_MODULE_ENABLED没打开。
4. HAL 工程的目标配置:宏、头文件路径与 DAP 下载三件套
4.1 C/C++ 页的宏:USE_HAL_DRIVER 和型号宏
Options for Target → C/C++ 标签 → Define 一栏:
USE_HAL_DRIVER, STM32F103xE第一项是 HAL 库的总开关。没有它,stm32f1xx.h里根本不会把 HAL 的头文件包含进来,编译直接报一堆类型未定义。第二项是型号宏,跟第 3 章那张表里的一列对齐。
两个宏之间用英文逗号分隔,不要加分号,不要加引号。写成STM32F103xE;这种,Keil 会把分号当成宏名的一部分,等于没定义,报错信息还特别模糊。CubeMX 生成的工程这两行是自动写好的,只有像书里第 11 章那样手工建库函数版工程时,才需要自己填。
同一页下面的 Optimization 先保持-O0或-O1。调-O3之后单步调试会跳行,变量被优化掉看不到值,调不通的时候先把优化关掉再说话。
4.2 Include Paths:五条路径缺一条就是一堆 cannot open
C/C++ 页的 Include Paths 里,以 MDK-ARM 目录为基准,这五条必须存在:
..\Core\Inc ..\Drivers\STM32F1xx_HAL_Driver\Inc ..\Drivers\STM32F1xx_HAL_Driver\Inc\Legacy ..\Drivers\CMSIS\Device\ST\STM32F1xx\Include ..\Drivers\CMSIS\Include第一条给main.h、stm32f1xx_hal_conf.h;第二条给stm32f1xx_hal_gpio.h这些外设头文件;第三条是旧版 API 的兼容层,用到早期__HAL_风格之外的函数时才会命中,留着不占地方;后两条分别给设备头文件和core_cm3.h这类内核头文件,少了它们报的是 CMSIS 层的错,跟 HAL 一点关系都没有。
排查有个笨办法但特别快:报错信息cannot open source input file后面跟的那个文件名,去Drivers目录下搜一遍它在哪个文件夹,把那个文件夹加进 Include Paths,比对着教程一行行抄路径靠谱得多。
4.3 Debug 页:CMSIS-DAP 的三项配置
书里第 3 章讲 DAP 仿真器配置,落到 Keil 里就是三处。Options for Target → Debug 标签:
Use 下拉选CMSIS-DAP Debugger,然后点右边的 Settings。弹出的窗口里 Port 选SW,不要选 JTAG;Max Clock 先降到 1MHz。左边 SW Device 框里应该能看到 IDCODE 和 Device Name,看到这两行才算握手成功。
这一页是空的,后面全是白搭。空的时候按顺序查三件事:板子有没有独立供电、SWDIO / SWCLK / GND 三根线有没有接反(板子上都有丝印,别信杜邦线的颜色)、上一次调试是不是没正常退出导致调试口被占用。实在连不上,把 Max Clock 降到 100kHz 再试一次,线长一点的时候这个很管用。
接着切到 Flash Download 标签:勾上Reset and Run,这样下载完程序自己就跑起来,不用手动按复位。Programming Algorithm 里点 Add,按容量等级选算法:
- 64KB / 128KB 的中容量型号 →
STM32F10x Med-density Flash - 256KB / 512KB 的大容量型号 →
STM32F10x High-density Flash
算法选错的表现很典型:进度条走到一半停住,报Cannot access target或者Programming error @ 0x08000000,看着像硬件坏,其实是算法地址范围跟芯片对不上。
4.4 Utilities 页:别让它去调别的下载器
Utilities 标签页跟 Debug 页必须是同一个调试器。勾Use Debug Driver,和 Debug 页保持一致;勾Update Target before Debugging,避免下载进去的还是上一版程序在跑。
这一页最容易出的问题是:Debug 页改成了 DAP,Utilities 页还停在 ULINK 或 J-LINK,点下载走的是另一条通路,报错信息跟仿真器本身完全对不上。有人在接线和驱动上折腾一晚上,最后发现就是这两页不一致。
4.5 启动文件与向量表:型号对不上就是 HardFault
工程树里启动文件叫startup_stm32f103xe.s,跟第 3 章表里的型号宏一一对应,两个必须同时改。
大容量板子挂了中容量的启动文件,中断向量表只覆盖到中容量那部分外设,一开定时器中断或者串口中断,中断号越界,程序直接跳到HardFault_Handler里死循环。这个现象特别迷惑:main 里的代码一行没执行错,就是灯不亮。
还有一处:system_stm32f1xx.c里的VECT_TAB_OFFSET一般保持0x0。做 IAP 或者带 bootloader 的工程才需要改,改了但没配套调整分散加载文件,程序会从错误的地址取向量表,表现同样是 HardFault。新手阶段别碰这个宏。
5. 装完最容易翻车的五处:按现象、原因、解决逐条排查
5.1 编译阶段的两条:头文件找不到、代码超限
现象一:cannot open source input file "stm32f1xx_hal.h": No such file or directory
原因有两种。一种是 Include Paths 里漏了..\Drivers\STM32F1xx_HAL_Driver\Inc;另一种是手工建工程时压根没把 HAL 库源码复制进工程目录,路径写了但目录是空的。
解决:先按 4.2 把五条路径补齐。补完还报,就去工程目录下看Drivers\STM32F1xx_HAL_Driver\Inc里到底有没有文件,空的就回 CubeMX 重新 GENERATE CODE,并确认勾了 Copy only the necessary library files。从别处拷来的例程最容易出这个问题——对方的目录结构和你本地的不是一回事。
现象二:*** FATAL ERROR L250: CODE SIZE LIMIT IN RESTRICTED VERSION EXCEEDED
原因就一个:Keil 评估版 32KB 上限。HAL 库工程本身就大,加个串口和 printf 轻松超线。
解决:File → License Management 看授权状态,走正规渠道处理。别在这上面折腾编译优化和裁剪代码,那只是把问题往后拖,等工程再大一点照样撞墙。
5.2 下载阶段的两条:仿真器连不上、Flash 算法不对
现象三:No Cortex-M Device found in JTAG chain或RDDI-DAP Error
原因通常是三个:SWD 接线有问题(SWDIO、SWCLK、GND 三根必须接,有的板子还要接 3.3V 参考电压);Port 还停在 JTAG 没切成 SW;调试时钟太快,杜邦线稍微长一点就握手失败。
解决:Port 改 SW,Max Clock 从 1MHz 甚至 100kHz 起步,能连上再往上加;接线按板子丝印一对一核对。还连不上就按一下板子复位再点下载,或者把 Utilities 页的 Update Target before Debugging 取消试一次,这个选项在某些板子上反而会干扰连接。
现象四:Programming error @ 0x08000000或Cannot access target
原因:Flash 算法跟芯片容量等级不匹配;或者芯片被读保护了,下载器读不到 Flash。
解决:按 4.3 换成对应容量等级的算法。如果是读保护,用 STM32CubeProgrammer 连一次做全片擦除,把保护解除,再回 Keil 正常下载。这一步会把芯片里原有程序清掉,心里有数再动手。
5.3 运行阶段的一条:下载成功但灯不亮
现象五:下载显示成功,复位后没反应,调试器一挂上去就停在HardFault_Handler
原因有三种常见情况。一是宏定义和启动文件跟实际型号不匹配,这是最高频的;二是中断服务函数重名——HAL 库的回调机制下,中断里应该调HAL_XXX_IRQHandler,业务逻辑写在HAL_XXX_Callback里,自己又照着手册写一遍同名中断函数,链接时不一定报错,运行时行为就乱了;三是自己写了SystemInit或HAL_Init的替代实现,把原来的覆盖掉。
解决:先对着第 3 章那张表核宏和启动文件,两分钟能排掉大半;再看stm32f1xx_it.c里的中断函数有没有重复定义;最后在HardFault_Handler里打断点,看 LR 寄存器和栈里的返回地址,能直接定位到出事的那行代码。这一招比盲猜快得多,值得提前学会。
6. 装完怎么验证:编译产物、下载日志和板子上那盏灯
环境装没装好,不看 Keil 有没有打开,看三样东西。
第一样是编译产物。Build 一次之后,MDK-ARM目录下应该有.axf和.hex,Build Output 里那行Program Size: Code=xxxx RO-data=xxx RW-data=xxx ZI-data=xxx的四个数字都不为 0。Code 是 0 说明工程里一个.c都没参与编译,多半是文件没加进 Target,跟环境无关但同样跑不起来。
第二样是下载日志。点完 Download,Build Output 底部应该依次出现Erase Done、Programming Done、Verify OK、Application running ...。四行齐了才算真的写进 Flash;只出现前两行、卡在 Verify,回去看 4.3 的算法选择。
第三样是板上现象。在main.c的/* USER CODE BEGIN 3 */和/* USER CODE END 3 */之间写点灯:
/* USER CODE BEGIN 3 */ while (1) { HAL_GPIO_TogglePin(LED_G_GPIO_Port, LED_G_Pin); /* 翻转 LED 引脚电平 */ HAL_Delay(500); /* 依赖 SysTick 的毫秒延时 */ } /* USER CODE END 3 */HAL_GPIO_TogglePin的两个参数来自 CubeMX 生成的宏定义,引脚在 GPIO 配置里标了 User Label 之后才会有LED_G_Pin这种名字;HAL_Delay的节拍来自 SysTick,时钟树没配对的话,500ms 会变成 300ms 或 700ms,肉眼看不出来但用示波器一量就露馅。
三步对照着看,定位很快:
| 检查项 | 正常表现 | 异常指向 |
|---|---|---|
| 编译产物 | 有 .axf/.hex,Program Size 四项非 0 | 文件没加进 Target,或宏缺失 |
| 下载日志 | 四行全过,Application running | Flash 算法、读保护、接线 |
| 板上现象 | LED 按 500ms 稳定翻转 | 时钟配置、启动文件、中断冲突 |
想再验一步就把串口带上:CubeMX 里开 USART1,波特率 115200,在 while 循环里加一句HAL_UART_Transmit(&huart1, (uint8_t *)"ok\r\n", 4, 100);,串口助手里连续收到ok说明时钟和总线频率都对;收到乱码就回去查 APB2 是不是 72MHz、HSE 是不是真的起来了。
从那以后我每次装完这套环境,都强制走一遍“空工程编译 → 点灯 → 串口发一行”这三步,一步不过就不往下看例程。装环境这事没有后悔药,前面少验一步,后面就得拿半天去猜是代码问题还是配置问题。希望帮到你。
本文还有配套的精品资源,点击获取