ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Keil MDK工程创建全流程解析:从芯片选型到调试烧录的避坑实战

Keil MDK工程创建全流程解析:从芯片选型到调试烧录的避坑实战 被 Keil 工程创建折磨过的朋友应该都懂那种感觉明明照着教程一步一步点结果不是找不到芯片型号、就是编译报错、再不然就是下载不进去最后只能对着屏幕怀疑人生。我在嵌入式这一行干了十多年ARM Cortex-M 的工程前前后后建过几百个踩过的坑比大部分教程里写的步骤还多。这篇就把我平时带新人最常用的一套 Keil 工程创建流程完整放出来把每一步背后为什么要这么做也讲透。你照着走完一遍不仅能从零建出一个能编译、能烧录、能调试的 Cortex-M 工程以后遇到 80% 的常见报错也能自己排查个八九不离十。这篇内容适合刚接触嵌入式的学生、从 51 单片机转到 ARM 的老鸟以及被 Keil 各种报错折磨到头疼的工程师。文章不会只讲“点哪里”而是把原理、操作、避坑全揉在一起保证你看完能真正理解工程结构而不是只记住操作路径。1. 动手之前先把 Keil 环境和芯片支持包装到位很多人一上来就急着点 New Project结果卡在第一步的芯片选择窗口里发现自己要找的 STM32 系列根本没出现在列表里。这个问题的根源不是 Keil 软件本身而是 Keil 5 之后的“芯片支持包”机制没搞明白。1.1 MDK 和 C51 根本不是同一个东西别装混了Keil 这个品牌下面其实有两条完全不同的产品线Keil C51 和 Keil MDK。C51 是给 8051 内核单片机用的也就是很多人大学时学的那种 STC89C52MDKMicrocontroller Development Kit才是给 ARM Cortex-M 用的老一点的叫法叫 MDK-ARM。如果你只装了 C51那不管怎么折腾都别想在工程里找到 Cortex-M 芯片。这个坑特别隐蔽因为两个软件的界面长得几乎一模一样都是 uVision。你从官网下载安装包时一定要看清楚下载的是 MDK 还是 C51。我这里说的整个流程全部基于 MDK 5 之后的版本也就是集成 Pack Installer 的 uVision5。安装时默认装到C:\Keil_v5就行不建议装到带中文和空格的路径下比如D:\软件\Keil这种后面编译经常会冒出莫名其妙的路径问题咱们从一开始就把隐患掐掉。顺带提醒一句授权的事。MDK 可以到官网下载评估版有 30 天全功能试用期。评估期过了之后界面会有个 16KB 代码限制的提示这时候建议直接申请免费评估授权或购买正版 license网上那些注册机、破解包尽量别碰安全风险和合规问题都不小而且调试器连不上、编译器报错这类问题跟破解不干净往往也有关系。我遇到过不止一个学员工程本身没问题就是迟迟不解决授权还误以为是配置错了。1.2 没有 Pack芯片列表就是空的Keil 5 的大改动就是引入了 CMSIS Pack 机制。以前 Keil 4 装完软件就自带一堆厂商芯片支持Keil 5 开始把这些支持包拆成了单独的 Pack需要联网下载。所以你在电脑上装完 MDK 官网最新版之后芯片列表里没东西是正常的先装 Pack 再说。Pack 的安装路径有两条。第一条是打开 Keil 后点击工具栏上的 Pack Installer 图标在里面找到你的芯片厂商。以 STM32F1 为例展开 STMicroelectronics 节点找到 STM32F1 Series Device Support 这个包点 Install 就行。国内网络访问 Pack 服务器偶尔很慢如果实在装不动第二条路是去 Arm 官网的 CMSIS Pack 页面搜“STM32F1”下载对应的离线.pack文件。下载后直接双击这个文件Keil 会自动导入。离线包这点建议所有人存一份公司内网或者隔离网环境开发时离线包就是救命稻草。装包的时候顺便养成一个好习惯看一下 Pack 版本。同一个芯片可能有好几个历史版本一般选最新的 Stable 版本就行但如果你的工程要给别人协作或者公司有统一规范Pack 版本最好大家保持一致否则容易出现“我这边编译好好的他那边缺这个文件”的尴尬。我后面会在工程管理那节再展开讲。1.3 编译器版本AC5 还是 AC6怎么选芯片支持包搞定之后还有一个容易被人忽略的配置编译器版本。Keil MDK 5.37 及更早版本默认自带 ARM Compiler 5也就是大家常说的 AC5但从某个版本开始新装好的 MDK 默认只带 ARM Compiler 6AC6实际是 ArmClang。这俩虽然都能编 Cortex-M但行为差异非常大。AC5 就是老牌的 armcc很多老工程、老教程、部分芯片厂商的库和例程都是基于 AC5 写的特点是编译快、语法宽松、兼容性好但编译器本身已经停止更新了最后的终极版本是 5.06 Update 7build 960。AC6 是新一代 ArmClang 编译器对 C99/C11 标准支持好编译告警和优化也更现代但语法检查更严格连数据类型转换的隐式警告都会列一堆出来。还在用 AC5 的旧工程直接切到 AC6经常爆出几十个 error 甚至几百个 error很多都是##运算符、__forceinline、内嵌汇编这类老语法不兼容导致的。我的建议是新工程直接用 AC6长痛不如短痛老工程、厂商老例程、老库就老实锁死在 AC5。如果你的 MDK 里找不到 AC5 选项需要去官网的 Legacy Compilers 下载页面单独装一个「Arm Compiler 5.06 Update 7」装好之后在魔术棒Options for Target的 Target 选项卡里的 ARM Compiler 下拉框切过去就行。顺便说一句AC5 和 AC6 的编译输出目录最好分开免得切换编译器后中间产物互相污染这个我后面会讲具体怎么处理。2. 手把手创建工程以一个 STM32 实例走完全流程理论铺垫完了下面正式开建。我用最经典的 STM32F103C8T6 作为示例芯片调试器用 ST-Link V2这是入门用户最常见的一套配置。整个流程你完全照做就行等熟悉以后再灵活变通。2.1 新建工程并选择目标芯片打开 Keil uVision5菜单栏选 Project - New uVision Project。弹出保存窗口时建议先在某个工作目录下建好工程文件夹结构再把这个.uvprojx工程文件存进去。比如MyProject/ ├── Project/ ├── User/ ├── Drivers/ └── Output/像这样把工程文件、用户代码、驱动、输出目录从第一天就分开后面你会感谢自己。工程文件名也尽量用英文不要用“新建工程最终版2”这种名字。路径里的中文和空格太多Keil 的 fromelf、链接器这些工具链组件比较“娇气”很容易在奇怪的地方报错。保存工程文件后会弹出 Device 窗口这就是选芯片的地方。如果之前 Pack 装好了这里就能看到各个厂商的树形列表。依次展开 STMicroelectronics - STM32F1 Series - STM32F103 - STM32F103C8选中它点击 OK。如果列表里找不到 STM32 相关节点说明刚才第一大步里的 Pack 没装好回去装完再继续。在选芯片这步有个小技巧窗口右上角有搜索框直接输芯片型号的关键字比如F103C8比一层层点目录快得多。选中型号后右边还会显示这个芯片的 Flash、RAM 大小和内核信息可以顺便看一眼型号对应的资源够不够你的需求。2.2 启动文件与 CMSIS 核心文件不是可有可无的“样板代码”选完芯片后Keil 会弹一个窗口问你“Copy STM32 Startup File to Project Folder?”中文界面大概是“复制启动文件到工程文件夹”这里一定要选“是”。这个启动文件叫startup_stm32f103xb.s别看它是个汇编文件它是整个 MCU 运行的“总导演”。单片机复位后CPU 就是从这段汇编代码开始执行的先建立栈指针然后调用SystemInit做时钟初始化再跳转到main。没有这个文件你的程序根本没机会跑起来。对于 STM32F103C8 这种 Flash 容量在 64KB 以内的芯片Keil 会自动匹配startup_stm32f103xb.s这个版本。不同容量的芯片对应的启动文件不同比如 F103 系列还有startup_stm32f103xa.s、startup_stm32f103xe.s等里面的向量表长度和 Flash 大小配置不一样不能随便混用。这个文件加到工程里后不用改理解它大概干了什么就行。除了启动文件一个标准工程还需要 CMSIS 相关文件。STM32 的话一般至少要有core_cm3.hCortex-M3 内核寄存器定义和system_stm32f1xx.c系统时钟初始化。这些文件通常不在 Keil 自动生成的列表里需要你自己添加。一个好办法是找一份同系列官方例程从里面把CMSIS和StdPeriph_Driver或者是更新的 HAL 库、LL 库整个文件夹复制过来在 Keil 工程里右键 Manage Project Items 建分组再 Add Existing Files 把.c文件加进去。2.3 添加应用代码配置头文件路径接下来写一个最简main.c在工程里右键 Source Group 1 - Add New Item to Group选择 C File文件名填 main创建后输入#include stm32f10x.h int main(void) { // 打开 GPIO 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 配置 PA1 为推挽输出 GPIO_InitTypeDef GPIO_InitStructure; GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); while(1) { GPIO_SetBits(GPIOA, GPIO_Pin_1); Delay(500000); GPIO_ResetBits(GPIOA, GPIO_Pin_1); Delay(500000); } }这段代码用的是标准外设库StdPeriph如果你想用 STM32Cube HAL 库思路是一样的只是 API 名字不同。自己练手的时候用哪种库不重要重要的是理解只要工程里包含头文件、main.c能引用到它们配置就对了一半。这里就到了很多人踩坑最密集的地方头文件路径。你写完#include stm32f10x.h后直接点编译十有八九会报fatal error: stm32f10x.h: No such file or directory。这是因为 Keil 只会在特定路径下搜索头文件你的工程目录不在它默认搜索范围内。解决办法是打开魔术棒Options for Target切到 C/C 选项卡在 Include Paths 一栏点右侧的三个点按钮把刚才从例程复制过来的所有包含.h文件的目录都加进去比如..\Drivers\CMSIS\Include、..\Drivers\CMSIS\Device\ST\STM32F1xx\Include、..\Drivers\StdPeriph_Driver\inc这些。加完之后会看到一个相对路径列表确认无误再编译。同一个选项卡里还有一个 Define 框这也是经常出问题的地方。用标准外设库的 F1 系列一般要填STM32F10X_MD根据容量选择 MD/HD/LD这个宏决定库代码编译时启用哪套外设配置。用 HAL 库则通常不需要具体以你参考的例程模板为准。如果摘了例程最好把它的 Define 原样复制过来别自己发挥。2.4 魔术棒核心配置这里决定你能不能烧录和调试代码写好了编译前还要过一遍魔术棒。首先在 Output 选项卡里勾上 Create HEX File这样编译后会生成.hex烧录文件用 ST-LINK utility 或者其他烧录工具离线烧录时就会用到。接着切到 Debug 选项卡。右上角有个 Use 单选框默认选的是 Simulator软件模拟要改成你的硬件调试器。我用的是 ST-Link就选 ST-Link Debugger用 J-Link 就选 J-Link / J-TRACE Cortex用 DAP-Link 选 CMSIS-DAP Debugger。选好之后点旁边的 Settings进入更细的调试器配置界面。在 Debug 子页里Port 选择 SWST-Link/V2 一般用 SWD 模式只用四根线比标准 JTAG 省引脚Max Clock 可以先保持自动或者 1MHz——注意如果连接不稳定先把速率降到 1MHz这是后面排查连接问题常用的降级手段。右边的 Flash Download 子页是烧录的关键要在 Programming Algorithm 区域添加对应的 Flash 算法。STM32F103C8 对应的是STM32F10x Med-density Flash 64K如果这里列表是空的烧录时会报Error: Flash Download failed - Cortex-M3就是这个原因。选完算法后把右下角的 Reset and Run 勾上烧录完程序会自动复位运行省得每次断电重启。最后回到 Utilities 选项卡勾选 Use Debug Driver这样点下载按钮时就不需要再做一次单独的 Flash 工具配置直接用调试器设置里的配置烧录。到这里一个能编译、能烧录、能进调试的工程框架就搭起来了。3. 把关键选项背后的原理讲清楚很多人建工程全靠记步骤换个芯片型号就不会了原因就是没搞懂每个选项到底在干什么。这一节我不讲操作专门讲原理把这些选项幕后的事情说透。3.1 分散加载文件链接器眼中的内存地图编译链接 C 程序时链接器需要知道代码和变量该放到芯片的哪个地址。Keil 里这个信息由分散加载文件后缀.sct提供默认情况下 Keil 会根据你选择的芯片型号自动生成一份你不主动指定它它就在后台默默工作大多数工程根本不用管。比如 F103C8 默认的.sct大概就是LR_IROM1 0x08000000 0x00010000 { ER_IROM1 0x08000000 0x00010000 { *.o (RESET, First) *(InRoot$$Sections) .ANY (RO) } RW_IRAM1 0x20000000 0x00005000 { .ANY (RW ZI) } }看着神秘拆开就一句话代码放 Flash0x08000000 起始变量放 RAM0x20000000 起始。什么时候你需要手动改它最典型的场景是 Bootloader 加 App 的分区设计。比如 Bootloader 占用 0x08000000 开始的 32KBApp 要放在 0x08008000 开始的位置那么 App 工程就必须在魔术棒的 Target 选项卡里把 IROM1 的起始地址改成0x08008000大小改成剩余空间再配合烧录时的偏移设置。如果你不理解.sct的分区概念看到这种需求基本就懵了。还有一类场景是在 RAM 里跑代码比如某些 Flash 锁死后的恢复程序、或者把关键函数搬到 RAM 执行以规避 Flash 等待时间这就要用.sct的 overlay 或RW_IRAM扩展特性。这种属于进阶玩法日常开发不强制但知道.sct对应的是“内存地图”这个概念排查链接问题会容易很多。3.2 Debug 设置里的那些“小开关”Debug 设置不止是选个调试器这么简单。往下数几级你会发现很多平时不会碰的选项但恰恰是它们在关键时刻决定你能不能顺利调试。先说 Port 和 Speed。SWD 协议理论上速度可以拉到很高但实际取决于你的线材质量、线缆长度和目标的供电稳定性。我用 ST-Link 默认的 4MHz 偶尔会连不上降到 1MHz 就稳了。这是排查“连接不稳定”时最快的一招。另外如果你的板子上 SWDIO/SWCLK 两个引脚被其他外设占用或者线上有 100nF 以上的大电容也会导致调试器握手失败表现为could not stop cortex-m device这类报错。再看 Flash Download 里的Erase Full Chip和Erase Sectors。默认是 Erase Sectors按扇区擦除烧录速度更快适合日常调试。但如果你的程序跑飞把 Flash 里其他扇区写坏了或者改了向量偏移却总是诡异的跳转不对可以手动选 Erase Full Chip 做一次全片擦除很多“奇怪状态”都能靠这招救回来。还有 Reset and Run 这个选项建议勾上。不勾的话烧录完程序其实已经写进 Flash 了但 CPU 还停在调试会话的暂停状态每次都要手动复位才能看到现象对于刚入门的人来说很容易误以为“烧录失败”。勾上之后下载完自动复位观察现象直接很多。3.3 编译输出AXF、HEX、BIN 到底有什么区别编译完成后你在 Output 文件夹里能看到一堆文件。.axf是 ARM 的完整可执行文件包含调试符号表和程序数据调试器跟踪源码行号、查看变量都依赖它直接烧录也能用但一般不用。.hex是 Intel HEX 格式纯程序数据加校验信息几乎所有的烧录工具都认适合量产时用。.bin是纯二进制数据没有地址信息烧录时必须指定起始地址常用于 U 盘拖拽烧录、OTA 升级这种场景。Keil 默认只生成.axf你需要在魔术棒 Output 里勾上Create HEX File才会生成.hex。.bin的生成方式有两种一种是勾选Create Batch File然后自己写一行命令行调用另一种更常用是在 User 选项卡的 After Build 里加一句fromelf.exe --bin -o $LL.bin $LL.axf编译完成后自动从 axf 转换出 bin。这里就会用到 fromelf 这个工具。平时最常见的报错*** error: createprocess failed, command: c:\keil_v5\arm\armcc\bin\fromelf...根子往往是你的工程路径有中文或空格、MDK 安装路径权限不对或者这个命令里的路径变量解析失败。后面问题章节我会专门说。4. 常见问题与排查技巧实录这一节是我之前带人时被问得最多的问题集合每一个都是真实场景你大概率会碰到。4.1 could not stop cortex-m device八成的锅在硬件连接这是嵌入式开发十大经典报错之一。第一次遇到的人往往以为是 Keil 配置坏了其实八成是硬件层面的问题。你可能在点击下载按钮后看到* JLink Info: Could not stop Cortex-M device!或者 ST-Link 版本类似的信息。这时候按下面顺序逐个排查第一步确认调试器的 3.3V、SWDIO、SWCLK、GND 四根线接对了没有。很多人会下意识说“接对了”但拿万用表量一下才靠谱。TXD 和 RXD 反接、DEBUG 口附近焊锡短路我都碰过。第二步确认板子上电了测量核心芯片引脚电压正常部分开发板只插调试器不单独供电可能导致调试器供电能力不足。第三步检查复位电路STM32 的 NRST 引脚如果被强拉低或外接电容过大会卡住内核导致调试器无法控制 CPU。第四步把调试器速度降到 1MHz 再试。第五步换一根杜邦线或者缩短线缆长度杜邦线互相干扰在 SWD 高速模式下非常常见。做过以上五步还是不行再考虑是不是调试器固件过老或是山寨盗版调试器更新固件或换一个调试器测试。我遇到的最邪门一次是 PCB 板上 SWDCLK 走线下面正好走了一根大电流电源线干扰了时钟信号重新走线后才好。4.2 CreateProcess failed路径和工具的坑这个报错是新手重灾区完整信息类似*** error: createprocess failed, command: C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o .\Output\xxx.bin .\Output\xxx.axf出现这个错误最常见的原因有四个。第一工程路径里有中文或空格Keil 系列工具链对非 ASCII 路径支持差fromelf 直接创建不了 subprocess。解决办法就是把整个工程迁移到纯英文路径下。第二MDK 安装路径本身被系统保护或者无写权限比如装在C:\Program Files (x86)\Keil_v5这种目录某些操作用户权限不足也会触发 CreateProcess 失败会建议直接手动用管理员权限运行 Keil但最干净的方案还是改装到盘符根目录C:\Keil_v5。第三User 选项卡里自定义的命令写错了路径比如 fromelf.exe 的绝对路径打错或者环境变量没设置对。第四杀毒软件拦截了 Keil 的 subprocess 调用某些国产安全软件会把 fromelf 当成未知程序拦截加白名单可解决。排查时必须先看完整报错行的第二个等号后面那一段它会把真正没执行的命令行完整打出来。照着这条命令自己复制到 CMD 里跑一遍如果 CMD 里也报错那就是命令本身的问题如果 CMD 里能跑通那就是 Keil 调用环节的问题这样一分开问题范围瞬间缩小一半。4.3 调试模式下怎么看结构体变量很多人进了 Keil Debug 模式后想知道某个结构体变量的值却在 watch 窗口看不到完整内容。这其实是个操作习惯问题。在 Keil 的 Debug 会话中先用单步执行或者运行时把程序停在你关心的位置然后在 Watch 窗口的输入框里输入变量名回车。这时候你会看到变量名、类型和第一层值。重点来了结构体变量名左边有个小箭头点开它才会展开里面所有成员。而且如果你还在跑的时候修改了某个成员watch 窗口不会自动刷新要手动点一下刷新按钮或者让程序停下来才会同步。更高效的方式是用 Live Variables 窗口。在 Debug 菜单里打开 View - Watch 或者 View - Live Variables把变量拖到 Live 窗口程序全速运行时能实时看到某些全局结构体的值变化不用频繁暂停。不过要注意如果优化级别开得太高比如 AC6 的 -O3某些局部变量会被优化掉watch 窗口会显示optimized out这时候把优化等级降下来才可能看到。调试期间最省心的做法是直接用 Debug 选项里的 -O0 优化编译。外设寄存器想看直接打开 Peripherals 菜单比如 GPIOA 的某个寄存器点开对应外设窗口就能看到这个按外设分组比裸看内存直观。如果连变量地址、原始字节都想看就用 Memory 窗口手动输入变量名或者一个绝对地址再选择显示的宽度非常灵活。4.4 烧录失败No Algorithm found 与 Flash Download failed除了连接问题烧录失败的另一大来源是 Flash 算法配置不对。提示No Algorithm found时先去我前面说的 Debug 选项卡 - Settings - Flash Download 页里看 Programming Algorithm 列表有没有内容。空列表或者列表里的算法型号跟芯片容量不匹配都会报错。以 STM32F103 家族为例Flash 算法有STM32F10x Low-density Flash 32K、STM32F10x Med-density Flash 64K、STM32F10x High-density Flash 512K这几个大分类。如果把 High-density 的算法用在一个 64KB 芯片上有些调试器能自动识别还勉强能用但原理上不应该这样因为它按 512K 的扇区布局去擦写跟实际的 64K 布局有差异轻则浪费时间重则烧录校验失败。遇到 Flash 相关报错先把算法删了重新添加正确型号再把擦除方式从 Sectors 改成 Full Chip 试试这两个操作可以解决大多数烧录异常。另外如果程序里对 Flash 做了写保护或者读保护某些情况下调试器也无法烧录。这时候要用 ST 官方的 STM32CubeProgrammer 执行全片擦除解除保护之后再回到 Keil 里烧录。有人可能会问为什么不在 Keil 里直接解决——理论上可以但各种保护位的处置逻辑各厂商调试器实现不同Keil 里能处理的范围有限还是专用工具最稳。5. 工程管理的一些老实建议工程创建这事除了“怎么建”还有“怎么管”。特别是当你开始团队协作、或者同时维护多个产品线时工程管理的混乱程度能直接决定加班的多少。首先建议每个产品项目都有一个固定的目录模板跟我在 2.1 节展示的那样把Project、User、Drivers、Output分开。Git 版本管理时.uvoptx、Output目录里那些.o、.axf、.hex、.map文件都应该进.gitignore只提交源码、工程文件和必要的链接脚本。否则每次编译完都会产生几百个二进制变更记录Diff 全是噪音。.uvprojx文件是可以提交的但小心多人同时修改它产生的冲突这个我觉得备个案在这里即可细节展开又是另一篇文章。其次同一团队内尽量统一 Keil 版本和 Pack 版本。之前有同事用 MDK 5.36我用 5.41同一个工程在两边编译AC6 版本内部有小差异结果他那边编了一次没问题我这边因为默认告警等级不同多出来一堆 warning再合并代码时极其痛苦。后来我们规定一律用 5.38 加指定的 STM32F1 Pack 版本这类问题基本消失。最后多维护一个“最小模板工程”很值。把你最常用的芯片、最基础的配置封装成模板工程新建项目直接复制模板改名字比每次从零点击要快太多。模板里把 LINK、启动文件、标准库初始化、printf 重定向都配好之后你只需要集中精力写业务代码。这是最隐蔽但最提效的一个习惯。6. 几句实在话说到底Keil 工程创建是个熟练工种多建几次自然就熟。但“会点按钮”和“懂原理”是两码事我见过太多人建了几年工程一遇报错还是只能截图问别人。所以你在跟着教程走的时候不妨多问自己一句这一步选的芯片型号、这个启动文件、这个烧录算法到底是在配置什么想通了以后就算换 RISC-V、换其他 IDE你也只是换一套操作路径而不是从零开始。我个人还有一个雷打不动的习惯每次新建完工程第一时间把“能编译、能下载、能进调试”这三关全跑通再往里面写业务逻辑而不是等项目写完一堆代码再回过头调试环境。环境通了后面一切才顺。希望这篇指南能帮你少走一些弯路那些我已经替你踩过的坑你就别再去踩一遍了。
返回列表