文章目录
-
- [1. Project (工程文件目录)](#1. Project (工程文件目录))
- [2. Libraries (固件库目录)](#2. Libraries (固件库目录))
- [3. User (用户代码目录)](#3. User (用户代码目录))
- [4. Output (输出结果目录)](#4. Output (输出结果目录))
- [5. Listing (列表清单目录)](#5. Listing (列表清单目录))
- 手动搭建
-
- 第一步:建立文件夹结构
- 第二步:导入官方库文件 (Libraries)
- [第三步:在 Keil 中新建工程 (Project)](#第三步:在 Keil 中新建工程 (Project))
- [第四步:魔术棒 (Options for Target) 核心配置](#第四步:魔术棒 (Options for Target) 核心配置)
- [第五步:编写 main.c 并编译](#第五步:编写 main.c 并编译)
- 常见错误
-
- [1. 为什么报错?](#1. 为什么报错?)
- [2. 怎么解决?(三步修正法)](#2. 怎么解决?(三步修正法))
-
- 第一步:切换正确的下载器类型
- 第二步:确认硬件连接成功
- [第三步:配置 Flash 下载](#第三步:配置 Flash 下载)
- [3. 排查小清单](#3. 排查小清单)
- 问题2:
野火(FireBull)以及大多数基于 Keil MVD 环境的 STM32 教程,采用这种文件夹结构主要是为了 实现"工程模块化" 。这种分类方式能让代码、库文件和编译生成物互不干扰,方便移植和维护。
以下是各个文件夹及其内部核心文件的详细作用说明:
1. Project (工程文件目录)
这是整个工程的"大本营",存放的是与 Keil 软件本身相关的文件。
- 作用:保存工程的配置信息,如芯片型号、调试器设置、编译选项等。
- 核心文件 :
*.uvprojx:最重要的文件,双击它即可打开整个工程。*.uvoptx:保存你的个人配置(如断点、窗口布局)。*.uvguix.*:保存图形界面设置,删除不影响工程编译。
2. Libraries (固件库目录)
这里存放的是 ST 官方提供的 标准外设库 (Standard Peripherals Library)。
- 作用:提供操作寄存器的函数接口,让你不需要直接背寄存器地址。
- 包含内容 :
CMSIS:内核支持层。包含core_cm3.c(内核功能)stm32f10x.h和system_stm32f10x.c(时钟配置)以及startup文件夹存放启动文件。startup_stm32f10x_hd.s:汇编启动文件(非常关键,负责初始化堆栈、向量表并跳转到 main)。
FWlib固件库(FW的单词是Firmware,即固件的意思。)inc文件夹:存放.h头文件(如stm32f10x_gpio.h)。src文件夹:存放.c源文件(如stm32f10x_gpio.c)。
3. User (用户代码目录)
这是你编写逻辑代码的地方,也是开发过程中最常改动的文件夹。
- 作用:存放用户业务逻辑、中断处理函数以及配置文件。
- 核心文件 :
main.c:程序的入口主函数。stm32f10x_it.c / .h:专门存放中断服务函数(Interrupt Service Routines)。stm32f10x_conf.h:库配置头文件,用于开关各个外设库的编译。- 自己写的驱动文件夹,例如
bsp文件夹
4. Output (输出结果目录)
这里存放的是编译器(Compiler)和链接器(Linker)运行后生成的二进制数据。
- 作用:存放编译产生的中间文件和最终可执行文件。
- 核心文件 :
*.hex / *.bin:最终烧录到单片机里的文件。*.obj:每个.c文件编译后生成的二进制目标文件。*.axf:包含调试信息的文件,仿真调试时需要它。
5. Listing (列表清单目录)
这里存放的是编译过程中的统计信息和辅助说明文件。
- 作用:用于查看代码占用的内存大小、汇编对照等,方便优化代码。
- 核心文件 :
*.map:非常有用。它详细列出了每个函数占用的 Flash 和 RAM 空间大小,以及堆栈的分配情况。*.lst:C 语言对应的汇编代码清单。
总结对照表
| 文件夹 | 属性 | 修改频率 | 建议 |
|---|---|---|---|
| User | 自己的代码 | 极高 | 逻辑代码全部放这里 |
| Libraries | 官方的库 | 极低 | 不要轻易修改里面的源码 |
| Project | 环境配置 | 低 | 备份工程时,这个文件夹是核心 |
| Output | 自动生成 | 无需手动修改 | 空间不够时可以清空,重新编译会生成 |
| Listing | 编译报告 | 仅查看 | 排查内存溢出(Stack Overflow)时看 map 文件 |
手动搭建
手动搭建一个标准的野火风格 STM32 工程模板,是深入理解单片机底层逻辑的必经之路。你可以按照以下 5 个核心步骤 进行:
第一步:建立文件夹结构
在电脑硬盘上新建一个工程根文件夹(如 Template),在里面手动创建以下文件夹:
Project:存放 Keil 工程文件。Libraries:存放 ST 标准库文件。User:存放用户代码。Output:存放编译生成的中间文件。Listing:存放编译清单文件。
第二步:导入官方库文件 (Libraries)
你需要从 ST 官网或野火提供的资料包中拷贝固件库文件:
- 将库中的
CMSIS文件夹(包含内核支持文件)拷贝到Libraries。 - 将库中的
STM32F10x_StdPeriph_Driver文件夹(包含inc和src)拷贝到Libraries并重命名FWlib。 - 关键: 在
User文件夹中放入三个必要文件:main.c(自己新建)stm32f10x_it.c/.h(从库例程中拷贝)stm32f10x_conf.h(从库例程中拷贝,用于配置外设开关)- 自己编写的驱动,新建文件夹(例如bsp文件夹)
第三步:在 Keil 中新建工程 (Project)
- 打开 Keil5,点击 Project -> New uVision Project ,保存路径选择刚才创建的
Project文件夹。 - 在弹出的设备选择框中,搜索并选择
STM32F103VE。 - Manage Project Items (三个色块小图标) :
- 在 Project Targets 里新建组(Groups),命名为
STARTUP、LIBRARY、USER、CMSIS。 - 往各组添加文件:
STARTUP: 添加startup_stm32f10x_hd.s(汇编启动文件)。FWLB: 添加Libraries/FWlib/src下的所有.c文件。USER: 添加User文件夹下的所有.c文件。CMSIS: 添加core_cm3.c和system_stm32f10x.c以及对应的头文件。
- 在 Project Targets 里新建组(Groups),命名为
第四步:魔术棒 (Options for Target) 核心配置
这是最容易出错的一步,点击"魔术棒"图标进行设置:
- Output 选项卡 :点击
Select Folder for Objects,选择你创建的Output文件夹。勾选Create HEX File。 - Listing 选项卡 :点击
Select Folder for Listings,选择你创建的Listing文件夹。 - C/C++ 选项卡 (极其重要) :
- Define : 输入
STM32F10X_HD, USE_STDPERIPH_DRIVER(告诉编译器芯片容量和使用标准库)。 - Include Paths : 点击右侧三个点,把以下路径都添加进去:
..\Libraries\CMSIS..\Libraries\FWlib\inc..\User
- Define : 输入
第五步:编写 main.c 并编译
在 main.c 中写一个最简单的死循环:
c
#include "stm32f10x.h"
int main(void) {
while(1) {
// 等待编写 GPIO 控制代码
}
}
点击 Build (F7) 。如果下方显示 0 Error(s), 0 Warning(s),说明模板搭建成功了!
💡 一个进阶小技巧
在 User 文件夹里,通常建议再建一个名为 bsp (Board Support Package) 的子文件夹,专门用来放你自己写的驱动(比如 bsp_led.c, bsp_usart.c),这样你的 main.c 会变得非常清爽。
常见错误

这就好比你已经写好了一封信(代码编译成功),但邮递员(下载器)找不到邮筒 ,或者你叫错了邮递员的名字 。
这个错误的原因非常直接:Keil 默认设置的下载器型号和你手上实际使用的不匹配。
1. 为什么报错?
从图片看,Keil 正在尝试寻找一个叫 ULINK2/ME 的下载器,但它没找到。
- 野火的 STM32 开发板通常配套的是 DAP-Link (最常见)或者 ST-Link 、J-Link。
- 如果你用的是野火自带的那根彩色排线的下载器,它大概率是 CMSIS-DAP(即 DAP-Link)。
2. 怎么解决?(三步修正法)
第一步:切换正确的下载器类型
- 点击"魔术棒"图标(Options for Target)。
- 切换到 Debug 选项卡。
- 在右上角的下拉框中,把
ULINK2/ME Cortex-M Driver改选为CMSIS-DAP Debugger(如果你用的是 DAP-Link)或者ST-Link Debugger。
第二步:确认硬件连接成功
- 选好
CMSIS-DAP Debugger后,点击它旁边的 Settings 按钮。 - 在弹出的窗口中,看 SW Device 那个框里是否出现了一串 16 进制的代码和芯片名称(如
0x1BA01477 ARM CoreSight SW-DP)。- 如果有:说明电脑识别到了单片机,连接成功。
- 如果是空白:检查你的下载器和开发板连接线是否插反了,或者开发板是否没上电。
第三步:配置 Flash 下载
- 在刚才的 Settings 窗口里,切换到 Flash Download 选项卡。
- 勾选 Reset and Run(这样下完程序后它会自动运行,不用你按复位键)。
- 点击 OK 退出。
3. 排查小清单
如果改了设置还不行,请检查:
- USB 线:下载器是否已经插在电脑上,驱动是否安装(通常 DAP-Link 免驱)。
- 电源:开发板上的电源开关开了吗?
- 引脚 :下载器的排线是否对应插在了板子的
SWD接口上?
问题2:
c
/* 直接操作寄存器的方法控制IO */
#define digitalHi(p,i) {p->BSRR=i;} //输出为高电平
#define digitalLo(p,i) {p->BRR=i;} //输出低电平
#define digitalToggle(p,i) {p->ODR ^=i;} //输出反转状态
这个对ODR寄存器进行异或操作,无非是如果i是0x20 也就是第6位是1,这里和其进行异或操作,如果通过前面的BSRR寄存器,将第6位设置了高电平,也就是1,这个ODR寄存器第6位也会同步更新吗?BSRR和BRR寄存器对第6位的更改,都会同步更新到ODR吗?
核心逻辑:谁才是真正的"状态持有者"?在 STM32 的 GPIO 硬件电路中,ODR (Output Data Register) 才是那个真正连接到输出驱动器、决定引脚高低的"核心寄存器"。ODR (输出数据寄存器):它是可读可写的。它的每一位直接对应引脚的状态。如果你直接往 ODR 写 1 或 0,引脚电平就变。BSRR / BRR (设置/清除寄存器):它们是只写的。它们更像是 ODR 的"远程遥控器"。当你向 BSRR 的第 n n n 位写 1 时,硬件内部逻辑会触发一个动作,把 ODR 寄存器的第 n n n 位强制设为 1。同理,写 BRR 会把 ODR 对应位强制清 0。