☰
STM32F103 HAL库环境搭建与DAP调试避坑指南
2026/10/29 16:31:51 网站建设 项目流程

简介:《野火EmbedFire STM32 HAL库开发实战指南—基于F103》是一份面向嵌入式初学者与入门工程师的PDF教程,围绕STM32 HAL库展开,帮助读者从工具链搭建过渡到外设编程与小型项目实践。全书从KEIL5安装、STM32芯片包配置讲起,逐步说明DAP仿真器的硬件连接、Debug选项设置与程序下载流程,再进入GPIO、中断、定时器和串口通信等常用外设的HAL库配置方法,并配合LED闪烁、串口协议实现与数据采集等实战案例,配有硬件说明、学习顺序建议与技术论坛入口,适合按章节循序推进。整包仅含1个PDF文件,体积约43.41MB,单文件即可离线阅读,便于打印或分屏对照操作。目前已有1881人学习,可作为STM32入门阶段系统梳理HAL库与开发环境的参考资料。

1. 版本先对上:F103-MINI 的 STM32 HAL 库环境,装错一步后面全是坑

在 F103-MINI 这块板子上第一次安装 STM32 HAL 库开发环境,真正让人反复返工的往往不是代码,而是版本对不上。这套教程对应的工具链固定成四件事:Keil5(MDK-ARM)作为 IDE 与编译器、STM32F1 系列芯片包、DAP 仿真器驱动、STM32CubeMX 负责生成 HAL 库初始化代码。四个人凑齐了,基于 ARM Cortex-M3 的 STM32F103 才能从“能编译”走到“能下载、能跑”。少装一个芯片包,新建工程时器件列表里就是空的;仿真器选项卡填错一格,编译零错误却死活下载不进去;CubeMX 的时钟树点错一个倍频系数,串口打印全是乱码。

这一套下来最怕的就是顺序乱:先装 CubeMX 再补 Java、先建工程再补 pack、装完 Keil 直接插板子。下面按我实际装机的顺序走一遍,每步都给出能直接照抄的参数,最后再把装坏之后怎么救讲清楚。

2. Keil5 安装与 STM32F1 芯片包:从双击安装包到能新建工程

Keil5 装得好不好,差别不会立刻出现,只有过了两三天,才会在某次编译或某次下载时别扭地显现出来。所以每次给新机器配环境,我都不图快,按照“确认安装包 → 定安装路径 → 装芯片包 → 定编译器版本 → 配宏和头文件路径”这个固定顺序走,宁可多花二十分钟,也省得后面花一整晚排查。

2.1 安装前三件事:安装包版本、路径、管理员权限

安装包拿到手先看文件名和大小,MDK5 的安装包是个单独的 exe,几百兆量级;如果下载下来只有几十兆,那多半是下载器抓到的错误页面,装到一半会提示组件缺失。安装之前把 Keil、CubeMX、串口助手全部关掉,杀毒软件的实时防护临时关掉——它会拦截 Pack 解压和 armcc 的临时文件写入,表现是安装进度条卡在最后一步不动,或者装完打开 Pack Installer 一片空白。

安装路径是第一个分水岭。我见过太多因为路径里带中文或空格,导致 Pack Installer 打不开、编译中间文件报权限错误的案例,所以路径规则我从来不含糊:

安装路径实际结果
C:\Keil_v5推荐,短、无空格、无中文,Pack 写入不会触发 UAC 拦截
C:\Program Files\Keil_v5部分组件写入需要提权,编译中间文件偶发权限报错
D:\嵌入式\keil5中文路径在 Pack Installer 与 armcc 调用链里容易出错
D:\Keil v5路径带空格,个别老版本工程脚本解析失败

安装程序右键选择“以管理员身份运行”,不要直接双击。安装过程中会弹出一堆驱动安装确认(ULINK、J-LINK 之类的 USB 驱动),这里全部点安装,即便你手上只有 DAP 也用得上,后面换仿真器不用重装。装完最后会问要不要启动 Pack Installer,先点否——芯片包我们下一步单独装,让它自己联网拉容易超时。

提示:安装完成后先别插开发板。Keil 首次启动会初始化许可证和配置文件,此时插着板子,Windows 有可能把板子的虚拟串口识别成未知设备并装上错误驱动,后面排查时会多出一条干扰线索。

2.2 装完立刻补 STM32F1 芯片包,别等新建工程才发现列表是空的

Keil5 本体只是个空壳,它不认识任何一颗芯片。所谓“芯片包”(Device Family Pack,DFP)里装的是三样东西:启动文件startup_stm32f10x_hd.s、Flash 下载算法、调试时用的寄存器描述文件(SVD)。少了它,新建工程时 Device 那一栏就是空的,下载时 Programming Algorithm 里也选不到任何算法。

安装有两条路,我一般优先走离线:

  1. 拿到Keil.STM32F1xx_DFP.x.x.x.pack文件,直接双击,它会自动关联到已安装的 Keil 并开始安装;
  2. 或者在 Keil 里点工具栏的 Pack Installer,左侧 Devices 找到 STM32F1 Series,右侧 Packs 里点 Install,走在线安装。

离线包的优势是版本可控,团队里几个人装出来的 DFP 版本一致,编译行为就不会有差异。在线安装则可能出现“当前最新版 DFP 已经不支持老启动文件命名”的情况,表现为编译时报找不到startup_stm32f10x_hd.s。装完之后验证方式很简单:Keil 里 Project → New uVision Project,随便找个目录建,弹出的器件选择框里能展开 STM32F1 Series 并看到 STM32F103RC、STM32F103VE 这类具体型号,就说明包装好了。看不到就打开 Pack Installer 看 STM32F1 那一行是不是显示 Installed。

顺带说一句 Flash 算法怎么选,这步在后面 DAP 配置里还会用到,先记住对应关系:

芯片型号后缀Flash 容量密度等级Keil 里对应的下载算法
C8 / CB64K / 128KMedium-densitySTM32F10x Med-density Flash
RC / VC256KHigh-densitySTM32F10x High-density Flash
ZE / VE512KHigh-densitySTM32F10x High-density Flash

选错算法的典型现象是下载时提示“Flash Download failed - Target DLL has been cancelled”,或者下载进度到一半直接报擦除错误。程序本身没问题,问题在于算法只认识前 128K 的地址空间。

2.3 编译器版本:AC5 与 AC6 的取舍,还有那个 32KB 限制

这个坑是我踩得最深的一个。MDK 从 5.37 版本开始,安装包里不再自带 ARM Compiler 5(也就是大家常说的 AC5、armcc),默认只带 ARM Compiler 6(armclang)。而这套 F103 教程里的例程,当年是按 AC5 写的,语法上有一批 AC6 不认的写法:__asm关键字、内联汇编的__ASM volatile形式、部分老版本__weak修饰。打开工程直接编译,报错信息通常是:

Error: *** Target 'LED' uses ARM-Compiler 'Default Compiler Version 5' which is not available.

解决办法有两个,看你的场景选:

  • 想和教程完全对齐,去官网单独下载 ARM Compiler 5 的安装包,装到 Keil 目录下的ARM\ARMCC,然后在 Options for Target → Target → ARM Compiler 里选 “Use default compiler version 5”;
  • 想用新工具链,就切到 AC6,然后把内联汇编改成__ASM volatile的写法,__weak换成__attribute__((weak))。工程量不大,但凡是教程里带汇编的章节(启动文件、寄存器操作)都得跟着改。

我自己的习惯是:跟教程走一遍的时候用 AC5,把例程跑通;等自己写业务代码再切 AC6,顺手把编译告警清干净。两种方式不要混着来,同一个工程里一会儿 AC5 一会儿 AC6,编出来的中间文件不兼容,最直接的表现是“全编译能过、增量编译报一堆重复定义”。

另一个绕不开的限制是 MDK-Lite 的 32KB 代码上限。F103-MINI 这套教程前期的点灯、按键、串口例程基本都在这个范围内,超了会报:

Error: L6050U: The code size of this image (34xxx bytes) exceeds the maximum allowed for this version of the linker.

这不是环境装坏了,是许可证限制。前期验证阶段够用,等你要往工程里塞 FatFs、USB 之类的大组件时,再考虑正式许可证。别在环境搭建阶段为了这个报错反复重装,浪费时间。

2.4 新建工程的三个宏和五条头文件路径

工程建起来之后,真正决定能不能编译通过的是 Options for Target 里的两处配置。第一处是 C/C++ 选项卡的 Define 宏:

/* Options for Target -> C/C++ -> Define,多个宏用英文逗号分隔,不要加分号 */ USE_HAL_DRIVER, STM32F103xE

USE_HAL_DRIVER决定了stm32f1xx.h里要不要把 HAL 库的头文件包含进来,删掉它,编译到HAL_GPIO_Init就会报隐式声明;STM32F103xE决定选中哪一份启动文件和寄存器定义,其中xE对应 512K Flash 的大容量型号,如果你的板子是 RCT6(256K),这里要写STM32F103xE对应的中等容量宏得看具体器件头文件的分类,写错了的表现是中断向量表和实际芯片对不上——中断进不去,或者进了错误的中断服务函数。这类问题属于典型的“编译能过、运行不对”,排查起来毫无头绪。

第二处是 Include Paths,也就是头文件搜索路径。HAL 库的目录结构分层很清楚,少加一条就报cannot open source input file "stm32f1xx_hal.h":

..\User ..\Drivers\STM32F1xx_HAL_Driver\Inc ..\Drivers\STM32F1xx_HAL_Driver\Inc\Legacy ..\Drivers\CMSIS\Device\ST\STM32F1xx\Include ..\Drivers\CMSIS\Include

第一条放用户代码和main.h,第二条是 HAL 驱动本体,Legacy目录里是旧版 API 的兼容层,老教程里偶尔会用到;后两条是 CMSIS 的器件定义和内核定义,core_cm3.h就在最后一条路径下。这五条缺一条,编译就停在某个头文件找不到的地方。我在团队里推行过一个笨办法:把这段路径列表直接贴在工程目录的readme.txt里,谁装环境就照着填,比口头描述靠谱得多。

3. DAP 仿真器配置:Debug、Utilities 与 Settings 三处要一起改

编译通过只是走了一半,能不能把程序烧进芯片、烧进去之后能不能跑,全看仿真器这一块。DAP 这类 CMSIS-DAP 仿真器便宜、免驱(Windows 10 以上自带 HID 驱动)、支持 SWD 两线调试,是 F103 这类小板子最省事的选择。但它有个特点:Keil 里跟它相关的配置分散在三个地方,只改其中一处,程序要么下载失败,要么下载成功但不运行。

3.1 接线与供电:SWD 四根线的顺序和共地

DAP 仿真器到开发板之间一般接四根线:3V3、GND、SWDIO、SWCLK。有的仿真器还带RESET,可以接也可以不接,接了之后“Connect under Reset”模式更好用,不接就靠软件复位。接线的顺序我习惯先接 GND 再接电源和信号,理由很朴素:带电插拔时先接触地线,能避免信号线上出现瞬态电平把 IO 打坏,虽然概率不高,但一块板子几十块钱,没必要赌。

供电这块得分清楚谁给谁供电。F103-MINI 板上自带 USB 供电和 3.3V LDO,正常情况是板子自己供电,仿真器的3V3悬空不接,只接GND、SWDIO、SWCLK三根。如果板子没插 USB,只靠仿真器的 3.3V 供电,遇到功耗稍大的外设(比如点亮一排 LED、驱动 OLED)就会掉压,表现为下载时好时坏、调试时突然断开连接。我一般统一约定:板子自己插 USB 供电,仿真器只做信号连接。

接好之后插上电脑,设备管理器里应该能看到一个 HID 设备,名字里带 CMSIS-DAP。看不到就换根 USB 线——这个血泪经验值得单独说一句,很多“仿真器坏了”最后查出来是充电线只有电源针没有数据针。

3.2 Debug 与 Utilities 两个选项卡必须指向同一个驱动

打开 Options for Target → Debug 选项卡,右上角的下拉框选CMSIS-DAP Debugger,然后点旁边的 Settings。弹出的窗口里做三件事:

配置位置配置项建议取值说明
Debug → SettingsPortSW只用 SWD 两线,JTAG 五线在这类板子上没必要
Debug → SettingsMax Clock1 MHz排线较长或干扰大时降频,识别更稳
Debug → SettingsSW Device能列出 IDCODE这一栏为空说明根本没连上芯片
UtilitiesUse Target Driver for Flash Programming勾选,并选 CMSIS-DAP下载和调试共用同一驱动
Utilities → SettingsProgramming Algorithm按上文的密度等级选决定擦写哪片 Flash

Settings 窗口左侧 SW Device 那一栏是判断生死的分水岭。如果里面显示了一串 IDCODE 和器件名称,说明链路通了;如果显示Error: No Cortex-M SW Device Found,先别怀疑软件配置,去查供电和接线,具体排查方法放在第 5 章。

Utilities 选项卡容易被忽略。它的作用是告诉 Keil“烧录时用哪个工具”,如果不勾选Use Target Driver for Flash Programming,Keil 会去用默认的 ULINK 配置,现象是点下载按钮后弹窗提示找不到 ULINK。Debug 选项卡里配好了 DAP,Utilities 里没跟上,这是最典型的“只改一半”。

3.3 Flash 算法、复位方式与 Reset and Run:下载成功却不跑的三个源头

点 Utilities 旁边的 Settings,进入 Flash Download 页,这里有三项配置需要一次性配好:

第一项是 Programming Algorithm 列表。点 Add,按 2.2 节的密度对照表选对应算法,比如 256K 的 RCT6 选STM32F10x High-density Flash 256K。列表里如果出现两条算法,删掉多余的那条,否则会出现“擦除地址越界”的报错。

第二项是 Reset 方式。默认勾选Reset and Run的话,下载完成后芯片会自动复位并运行;不勾的话,程序烧进去了但停在复位向量处等你手动按复位键。很多新手的第一反应是“下载成功了怎么灯不亮”,实际上板子正在等你按一下 NRST。

第三项是Connect模式。正常用Normal,如果板子里的程序把 SWD 引脚复用成了普通 GPIO(常见于低功耗例程),芯片一上电就脱离了调试状态,这时要改成Connect under Reset,让仿真器在芯片复位还没跑用户代码的那一瞬间抢占连接。这个选项在 Debug → Settings 里也能设,两处保持一致。

配置完点 OK,回到主界面按 F8 或者点下载按钮。输出窗口正常应该看到这几行:

Load "..\\Objects\\LED.axf" Erase Done. Programming Done. Verify OK. Application running ...

看到Application running才算真正走完。只看到Programming Done而没有最后一行,说明 Reset and Run 没勾,或者复位电路上有问题。

4. STM32CubeMX 与 HAL 库工程:Java 依赖、时钟树与用户代码区

教程里前后出现了两种建工程的方式:一种是纯手工建,从启动文件、CMSIS、HAL 驱动一个个往里加;另一种是用 CubeMX 图形化配置后生成。前者适合理解 HAL 库的目录结构,后者是实际项目里的主流做法。CubeMX 这条路上有三个容易翻车的点:Java 运行时、时钟树、用户代码区。

4.1 Java 运行时先装,CubeMX 才不会白屏

CubeMX 是基于 Java 的桌面程序,早期版本对 Java 运行时(JRE)有硬依赖,机器上没装或者装了个位数不对的版本,双击图标后要么没反应,要么弹出一个空白窗口然后消失。安装顺序必须是先 Java 后 CubeMX:

  1. 装 64 位 JRE,安装路径同样不要带中文和空格;
  2. 再装 CubeMX 本体,安装目录单独建,不要塞进 Keil 目录里;
  3. 首次启动时如果提示选择固件库仓库路径(Repository Folder),指定一个纯英文路径,比如D:\STM32Cube\Repository。

第三点很关键。CubeMX 生成代码时需要对应系列的固件包(F1 系列对应STM32Cube_FW_F1_Vx.x.x),仓库路径里带中文或者没权限写入,生成代码时会卡在最后一步,报一个含义模糊的Error generating code。老版本 CubeMX 需要手动点 Help → Manage embedded software packages 去下载并安装 F1 固件包,新版可以直接从本地导入离线包。如果公司网络访问外网受限,就提前把固件包下载好放在仓库目录下,省得生成代码时干等。

4.2 时钟树:HSE 8MHz 到 72MHz 这条链的每一环

新建工程、选好 STM32F103 具体型号之后,第一件事是确认时钟源。F103 有两个时钟源可选:内部高速时钟 HSI(8MHz,精度差、受温度影响)和外部高速时钟 HSE(板上那颗 8MHz 晶振,精度高)。板上既然焊了晶振,就选 HSE,然后在 Clock Configuration 页把时钟树配到 72MHz 上限:

  • HSE 输入 8MHz;
  • PLL Source 选 HSE;
  • PLL Mul 选 ×9,得到 72MHz;
  • System Clock Mux 选 PLLCLK,SYSCLK 就是 72MHz;
  • AHB Prescaler 选 /1,HCLK 保持 72MHz;
  • APB1 Prescaler 选 /2,得到 36MHz——这是 F103 的硬性限制,APB1 上的外设时钟不能超过 36MHz;
  • APB2 Prescaler 选 /1,保持 72MHz。

CubeMX 会在时钟图上用红色标出超限的节点,看到红色就说明某一环配错了。配完之后它生成的SystemClock_Config函数大致长这样,理解这段代码比记住界面上点了哪几下更重要:

void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /* 打开 HSE 并配置 PLL:8MHz * 9 = 72MHz */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; HAL_RCC_OscConfig(&RCC_OscInitStruct); /* 切换系统时钟源到 PLL,并设置各总线分频 */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2); }

HSEPredivValue是 F103 特有的预分频,接 8MHz 晶振时选不分频;PLLMUL决定倍频系数,写错就会得到 36MHz 或者干脆起振失败。FLASH_LATENCY_2是 72MHz 下的等待周期数,低于 48MHz 用 1、高于 48MHz 必须用 2,配错的表现是程序跑飞、偶发 HardFault,这类问题最容易让人误以为是代码写错了。

还有一个关联参数在stm32f1xx_hal_conf.h里:

/* 外部晶振频率,必须和板子上实际焊的晶振一致 */ #if !defined (HSE_VALUE) #define HSE_VALUE 8000000U /* 8MHz,填成其他值会导致串口波特率整体偏移 */ #endif

HAL 库靠这个宏计算各个外设的时钟,进而算出串口波特率的分频值。如果板上是 8MHz 而你把它改成了 12MHz,代码能编译能跑,但串口输出全是乱码,且没有任何报错提示。我在现场排查过一次整整花了两个小时,最后发现就是这一行。

4.3 工程属性与用户代码区:重新生成代码不丢逻辑的唯一办法

Project Manager 页里有两个设置决定了生成出来的工程能不能直接编译:

Toolchain/IDE 选MDK-ARM,版本选 V5;Code Generator 里勾选Generate peripheral initialization as a pair of .c/.h files per peripheral,这样每个外设的初始化代码会单独成文件,后面改起来不会全挤在main.c里。

最重要的一条是用户代码区。CubeMX 生成的代码里,所有你能改的地方都被注释块包着:

/* USER CODE BEGIN 2 */ /* 你自己的初始化代码写在这里 */ /* USER CODE END 2 */ while (1) { /* USER CODE BEGIN 3 */ HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin); HAL_Delay(500); /* USER CODE END 3 */ }

BEGIN和END之间的内容会被 CubeMX 记住,下次重新生成代码时原样保留;写在注释块外面的代码,重新生成时会被直接覆盖掉,而且没有任何提示。这个破坏属于典型的“悔之晚矣”,我现在的习惯是:工程一建好,先往USER CODE区里写一行// here占位,确认重新生成代码后它还在,再开始写业务逻辑。

另外,生成的工程第一次打开时,Keil 里的宏定义和头文件路径已经自动配好了(就是第 2.4 节那套),但芯片包和编译器版本还是得自己确认一遍。CubeMX 不认识你机器上装的是 AC5 还是 AC6,它只写工程文件。

5. 排查清单:六条把环境装坏的真实记录

下面这六条都是我在装这套环境时真实遇到过的,顺序按遇到频率从高到低排,每条按“现象 → 原因 → 解决”写,遇到问题可以对着翻。

第一条:编译报cannot open source input file "stm32f1xx_hal.h"

现象是点编译,第一行就中断,错误指向stm32f1xx.h里的包含语句。原因是 Include Paths 里缺了Drivers\STM32F1xx_HAL_Driver\Inc,或者虽然加了但路径层级写错一级(比如加成了Drivers\Inc)。解决方式是对着第 2.4 节的五条路径逐条核对,Keil 的 C/C++ → Include Paths 里能看到每一条的完整展开路径,发现哪条显示为无效就删掉重新加。还有一种隐蔽情况:工程是从别人机器上拷来的,路径里带着对方的绝对路径(比如C:\Users\xxx\Desktop\...),这种路径在你机器上不存在,必须全部换成相对路径..\Drivers\...。

第二条:下载时提示No Cortex-M SW Device Found或RDDI-DAP Error

现象是点下载按钮,Keil 卡住几秒后弹错误框,Debug Settings 左侧 SW Device 一片空白。原因按概率排:一是板子没供电,只接了 SWDIO/SWCLK/GND 却没插 USB;二是杜邦线接触不良或者SWDIO和SWCLK接反了;三是芯片里的程序把 SWD 引脚复用掉了。解决办法是先用万用表量板子 3.3V 是否有电,再换一组线,最后在 Debug → Settings 里把 Connect 改成Connect under Reset并勾选Reset after Connect,让仿真器在芯片跑用户代码之前抢占连接,连上之后立刻全片擦除,把复用引脚的程序清掉。

第三条:下载成功,但程序不运行,必须手动按复位

现象是输出窗口显示Programming Done和Verify OK,没有Application running,板子毫无反应。原因通常是 Reset and Run 没勾选,或者 BOOT0 跳线被拨到了 1(从系统存储器启动)。解决方式是 Utilities → Settings → Flash Download 里勾上Reset and Run,然后检查板子上的 BOOT0、BOOT1 跳线帽位置,正常运行必须都在 0。这条我遇到过两次,第一次排查了半小时硬件,最后发现是同事借板子时把跳线帽挪了位置。

第四条:串口打印全是乱码,但程序逻辑明显在跑

现象是串口助手收到一堆烫烫烫或者随机字符,改成各种波特率都不对。原因基本锁定在时钟:HSE_VALUE定义的频率和板载晶振不一致,或者时钟树配成了 HSI 而代码里按 HSE 算。解决方式是先确认板上晶振丝印是 8.000,再检查stm32f1xx_hal_conf.h里HSE_VALUE是否为8000000U,最后用调试器读一下SystemCoreClock变量的实际值(具体方法见第 6 章)。三个地方对齐了,115200 一次就能通。

第五条:CubeMX 重新生成代码后,自己写的逻辑全没了

现象是改完 CubeMX 配置点 GENERATE CODE,回到 Keil 编译发现main.c里的初始化代码消失,编译报一堆未定义。原因就是代码写在了USER CODE BEGIN/END注释块外面,被生成器当成“可覆盖区域”清掉了。解决办法只能是从版本管理里找回上一次的代码,然后全部挪进用户代码区。如果没做版本管理,那就只能重写——这也是为什么我从那以后所有 CubeMX 工程都在 git 里留一个初始提交,专门用来做“后悔药”。

第六条:Keil 编译报Target uses ARM-Compiler 'V5' which is not available

现象是打开老工程或者教程配套工程,一编译就报编译器不存在,Target 选项卡里 ARM Compiler 显示为灰色或者选不到 V5。原因是 MDK 5.37 之后的版本不再内置 AC5。解决办法有两种:装回 ARM Compiler 5 组件,或者把工程切到 AC6 并处理语法差异。切 AC6 时最先撞上的通常是启动文件里的内联汇编和__weak修饰,报错集中在core_cm3.h和stm32f1xx_hal_def.h,这时不要急着改库文件,先在 C/C++ → Misc Controls 里加上-Wno-inline-asm之类的兼容开关,能压掉大部分告警。真正需要改代码的地方不多,但每一处都得改对,否则编出来的行为不对。

6. 装完怎么验证:点灯、读时钟、串口打一行字

环境装完,很多人点一下编译通过就以为结束了,等到写业务代码时才发现问题,那时候已经分不清是新代码的问题还是环境的问题。我的做法是做三步验证,每步都给出唯一的、可观测的结果,任何一步不对都能立刻定位到某一层。

第一步是点灯,验证的是 HAL 库、启动文件、下载链路整条通路:

/* main.c 的 while(1) 内,注意写在 USER CODE BEGIN 3 和 END 3 之间 */ while (1) { HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin); /* 翻转 LED 电平,引脚宏由 CubeMX 生成 */ HAL_Delay(500); /* 依赖 SysTick 的 1ms 中断 */ }

HAL_Delay的精度取决于 SysTick 配置,HAL_Init()里会调用HAL_InitTick把它配成 1ms。如果在高于 SysTick 优先级的中断服务函数里调HAL_Delay,程序会直接卡死——因为默认的TICK_INT_PRIORITY是最低优先级,中断嵌套时 SysTick 永远进不来。这个现象极具迷惑性,表现为“灯亮过一次就再不动了”。

第二步是读时钟,验证的是时钟树和HSE_VALUE:

/* 在 main 函数里 HAL_Init 之后打印一次 */ printf("SYSCLK = %lu Hz, HCLK = %lu Hz\r\n", HAL_RCC_GetSysClockFreq(), HAL_RCC_GetHCLKFreq());

正常应该输出SYSCLK = 72000000 Hz, HCLK = 72000000 Hz。如果读到 8000000,说明 PLL 没起来,系统还在用 HSI;如果读到 72000000 但串口是乱码,那问题就在波特率计算或者HSE_VALUE定义上,跟时钟树无关。用 printf 需要先把fputc重定向到串口,这个在教程的串口章节里有现成写法,直接抄。

第三步是断点验证,验证的是调试链路。在HAL_GPIO_TogglePin那一行打个断点,按 F5 全速运行,程序应该停在这里;打开 Peripherals 菜单里的 System Viewer,能看到 GPIO 寄存器的值随着单步执行变化。这一步能过,说明 DAP 的下载、调试、寄存器查看三条链路都是通的。

验证步骤观测点通过的标志失败指向
点灯板载 LED500ms 周期闪烁HAL 库、启动文件、下载算法
读时钟串口输出72000000 Hz时钟树、HSE_VALUE、晶振
断点Keil 停住 + 寄存器变化单步可观测DAP 配置、复位方式

三步全部通过之后,这套环境才算真正装完。后面再学 GPIO 输入、外部中断、定时器,出的问题都是配置问题,不会再有“到底是环境坏了还是代码错了”这种扯不清的情况。

从那以后我每次把环境交付给别人,或者换一台新机器重装,都强制走一遍这三步——点灯、读时钟、打一行字,一步都不跳。省下来的那十分钟,往往要用一晚上去还。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询