3. 开发环境搭建
说实话,我见过太多新手一上来就打开 Keil 点「New Project」,然后所有文件堆在一个目录里。三个月后自己都找不到哪个是哪个。嗯,这活儿我干过,所以今天咱们先把架子搭好。
一个清晰的工程目录,说白了就是给你的代码安个家。每个房间干什么用,提前规划好,后面写代码、找 bug、移植项目,都会省心很多。
3.1 为什么需要规划目录结构?
你可能觉得:「不就是几个 .c 和 .h 文件吗?至于这么讲究?」
我跟你讲,至于。我在一个老项目里见过一个文件夹塞了 200 多个文件,光找 main.c 就花了五分钟。后来重构的时候,我花了整整两天才理清楚哪些文件有用、哪些是废弃的。
一个好的目录结构,能帮你做到三件事:
- 快速定位:头文件在哪、启动文件在哪,一眼就知道
- 方便移植:换芯片型号时,只需要替换启动文件和链接脚本
- 团队协作:同事接手你的项目,不用问「这个文件放哪了」
核心原则:按功能分类,而不是按文件类型。把「干什么用的」和「放哪的」对应起来。
3.2 标准目录结构长什么样?
我个人习惯用这套结构,在 STM32 官方例程的基础上做了精简。你拿过去就能用:
Project_Name/
├── Inc/ # 头文件(.h)
├── Src/ # 源文件(.c)
├── Startup/ # 启动文件(.s)
├── User/ # 用户主程序(main.c + 自定义模块)
├── Linker/ # 链接脚本(.ld 或 .sct)
├── Doc/ # 文档(可选)
└── Output/ # 编译输出(.hex, .bin, .map)
每个文件夹的职责很清晰:
| 文件夹 | 放什么 | 我踩过的坑 |
|---|---|---|
| Inc | 所有 .h 头文件,包括外设驱动、系统配置 | 有人把 .c 也塞进来,结果编译时链接器找不到定义 |
| Src | 所有 .c 源文件,除了 main.c | main.c 单独放 User 里,方便快速定位入口 |
| Startup | 汇编启动文件 startup_stm32xxx.s | 不同芯片型号的启动文件不能混用,我吃过亏 |
| User | main.c 和你自己写的业务逻辑模块 | 建议把硬件抽象层也放这里,方便复用 |
| Linker | 链接脚本,比如 STM32F103C8T6_FLASH.ld | 链接脚本错了,程序跑不起来,而且很难排查 |
| Output | 编译生成的 .hex、.bin、.map 文件 | 记得加到 .gitignore 里,不然仓库会变得很臃肿 |
小技巧:Output 文件夹我习惯在 Keil 里设置成临时目录,每次编译自动清空。这样不会把旧文件和新文件搞混。
3.3 从零开始创建文件夹
好,咱们动手。打开你的工作目录,比如 D:\STM32_Projects\,新建一个项目文件夹,比如 My_Template。
然后按下面的步骤来:
- 创建顶层文件夹:
My_Template - 在 My_Template 下创建子文件夹:
Inc、Src、Startup、User、Linker、Doc、Output - 检查一下:目录结构应该和我上面列的一模一样
你可能会问:「Doc 文件夹有必要吗?」
我的建议是:有。哪怕只放一个 README.md,写上这个工程是干什么的、用的哪个芯片、库版本是多少。三个月后你回头看,会感谢现在的自己。
注意:文件夹名字不要用中文,不要带空格。Keil 对中文路径的支持不太好,我曾经因为路径里有中文,编译报了一堆莫名其妙的错误。
3.4 复制必要的启动文件和链接脚本
这是整个模板里最关键的一步。启动文件和链接脚本,决定了你的芯片能不能正常跑起来。
3.4.1 启动文件(Startup)
启动文件是汇编写的,负责三件事:
- 设置堆栈指针
- 初始化中断向量表
- 调用 SystemInit 和 main 函数
不同型号的 STM32,启动文件不一样。以最常用的 STM32F103C8T6 为例:
- 文件名叫
startup_stm32f10x_md.s(MD 表示中等容量) - 从 ST 官方固件库的
Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\arm目录下复制 - 粘贴到你的
Startup文件夹里
我刚开始做项目时,直接复制了同事的启动文件,结果芯片是 STM32F103VET6(高容量),启动文件却是 md 的。程序能下载,但一运行就进 HardFault。查了两天才发现是启动文件不匹配。
避坑指南:启动文件的选择规则很简单——看芯片的 Flash 容量。小于 64KB 用 ld(低容量),64KB~128KB 用 md(中等容量),大于 256KB 用 hd(高容量)。
3.4.2 链接脚本(Linker)
链接脚本告诉编译器:你的代码该放在 Flash 的哪个地址,变量该放在 RAM 的哪个地址。
对于 STM32F103C8T6:
- Flash 起始地址:0x08000000,大小 64KB
- RAM 起始地址:0x20000000,大小 20KB
链接脚本可以从这几个地方获取:
- STM32CubeMX 生成的工程里,
STM32F103C8Tx_FLASH.ld - 官方固件库的
Project\STM32F10x_StdPeriph_Template目录下 - 或者自己写一个(我建议新手先用现成的)
复制到你的 Linker 文件夹里。注意文件名要和你用的芯片对应,比如 STM32F103C8T6_FLASH.ld。
/* 一个典型的链接脚本片段 */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
}
这段代码定义了存储器的布局。如果你换了芯片,比如换成 STM32F103VET6(Flash 512KB,RAM 64KB),只需要改 LENGTH 的值就行。
我的习惯:链接脚本我会保留一份原始版本在 Linker 文件夹里,然后在 Keil 里引用它。这样换芯片时,只需要替换这一个文件,不用改工程配置。
3.5 目录结构总览(SVG 图)
下面这张图帮你把整个结构串起来。看一眼就明白每个文件夹的定位和关系:
3.6 检查清单:你的文件夹准备好了吗?
做完上面这些,对照这个清单检查一遍:
- 项目根目录名字没有中文和空格
- Inc、Src、Startup、User、Linker、Doc、Output 七个文件夹都建好了
- Startup 文件夹里有正确的启动文件(.s)
- Linker 文件夹里有对应的链接脚本(.ld)
- 启动文件和链接脚本的芯片型号一致
都打勾了?好,那你的工程架子就搭好了。下一步咱们往里面填代码——把 main.c 写出来,让板子上的 LED 先闪起来。