从零开始搭建一个可烧录、可调试的Keil5工程:新手避坑指南
你有没有过这样的经历?
刚装好Keil5,信心满满地点开“新建工程”,结果在一堆弹窗和选项中迷失方向——芯片选哪个?启动文件怎么加?头文件路径报错怎么办?编译完没有HEX文件……最后只能照着视频一步步“复制粘贴”,却始终不知道每一步到底在干什么。
别担心,这几乎是每个嵌入式初学者都会踩的坑。今天我们就来彻底讲清楚:如何从零开始,亲手搭建一个真正可用、结构清晰、能下载、能调试的标准Keil5工程。
我们不堆术语,不走形式,只讲你真正需要知道的实战逻辑。
第一步:不是写代码,而是“告诉Keil你的硬件是谁”
很多人一上来就想写main()函数,但其实第一步根本不是写代码,而是让Keil认识你的MCU。
当你点击Project → New µVision Project并保存为Blink_LED.uvprojx后,Keil会立刻弹出一个对话框:
“Select Device for Target ‘Target 1’”
这就像是在问:“你说你要开发,那你目标板上到底用的是哪颗芯片?”
比如你手里的开发板是STM32F103C8T6,那就在这里找到它:
- 制造商选STMicroelectronics
- 型号找STM32F103C8
- 点击OK
这时Keil干了三件关键的事:
自动设置内存布局
它知道这片Flash从0x08000000开始,大小是64KB;SRAM起始于0x20000000,共20KB。这些信息直接写进了链接器配置里。预加载CMSIS核心头文件
Cortex-M系列的寄存器定义(如NVIC、SysTick)都来自CMSIS标准,Keil已经内置了core_cm3.h这类文件,无需手动添加。提示是否添加启动文件
接下来它会问你:“要不要复制标准的启动代码?”
——一定要点Yes!
否则你就得自己去找startup_stm32f103xb.s这个汇编文件,而新手往往连它藏在哪都不知道。
📌 小贴士:GD32用户注意!虽然GD32F103兼容STM32F103,但Keil数据库里没有GD的官方支持。你可以先选STM32型号,后续再替换正确的启动文件和system初始化代码。
第二步:理解那个神秘的.s文件——启动代码到底做了什么?
现在你的工程里应该多了一个叫startup_stm32f103xb.s的文件,放在“Source Group 1”里。
这个名字长得不像人写的,但它至关重要。我们可以把它看作是程序的“开机自检程序”。
它的核心任务只有四个:
| 阶段 | 动作 |
|---|---|
| 1. 设栈 | 把主堆栈指针MSP指向RAM顶部 |
| 2. 搬数据 | 把.data段从Flash搬到SRAM(因为变量要可读写) |
| 3. 清bss | 把.bss段清零(未初始化全局变量默认为0) |
| 4. 跳main | 执行__main,最终进入我们的main()函数 |
如果你打开这个文件,会看到类似这样的代码:
AREA RESET, DATA, READONLY EXPORT __Vectors EXPORT __initial_sp __Vectors: DCD __initial_sp ; 栈顶地址 DCD Reset_Handler ; 复位处理函数 DCD NMI_Handler DCD HardFault_Handler ; ... 其他异常这里的__initial_sp实际上是一个符号,在链接时会被替换成SRAM的最高地址(比如0x20005000)。而Reset_Handler就是复位后CPU第一个执行的地方。
接着往下看:
Reset_Handler PROC LDR R0, =__main BX R0 ENDP这里跳转到了__main,而不是直接进main()。为什么?
因为中间还有一层C运行时环境需要初始化——这部分由编译器库完成,包括前面说的数据搬运和清零操作。
⚠️ 千万别删掉
Reset_Handler或改名!否则整个程序就断在起点了。
第三步:别再把所有文件丢进同一个篮子——合理分组有多重要?
Keil允许你在左侧Project窗口中创建多个“Group”,这不是为了好看,而是为了管理复杂度。
想象一下,如果有一天你要加入FreeRTOS、FatFS、WiFi驱动、USB协议栈……几百个文件全挤在一个目录下,你还找得到自己的main.c吗?
建议这样组织:
Core ├── startup_stm32f103xb.s ├── system_stm32f103xb.c └── main.c Drivers ├── stm32f1xx_hal.c └── stm32f1xx_hal_gpio.c User ├── gpio.c └── usart.c Middleware └── FreeRTOS ├── tasks.c └── queue.c怎么做?
- 右键点击“Source Group 1” → Add Group…
- 新建名为
Core、Drivers等的分组 - 把对应文件拖进去(物理路径不变)
- 可以为每个Group单独设置Include路径和宏定义!
例如,Drivers组需要用到HAL库的头文件,就可以右键该组 → Options for Group → C/C++ → Include Paths 添加:
..\Drivers\STM32F1xx_HAL_Driver\Inc ..\Middlewares\FreeRTOS\Source\include这样做的好处是:不同模块可以有不同的编译条件,避免全局污染。
第四步:编译成功≠能用——这几个选项必须设对
很多人编译通过就以为万事大吉,结果发现程序下不去、看不到变量、没法调试……问题出在哪?就在“Options for Target”里。
必须勾选的三项
✅ Create HEX File
路径:Output 标签页 → 勾选Create HEX File
作用:生成可用于ISP烧录或J-Link下载的Intel HEX格式文件。
如果没有这个选项,即使.axf编译成功,你也无法将程序固化到Flash中。
💡 提示:某些工具链(如PlatformIO)默认输出BIN,但在Keil中HEX更常用。
✅ 设置Include Paths
路径:C/C++ 标签页 → Include Paths
常见路径包括:
.\Inc .\Src .\Drivers\CMSIS\Device\ST\STM32F1xx\Include .\Drivers\CMSIS\Include否则会报错:
fatal error: stm32f10x.h: No such file or directory记住:路径是相对于工程文件(.uvprojx)的位置,使用\或/都可以,但不要有中文或空格。
✅ 定义必要的宏
同一页面下的Define输入框:
STM32F103xB, USE_HAL_DRIVER这两个宏的作用非常关键:
-STM32F103xB:让头文件知道自己是哪种封装和容量等级
-USE_HAL_DRIVER:启用ST的HAL库初始化流程
写错了会怎样?
比如写成STM32F103XB(少下划线),HAL库就不会识别,导致RCC、GPIO等外设无法正常工作。
第五步:连接真实世界——调试器怎么配才不翻车?
终于到了下载程序的时候。插上ST-Link,按下Load按钮,却弹出:
“No target connected”
别急,先检查以下几点:
1. 接线是否正确?
SWD模式只需要4根线:
- SWCLK → PA14
- SWDIO → PA13
- GND → 共地
- VDD → 接电源(可选,用于检测电平)
少一根都不行。
2. 调试器设置对了吗?
进入Options for Target → Debug:
- Use: 选择ST-Link Debugger
- 点击 Settings
在新窗口中:
-Connect: 改为“Under Reset”
这样可以在芯片复位状态下连接,避免因低功耗模式导致脱机。
-Flash Download: 点击Add,选择对应的算法
对于STM32F1系列,通常是:STM32F1xx Flash (Medium Density).alg
如果没添加算法,下载时就会失败,提示“Programming Algorithm not found”。
3. 如何启用串口打印?用SWO还是semihosting?
如果你想在调试时看到printf输出,有两种方式:
方式一:Semihosting(适合仿真)
- 在C/C++中添加宏:
__MICROLIB - 勾选Use MicroLIB
- 使用
printf("Hello World\n"); - 输出显示在Keil的Debug (printf) Viewer中
缺点:每次调用printf都会暂停程序,不适合实时系统。
方式二:SWO Trace + ITM(真正在跑)
需要:
- 芯片支持ITM(Cortex-M3/M4以上)
- 多接一根SWO引脚(PA10)
- 在调试设置中启用Trace
- 使用ITM_SendChar()发送字符
这种方式才是真正“后台打印”,不影响程序运行。
常见问题现场排雷
❌ 编译报错:Undefined symbol Reset_Handler
原因分析:
- 启动文件没加入工程
- 文件类型被误设为“Ignore”
- 启动文件本身缺失关键标签
解决方法:
1. 检查Project中是否有startup_xxx.s
2. 右键该文件 → Properties → Build Action 应为Assemble
3. 打开文件确认存在Reset_Handler PROC
❌ 找不到stm32f10x.h
典型错误提示:
fatal error: stm32f10x.h: No such file or directory排查步骤:
1. 是否已下载STM32CubeF1包并导入相关文件?
2. Include Paths是否包含头文件所在目录?
3. 路径是否用了绝对路径?建议改为相对路径,提升移植性。
❌ 编译成功但没生成HEX文件
可能原因:
- Output → Create HEX File 未勾选
- 许可证过期,处于评估模式限制
- 输出路径权限不足或被占用
解决方案:
- 明确勾选生成HEX
- 查看许可证状态(File → License Management)
- 更改输出目录至非系统盘路径
工程模板设计建议:让你的项目更专业
目录结构规范化
推荐采用如下目录划分:
/Blink_LED │ ├── Project │ └── Blink_LED.uvprojx │ ├── Core │ ├── startup_stm32f103xb.s │ ├── system_stm32f103xb.c │ └── main.c │ ├── Drivers │ └── HAL_Library │ ├── stm32f1xx_hal.c │ └── ... │ ├── Inc │ └── user_config.h │ ├── Src │ └── gpio.c │ ├── Objects ← 自动生成,不必提交版本控制 └── Listings ← 日志输出目录版本控制注意事项
.gitignore至少包含:
*.uvoptx *.uvprojx Objects/ Listings/ *.log .DSLock保留哪些?
-.c,.h,.s源码
- 工程文件(方便团队共享)
-Readme.md(说明依赖项、编译方式)
写在最后:掌握Keil5工程创建,其实是掌握一种思维方式
你会发现,keil5怎么创建新工程这个问题背后,其实是一整套嵌入式开发的认知框架:
- 你知道了芯片选择不只是填个名字,而是决定了内存映射和底层行为;
- 你明白了启动文件不是摆设,而是程序得以运行的前提;
- 你学会了通过分组和路径管理构建可维护的代码体系;
- 你也掌握了从编译到下载全过程的关键控制点。
这不是简单的菜单操作,而是一种工程化思维的建立。
下次当你拿到一块新的STM32板子,或者要帮同事排查一个“程序下不去”的问题时,你会比别人更快定位根源——因为你已经看透了Keil背后的逻辑链条。
🔧 所以说,学会新建工程,不是学会了“新建”这件事,而是拿到了通往嵌入式世界的入场券。
如果你正在学习STM32、准备电赛、或是想转型嵌入式开发,不妨动手照着这篇指南完整走一遍。哪怕只是点亮一个LED,那也是你真正掌控硬件的第一步。
欢迎在评论区分享你的第一个Keil工程截图,我们一起debug成长 🛠️