手把手教你从零搭建STM32开发环境:Keil5工程创建全解析
你有没有遇到过这样的场景?刚拿到一块STM32最小系统板,打开Keil5却不知道从哪下手——新建工程后一片空白,编译报错一堆“undefined symbol”,下载程序后单片机毫无反应……别急,这几乎是每个嵌入式新手都会踩的坑。
今天我们就以STM32F103C8T6(俗称“蓝丸”)为例,带你用Keil MDK-ARM 5(简称Keil5)从零开始,一步步搭建一个可运行、可调试、结构清晰的标准工程模板。整个过程不依赖STM32CubeMX,完全手动配置,让你真正理解每一个步骤背后的原理。
为什么你的Keil工程总是出问题?
在正式动手前,先搞清楚一个关键问题:为什么很多人照着教程做,最后还是失败?
根本原因在于——他们只是“复制操作”,而没有理解每个环节的作用。比如:
- 启动文件选错了会怎样?
- 宏定义没加会导致什么后果?
- 头文件路径漏了一条会发生什么?
这些问题的答案,藏在工具链的工作机制里。我们先快速扫一眼背后的技术支撑。
STM32是怎么启动的?
当你按下复位键,CPU做的第一件事不是执行main()函数,而是:
- 从Flash地址
0x0800_0000开始读取初始堆栈指针(MSP) - 跳转到复位向量(Reset Handler),进入汇编写的启动代码
- 执行
SystemInit()初始化时钟 - 初始化
.data和.bss段(复制已初始化变量、清零未初始化变量) - 最终调用C语言的
main()
这个过程中,启动文件、CMSIS标准、HAL库初始化缺一不可。任何一个环节出错,程序就可能“卡住不动”。
第一步:创建工程 & 选择芯片
打开Keil μVision5,点击菜单栏:
Project → New uVision Project
弹出对话框,选择你的项目路径,建议组织为如下结构:
MyProject/ ├── Project/ ← 存放.uvprojx工程文件 ├── Src/ ← C源码 ├── Inc/ ← 头文件 ├── Core/ ← 内核相关(启动文件、system等) └── Drivers/ ← HAL库等驱动代码输入工程名(如STM32_Minimal.uvprojx),保存后会跳转到“Select Device”界面。
搜索STM32F103C8,选择具体型号:
👉STMicroelectronics → STM32F103C8Tx
⚠️ 注意后缀含义:
-T表示LQFP48封装
-x中的8表示64KB Flash(不是128KB!)
选错型号可能导致链接脚本不匹配或外设定义错误。
确认后,Keil会自动加载该芯片的基本信息,包括内存布局、寄存器定义等。
第二步:引入CMSIS与启动文件(最关键一步)
接下来是决定工程能否跑起来的核心环节。
点击工具栏上的魔术棒图标(Options for Target),切换到“Manage Run-Time Environment” (RTE)标签页。
你会看到一个模块化配置窗口,这是Keil5的一大特色功能。
展开以下两项并勾选:
- ✅CMSIS → CORE
包括内核寄存器访问头文件和基本运行支持 - ✅Device → Startup
自动添加对应芯片的启动文件(这里是startup_stm32f103xb.s)
📌 关键提示:
xb对应的是64KB Flash容量组(64~128KB),正好适用于C8T6。如果用了xc(128KB以上)可能会导致中断向量表偏移!
点击OK后,Keil会在左侧“Project”面板中自动生成两个Group:
-CMSIS
-Device
并且将startup_stm32f103xb.s添加进工程。
此时,编译一下试试?
你会发现仍然报错:“cannot open source input file ‘stm32f1xx.h’: No such file or directory”
别慌,这才刚开始。
第三步:添加必要源文件与目录结构
现在我们要把缺失的文件补全。这些文件可以从ST官网下载的HAL库包中获取,也可以直接从STM32CubeF1软件包提取。
我们需要添加以下几类文件:
1. 系统级文件(放入/Core目录)
| 文件 | 来源 | 作用 |
|---|---|---|
system_stm32f1xx.c | STM32Cube_FW_F1_V1.8.5\Drivers\CMSIS\Device\ST\STM32F1xx\Source\Templates | 实现SystemInit()函数,配置HSI/HSE、PLL等时钟源 |
stm32f1xx_it.c | 同上路径下的 Exceptions 模板 | 放置所有中断服务函数(NMI_Handler, HardFault_Handler 等) |
同时,在/Inc目录下添加对应的头文件:
-stm32f1xx_it.h
-main.h
2. HAL库文件(放入/Drivers/STM32F1xx_HAL_Driver)
至少需要加入以下.c文件到工程:
stm32f1xx_hal.c stm32f1xx_hal_cortex.c stm32f1xx_hal_rcc.c stm32f1xx_hal_rcc_ex.c stm32f1xx_hal_gpio.c🔍 小技巧:你可以只加入这些文件,而不必把整个HAL库都拖进来,节省空间且便于管理。
将这些文件分别归入名为 “HAL Library” 或 “Drivers”的Group中。
第四步:配置编译选项(让编译器认识你的代码)
再次打开“Options for Target”(魔术棒),进行关键设置。
【Target】选项卡
- XTAL (MHz):设置为外部晶振频率,例如
8.0 - Operating:选择
On-chip ROM & RAM(使用片上存储器)
Keil会根据所选芯片自动分配Flash(0x08000000起始)和SRAM。
【C/C++】选项卡
这是最容易出错的地方!
添加宏定义(Define)
在“Define”栏中输入:
USE_HAL_DRIVER,STM32F103xB❗解释:
-USE_HAL_DRIVER:启用HAL库条件编译
-STM32F103xB:表示中等容量产品线(64KB Flash),必须与芯片匹配!
如果不加这两个宏,编译器就不会包含HAL相关的实现代码,导致大量“undefined”错误。
添加头文件路径(Include Paths)
点击右侧“…”按钮,添加以下路径:
.\Inc .\Core .\Drivers\CMSIS\Include .\Drivers\CMSIS\Device\ST\STM32F1xx\Include .\Drivers\STM32F1xx_HAL_Driver\Inc确保每一级头文件都能被找到,尤其是stm32f1xx.h和core_cm3.h。
【Output】选项卡
勾选:
- ✅ Create HEX File
生成可用于ISP烧录的HEX文件
- ✅ Create Debug Information
保留调试符号,方便后续调试
【Debug】选项卡
选择调试器类型,例如:
-Use: ST-Link Debugger
点击右侧“Settings”,进入调试设置:
- 在“Flash Download”页,勾选:
- ✅ Program
- ✅ Verify
(确保程序正确写入并校验)
还可以在这里选择是否“Reset and Run”,即下载完成后自动运行程序。
第五步:编写最简主程序验证工程
现在工程框架已经搭好,来写一段最简单的代码测试是否能正常工作。
main.c 示例代码
#include "main.h" // 假设LED连接在PA5(常见蓝丸板载LED) #define LED_PIN GPIO_PIN_5 #define LED_GPIO_PORT GPIOA void SystemClock_Config(void); int main(void) { // 初始化HAL库(必须第一步调用) HAL_Init(); // 配置系统时钟为主频72MHz(HSE=8MHz, PLL×9) SystemClock_Config(); // GPIO初始化 __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef gpio = {0}; gpio.Pin = LED_PIN; gpio.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出 gpio.Pull = GPIO_NOPULL; gpio.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(LED_GPIO_PORT, &gpio); while (1) { HAL_GPIO_TogglePin(LED_GPIO_PORT, LED_PIN); HAL_Delay(500); // 半秒闪烁一次 } }main.h
#ifndef __MAIN_H #define __MAIN_H #include "stm32f1xx_hal.h" void SystemClock_Config(void); #endifsystem_clock_config.c(可由CubeMX生成,也可手写)
这部分代码用于配置PLL倍频使主频达到72MHz,典型如下:
void SystemClock_Config(void) { RCC_OscInitTypeDef osc_init = {0}; RCC_ClkInitTypeDef clk_init = {0}; /** Initializes the RCC Oscillators */ osc_init.OscillatorType = RCC_OSCILLATORTYPE_HSE; osc_init.HSEState = RCC_HSE_ON; osc_init.HSIState = RCC_HSI_OFF; osc_init.PLL.PLLState = RCC_PLL_ON; osc_init.PLL.PLLSource = RCC_PLLSOURCE_HSE; osc_init.PLL.PLLMUL = RCC_PLL_MUL9; // 8MHz × 9 = 72MHz if (HAL_RCC_OscConfig(&osc_init) != HAL_OK) { while (1); } /** Initializes the CPU, AHB and APB buses clocks */ clk_init.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; clk_init.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; clk_init.AHBCLKDivider = RCC_SYSCLK_DIV1; clk_init.APB1CLKDivider = RCC_HCLK_DIV2; clk_init.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&clk_init, FLASH_LATENCY_2) != HAL_OK) { while (1); } }编译 & 下载:见证成果时刻!
点击顶部的“Build”按钮(小锤子图标),等待编译结果。
如果一切顺利,你会看到:
".\Output\STM32_Minimal.axf" - 0 Error(s), 0 Warning(s).接着点击“Download”(向下箭头图标),Keil会通过ST-Link将程序烧录进MCU的Flash中。
如果你的板子上有LED,并且接线正确,应该能看到它开始以500ms周期闪烁!
🎉 恭喜你,第一个完整的Keil5工程成功运行!
常见问题排查清单
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
编译时报undefined symbol | 未添加HAL源文件或宏未定义 | 检查是否加入了stm32f1xx_hal.c并正确定义USE_HAL_DRIVER |
| 程序无法下载 | SWD接线错误 / 调试器未识别 | 检查VCC、GND、SWCLK、SWDIO是否连通;尝试更新ST-Link固件 |
| LED不亮 | 引脚配置错误或时钟未启用 | 使用__HAL_RCC_GPIOx_CLK_ENABLE()启用对应GPIO时钟 |
HAL_Delay()不准 | 未调用SystemClock_Config() | 确保在HAL_Init()后立即配置系统主频 |
| 程序跑飞 | 启动文件与Flash大小不符 | 更换为startup_stm32f103xb.s(非xc/zd版本) |
工程结构最佳实践(团队协作推荐)
为了提升可维护性和移植性,建议采用以下目录规范:
Project/ ├── Project/ # Keil工程文件 │ └── STM32_Minimal.uvprojx ├── Src/ │ ├── main.c │ ├── system_clock_config.c │ └── stm32f1xx_it.c ├── Inc/ │ └── main.h ├── Core/ │ ├── startup_stm32f103xb.s │ └── system_stm32f1xx.c ├── Drivers/ │ └── STM32F1xx_HAL_Driver/ │ ├── Inc/ │ └── Src/ └── Output/ ├── Objects/ └── Lists/💡 提示:将
.uvoptx和.uvgui文件加入.gitignore,避免提交个人调试配置。
总结:掌握本质,才能灵活应对
通过本次实战,你应该已经明白:
- Keil5工程的本质是一组配置 + 一组源文件的集合;
- CMSIS启动文件决定了程序如何开始;
- 宏定义和头文件路径是编译成功的钥匙;
- HAL库初始化顺序必须严格遵守;
- 合理的工程结构是长期维护的基础。
这套流程不仅适用于F1系列,稍作修改即可用于F4、G0、L4等其他STM32平台。
下次当你再问“keil5怎么创建新工程”时,希望你能自信地说:我知道每一步背后发生了什么。
如果你正在准备嵌入式面试,或者想建立自己的通用模板,不妨把这篇文章对应的工程打包存档,未来直接复用,效率翻倍。
如果你在搭建过程中遇到了其他问题,欢迎在评论区留言讨论,我们一起解决实际工程难题。