1. 工程创建前的准备工作1.1 为什么要单独写一篇“新建工程”的教程很多刚接触STM32的朋友下载好Keil MDK之后第一步就卡在了“新建工程”上。明明照着网上教程一步一步点结果要么是编译报几十个错误要么是芯片型号找不到要么是生成的工程文件乱七八糟。说白了Keil MDK作为一款专业嵌入式IDE它的工程组织方式和VS、Arduino这类工具差别很大尤其是针对STM32标准外设库的工程需要手动添加启动文件、内核文件、外设驱动文件还要配置各种编译选项任何一个环节漏了都会出问题。这篇文章我会完全站在实操角度从零开始新建一个基于STM32F103系列芯片的MDK工程用的是经典的“标准外设库”方式也就是很多学校、培训机构还在用的那一套。整个过程包含每一步的界面位置、参数选择和背后的原因照着走一遍基本能跑通。1.2 软件版本和固件库的准备先明确一下我用到的软件版本免得你后面操作时界面不一样软件推荐版本说明Keil MDK5.36或5.375.2x、5.3x界面基本一致5.38以上也没问题芯片支持包STM32F1xx_DFP 2.3.2或更高用于识别STM32F1系列芯片标准外设库STM32F10x_StdPeriph_Lib_V3.5.0经典版本网上资源最多调试器驱动ST-Link驱动或J-Link驱动烧录调试用的后面会详细说如果你还没有安装MDK这里稍微提醒一句MDK的安装包会同时包含“Keil uVision5”和“Pack Installer”前者是主界面后者是用来装芯片包的。安装完成后第一次打开软件时Pack Installer会提示你在线安装芯片包但国内网络经常很慢建议你直接在Keil官网下载对应芯片型号的DFP包然后双击安装这样省时间也省心。提示STM32 标准外设库现在官方已经不再维护了新项目建议用STM32CubeMXHAL库。但如果你的课程设计、毕业设计或公司老项目还在用标准库新建工程的方法依然是绕不开的一步。1.3 建立工程文件夹结构新建工程之前先把文件夹规划好。这步很多人会忽略但这恰恰是工程后期好维护的关键。我习惯的项目目录结构是这样的LED_Demo/ ├── USER/ // 用户代码main.c、stm32f10x_it.c等 ├── HARDWARE/ // 自己写的硬件驱动代码 ├── SYSTEM/ // 延时、串口、GPIO等基础功能代码 ├── CORE/ // 启动文件和内核文件 ├── STM32F10x_FWLib/ // 标准外设库的src和inc ├── OBJ/ // 编译输出文件夹 └── List/ // 编译列表文件你可以不用完全照抄这个结构但至少要有“用户代码目录”和“库文件目录”的区分意识。很多新手把main.c、启动文件、外设库全放在一个文件夹里到后面文件一多就分不清谁是谁了。更麻烦的是MDK工程一旦移动位置如果路径配置是绝对路径还得重新设置一堆东西所以文件夹结构从一开始就规划清楚能省很多事。2. Keil MDK 新建工程的核心流程2.1 创建新工程并选择芯片型号打开Keil uVision5菜单栏点 Project然后选 New uVision Project。这里会弹出一个保存工程的窗口你需要先切换到第1.3节建好的目录下比如 LED_Demo 文件夹。在USER子目录下新建一个文件夹或者直接定位到USER目录。给工程取个名字建议用英文或者拼音比如“LED_Demo”不要用中文名。虽然MDK高版本支持中文路径但兼容性仍然不稳定后面用GIT管理、用脚本编译时大概率会踩坑。确定工程名后点保存会弹出芯片选择窗口。在搜索框输入 STM32F103C8然后展开 STMicroelectronics → STM32F1 Series → STM32F103选择具体的型号如果是小容量板子C8T6选 STM32F103C8如果是大容量芯片ZET6、RCT6选 STM32F103ZE 或 STM32F103RC选好型号后点 OKMDK会弹窗提示“Copy STM32 Startup Code to Project Folder?”意思是“是否把启动文件复制到工程文件夹里”。这里点“是”MDK会自动根据你选的芯片型号匹配对应的启动文件比如 startup_stm32f10x_md.s并复制到当前工程目录下。注意启动文件的选择和芯片容量有关。MD是中等容量64KB以下FlashHD是大容量256KB以上FlashLD是小容量。选错启动文件会导致系统初始化失败常见的现象是代码编译没问题但下载后程序不跑。2.2 认识MDK的工程管理窗口新建完工程后左侧 Project 窗口会看到 Target1 这个默认目标。很多新人不习惯MDK的层级结构我把它的逻辑讲清楚Target1 └── Source Group 1 └── startup_stm32f10x_md.s // 刚才复制的启动文件Target 相当于“构建目标”可以理解为一套完整工程的编译配置。一个工程可以存在多个Target比如一个用于调试Debug、一个用于发布Release每个Target可以拥有不同的源文件组合和编译选项。默认情况下一套工程就一个Target不需要改。Source Group 则是源文件分组相当于工程里的“文件夹”你可以建多个Group来组织代码。每个Group只负责逻辑分组不影响编译结果但好的分组习惯能让你半年后回看代码时依然一目了然。2.3 向工程中添加源文件在 Source Group 1 上右键选择 Add Existing Files to Group Source Group 1可以往工程里添加 .c 和 .s 文件。但在此之前我建议先把Group的名字改好并建立多个分组比如LED_Demo/ ├── USER ├── HARDWARE ├── SYSTEM ├── CORE ├── STM32F10x_FWLib ├── OBJ └── List这里我推荐你最终形成这样的分组分组名称对应文件夹需要添加的文件USERUSER目录main.c、stm32f10x_it.c、system_stm32f10x.cCORECORE目录startup_stm32f10x_md.s、core_cm3.cFWLibSTM32F10x_FWLib/srcstm32f10x_gpio.c、stm32f10x_rcc.c等外设库源文件SYSTEMSYSTEM目录delay.c、sys.c、usart.c如果有的话HARDWAREHARDWARE目录led.c、key.c等自己的硬件驱动分组名称可以随便起关键是和文件夹对应上。你可以右键Source Group 1选择 Manage Project Items在里面自由添加和重命名Group。标准外设库源文件不需要一次性全部添加用到哪个外设就添加哪个的 .c 文件比如用GPIO就加 stm32f10x_gpio.c用串口就加 stm32f10x_usart.c。全加进去也能编译但会拉长编译速度而且有的库文件之间相互依赖全添加并不会引入编译错误只是没有必要。2.4 配置魔法棒里的关键参数源文件添加完成后还没到写代码那一步。MDK新建工程的第一个“大坑”就是魔法棒Options for Target配置这一步少了任何一个都会导致编译不过或下载失败。点工具栏上的“魔术棒”图标全称 Options for Target依次检查以下页面Target选项卡Xtal(MHz)填外部晶振频率STM32F103开发板一般是8MHz或25MHz看板子上晶振的实际值。填错不影响软件仿真但影响串口波特率计算和部分外设时序。Code Generation → ARM Compiler选“Use default compiler version 5”或Version 6都可以。标准外设库在老版本MDK下默认用V5编译器新版MDK装好后默认是V6V6对C语言语法检查更严格标准库代码有时会报一堆警告。如果你只是学标准库建议选V5代码兼容性最好。新版MDK如果默认没有V5编译器可以装AC5的扩展包但更简单的办法是在这里改成V6然后在C/C选项卡里加 --c99 或者 --gnu 编译选项。Output选项卡勾选 Create HEX File这样编译后会生成 .hex 文件方便用烧录软件下载。如果只用J-Link、ST-Link在线调试不勾选也能烧录但生成HEX文件是一个好习惯后面用ISP下载、量产烧录都需要它。Select Folder for Objects点击后面的按钮把输出文件夹指向前面建好的 OBJ 目录。这样编译产生的 .axf、.hex、.o 文件都收拢到一个目录里工程根目录不会变得乱七八糟。Listing选项卡Select Folder for Listings设置成 List 目录存放编译时的列表文件。这步不是必须的但设了之后工程会很干净。C/C选项卡这里是最关键的下拉到后面详细讲。Debug选项卡选择调试器常用的是 ST-Link Debugger 或 J-LINK。右上角下拉框选好之后点旁边的 Settings检查是否能正常识别到设备ID如果识别不到多半是驱动没装好或者接线有问题。2.5 C/C选项卡里的宏定义和头文件路径这个单独的配置项值得单独拿出来讲因为新手最容易在这出错。C/C选项卡里有两个核心区域Define宏定义区域对于STM32F103系列需要定义两个关键宏STM32F10X_MD, USE_STDPERIPH_DRIVER为什么需要这两个宏我用大白话说USE_STDPERIPH_DRIVER告诉标准外设库“你要启用外设驱动”。不定义这个宏stm32f10x.h 里的很多外设寄存器结构体和库函数声明根本不会编译进去调用任何外设函数都会报“identifier is undefined”。STM32F10X_MD告诉标准外设库“你的芯片容量是中等容量”。这个宏控制芯片型号相关的寄存器定义比如Flash容量、内存映射。选错了外设库在编译时不知道用哪种寄存器布局会报错或行为异常。具体到不同容量芯片型号芯片定义宏启动文件STM32F101/103 小容量LDSTM32F10X_LDstartup_stm32f10x_ld.sSTM32F101/103 中等容量MDSTM32F10X_MDstartup_stm32f10x_md.sSTM32F101/103 大容量HDSTM32F10X_HDstartup_stm32f10x_hd.sSTM32F105/107 互联型STM32F10X_CLstartup_stm32f10x_cl.s这个表和启动文件必须一一对应我之前见过一个同学用的C8T6中等容量却把宏定义成了 STM32F10X_HD结果编译通过但程序跑起来直接HardFault排查了半天。Include Paths头文件包含路径区域点击 Include Paths 后面的“...”在这里添加所有包含 .h 头文件的目录。标准外设库和用户代码需要添加的头文件目录至少包含USER目录main.h、stm32f10x_it.h这些CORE目录core_cm3.h、core_cmInstr.h等ARM内核文件STM32F10x_FWLib/inc所有标准外设库的头文件都在这SYSTEM目录delay.h、sys.h等如果有很多新手在编译时报“fatal error: stm32f10x.h: No such file or directory”十有八九就是这个Include Paths没有添加完整。2.6 编译验证与常见错误排查配置完上面的选项我们先不急着写功能代码用最基础的main函数验证一下编译链路是否通畅。新建一个main.c输入#include stm32f10x.h int main(void) { while(1); }点工具栏上的 Build 按钮或者按F7观察编译输出窗口。如果一切正常最后一行会显示0 Error(s), 0 Warning(s)如果出现错误不要慌看错误信息的关键字对照下面表格快速定位错误关键字含义解决方法No such file or directory头文件找不到检查Include Paths看路径有没有加错或漏加undefined identifier类型或函数未定义检查宏定义是否遗漏比如 USE_STDPERIPH_DRIVERMultiple definition重复定义同一个 .c 文件添加了多次或变量在头文件里定义了incompatible type类型不兼容常见于V6编译器标准库老代码语法和V6冲突换编译器或加编译选项cannot open source input file源文件路径不对添加源文件时路径失效删掉重新添加No space in execution regionsFlash空间不足芯片容量选小了或者代码量确实超了DSP instruction build failed内建指令不支持选错内核文件C8T6是M3内核不要用M4的内核库编译通过后第一阶段的“新建工程”其实就已经成功了。接下来你可以往工程里添加自己的功能代码了。2.7 配置下载器并烧录验证编译通过不等于程序能在板子上跑还要把程序下载到芯片里。点击魔术棒进入 Debug 选项卡先选对你手上的调试器用ST-Link的选 ST-Link Debugger。用J-Link的选 J-LINK。用DAP-Link或者CMSIS-DAP的选 CMSIS-DAP Debugger。选好之后点旁边的 Settings在 Debug 页面里看 Port 选的是 SW串行线模式还是 JTAG。现在主流调试器默认SW就行注意J-Link的SW接口对于STM32F103C8T6这种小封装板子连接比较方便。看右上角的 SW Device 或 IDCODE 区域如果识别到芯片ID说明连接正常。如果显示 No Device Found检查接线和驱动。切到 Flash Download 页面勾选 Reset and Run这样程序下载后自动复位运行省去手动按复位键的麻烦。Programming Algorithm 区域要有芯片对应型号的Flash算法如果没有点 Add 添加 STM32F10x Med-density Flash。然后回到主界面点 Load或F8下载程序。下载成功后在状态栏会显示 Erase Done、Programming Done、Verify OK程序就开始运行了。如果你的板子上刚好有LED灯写个翻转GPIO的程序就能看到效果。3. 新建工程时容易被忽略的细节3.1 关于启动文件的选择逻辑很多人会把启动文件当成“必须复制进去的一个固定文件”其实启动文件的选择是由芯片容量和型号决定的。启动文件的作用是设置初始堆栈指针、初始化向量表、调用 SystemInit 函数、然后跳转到 main。如果启动文件和芯片实际型号不匹配系统时钟初始化就会出错现象是程序能下进去但完全没有反应。这里给一个实际的规避技巧选完芯片型号后MDK自动复制启动文件到工程目录这时候直接打开 Copy 过来的启动文件看名字。如果选择的是 STM32F103C8MDK复制的是 startup_stm32f10x_md.s这就是正确的。你不确定也没有关系用文本编辑器打开启动文件谷歌搜下第一行注释资料里会明确标注适用的容量范围。3.2 项目编码导致的注释乱码问题MDK默认的编辑器编码是ANSI而很多新下载的标准库和例程是用UTF-8编码保存的。直接打开别人的工程中文注释可能全是乱码。这个问题现在很常见解决的办法有两个方向把MDK的编辑器编码改成UTF-8在 Edit → Configuration → Editor → Encoding 里选UTF-8。保持ANSI编码把库文件的注释改成英文或直接忽略。我的建议是用方案一统一改成UTF-8。很多新版MDK默认就是UTF-8如果你的MDK打开多个工程时有的中文正常有的乱码大概率是工程文件本身的编码混用了这时候写代码时自己新建的文件尽量也用UTF-8保存保持统一。3.3 左侧工程树不显示新添加的文件有些时候你把.c文件从外部复制到了工程目录下但在MDK左侧Project窗口里看不到。这是因为MDK的工程窗口并不是“自动扫描文件夹”你复制到文件夹里的文件不会自动加到工程中。操作方法很简单直接在Project窗口的对应Group上右键 → Add Existing Files手动选择需要加入的文件。如果你是想把整个文件夹作为分组导入可以在 Manage Project Items 里新建一个Group再添加文件。还有一种情况文件明明已经添加了却不显示这时候在Project窗口顶部有个“三个方块”的图标那是Show Includes不会影响显示。右键Group选择 Expand All所有文件就都会展开。3.4 编译器版本对工程的影响MDK 5.37及以上版本默认安装的是ARM Compiler 6AC6。AC6的编译器来自LLVM对C标准支持更好代码优化也更激进但问题在于它跟传统AC5相比对隐式声明的容忍度为零标准外设库的老代码在AC6下经常报几千个警告甚至编译失败。如果你用的是标准外设库建议在魔术棒 Target 选项卡里把 ARM Compiler 切换回 V5.06。如果你手头MDK没有V5也可以去Keil官网下载ARM Compiler 5的扩展包安装后在编译器下拉菜单里选择。还有一种方案保持AC6然后在C/C选项卡的 Misc Controls 里输入--gnu这个选项会开启GNU编译模式AC6对标准库老代码的兼容性会好不少。但说实话做标准库开发最省心的还是直接上AC5。3.5 新建工程时的路径问题MDK默认存储的是相对路径意思是工程文件(.uvprojx)所在的目录是基准所有源文件路径都会相对于工程文件来解析。如果你把整个工程文件夹移动位置重新打开后一般还能正常编译这比绝对路径时代好多了。但你需要注意如果你把工程文件丢到了C盘用户目录下Windows的UAC权限控制可能会导致编译时无法在OBJ目录写文件报错信息千奇百怪。遇到这种情况直接右键工程文件夹 → 属性取消“只读”或者把整个工程文件夹挪到非系统盘目录比如 D:\Work\Projects\LED_Demo。4. 从标准库工程平滑过渡到CubeMX工程4.1 为什么现在越来越多的人用CubeMX新建工程前面一直聊的是标准外设库方式这是老一批工程师的经典路线。但如果你刚开始学STM32打开网上的招聘要求很多岗位都写了“熟悉STM32CubeMX和HAL库”而不是“熟悉标准外设库”。原因是HAL库把所有外设初始化过程抽象成了图形化配置芯片引脚分配、时钟树、外设模式全部用鼠标就能点出来生成的初始化代码几乎是零错误率的。CubeMX新建工程的核心思路是选择一个具体的芯片型号。配置引脚功能比如把PA5设置成GPIO输出。配置时钟树系统时钟从HSI还是HSE取PLL分频倍频系数填一下。自动生成MDK工程骨架里面已经包含main.c、stm32f1xx_hal_msp.c等。生成的工程System Clock、GPIO初始化这些代码都是自动配好的你只需要在自己的while循环里写业务逻辑。外面很多人调侃“CubeMX降低了嵌入式入门门槛”其实也不完全是贬义HAL库的可移植性确实比标准库好很多。4.2 Keil MDK在CubeMX生成工程中的角色CubeMX生成的工程默认是MDK格式的最终还是要用Keil来编译和下载。这也是为什么Keil MDK依然不可绕过的原因。我见过有些同学误认为CubeMX生成完代码等于程序写完一半实际上生成出来的只是一个骨架真正的业务逻辑还是要你在MDK里慢慢写。而且CubeMX生成的工程文件结构更复杂如果手动往工程里添加额外的源文件同样要回到Keil的Manage Project Items操作。所以学会在MDK里手动添加文件、配置编译选项、设置头文件路径这些基本功跟用不用标准库没关系任何时候都用得上。4.3 标准库工程和HAL库工程的目录差异对比一下你就能直观感受区别标准库工程的USER目录下通常是main.c、stm32f10x_it.c、system_stm32f10x.c而HAL库工程里则是main.c、stm32f1xx_it.c、stm32f1xx_hal_msp.c。外设库方面标准库每个外设对应一对.c和.h文件比如stm32f10x_gpio.c、stm32f10x_usart.cHAL库则是stm32f1xx_hal_gpio.c、stm32f1xx_hal_uart.c这种命名方式。如果你两种工程都接触过你会发现它们的本质逻辑是一样的都是“初始化外设→实现功能→编译下载”。区别只在于初始化代码是手写还是生成。5. 新建工程过程中的高频问题和排查技巧5.1 Keil MDK打开后界面空白或卡死这个问题出现在刚安装完MDK双击桌面图标却只看到空白窗口的场合。正常情况下MDK启动时会扫描已安装的芯片包如果Pack Installer还没有完成初始化主界面可能处于异常状态。解决办法菜单栏 Project → Open Project随便打开一个工程文件看看是否恢复正常。如果打开任何文件都卡死考虑重新安装MDK或者把C盘下的Keil配置缓存目录删掉再试。MDK的配置缓存一般在 %APPDATA%\Keil删掉之后重启软件会重新生成是一个很有效的“重置”手段。5.2 添加库文件时提示文件不存在在Manage Project Items里添加文件后如果对应文件被移动或重命名编译时会报 file not found。双击错误信息会跳转到当前工程文件列表你会发现对应文件名旁边有一个黄色的感叹号。处理办法在Project窗口对应文件名上右键 → Options点击“...”重新定位文件位置。如果文件已经被删了就右键Remove从工程里移除。为了防止这种问题平时移动文件尽量在外部完成后再在MDK里重新添加不要在工程窗口内拖动文件容易留下无效引用。5.3 Debug模式下载成功但程序不运行下载成功但程序不运行这是非常经典的仿真问题。可能的原因Flash Download 页面没勾选 Reset and Run程序下载了但复位后停在启动代码没有跳转到main。芯片供电或晶振异常板子的BOOT0跳线没设置到“从Flash启动”或者外部晶振虚焊导致SystemInit卡死。STM32F103的BOOT0接GND是正常运行模式BOOT0接3.3V是ISP下载模式如果跳线帽设置错程序下进去后不会运行。启动文件选错这个前面详细说过检查启动文件和芯片型号是否匹配。调试器Settings里的Flash Algorithm缺失Flash下载算法不匹配也会导致烧录成功但程序不跑最常见的是中等容量芯片用了大容量Flash算法CRC校验失败。5.4 Keil编译速度越来越慢如果你的工程文件很多编译速度慢是正常的。但有人会遇到第一次编译很快改一个文件之后全工程重新编译这种情况多半是头文件依赖太复杂或者在多个源文件里包含了同一个大块头头文件比如把 stm32f10x.h 或 main.h 包含到了每个.c文件里。优化思路在C/C选项卡里开启 One ELF Section per Function这样链接器只链接用到的函数编译速度快一些。尽量在 .h 文件里少写大段宏定义能放到 .c 的就放 .c。用到的外设库文件只添加必要的别全量添加标准外设库所有.c文件。5.5 更换电脑或工程路径后无法编译工程文件复制到另一台电脑后无法编译大概率是头文件路径或调试器配置不同。MDK的Include Paths里如果记录的是相对路径一般没问题但如果你在旧电脑上使用的是绝对路径换环境后又忘记改就会报错。遇到这种情况打开魔术棒 → C/C → Include Paths重新确认路径。另一个高频问题是新电脑上芯片包没装编译时提示找不到设备这时去Pack Installer里安装对应的DFP包即可。5.6 Keil工程里中文注释乱码的打通方案前面提过编码问题这里给一个最省心的完整打通方案把所有源代码文件的编码统一转成UTF-8Visual Studio Code里可以批量转。在MDK的 Edit → Configuration → Editor → Encoding 里把默认编码改成 UTF-8。不要手动把某个文件保存成GB2312再混合另一个UTF-8文件。如果只是打开别人的工程且不想改库文件编码只在MDK的Configuration里切换Encoding到UTF-8往往就够了。6. 一个完整的STM32最小工程示例6.1 目标需求为了验证前面所有步骤是否配置正确我建议你新建一个最简单的LED闪烁工程。这个工程用到的资源最少一旦跑通说明整套开发环境、工程配置、编译下载链路都已经打通后续做串口、中断、定时器都是在它基础上做加法。硬件上只需要一块STM32F103C8T6最小系统板、一个板载LED通常接在PC13部分板子在PB0或PA1、一个ST-Link或者串口烧录线。6.2 添加用户代码工程建好并完成编译配置后在USER分组中编辑main.c输入以下代码#include stm32f10x.h void Delay_Init(void); void Delay_MS(uint32_t ms); void GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_13; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Init(GPIOC, GPIO_InitStructure); } int main(void) { GPIO_Config(); while(1) { GPIO_SetBits(GPIOC, GPIO_Pin_13); Delay_MS(500); GPIO_ResetBits(GPIOC, GPIO_Pin_13); Delay_MS(500); } }这段代码的逻辑不复杂开启GPIOC端口时钟。STM32的外设时钟默认是全关的想要用哪个外设先打开对应时钟这是标准库开发最容易遗漏的一步。把PC13配置为推挽输出最大翻转速度50MHz。主循环里先置高电平延时再置低电平延时LED就会闪烁。6.3 实现一个简单的软件延时函数为了保证代码可读性我在上面引用了 Delay_MS 函数这里给你一种最简实现不需要启动定时器static uint32_t fac_us 72; static uint32_t fac_ms 72000; void Delay_Init(void) { fac_us SystemCoreClock / 1000000; fac_ms fac_us * 1000; } void Delay_MS(uint32_t ms) { uint32_t i; for (i 0; i ms; i) { uint16_t j; for (j 0; j fac_ms / 1000; j) { __nop(); } } }这只是一个粗略的软件延时取决于你用的编译优化等级实际时间会有些误差。精确延时用SysTick或者定时器实现更好但作为验证LED闪烁足够了。注意这段Delay_MS的写法里用到了 SystemCoreClock标准库的 system_stm32f10x.c 会初始化这个变量。如果你的工程没加system_stm32f10x.c或者没在宏定义里启用SystemCoreClock会是默认值0延时函数会异常。标准库工程里建议始终添加system_stm32f10x.c到USER分组。6.4 下载运行和观察结果编译无误后按F8下载程序。下载完成后板载LED应该开始以大约1秒的周期闪烁。如果LED不亮按前面第5.3节的方法挨个排查先测板子供电。再看GPIO口是不是PC13部分板子LED在别的引脚。再检查下载是否真的成功下载时观察Keil输出窗口的Verify OK提示。能看见LED闪起来你到这一步已经迈过了STM32开发最痛苦的学习门槛。第一个工程跑通后后面学定时器、串口、中断其实都是在第一个工程配置好的环境基础上加代码而已。7. 我从第一套MDK工程中学到的事回头看我第一次折腾Keil MDK新建工程前后花了两三天踩的坑比这篇文章里写的还多。当时没人告诉我三件事现在分享出来希望你能少走点弯路。第一工程目录结构必须在建工程之前想清楚。很多教程一上来就让你建工程忽略了目录规划。但等你工程写大了代码文件超过20个就会发现没有目录管理简直就是灾难。现在我的习惯是每次从CubeMX或者新建工程的第一步一定先按功能建好文件夹再谈写代码。第二MDK魔法棒里的每个参数背后都有原因。养成一个习惯遇到不懂的选项先查资料别怕点错。C/C选项卡、Debug设置、Flash Download这几个页面值得花半小时从头到尾看一遍。因为MDK不像Arduino那样开箱即用它更像一把专业工具工具用顺了后面效率自然就上来了。第三也是最想强调的不要过度依赖自动生成的工程。用CubeMX生成工程很高效但自动生成意味着你不了解底层发生了什么。出问题的时候比如时钟配错了、引脚冲突了你必须有能力回到寄存器层面去排查。这也是我建议新手无论如何都要亲手搭一次标准库工程的原因。最后再给你一个锦上添花的小技巧把工程配置文件.uvprojx、.uvoptx用版本管理工具管起来源文件目录按功能分好每次编译通过马上提交一次代码。如果哪次改坏了一秒回滚省下来的时间远比管理成本多。等你在MDK里新建工程的流程熟练到不用想那时候你才真正摆脱了环境配置的束缚可以安心去做功能逻辑了。