news 2026/11/5 5:35:24

从 F429 说起基于Keil5搭建STM32工程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从 F429 说起基于Keil5搭建STM32工程

本笔记从零讲清一个主要问题:标准库工程怎么搭(以 F429 实操为主线);

- 适用范围:F1 / F2 / F4 / L1系列(官方提供标准库的老系列)
- 不适用:F7 / H7系列——官方未发布 SPL,工程结构直接采用 HAL/LL 库方案

一、主线总览:两个阶段

阶段一:寄存器工程(最小骨架)──先让"裸芯片"能编译 │ 往骨架上加三样东西 ▼ 阶段二:固件库工程(= 寄存器工程 + 外设函数库)

二、工程文件的五大角色

角色

F1(江协 F103 工程)

F4(F429 工程)

作用

① 启动文件

(.s 汇编,唯一的汇编文件)

startup_stm32f10x_md.s

startup_stm32f429_439xx.s

上电第一段代码:建堆栈、中断向量表、跳到 main。必须选对:

  • F1 按容量分(md=中容量/hd=大容量…)
  • F4 按具体型号分(_429_439xx)

② CMSIS 内核文件

(ARM 写的,任何公司的 Cortex-M 芯片通用)

core_cm3.c

+core_cm3.h

core_cm4.h

+core_cmFunc.h

+core_cmInstr.h

+core_cmSimd.h

定义 NVIC/SysTick 等内核寄存器访问。可以理解为F4是F1的core_cm3.c拆开成立多个.h文件

F4 没有 core_cm4.c:老版 CMSIS(1.3 时代)把 SysTick_Config 等放 .c 里;新版(3/4 起)全改成 static inline 放 .h 里,不再需要 .c

③ 设备层文件

(ST 写的)

stm32f10x.h

、system_stm32f10x.c/.h

stm32f4xx.h

、system_stm32f4xx.c/.h

定义该芯片所有外设寄存器地址、中断号;SystemInit()配时钟

system_xxx.c是时钟初始化

④ 外设库驱动

stm32f10x_gpio.c/.h

、misc.c等

stm32f4xx_gpio.c/.h

、misc.c等

GPIO_Init()、USART_SendData() 这些库函数本体。

⑤ 用户/配置文件

main.c

、stm32f10x_it.c/h

、stm32f10x_conf.h

main.c

、stm32f4xx_it.c/h

、stm32f4xx_conf.h

用户代码 +中断服务函数 +xxx_conf.h

(库裁剪总开关,决定包含哪些外设头文件)

阶段一:寄存器工程

1.下载标准外设固件库

首先根据芯片型号从[STM社区](https://www.stmcu.org.cn/)下载标准外设固件库

2.创建文件夹(此处以F429IGTx为例)

在工作根目录下创建文件夹(这个文件的分类根据个人喜好来)

  1. lib_start
  2. user

在固件库压缩包解压得到的STM32F4xx_DSP_StdPeriph_Lib_V1.9.0文件夹中拷贝启动文件、核心文件和设备文件到文件夹中

这里简单介绍一下,Libraries\CMSIS文件夹下是CMSIS设备文件、启动文件和核心文件的源路径,需要的文件都可以从这个目录下找到。而如果需要构建固件库工程,则需要从Libraries\STM32F4xx_StdPeriph_Driver中拷贝外设驱动文件,并且从Project\STM32F4xx_StdPeriph_Templates模板工程中拷贝配置文件(stmxxx_conf.h)和中断文件(stmxxx_it.c/.h)。

  1. ...\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Libraries\CMSIS\Device\ST\STM32F4xx\Source\Templates\arm找到对应的启动文件startup_stm32f429_439xx.s放到lib_start中

  1. ...\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Libraries\CMSIS\Device\ST\STM32F4xx\Source\Templates找到设备文件system_stm32f4xx.c放到lib_start中
  2. ...\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Libraries\CMSIS\Device\ST\STM32F4xx\Include找到设备文件system_stm32f4xx.h、stm32f4xx.h放到lib_start中
  3. ...\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Libraries\CMSIS\Include找到核心文件,放到lib_start中

其中arm_math.h、arm_common_tables.h、arm_const_structs.h【CMSIS-DSP 数字信号处理库的头文件,只有做 FFT/滤波等算法才需要】

core_sc000.h、core_sc300.h【SecurityCore(SC000/SC300)芯片用的,与 F429 无关】

3.打开Keil新建工程

选择刚刚整理好文件的工作目录

(1)新建分组

新建两个分组lib_start和user(我的个人习惯是分组和实际路径一致)

将lib_start文件夹下的所有文件加入到lib_start分组中,点击ok

(2)添加路径

将新建的文件夹的路径都添加进来

(3)编译测试

在user分组下,新建main.c文件(注意添加的时候路径要选择到user文件夹下)

然后右击鼠标,点击insert加入头文件。之后写一个main函数的框架,最后空一行,再编译,可见编译通过0 error(s)

补充说明

1. 问题一

如果出现编译没有通过,并且出现“unknown register name 'vfpcc' in asm”这样的问题,则与编译器有关。

./libraries/startup\core_cmFunc.h(629): error: unknown register name 'vfpcc' in asm

629 | __ASM volatile ("VMSR fpscr, %0" : : "r" (fpscr) : "vfpcc");

原因:libraries/startup 里这套 CMSIS 头文件未支持AC6(armclang V6.24)——armclang 因为基于 Clang,会同时定义 __GNUC__,于是该头文件走进了 GCC 分支,而这个分支里的内联汇编把 "vfpcc" 当 clobber 名使用,armclang 不认识这个寄存器名,直接报错。

由于每个 .c 都包含了 core_cm4.h → core_cmFunc.h,所以几乎每个编译单元都报这同一个错(这就是为什么报错数量那么多)。

修复方法:

1.(推荐,最简单)打开 libraries\startup\core_cmFunc.h,定位到第 629 行,把:

__ASM volatile ("VMSR fpscr, %0" : : "r" (fpscr) : "vfpcc");

改成:

__ASM volatile ("VMSR fpscr, %0" : : "r" (fpscr) : "memory");
  1. 升级 CMSIS 头文件:用新版 CMSIS的 core_cm4.h / core_cmFunc.h / core_cmInstr.h 替换 libraries/startup 里的旧版本(新版对 AC6 有专门的 __ARMCC_VERSION 分支)。工程上更正规,但改动较大。
  2. 换 AC5 编译器:Options → Target → ARM Compiler 选 “Use default compiler version 5”。

阶段二:固件库工程

1.新建文件夹与拷贝

在寄存器工程的基础上,再添加上配置文件和外设驱动文件即可搭建好一个固件库工程。

在工作根目录下创建一个新的文件夹lib_driver

  1. 从\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Libraries\STM32F4xx_StdPeriph_Driver将inc和src两个文件夹内的文件全部拷贝到lib_driver下
  2. 从\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Project\STM32F4xx_StdPeriph_Templates将stm32f4xx_it.cstm32f4xx_it.h、stm32f4xx_conf.h拷贝到user目录下/工作根目录(个人习惯)

  1. 从\STM32F4xx_DSP_StdPeriph_Lib_V1.9.0\Project\STM32F4xx_StdPeriph_Templates将main.c和main.h拷贝到user目录下,毕竟Templates本身就是模板工程目录。

如果不拷贝则需要做适当的裁剪

2.文件过滤

此时,还需要对lib_driver中的文件做一个过滤,因为其中部分文件并不是F429的外设驱动文件(429没有这些外设),而是其他F4xx系列的如F407的。

  1. 必须删的:必须删:stm32f4xx_fsmc.c、stm32f4xx_fsmc.h
  2. 可以删:fmpi2c / dfsdm / dsi / spdifrx / qspi【429没有这些外设】

这些文件的#ifdef保护完好,F429 宏下内容被整体跳过,不报错、不占 Flash(链接器只把被调用的函数拉进固件),删除只是编译快几秒、日志干净,所以不是必须删,但是建议删去。

3.新建分组与添加路径、宏定义

在keil中添加lib_driver分组,并将剩余的文件加到相应分组

分组

路径

宏定义

添加宏定义:USE_STDPERIPH_DRIVER或者STM32F429_439xx,USE_STDPERIPH_DRIVER(用英文逗号分隔,不要加分号、不要换行)

其中STM32F429_439xx可以省略但是建议添加上。可以省略的原因是,当你在 Device 选项卡选了具体芯片(如 STM32F103C8),Keil 会自动在编译命令行里注入对应的型号宏,而DFP 自动加的宏不会显示在 Define 框里。

说明:添加宏定义在stm32f4xx.h中有代码印证【原因】

编译,通过。

如果不加宏定义就会出现如下错误

如果选择不拷贝模板工程的main.c/.h

1.问题
user/stm32f4xx_it.c(137): error: call to undeclared function 'TimingDelay_Decrement'; ISO C99 and later do not support implicit function declarations 137 | TimingDelay_Decrement();

含义

在 stm32f4xx_it.c(中断服务函数文件)第 137 行调用了函数 TimingDelay_Decrement(),但编译器在这个文件里找不到该函数的声明。

  • 在 C99 及之后的标准中,调用未声明的函数属于错误(旧 C89 只是警告,会隐式声明为 int func())。
  • AC6 默认遵循较新的 C 标准,所以直接报 error,导致编译中断。

原因分析

TimingDelay_Decrement()通常出现在 ST 官方例程的main.c中,配合 SysTick 中断使用:

报错说明工程里可能存在以下情况之一:

  1. 该函数只写在了main.c里,没有在main.h或其他头文件中声明,stm32f4xx_it.c包含的头文件里看不到它;
  2. 原例程中main.c有这个函数,但当前main.c被改写/精简,函数体删掉了,却忘了同步修改stm32f4xx_it.c;
  3. 函数声明所在头文件没有加进stm32f4xx_it.c的#include列表。

解决方法(任选其一)

方案

做法

① 补声明(推荐)

在main.h中添加:void TimingDelay_Decrement(void);,并确保stm32f4xx_it.c中#include "main.h"

② 直接声明

在stm32f4xx_it.c文件顶部(函数定义之前)加上extern void TimingDelay_Decrement(void);

③ 修改实现位置

把TimingDelay_Decrement()的实现和声明移到一个公共模块(如delay.c/delay.h),两边都包含该头文件

④ 删除调用

如果不再需要基于 SysTick 的延时,把SysTick_Handler()中第 137 行的调用删掉或替换为新工程的时基函数(如 HAL 的HAL_IncTick())

⚠️ 注意:如果选择删除,还要确认main.c中的Delay()/SysTick 配置代码是否需要同步调整,否则程序运行时延时功能会失效。

2.main.c/.h模板与线索

官方的模板文件中,在main.c文件中定义了TimingDelay_Decrement,并在main.h中声明。

该函数在stm32f4xx_it.c文件中被调用。

自己建 main.c 的正确做法【参考AI】

完全从零手写 main.c + main.h(理解原理后再用)

main.h
#ifndef __MAIN_H #define __MAIN_H #include "stm32f4xx.h" /* 芯片寄存器/外设定义,几乎所有文件都要 */ /* 需要被其他文件调用的函数,都在这里声明 */ void TimingDelay_Decrement(void); void Delay(uint32_t nTime); #endif
main.c
#include "main.h" static __IO uint32_t TimingDelay; /* SysTick 递减计数 */ void Delay(uint32_t nTime) { TimingDelay = nTime; while (TimingDelay != 0); /* 忙等 */ } void TimingDelay_Decrement(void) /* 由 SysTick 中断每 1ms 调一次 */ { if (TimingDelay != 0) TimingDelay--; } int main(void) { /* 1. 配置 SysTick = 1ms(模板 system_stm32f4xx.c 已把时钟配到 168MHz) */ if (SysTick_Config(SystemCoreClock / 1000)) while (1); /* 配置失败则死循环 */ while (1) { /* 你的代码写在这里 */ } }

同时检查stm32f4xx_it.c:

#include "stm32f4xx_it.h" #include "main.h" /* ← 确保有这一行,否则看不到声明 */

关键三步缺一不可:

  • 定义在 main.c;
  • 声明在 main.h;
  • 调用方(stm32f4xx_it.c)必须#include "main.h"。

另外新建文件后还要确认:文件已添加进 Keil 工程分组(右键 Group → Add Existing Files),且 main.h 所在目录在Options for Target → C/C++ → Include Paths里(否则#include "main.h"找不到文件)。

不用中断延时,最简版(连 main.h 都不要)

绕开 SysTick,把stm32f4xx_it.c里的TimingDelay_Decrement()调用注释掉(或换成吧相关程序移到其他文件中)

main.c

比如点个灯跑通流程

#include "stm32f4xx.h" int main(void) { RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN; /* 使能 GPIOE 时钟 */ GPIOE->MODER |= (1 << (13*2)); /* PE13 输出 */ while (1) { GPIOE->ODR ^= (1 << 13); for (volatile uint32_t i = 0; i < 2000000; i++); /* 粗略延时 */ } }

这种单文件、无跨文件调用的情况下,main.h 完全可以不要。

STM32 标准库(SPL)工程配置补充说明

适用范围:F1 / F2 / F4 / L1系列(官方提供标准库的老系列)
不适用:F7 / H7系列——官方未发布 SPL,工程结构直接采用 HAL/LL 库方案

要点速查表

配置项正确做法常见误区
头文件入 Groups不需要误以为不加入就编译不到
多套头文件并存自备路径排到 Include Paths最前混用 DFP 版本导致宏冲突
模板适用系列F1 / F2 / F4 / L1(有 SPL)F7 / H7 无 SPL,不能用此结构
子目录搜索每个子目录逐条添加以为-I会递归下钻

一、头文件是否需要加入 Groups?

不需要,加不加都不影响编译。

结论:core_cm4.h、stm32f4xx_gpio.h等头文件无需添加进工程分组,只需保证Include Paths 配置正确,编译器能找到它们即可。

项目说明
Keil 实际编译的文件仅.c/.s/.cpp
.h文件的角色只在预处理阶段被#include展开,不参与独立编译
是否要拖进 Groups不需要。留着无害,但纯属“摆设”

二、头文件版本冲突隐患(重要)

问题描述

工程中可能同时存在两套设备头文件:

  • ..\libraries\startup\stm32f4xx.h—— 自备版本(2022/2/11)
  • KeilDFP(Device Family Pack)自带版本

两套版本若混用,可能出现宏定义不一致、莫名警告等问题。

解决方案

在Options → C/C++ → Include Paths中,把

..\libraries\startup

调整到列表最前面(选中后点击↑箭头上移)。

原理:编译器按 Include Paths 的排列顺序依次搜索头文件,排在前面的路径优先命中。将自己文件夹置顶,即可确保优先使用自备的整套 CMSIS / 设备头文件,避免版本错配。

三、Include Paths 是否支持递归搜索子目录?

不支持。

  • Keil 的头文件搜索路径不递归:-I参数只在指定的那一层文件夹内查找,不会自动下钻子目录。
  • 这一点与GCC 的-I行为一致——同样是“指定哪层,就只搜哪层”。

结论:不能只写一个lib就指望它自动找到lib/a、lib/b。每个包含头文件的子目录,都必须逐条显式写入 Include Paths。

# 错误示范(递归不生效): ..\lib # 正确写法(逐一列出): ..\lib ..\lib\a ..\lib\b

参考文章

正点原子STM32F429阿波罗板,固件库工程搭建_正点原子阿波罗-CSDN博客

STM32工程创建(STM32F407)_stsw-stm32065-CSDN博客

STM32系列官方标准固件库的完整下载流程_stm32固件库下载-CSDN博客

STM32F429入门(八):初识固件库_stm32f429固件库-CSDN博客

STM32F429ZI Discovery板级支持包(BSP)深度解析-CSDN博客

No.1 STM32F429IGT6开发板简介 (STM32F429/F767/H743)_stm32f429igt6中文说明-CSDN博客

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/5 5:34:54

光缆色序零配置自动换型 基于 OCR 与几何特征的色谱自匹配方案

1. 多规格混线的模板选错风险 光缆行业的生产模式正在发生结构性变化。过去一条线动辄跑几万公里同一规格&#xff0c;如今订单碎片化&#xff0c;多是小批量、多品种&#xff1a; 上午跑 12 芯标准色谱&#xff08;蓝橙绿棕灰白、红黑黄紫粉青&#xff09;&#xff1b;下午切到…

作者头像 李华
网站建设 2026/11/5 5:34:42

项目工作总结

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/5 5:32:31

智能体“自适应慢思考”的算力黑洞:当 Claude 5.5 与 GPT-6.1 Sol 陷入推理自旋,为什么高并发架构必须重构“认知预算熔断与快慢推测执行”?

作者&#xff1a;一缕82年的清风 定位&#xff1a;【前沿极客情报局】 文章概览&#xff1a;大模型慢思考带来的推理自旋正在引爆生产级高并发系统的尾延迟灾难。本文深入剖析智能体无休止反思与队列饥饿根因&#xff0c;拆解自适应认知预算熔断与异构快慢推测执行架构&#xf…

作者头像 李华
网站建设 2026/11/5 5:30:52

稀土功能纤维的制备原理与功能特性

稀土在很多人印象中&#xff0c;是用于永磁电机、催化剂、激光晶体的工业原料。但近两年&#xff0c;稀土正在以一种更贴近生活的方式进入日常——功能纤维。稀土如何“织”进衣服里稀土功能纤维的制备&#xff0c;并不是简单地把稀土粉末混入纺丝液。以包头稀土研究院发布的“…

作者头像 李华
网站建设 2026/11/5 5:30:10

LLM-as-judge评分器

加权Cohens Kappa系数是专门用于‌有序分类标注场景‌&#xff08;比如1-5分质量打分、严重/一般/轻微/无错误这类有等级顺序的标注&#xff09;的一致性衡量指标&#xff0c;相比普通Cohens Kappa&#xff0c;它会给不同程度的打分分歧赋予不同权重&#xff0c;更贴合标注场景…

作者头像 李华