STM32F103 HAL库零基础入门:环境搭建与LED点灯实战

发布时间:2026/10/29 6:48:39
STM32F103 HAL库零基础入门:环境搭建与LED点灯实战 简介《野火 EmbedFire STM32 HAL 库开发实战指南—基于 F103》面向零基础及入门阶段的嵌入式学习者围绕 STM32F103 与意法半导体 HAL 库展开帮助读者跨过工具链配置、外设驱动与程序下载调试的门槛。全书从 STM32 基础概念与学习路径讲起依次覆盖 KEIL5 安装与芯片包配置、DAP 仿真器硬件连接及 Debug、Utilities 参数设置再深入 GPIO、中断、定时器、串口通信、ADC 等外设的 HAL 库配置方法并以 LED 闪烁、串口协议实现及简单控制、数据采集项目收束配合技术论坛便于交流排错。压缩包仅含 1 个 PDF 文件大小约 43.41MB适合作为随查随用的案头教程。目前已有 1881 人学习可帮助初学者建立从环境搭建到外设实战的完整开发链路也可供有经验者查阅 HAL 库接口用法。1. 一块会眨眼的灯藏着 STM32 入门的全部秘密很多人第一次听到 STM32 和 HAL 库脑子里浮现的是密密麻麻的引脚、看不懂的英文手册下意识觉得这是「电子专业的人才碰的东西」。换个说法就好懂了STM32 是装在一颗芯片里的小管家你给它几条指令它就能按你的意思去开灯、读按键、往外发数据而 HAL 库是别人提前替你写好的一整套常用指令模板让你不用去背每个寄存器的地址也能把这位小管家使唤起来。这份基于 F103 的 HAL 库开发实战指南就是带着完全零基础的人从装软件、接板子一路走到亲手点亮第一颗 LED 灯。它适合没碰过单片机、但愿意花几个晚上动手试的人不需要你先学完电路和汇编再回来。2. 开工前把三件套备齐KEIL5、芯片包和 DAP 仿真器2.1 为什么第一件事不是写代码而是装环境新手最容易犯的错是翻到有代码的那一页就跳过前面的安装步骤照着敲一屏幕然后连编译按钮在哪都找不到。嵌入式开发和写网页最大的区别是代码写完不是丢进浏览器就能跑它要走完「编译成机器码 → 通过一条专用通道送进芯片 → 芯片复位后从头执行」这一整条链路。链路里任何一个环节没配好你看到的往往不是报错而是「什么都没发生」这才是最让人崩溃的地方。我把这一套比作做菜KEIL5 是厨房负责把源码加工成能下锅的成品芯片包是食材清单告诉厨房这颗 F103 有哪些引脚、哪些寄存器可用DAP 仿真器是传菜的传送带把做好的程序端到板子上。三样缺一菜要么做不出来要么做出来送不到桌上。教程把安装 KEIL5 和配置仿真器放在很靠前的位置不是凑页数而是因为这一步没过关后面所有实验都是空中楼阁。我第一次装的时候光「在新建工程时找不到芯片型号」就卡了大半个晚上一度以为安装包是坏的后来才发现是芯片包压根没装。这种问题没有任何报错提示只能靠经验判断。所以别急着跳先把环境跑通这是整条学习路径里唯一省不掉的一步。2.2 安装 KEIL5路径别带中文这一步错了后面全是玄学安装包拿到手先别急着双击。右键选「以管理员身份运行」这一步能避开后面一大半和权限相关的怪问题比如装到一半提示写入失败、或者装完打不开。安装路径是第一个坎。不少人习惯一路点下一步结果装到用户目录下面路径里带着自己的中文名之后编译就冒出一堆看不懂的编码错误。稳妥的做法是手动改成一个纯英文、无空格的路径比如D:\Keil_v5。步骤清单右键安装包以管理员身份运行一路 Next到选择安装路径那一步停下手动改成纯英文路径姓名、公司之类的信息随便填等待进度条走完首次启动若弹出许可证相关窗口按教程里给的步骤处理别自己乱点装完先别关接着装芯片包。提示安装路径定下来之后就别再搬了后面工程里的路径配置会引用它搬一次要重新配一堆东西。装完 KEIL5 只是有了厨房还不认识 F103 这颗芯片。芯片包通常是一个后缀为.pack的文件双击它安装程序会自动找到 KEIL 的安装位置并把支持文件塞进去。装好之后回到 KEIL打开 Pack Installer能在列表里看到 F1 系列那一栏变成已安装状态就说明成了。这一步没有任何成就感但它决定了你后面新建工程时能不能在器件列表里搜到自己的型号。2.3 装 DAP 驱动并接线让电脑「认得出」你的板子DAP 仿真器是电脑和板子之间的翻译官。电脑只有 USB 口板子上只有几个裸露的排针两边语言不通得靠它转一道。它一般有四根线要接SWDIO、SWCLK、GND以及 3.3V。前三根是必须的分别负责数据、时钟和共地3.3V 那根视板子供电情况而定如果板子已经单独插了电源就只接三根信号线加地线别两边同时供电。接线的顺序建议是先接 GND再接信号线最后接电源拔的时候反过来。这不是讲究是血泪经验——带电插拔信号线运气不好会打坏 IO 口。驱动装好之后把仿真器插进电脑 USB 口打开设备管理器正常情况下会多出一个和 CMSIS-DAP 相关的设备。如果那里是个带黄色感叹号的未知设备说明驱动没装上回到教程对应的那一节重新装一遍别硬着头皮往下走。2.4 在 KEIL 里认一次板子把程序送进去环境装完先做一次「能不能连上」的验证别等写完代码才发现连不上。打开 KEIL点工具栏上那个像魔术棒一样的按钮Options for Target在 Debug 标签页里把调试器选成 CMSIS-DAP Debugger再点右边的 Settings。这时候会弹出一个小窗口左边是连线和协议选项右边是一块区域显示扫描到的设备。如果接线正确、板子通了电那块区域里应该出现一行带 IDCODE 的记录。看到它就等于电脑和芯片握手成功了。这一行信息非常重要它是后面所有排查的起点连 IDCODE 都读不到问题一定出在硬件、接线或驱动上和你写的代码一行关系都没有。接着在同一个窗口里切到 Flash Download 标签页勾上 Reset and Run。这个选项的意思是「程序下载完自动复位运行」不勾的话你得手动按一下板子上的复位键才看得到效果很多新手就是在这里以为程序没下载成功。配置完之后点确定回到主界面按 F8 编译下载。第一次下载成功KEIL 底部的输出窗口会打印出烧写进度和校验结果看到全绿的字样这一关就算过了。3. 第一个能跑的例子用 CubeMX 点几下点亮一颗 LED3.1 为什么第一个实验一定选点灯嵌入式入门有个不成文的规矩不管学什么芯片第一个程序都是点灯。原因很实在——灯是你能用肉眼直接确认「程序真的跑起来了」的唯一信号。串口打印需要你另外打开一个软件看屏幕显示需要先驱动屏幕只有 LED一根引脚电平一变它立刻亮中间没有任何缓冲环节。对零基础的人来说第一周最难熬的不是知识难而是「我做了这么多事什么都没发生」的挫败感。点灯这个实验的意义就是尽快给你一次确定的正反馈。灯亮了说明工具链对了、下载通了、时钟跑了、GPIO 配好了这四件事同时成立后面才有继续往下走的底气。教程里其实给了两条路一条是纯寄存器版手动往寄存器里写值一条是用 CubeMX 生成工程再调 HAL 库函数。我建议新手先走第二条。不是寄存器不重要而是寄存器版要你先理解存储器映射、位操作、启动文件门槛太陡容易在第一周就放弃。等灯点亮了、有手感了再回头看寄存器那几章你会发现原来 HAL 库替你做的就是这么点事那时候理解起来轻松得多。3.2 在 CubeMX 里点五下把一根引脚配成输出CubeMX 是芯片厂商提供的图形化配置工具作用是把「查手册、算地址、写初始化代码」这些机械活变成点鼠标。打开软件新建工程在搜索框里输入你板子上芯片丝印的那串型号比如 STM32F103C8T6右边会列出匹配的型号双击选中。进去之后是一个芯片的俯视图四周密密麻麻全是引脚名。别慌这一步只用点五下左侧菜单找到 SYS把 Debug 一项选成 Serial Wire。这是给 DAP 仿真器留的调试通道不选的话下载一次程序芯片就被锁住下次连不上只能想办法解锁左侧菜单找到 RCC把高速外部时钟 HSE 选成 Crystal/Ceramic Resonator也就是外部晶振在芯片图上找到接 LED 的那根引脚教程配套板子一般是 PA5 之类以原理图为准左键点它选 GPIO_Output右键这根引脚进入详细设置把输出速度设成 Low把默认电平设成 Low也就是上电默认灭顶部切到 Project Manager 标签填工程名、选一个纯英文路径、Toolchain 选 MDK-ARM然后点右上角 Generate Code。生成完之后点 Open ProjectKEIL 会自动打开这个工程该加的库文件、启动文件、时钟配置代码全都已经摆好了。这就是 HAL 库生态最舒服的地方初始化那几百行代码工具替你写完了。注意SYS 里的 Debug 一定要选成 Serial Wire 再生成代码。这一步漏掉程序下载进去之后调试口就关了第二次下载会提示找不到目标只能靠上电瞬间抢时间或者用专用工具解锁非常折腾。3.3 生成的 main 函数长什么样你的代码该写在哪KEIL 打开工程后展开左侧目录树找到Core/Src/main.c双击打开。你会看到一大堆代码别被吓到真正要看的只有两个地方一个是main函数开头那一段带着MX_GPIO_Init()、MX_RCC_Init()之类名字的调用另一个是最后的while (1)循环。代码大致长这样int main(void) { /* 芯片复位后先做一堆底层初始化这段是工具生成的别动 */ HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); /* 用户代码要写在下面这两个注释之间 */ /* USER CODE BEGIN 2 */ /* USER CODE END 2 */ while (1) { /* USER CODE BEGIN 3 */ HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // 翻转 PA5 的电平 HAL_Delay(500); // 停 500 毫秒 /* USER CODE END 3 */ } }上面这段代码里HAL_GPIO_TogglePin的作用是把指定引脚的电平从高变低、从低变高参数一GPIOA是端口号参数二GPIO_PIN_5是引脚号合起来就是「A 口的第 5 号脚」。HAL_Delay(500)是 HAL 库提供的毫秒级延时参数单位是毫秒填 500 就是半秒。两个函数一配合灯就变成亮半秒、灭半秒地闪。最关键的一条规矩你自己的代码必须写在USER CODE BEGIN和USER CODE END这两组注释之间。这两组注释是给 CubeMX 看的标记它重新生成代码时会跳过这中间的内容写在标记外面的代码下次一生成就被覆盖掉白写。3.4 编译、下载、看灯亮第一次闭环代码写完按 F7 编译。底部输出窗口最后一行如果显示 0 Error(s)说明编译通过有警告可以暂时不管但错误必须清零。接着按 F8 下载。下载成功后板子会自动复位你应该看到那颗 LED 开始规律地闪烁。如果灯没反应先别怀疑人生按顺序确认三件事板子是不是真的通电了有的板子下载口供电不够要单独插 USBHAL_Delay的时间是不是设得太长比如写成了 5000 看着就像常亮引脚号是不是和原理图对得上。这三条能解释新手八成的「灯不亮」。到这里你已经完整走完了一遍「配置 → 生成 → 写代码 → 编译 → 下载 → 验证」的闭环。这条闭环是所有 STM32 项目的骨架后面学串口、学定时器、学中断走的都是同一套动作只是中间填的内容不一样。4. HAL 库到底替你干了什么从寄存器到 GPIO 结构体4.1 同一件事的两种写法改寄存器 VS 调函数要理解 HAL 库的价值最好的办法是把同一件事用两种方式各写一遍。点亮一颗灯本质上就是让某个寄存器里的某一位变成 1 或者 0。寄存器版的写法是这样的/* 第一步打开 GPIOA 的时钟不打开外设不工作 */ *(unsigned int *)0x40021018 | (1 2); /* 第二步把 PA5 配成通用推挽输出、速度 50MHz */ *(unsigned int *)0x40010800 ~(0xF 20); // 先清掉这 4 位 *(unsigned int *)0x40010800 | (0x3 20); // 再写进配置值 /* 第三步把 PA5 拉低灯亮低电平点亮 */ *(unsigned int *)0x4001080C ~(1 5);这段代码里全是十六进制地址和移位操作。地址0x40021018是时钟使能寄存器的位置0x40010800是 GPIOA 的配置寄存器0x4001080C是输出数据寄存器。写错一个数字程序不报错只是没反应排查起来像在黑匣子里摸。HAL 库版的写法/* 第一步打开 GPIOA 时钟函数名就把意图写清楚了 */ __HAL_RCC_GPIOA_CLK_ENABLE(); /* 第二步用一个结构体描述引脚要怎么配 */ GPIO_InitTypeDef gpio {0}; gpio.Pin GPIO_PIN_5; // 第 5 号引脚 gpio.Mode GPIO_MODE_OUTPUT_PP; // 推挽输出 gpio.Pull GPIO_NOPULL; // 不上拉也不下拉 gpio.Speed GPIO_SPEED_FREQ_LOW; // 低速就够了 HAL_GPIO_Init(GPIOA, gpio); /* 第三步拉低电平 */ HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);两段代码做的事一模一样但第二段的每一个字段都在说人话。你不需要知道寄存器地址只需要回答四个问题哪个脚、什么模式、要不要上下拉、多快。这就是 HAL 库的核心设计思路——把「地址 位运算」翻译成「结构体 枚举」。当然代价也有HAL 库多了一层函数调用代码体积更大、执行稍慢。但对入门阶段来说这点开销完全不值一提你要优先解决的是「能不能跑起来」不是「省不省那几十个时钟周期」。4.2 把初始化参数表读一遍你就掌握了八成的外设GPIO_InitTypeDef这个结构体只有四个字段但它们是理解所有 HAL 外设的模板。后面学串口、学定时器、学 ADC套路完全一样定义一个结构体填几个字段调一个 Init 函数。字段常用取值什么时候改它PinGPIO_PIN_0 到 GPIO_PIN_15可或运算组合换引脚时改Mode输入、输出推挽、输出开漏、复用、模拟决定这根脚是读还是写Pull无上下拉、上拉、下拉接按键、接悬空信号时必须设Speed低、中、高、超高驱动 LED 用低就行驱动高速信号才调高Mode 里的「推挽」和「开漏」是新手的第一个坎。推挽输出能主动把电平拉到高也能拉到低驱动 LED 直接用这个开漏输出只能拉低高电平得靠外部上拉电阻多用于总线通信。你现在只需要记住点灯、驱动蜂鸣器一律选推挽。Pull 这一栏更容易被忽略。如果一根输入引脚悬空不接任何东西它的电平是随机的读出来一会儿 0 一会儿 1俗称「浮空」。接按键的时候必须配上拉或下拉让它在没人按的时候有个确定的电平否则你会以为程序出了问题其实是硬件在飘。4.3 把输出改成输入按键检测怎么写灯会点了反过来读一个按键状态就是把这根引脚从输出改成输入。原理图上按键一般是一头接地、一头接引脚按下时引脚被拉到地。所以配置成输入上拉最合适没人按时上拉电阻把它拉到高电平读到 1按下时被拉到地读到 0。/* 按键初始化PA0 配成输入上拉 */ GPIO_InitTypeDef key {0}; key.Pin GPIO_PIN_0; key.Mode GPIO_MODE_INPUT; // 输入模式 key.Pull GPIO_PULLUP; // 内部上拉松开时读 1 HAL_GPIO_Init(GPIOA, key); /* 在主循环里读状态 */ while (1) { if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) GPIO_PIN_RESET) { HAL_Delay(20); // 等 20 毫秒避开按键抖动 if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) GPIO_PIN_RESET) { HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // 确认按下再翻转灯 } while (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) GPIO_PIN_RESET); // 等松手 } }HAL_GPIO_ReadPin返回的是一个枚举要么GPIO_PIN_SET高电平要么GPIO_PIN_RESET低电平。上拉配置下按下读到 RESET这条判断就成立。代码里那个HAL_Delay(20)是干什么的机械按键在按下和松开的瞬间金属片会弹跳几下产生几个毫秒的电平抖动程序读得太快会误判成按了好几次。延时 20 毫秒再读一次两次都确认按下才算数这就是最简单的软件消抖。最后那个空循环是等松手不加的话你按一下程序会在按住的这段时间里疯狂翻转。这段代码写得不算漂亮但它把「输入、上拉、消抖、边沿判断」四个概念一次性串起来了是新手理解 GPIO 输入最直观的例子。4.4 时钟没配对后面全白费前面两段代码里都有一行__HAL_RCC_GPIOA_CLK_ENABLE()很多人抄代码时会漏掉然后陷入「配置全对、灯就是不亮」的困境。原因是 STM32 为了省电所有外设默认都是关着的包括 GPIO。你得先给它供电行话叫开时钟配置才生效。这个设计的思路和家里的电闸一样每个房间外设都有独立的开关你不用哪个房间就把哪个闸拉下来。写代码时每用到一个外设第一件事就是给它开时钟这几乎是肌肉记忆。时钟还有另一层含义就是整个芯片的心跳。系统时钟决定了 CPU 跑多快、延时准不准、串口波特率对不对。教程里专门有一章讲用 HSE外部晶振和 HSI内部 RC配置系统时钟那就是在调这个心跳。新手阶段有个简单判断如果HAL_Delay(500)实际延时明显不是半秒比如快得多或者慢得多八成是时钟配置和板子上的晶振没对上。提示CubeMX 生成代码时时钟树那一页会显示最终的频率。新建工程后先扫一眼和你板子标称的主频对不上就回去检查 HSE 有没有选对外部晶振。5. 新手最容易卡住的五个坑现象、原因与解决5.1 编译通过下载时提示找不到目标现象很典型按 F8 之后弹窗说连不上设备或者 Settings 窗口右边那块区域空空如也读不到 IDCODE。原因通常有三个。一是 SYS 里的 Debug 选项没选 Serial Wire芯片的调试口被关掉了这是最常见的一个二是接线问题SWDIO 和 SWCLK 接反、GND 没接、杜邦线接触不良三是板子没供电只插了仿真器但板子自己没电。解决办法按顺序排先看接线重点确认 GND 那根再确认板子电源指示灯亮不亮如果都正常还是连不上就按住板子复位键点下载的一瞬间松开这叫「抢复位」能在芯片启动的极短窗口里连上连上之后立刻重新生成一次带 Serial Wire 的工程刷进去把芯片救回来。5.2 程序下载成功灯就是不亮现象是输出窗口全绿说明烧写校验都通过了但板子上一点动静都没有。原因分两类。硬件类是引脚接错了比如代码里写 PA5原理图上 LED 其实接在 PC13或者 LED 是高电平点亮而你一直在拉低。软件类是初始化漏了开时钟__HAL_RCC_GPIOA_CLK_ENABLE()少写一行配置全都不生效。排查手法很土但有效先拿万用表量一下这根引脚在运行时有没有电压变化有变化说明程序在跑、是硬件接法问题没变化就回去查代码从时钟使能那一行开始逐行对。别一上来就怀疑芯片坏了芯片坏的概率远低于你少写一行。5.3 改了代码没变化像下载了个寂寞现象是明明改了HAL_Delay的数灯闪的频率一点没变。原因多半是代码写在了USER CODE BEGIN和USER CODE END外面CubeMX 重新生成时把它覆盖了你改的那份早就被冲掉或者是编译器没重新编译直接下载了旧的.hex。解决方式是养成习惯每次改完代码先按 F7 编译看输出窗口有没有重新编译的记录再按 F8 下载。另外任何时候都不要在生成代码的标记外面动手那是给自己埋雷。这个坑我踩过一次改了半天以为软件有 bug最后发现自己的修改根本不在文件里。5.4 串口打印乱码或者注释变成一堆问号现象是接上串口助手屏幕上出来一串看不懂的符号或者 KEIL 里中文注释显示成方块。原因是编码和波特率对不上。中文乱码看 KEIL 的设置编码选成 UTF-8 或 GB2312 要和文件本身一致改完记得保存重开串口乱码看波特率代码里初始化时填的是 115200串口助手那边也得是 115200两边差一点就全是乱码。还有一个容易忽略的点串口助手要选对 COM 口。电脑上插了好几个 USB 设备时COM 号会变插拔一次就得重新确认。5.5 加了个延时程序就卡死灯也不闪了现象是程序一跑就停在那儿灯常亮或者常灭复位也没用。原因通常是延时函数用错了场合。HAL_Delay依赖系统滴答定时器如果在中断服务函数里调用它而中断优先级又比滴答定时器高就会互相等待形成死锁还有一种情况是在时钟还没配置好之前就调用延时此时滴答定时器没启动函数一直等下去。解决办法很简单延时只放在主循环里用中断里要做延时就用计数器或者标志位别调阻塞函数。这条规矩在后面学中断那一章会反复出现先记住结论就行。6. 灯点亮之后接下来按什么顺序往下学第一次跑通之后最容易出现的情况是兴奋两天然后迷茫——教程那么厚下一章该看哪个我的建议是别按目录顺序从头啃到尾按「能做出东西」的顺序走每学一个外设就配一个小实验学完立刻有反馈。下面这个顺序是我带过几轮之后觉得最顺的顺序学什么配的实验大概花多久1GPIO 输出与输入LED 闪烁、按键控制灯两三个晚上2系统时钟 RCC改主频观察延时变化一个晚上3中断与 NVIC按键用中断方式触发两三个晚上4串口通信单片机往电脑发一句话两三个晚上5定时器用定时器精确闪灯一周6ADC 采样读一个电位器的电压一周为什么把中断放在串口前面因为串口接收数据天然就是中断驱动的先搞懂中断的「触发—进服务函数—清标志—返回」这条链路再去看串口接收回调逻辑是通的反过来先学串口你会被回调函数里那一堆陌生概念绕晕。而时钟排第二是因为后面所有外设的时序都和它有关早点弄明白「主频、分频、总线」这三者的关系能省掉后面无数次「为什么波特率算不对」的困惑。学每一章的时候我建议做一件事把教程里的例程跑通之后自己改一个参数看结果。比如把延时常数从 500 改成 100把引脚从 PA5 换到 PC13把上拉改成下拉。改动越小越好目的不是做出新功能而是确认自己真的知道每一行在控制什么。光看不改第二天就忘。还有一个具体技巧准备一个自己的「实验记录本」哪怕是手机备忘录。每跑通一个实验记三行——配了哪根引脚、关键函数叫什么、遇到什么问题怎么解决的。我当初学串口的时候光「波特率对不上」这一个问题就反复踩了三次后来记下来才彻底记住。最后说个我自己的教训。我刚开始学的时候贪快一口气把教程里 GPIO、中断、串口三章连着看完代码也照抄了三遍感觉很顺。结果一周后想独立写个按键控制灯的程序打开工程一片空白连结构体该填哪几个字段都想不起来。从那以后我每次学新外设都强制走一遍「关掉教程自己写一次」的流程写不出来就说明没学会回去再看。这个笨办法花的时间反而最少。希望帮到你早点把那颗灯点起来。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询