STM32CubeMX与Keil µVision:工程生成、下载调试全流程

STM32CubeMX与Keil µVision:工程生成、下载调试全流程 手头有一块 STM32F103C8T6 的最小系统板装完 STM32CubeMX配好 Keil µVision绝大多数人的第一件事都不是写业务逻辑而是想办法让板子上的灯先闪起来。可就是这么一件点灯级别的事第一次走这条路的人经常要卡一整晚CubeMX 生成完工程Keil 打开一编译就是一堆报错编译过了点下载又提示找不到设备下载成功了板子一点反应没有。这篇就把 STM32CubeMX 与 Keil µVision 这条链路从图形化配置到工程生成、从编译器选项到下载算法、从 Debug 窗口到常见报错排查完整地捋一遍把每一步为什么这么设讲清楚。标题里那个2我理解成两种可能一是系列教程的第二篇二是特指某个版本号里带 2 的分支。不管是哪种核心链路完全一致用 STM32CubeMX 做引脚、时钟、外设的图形化配置生成一份 MDK-ARM 工程然后交给 Keil µVision 做编译、下载和在线调试。这套流程的价值在于它把最枯燥、最容易算错的寄存器配置部分变成了鼠标点点点你只需要把精力放在应用逻辑上。适合刚接触 STM32 的新手也适合从寄存器或标准库迁过来、第一次用 HAL 库的老手——尤其是那些工程能生成但跑不起来的朋友。1. 为什么这套组合到今天还是主流选择1.1 图形化配置到底替你省掉了哪些活很多人对 CubeMX 的理解停留在帮你生成几个初始化函数其实它省掉的活比想象中多得多。以最典型的 F103 时钟配置为例手工写的话你需要使能 HSE、等待 HSERDY 标志、配置 Flash 预取和等待周期、设置 PLL 的倍频系数和时钟源、使能 PLL、等待 PLLRDY、切换系统时钟源、再依次配置 AHB/APB1/APB2 的分频系数最后还要更新 SystemCoreClock 变量。这一串动作里任何一步顺序错了芯片要么跑在一个错误的频率上要么直接卡在等待标志的 while 循环里出不来而这类问题的现象往往是程序像没跑一样非常难查。CubeMX 的时钟树界面把这个过程可视化了你只需要在图上填 HSE 是 8MHz、PLL 倍频填 9、APB1 分频选 2右边的实时计算会立刻告诉你 SYSCLK 是 72MHz、APB1 是 36MHz、APB2 是 72MHz如果超频或者某个外设时钟超限它会直接标红。生成代码时它按正确的顺序把 RCC 初始化、GPIO 初始化、外设句柄定义全部排好还包括中断优先级分组、NVIC 使能、DMA 通道分配这些容易被忽略的细节。对于新手来说这相当于把一本几百页的参考手册压缩成了几十个下拉框。除了时钟还有一个隐性收益是引脚冲突检查。手工配置时你把 PA9 同时配成 USART1_TX 和某个 PWM 输出编译器不会报错程序跑起来串口就是不出数据。CubeMX 会在你配置第二个功能时直接提示引脚已被占用把这类哑巴错误提前到配置阶段解决。1.2 版本搭配CubeMX、MDK、固件包三者怎么选这是最容易出问题、也最少有人讲清楚的一环。这三个东西版本不匹配就会出现生成的工程打不开编译报找不到编译器链接报一堆重复符号之类的怪现象。我按自己踩过的坑整理了一份搭配建议组件建议选择说明STM32CubeMX6.x 较新版本版本太老会缺少新芯片支持太新可能与旧固件包不兼容MDK-ARM5.3x 稳定版5.37 之后默认不再附带 ARM Compiler 5ARM 编译器AC6armclang优先AC5 语法宽松但已停止维护新工程建议直接上 AC6固件包CubeF1 1.8.x通过 CubeMX 内置的包管理器在线下载即可调试器驱动官方最新版ST-Link 固件也建议一并升级关于授权这里必须说一句网上流传的各类注册工具我强烈不建议用一是来路不明的可执行文件在你这台连着调试器的电脑上跑风险完全不可控二是商用场景下的合规问题会直接落到你头上。MDK 有面向非商业用途的社区授权教育和个人学习完全够用走正规渠道安装省心得多也不会出现今天能编译明天突然授权失效的尴尬。编译器选择上我的建议是新工程直接用 AC6。AC5 的语法检查非常宽松很多隐式类型转换、未声明函数它都睁一只眼闭一只眼代码搬到 AC6 上会瞬间爆出几百个 warning。既然迟早要面对不如一开始就用严格的标准写。但如果你接手的是维护多年的老工程里面有一堆依赖 AC5 特性的汇编文件或者第三方库那就老老实实装 AC5别为了先进给自己找麻烦。1.3 生成规则的核心USER CODE 段与 .ioc 文件CubeMX 生成代码时有个铁律只有/* USER CODE BEGIN xxx */和/* USER CODE END xxx */之间的内容会被保留其他部分在你下次重新生成时会被无情覆盖。这个机制很多人第一次用会翻车——把业务逻辑写在USER CODE BEGIN 2外面改了个引脚配置重新生成几百行代码瞬间蒸发。我自己的习惯是main.c里只放最外层的调度循环具体功能全部拆到独立的.c/.h文件里在 CubeMX 里通过新建文件或者在 Keil 里手动添加到工程。这样即使频繁重新生成业务代码始终安全。另外一点是.ioc文件才是这个工程的源头真相它记录了所有引脚和时钟配置。建议把.ioc和源码一起做版本管理但要把MDK-ARM目录下的构建产物、.uvguix这类用户界面配置文件排除掉——后者会因为窗口布局不同而频繁变动每次提交都是无意义的 diff。.ioc里记录的是配置意图而不是最终代码。团队协作时如果两个人同时改了同一个.ioc合并会非常痛苦。我的做法是谁动配置谁负责生成并提交生成后的代码其他人只拉取不改.ioc避免冲突。2. CubeMX 侧的配置直接决定后面顺不顺2.1 新建工程从选芯片到 Toolchain 的关键三选打开 CubeMX第一步是选芯片。这里有个小技巧不要按型号去猜用左上角的搜索框输入完整型号比如STM32F103C8T6在列表里点进去看右边的封装和引脚数是否和你手上的板子一致。选错了型号后面引脚数量对不上整个工程都要重来。进入配置界面后真正影响 Keil 侧体验的是 Project Manager 页面里的三项第一项是Toolchain/IDE一定要选MDK-ARM并且看清楚版本下拉框。选成别的比如 Makefile 或者 EWARM生成的目录结构完全不同Keil 打不开。第二项是Project Name 和 Project Location。这里有个血泪教训工程路径里不要出现中文、空格和特殊符号。Keil 对中文路径的支持时好时坏某些版本的调试器 DLL 在含中文的路径下会直接崩溃退出表现就是点击 Debug 图标后 Keil 自己关了很多人以为是调试器坏了其实是路径问题。建议路径写成D:\work\stm32\led_demo这种纯英文短路径。第三项是Firmware Package 的选择。CubeMX 会检测本地已安装的固件包版本如果没装会提示你下载。建议始终保持一个小版本内的最新比如 CubeF1 用 1.8.x 而不是 1.7.x因为低版本里的 HAL 库可能存在已知的 bug比如某些串口 DMA 的收发异常在新版本里已经修复你却在应用层苦苦找原因。2.2 时钟树以 F103C8T6 跑到 72MHz 为例时钟是整块板子的心跳配错了后面所有外设的时间基准都是错的。以 F103C8T6 加 8MHz 外部晶振为例标准配法如下配置项取值计算结果说明HSE8 MHz8 MHz外部晶振比内部 HSI 精度高PLL SourceHSE—用外部源避免 HSI 的温漂PLL Mul×98 × 9 72 MHzF103 的上限SYSCLKPLLCLK72 MHz系统主频AHB Prescaler/172 MHzHCLK喂给内核和 DMAAPB1 Prescaler/236 MHz上限 36MHz绝不能 /1APB2 Prescaler/172 MHz上限 72MHz这里有两个必须记住的点。一是APB1 的分频不能设成 1因为 F103 的 APB1 总线最高只支持 36MHz设成 72MHz 属于超频芯片可能能跑但绝对不稳定串口和定时器会随机出问题。二是Flash 等待周期CubeMX 会自动根据 HCLK 计算当 HCLK 大于 48MHz 时需要把 Latency 设成 2WS。这一步是自动的但如果你手工改过 RCC 配置就一定回头检查这个值等待周期不够会导致取指错误程序跑飞。配好之后建议在 CubeMX 里点一下 Clock Configuration 页面右上角的解决时钟问题或者手动确认没有红色警告再往下走。很多人图快时钟树没配就点生成结果用的是默认 HSI 8MHz 或者 64MHz串口波特率怎么算都不对还以为是串口配置的问题。2.3 Code Generator 页面逐项拆解这一页的选项直接决定生成出来的工程长什么样我逐项说Copy all used libraries into the project folder和Copy only the necessary library files的区别在于前者会把整个 HAL 库目录复制进工程体积几百兆后者只复制用到的外设驱动文件。个人建议选后者工程干净、提交到代码仓库也清爽。但要注意如果之后在 CubeMX 里新增了外设重新生成时会自动补上对应的驱动文件不用手工添加。Generate peripheral initialization as a pair of .c/.h files per peripheral这个选项我强烈建议勾上。不勾的话所有外设的初始化代码都堆在main.c里MX_GPIO_Init、MX_USART1_UART_Init挤在一起工程大了以后main.c能有上千行。勾上之后每个外设独立成gpio.c、usart.c配合.h里的句柄声明结构清晰得多。Keep User Code when re-generating是必须勾的这就是前面说的 USER CODE 保护机制不勾等于自废武功。Delete previously generated files when not re-generated这个选项要谨慎。勾上之后如果你临时把某个外设关掉再重新生成对应的.c/.h文件会被删掉。它本意是帮你清理无用文件但如果你的工程里有手工改过的文件被误删恢复起来很麻烦。我一般会勾但前提是业务代码绝对不放在生成文件里。2.4 生成后的目录长什么样点下 GENERATE CODE 之后你会得到一个类似这样的结构led_demo/ ├── Core/ │ ├── Inc/ // main.h, gpio.h, usart.h 等头文件 │ └── Src/ // main.c, gpio.c, usart.c, stm32f1xx_it.c 等 ├── Drivers/ │ ├── CMSIS/ // 内核头文件、启动文件相关 │ └── STM32F1xx_HAL_Driver/ ├── MDK-ARM/ // Keil 工程目录核心是 led_demo.uvprojx ├── led_demo.ioc // 配置源文件MDK-ARM目录里除了.uvprojx工程文件还会生成startup_stm32f103xb.s启动文件、.sct分散加载文件对于 F1 这类简单芯片一般用默认的就行以及编译输出目录。要打开工程双击.uvprojx第一次打开可能会弹出器件包未安装的提示点确认让 Keil 自动下载对应的 Device Family Pack 即可。这里提醒一句Drivers目录下的文件不要手工修改。想改 HAL 的行为正确做法是在自己工程的.c文件里重写对应的回调函数比如HAL_UART_RxCpltCallback或者用__weak覆盖。直接改 HAL 源码下次换芯片或者升级固件包时改动全丢而且别人克隆你的仓库也复现不出来。3. Keil µVision 里把工程真正跑起来3.1 首次打开器件包、编译器、MicroLIB 三件事工程打开之后别急着按编译先做三件事。第一件确认器件包。如果工具栏下方的工程树里芯片型号显示为灰色或者编译时报device not found说明对应的 DFP 没装好。点工具栏的 Pack Installer 图标找到 STM32F1 系列安装最新版即可。这一步在有网的机器上一次搞定之后离线也能用。第二件确认编译器版本。右键工程名选Options for Target在 Target 页找到 ARM Compiler 下拉框。如果这里写着Use default compiler version 5但你的 MDK 没装 AC5编译时就会报找不到编译器。这种情况下把它改成Use default compiler version 6然后要做好心理准备——AC6 会报出一批 AC5 时代不报的警告主要是隐式类型转换和未使用变量。这不是坏事是代码质量在提升。第三件关于 MicroLIB。同一个 Target 页面里有个Use MicroLIB复选框。如果你打算用printf往串口打日志这个必须勾上。不勾的话标准 C 库会依赖半主机模式程序一调用printf就卡死在BKPT指令上表现是运行到 printf 就不动了。勾上 MicroLIB 之后库体积更小、不需要半主机支持只牺牲一点点浮点格式化性能对嵌入式场景完全不亏。顺带说一下优化等级。开发阶段建议用-O0或者-Og别一上来就-O3。高优化等级下局部变量可能被优化进寄存器甚至直接消失你在 Debug 窗口里看变量永远显示not in scope或者一个乱七八糟的值会严重误导排查方向。等产品定版了再考虑调高优化这是顺序问题不能颠倒。3.2 调试器与 Flash 下载算法这是新手最容易卡住的地方也是能编译不能下载的根源。打开Options for Target的 Debug 页左上角的下拉框里要选对调试器。用 ST-Link 就选ST-Link Debugger用 DAP-Link 选CMSIS-DAP Debugger用 J-Link 选J-LINK / J-TRACE Cortex。这里选错了点 Debug 就会提示找不到设备——如果你看到的是No ULINK device found那基本可以确定是这里选成了 ULINK而你手上插的是 ST-Link两个东西完全不兼容。选中 ST-Link 之后点右边的Settings按钮进入调试器设置窗口在Debug 标签页把 Port 从 JTAG 改成SWD。SWD 只用两根线SWCLK 和 SWDIO占用引脚少、速度也够现在基本是默认选择。上方的 SW Device 列表里应该能看到芯片的 IDCODE 和型号如果这里是空的说明硬件连接有问题往下查接线和供电。在Flash Download 标签页确认 Programming Algorithm 里有对应的算法。以 F103C8T6 为例算法名字是STM32F10x Med-density Flash地址范围0x08000000起。注意 C8T6 的 Flash 是 64KB但 MDK 提供的 Med-density 算法覆盖 64KB 到 128KB 这一段选它是正确的。如果算法列表是空的点Add手动添加或者在 Pack Installer 里补装。同时记得勾上Reset and Run这样下载完自动复位运行不用每次都手动按复位键调试体验提升明显。3.3 从点灯到 printf 的完整验证流程配置做完写代码验证。第一步先点灯因为它是整个链路的最短验证路径——只要灯闪了说明时钟对了、GPIO 配对了、下载成功了、程序真的在跑。在 CubeMX 里把某个引脚比如 PC13最小系统板常见的板载 LED配成GPIO_Output并给它起个用户标签LED这样生成的代码里就会有LED_Pin和LED_GPIO_Port这两个宏可读性远好于GPIO_PIN_13和GPIOC。然后在main.c里找到对应的位置填代码/* USER CODE BEGIN WHILE */ while (1) { HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); HAL_Delay(500); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */HAL_Delay依赖 SysTick 中断CubeMX 生成的代码里已经默认初始化了直接用就行。下载运行灯应该以 1 秒为周期闪烁。如果灯常亮或者常灭先别怀疑代码去查两个地方一是板载 LED 的极性有些开发板是低电平点亮那TogglePin的效果看起来是一样的但HAL_GPIO_WritePin的参数就得反过来二是引脚是否真的配成了输出回 CubeMX 里确认一下。灯闪了之后第二步加串口打印。在 CubeMX 里把 USART1 配成 Asynchronous 模式波特率 115200、8 位数据、无校验、1 位停止位这是最通用的组合。生成代码后在 Keil 里添加printf的重定向#include stdio.h int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, HAL_MAX_DELAY); return ch; }这段代码放在main.c的USER CODE BEGIN 4段里比较合适或者单独建一个retarget.c加到工程里。前提是前面勾了 MicroLIB否则需要额外处理半主机相关的一堆符号。用 AC6 编译时如果提示和__use_no_semihosting相关的重复定义可以在文件开头加一句__asm(.global __use_no_semihosting\n\t);来显式声明不使用半主机。最后在循环里加上printf(tick %lu\r\n, HAL_GetTick());用串口助手打开对应端口看到滚动输出这条链路就彻底打通了。注意\r\n别只写\n很多串口助手的换行显示依赖回车符只写\n会出现整行挤在一起或者阶梯状换行。3.4 Debug 模式下看清结构体变量这是被搜索最多的一个具体问题Debug 时怎么在窗口里看到结构体成员。答案比想象的简单但有几个前提条件不满足就死活看不到。在 Debug 模式下打开View → Watch Windows → Watch 1把变量名输进去。如果你想看的是结构体把它展开就行了——Watch 窗口的树形结构支持逐级展开huart1展下去能看到Instance、Init、pRxBuffPtr这些成员。但如果你看到的是not in scope通常是三个原因一是变量是局部的且已被优化掉。把优化等级降到-O0基本能解决。二是变量还没执行到定义它的位置作用域还没生效单步走一行再看。三是结构体指针没有正确初始化地址是 0 或者野指针Watch 窗口打不开。有一个实用技巧是配合View → Periodic Window Update。勾上之后程序全速运行时 Watch 窗口里的值也会周期性刷新不用每次停下再刷新。这在观察一个结构体里的状态机字段变化时特别好用。但要注意这个功能读的是内存快照对于被优化进寄存器的变量仍然无效所以还是绕不开降优化这一条。另外如果你想在运行时改变量的值直接在 Watch 窗口里双击数值那一栏改就行改完立刻生效。调试状态机的时候手动把状态变量改成某个分支的值比重新烧一遍程序快得多。这个功能在排查某个分支到底进不进得去这类问题时效率极高。4. 常见问题与排查思路实录4.1 编译期常见报错怎么定位报cannot open source input file xxx.h。九成是头文件搜索路径没配全。CubeMX 生成的工程一般会自动配好Core/Inc和 HAL 的Inc目录但如果你自己在MDK-ARM目录下新建了文件夹放代码就要手动去Options for Target → C/C → Include Paths里把路径加进去。注意路径要加到文件夹层级不是加到.c文件。报一堆undefined symbol或者重复定义。先看是不是同一个.c文件被添加了两次工程树里展开看看有没有重名项。还有一种情况是stm32f1xx_it.c和别的地方同时定义了同一个中断服务函数比如你手工写了SysTick_Handler又保留了 CubeMX 生成的版本链接器就会报重复。解决办法是把中断处理逻辑写到 CubeMX 生成的USER CODE段里别另起炉灶。AC6 报implicit declaration of function。这是 AC6 比 AC5 严格的地方本质是你调用了某个函数但没包含它所在的头文件。认真补上头文件别用-Wno-implicit-function-declaration去压警告这是给自己埋雷。4.2 下载和调试阶段的坑点击 Debug 或 Download 时 Keil 直接闪退。这个现象描述起来吓人但原因通常很平凡。按下面的顺序排查第一确认工程路径没有中文和空格第二关闭同时运行的 STM32CubeProgrammer、STM32CubeIDE 等可能占用调试器的软件第三尝试换一个 USB 口尤其是别用前面板的口供电和信号质量都差第四以管理员身份运行 Keil第五更新 ST-Link 的驱动和固件。我在实际中遇到最多的是第二条调试器同一时间只能被一个进程占用两个软件抢它后启动的那个就崩了。提示No ULINK device found。前面提过这就是 Debug 页里选了 ULINK 但实际插的是别的调试器。改回 ST-Link 或 CMSIS-DAP 即可。这个报错文案确实有误导性让人以为是设备没插好。能连上但下载失败提示 Flash 校验错误。检查三处Flash Download 页里的算法是否选对、起始地址是否0x08000000、芯片是否被读保护如果之前误操作开了读保护需要用调试工具解锁。还有一种可能是供电不足下载瞬间电流拉高导致芯片复位换个稳定的 USB 供电或者外接电源试试。下载成功但程序不跑。先确认 Flash Download 页里勾了Reset and Run否则下载完芯片停在复位状态需要手动复位才运行。如果没有这个问题就回头检查时钟配置和启动文件是否匹配芯片型号——选错启动文件比如 F103xB 的工程用了 F103xE 的启动文件编译能过但中断向量表和内存布局都是错的程序行为完全不可预测。4.3 运行期异常的分层排查法程序下载进去能跑但行为不对我习惯按时钟 → 引脚 → 外设 → 应用这个顺序从下往上查因为越底层的问题表现越诡异越容易被误判成应用层 bug。时钟层。在main函数开头打印一下SystemCoreClock的值如果不是你预期的数字说明时钟树配置和生成代码不一致。常见于手工改过 RCC 但没重新生成或者选用的晶振频率和实际板载的不一样板子上是 8MHz 你配了 12MHz结果串口波特率全错。引脚层。用万用表或者示波器量一下引脚的实际电平确认它真的在被驱动。如果是输入引脚确认上下拉配置和外部电路匹配。这一层的问题经常表现为代码明明对但就是没信号。外设层。串口不出数据先确认三件事波特率和串口助手是否一致、引脚是否配成了正确的复用功能、是否需要手动使能外设时钟。最后一条在 HAL 库时代通常由MX_xxx_Init自动完成但如果你在生成代码之外手工操作了寄存器可能把时钟关掉了。应用层。到了这一层问题基本都能靠单步调试和 Watch 窗口定位了。我的习惯是在关键分支上加一个全局计数器或者状态变量用 Watch 窗口持续观察比疯狂打断点高效得多。4.4 一张速查表收尾现象最可能的原因处理方式编译报找不到编译器MDK 未装 AC5工程默认用 AC5Target 页切换为 AC6编译报头文件找不到Include Paths 缺路径C/C 页补齐路径点击 Debug 闪退路径含中文/调试器被占用改英文路径关闭其他工具No ULINK device foundDebug 页选错调试器改选 ST-Link 或 CMSIS-DAP下载后不运行未勾 Reset and RunFlash Download 页勾上printf 卡死未勾 MicroLIBTarget 页勾选 MicroLIB串口乱码时钟配置或波特率不匹配核对时钟树与串口参数Watch 窗口看不到变量优化等级过高变量被优化降到 -O0 重新编译重新生成后代码丢失写在 USER CODE 段之外业务代码拆到独立文件引脚无输出引脚功能配置错误回 CubeMX 检查引脚分配5. 把这套流程固化成自己的习惯5.1 用工程化思维管理 CubeMX 加 Keil 的组合用久了会发现真正拉开效率差距的不是会不会点 CubeMX而是有没有把这套工具链纳入版本管理。我的做法是.ioc文件、Core、Drivers三个部分进 GitMDK-ARM目录只保留.uvprojx和.sct这类必要的工程文件编译产物.o、.axf、.hex、Objects目录和.uvguix用户配置全部加进.gitignore。这样仓库体积能控制在几兆以内克隆下来直接能编译。固件包和器件包怎么处理如果团队里大家网络环境都不错可以在 README 里写明所需版本各自用 CubeMX 在线装。如果是离线环境就把固件包目录一并纳入内网仓库。这件事不做新人入职第一天光装环境就得耗半天。还有一个容易被忽视的点是.ioc的提交粒度。每次改配置就单独提交一次commit message 写清楚新增 USART1PA9/PA10115200别把配置改动和业务代码改动混在一起。出了问题想回滚配置时你会感谢当时的自己。5.2 几个能省下大量重复劳动的小技巧第一个技巧是建一个自己的最小工程模板。把常用的配置预设好——72MHz 时钟、SWD 调试口、串口 1 打印、一个空闲的定时器、LED 引脚printf重定向也提前写好。新项目直接复制这个.ioc改改芯片型号和业务外设就能开工省掉每次从零配时钟和串口的时间。我自己的模板用了快两年累计省下的时间相当可观。第二个技巧是善用用户标签。CubeMX 里配置引脚的时候把User Label填成LED、KEY1、UART1_TX这种有意义的名字生成的代码里就会自动出现LED_Pin这类宏。后续看代码HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET)的可读性远好于一堆GPIO_PIN_13而且换引脚时只需要在 CubeMX 里拖一下重新生成代码一行都不用改。第三个技巧是建立自己的报错速查记录。每次踩到新坑花两分钟把现象、原因、解决办法记到一个 Markdown 文件里。我这份记录现在有四十多条覆盖了从编译、下载到运行的各种问题。很多时候新问题的现象和旧问题一模一样翻一下记录两分钟解决不用重新经历一遍大海捞针。最后分享一个我实际用下来感受很深的体会这套工具链最省时间的地方永远不是它帮你写了多少行代码而是它把配置意图和生成代码这两件事解耦了。你可以随时回头改一个引脚重新生成而不用担心手写的逻辑被冲掉。前提是你得尊重它的规则——业务代码放 USER CODE 段或者独立文件配置文件交给.ioc管。把这两条守住STM32CubeMX 加 Keil µVision 这套组合用起来会相当顺手从最小系统到带 FreeRTOS 的完整项目都撑得住。