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。

然后按下面的步骤来:

  1. 创建顶层文件夹:My_Template
  2. 在 My_Template 下创建子文件夹:Inc、Src、Startup、User、Linker、Doc、Output
  3. 检查一下:目录结构应该和我上面列的一模一样

你可能会问:「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 图)

下面这张图帮你把整个结构串起来。看一眼就明白每个文件夹的定位和关系:

STM32 工程目录结构总览 My_Template/ Inc/(头文件) Src/(源文件) Startup/(启动文件) User/(用户代码) Linker/(链接脚本) Doc/(文档) Output/(编译输出) 关键说明 • 启动文件和链接脚本必须与芯片型号严格匹配 • User 文件夹放 main.c 和自定义模块,方便快速定位入口 • Output 文件夹建议加入 .gitignore,避免仓库臃肿

3.6 检查清单:你的文件夹准备好了吗?

做完上面这些,对照这个清单检查一遍:

  • 项目根目录名字没有中文和空格
  • Inc、Src、Startup、User、Linker、Doc、Output 七个文件夹都建好了
  • Startup 文件夹里有正确的启动文件(.s)
  • Linker 文件夹里有对应的链接脚本(.ld)
  • 启动文件和链接脚本的芯片型号一致

都打勾了?好,那你的工程架子就搭好了。下一步咱们往里面填代码——把 main.c 写出来,让板子上的 LED 先闪起来。