1. 环境准备:你的第一块STM32开发板与Keil 5

你好,我是老张,一个在嵌入式圈子里摸爬滚打了十多年的“老油条”。今天咱们不聊那些高深的理论,就实实在在地,手把手带你从零开始,把STM32F401CCU6的固件库给“搬”到你的Keil 5工程里。我知道,对于很多刚接触STM32的朋友来说,光是看到“固件库移植”这几个字就有点发怵,感觉要面对一堆看不懂的文件夹和复杂的配置。别担心,我当年也是这么过来的,踩过的坑、走过的弯路,今天都给你捋得明明白白。只要你跟着我的步骤走,我保证你能在半小时内,拥有一个干干净净、能直接写代码的工程模板。

咱们先说说你需要准备些什么。硬件上,你得有一块STM32F401CCU6核心的开发板,市面上很多,比如正点原子、野火的基础款都行,价格不贵。软件上,核心就是 Keil MDK-ARM,也就是我们常说的Keil 5。这里有个关键点,Keil 5的版本。我强烈建议你使用 Keil 5.36 或相近的版本。为什么特别强调这个?因为Keil 5后续的高版本(比如5.37之后)默认的编译器换成了ARM Compiler 6(AC6),这个编译器对老的固件库代码兼容性有时会出问题,会报一堆你看不懂的警告和错误,对新手极不友好。用5.36版本,我们默认使用ARM Compiler 5(AC5),能避开很多不必要的麻烦。如果你已经装了更高版本也没关系,后面我会教你怎么把编译器版本改回来。

除了Keil,你还需要三样“原材料”。第一,是 STM32F4的固件库,官方叫做“STM32F4xx_DSP_StdPeriph_Lib”,我们这里用 V1.8.0版本。这个库是ST官方提供的,里面包含了操作芯片所有外设(比如GPIO、串口、定时器)的函数,我们移植的就是它。第二,是 STM32F4系列的器件支持包(Device Family Pack),版本是 2.7.0。这个包相当于Keil 5认识STM32F401CCU6这颗芯片的“驱动程序”,不安装它,你在新建工程时就找不到这个型号。第三,我会在讲解过程中,引导你从零开始新建每一个文件夹和文件,这样你理解会更深刻。当然,如果你想要一个我配置好的、绝对干净的空白工程作为最终对照,也可以在常见的开发者社区找到名为“STM32F401CCU6空白工程”的资源,但那只是用于最终核验的“参考答案”,我们学习的过程必须自己动手。

2. 认识固件库:它到底是个啥?

在开始动手“搬砖”之前,我们得先搞清楚要搬的“砖头”——固件库,到底长什么样,有什么用。你可以把它想象成一个巨大的“工具箱”。ST公司的工程师们已经帮我们把操作STM32芯片内部各种复杂硬件(比如让一个引脚输出高电平、配置串口发送数据、使用定时器计时)的底层代码,写成了一个个现成的函数,打包好放在了这个工具箱里。我们做应用开发,就不用再去研究芯片手册里那些晦涩的寄存器地址了,直接调用工具箱里的函数就行,比如 GPIO_SetBits(GPIOA, GPIO_Pin_0) 就能让PA0引脚输出高电平,这大大降低了开发门槛。

现在,我们去ST官网下载这个“工具箱”,也就是 STM32F4xx_DSP_StdPeriph_Lib_V1.8.0。下载下来解压后,你会看到一个结构清晰的文件夹。别被它的庞大吓到,我们真正需要关心的只是其中一小部分。整个库的根目录下有很多文件夹,对我们最重要的就是 Libraries 文件夹,我们移植的核心内容全在这里面。另外,Project 文件夹里有一些官方提供的工程示例,我们可以“借用”其中的关键文件;Utilities 里是些板级支持包,前期用不上。还有一个非常有用的 _htmresc 文件夹,里面存放着库的帮助文档(通常是.chm格式),这是你日后查询每个函数怎么用的“圣经”,一定要善用。

让我们再深入一层,打开 Libraries 文件夹。里面主要有两个子文件夹:CMSISSTM32F4xx_StdPeriph_Driver。后者好理解,就是标准外设驱动,那些操作GPIO、USART的函数就放在它的 src(源代码)和 inc(头文件)里。那 CMSIS 是干什么的呢?它的全称是“Cortex Microcontroller Software Interface Standard”,你可以理解为ARM公司为所有Cortex-M系列内核芯片制定的一套软件接口标准。它保证了不同芯片厂商(如ST、NXP)的代码,在核心层面(比如中断管理、系统时钟)有一致的写法,提高了代码的可移植性。在 CMSIS 里,我们主要会用到 Device/ST/STM32F4xx 下的芯片特定启动文件、系统初始化代码,以及 Include 下的一些核心头文件。理解了这个结构,你后续的每一步操作就不会是盲目的了。

3. 动手移植:从零搭建你的工程骨架

好了,理论知识准备完毕,现在打开你的电脑,我们开始真正的实战。这个过程就像盖房子,先打好地基,立好柱子。

3.1 创建工程目录结构

首先,在你电脑上一个合适的位置(比如D盘根目录),新建一个文件夹,命名为你的工程,例如 My_STM32F401_Project。这个名字随意,但建议用英文。然后,在这个主文件夹里,再新建四个子文件夹,这是整个工程的骨架:

  • Libraries:用来存放我们从官方固件库里提取出来的“精华”代码。
  • User:这是你的“自留地”,你写的应用程序代码,如 main.c, main.h,以及中断服务函数文件等,都放在这里。
  • Project:专门用来存放Keil 5生成的工程文件(.uvprojx)以及编译过程中产生的输出文件(如.axf, .hex)。
  • Doc:存放项目相关文档,比如你的设计思路、引脚分配表等。这一步看似简单,但养成分类存放的好习惯,会让你的项目管理和团队协作轻松很多。

3.2 填充“工具箱”:复制与精简库文件

现在,我们把官方固件库里的“工具”搬到我们自己的“工具箱”(Libraries文件夹)里。找到你下载的 STM32F4xx_DSP_StdPeriph_Lib_V1.8.0 文件夹,将其下的整个 Libraries 文件夹复制到你刚创建的 My_STM32F401_Project\Libraries 中。注意,是复制源固件库的 Libraries 文件夹内容到目标位置,通常直接拖拽覆盖即可。

复制完后,我们的 Libraries 里应该包含了 CMSISSTM32F4xx_StdPeriph_Driver。为了工程简洁,我们需要做一次“断舍离”。进入 Libraries\CMSIS,你会发现里面可能还有 DSP_Lib, RTOS_API 等文件夹,这些是用于数字信号处理和实时操作系统的库,我们初期学习用不到。果断地,只保留 DeviceInclude 这两个文件夹,其他的全部删除。 这样做的目的是让工程结构更清晰,编译速度也更快。

接下来,处理启动文件。启动文件是用汇编写的,它定义了芯片上电后最先执行的一段代码,负责设置堆栈、初始化内存、然后跳转到我们的 main 函数。它因编译器和芯片型号而异。进入 Libraries\CMSIS\Device\ST\STM32F4xx\Source\Templates\arm 这个路径。你会看到一堆以 .s 结尾的启动文件,比如 startup_stm32f401xx.s。我们用的是Keil(即MDK),所以只需要保留 arm 文件夹下的内容。你可以把 arm 文件夹直接剪切到上一级 Templates 目录下,或者只把里面的 .s 文件复制出来备用。我个人的习惯是在 User 文件夹下单独建一个 Startup 子文件夹来存放这个启动文件,这样更规整。

3.3 借用官方模板:获取核心配置文件

官方固件库贴心地为我们准备了工程模板。现在,打开固件库包中的 Project\STM32F4xx_StdPeriph_Templates 文件夹。这里的文件是我们需要“借用”的。主要复制以下文件到你的 User 文件夹:

  • main.cmain.h:应用程序的主文件。
  • stm32f4xx_it.cstm32f4xx_it.h:中断服务函数文件,所有芯片级的中断处理函数都写在这里。
  • system_stm32f4xx.c:系统初始化文件,包含系统时钟配置函数 SystemInit()
  • stm32f4xx_conf.h这是非常重要的配置文件,它通过宏定义来决定你的工程要包含哪些外设驱动。比如,你只用到了GPIO和USART1,就可以只开启这两个,注释掉其他的,这样编译时就不会把无关的驱动代码包含进来,节省空间和编译时间。

这里有一个新手极易踩坑的地方:官方模板的 main.cstm32f4xx_it.c 里,默认 #include "main.h"。但我们的 main.h 是自己写的,可能和模板的结构不太一样。为了避免编译错误,最稳妥的方法是:在这两个.c文件里,将 #include "main.h" 注释掉或者删除,改为包含我们真正需要的头文件,比如 #include "stm32f4xx.h"。等你的 main.h 写好了,再根据情况决定是否加回来。

4. Keil 5工程配置:让一切运转起来

房子骨架搭好了,现在要通水通电,进行内部装修。这一步是在Keil 5软件里完成的,细节很多,咱们一步一步来。

4.1 创建工程与选择芯片

打开Keil 5,点击 Project -> New uVision Project...。在弹出的窗口里,导航到你之前创建的 Project 文件夹,给工程起个名字,比如 F401_Demo,点击保存。紧接着会弹出一个重要的窗口——“Select Device for Target”。这就是让你选择芯片型号。在搜索框里输入“STM32F401CC”,列表中应该会出现“STM32F401CCU6”。选中它,点击OK。如果你在这里找不到这个型号,那就说明你之前没有安装STM32F4的器件支持包(DFP 2.7.0)。你需要关闭这个窗口,点击Keil 5菜单栏的 Pack Installer(一个绿色小盒子图标),在线搜索并安装“Keil::STM32F4xx_DFP”这个包,版本选2.7.0。

4.2 管理工程文件组与添加文件

工程创建好后,左侧的“Project”窗口是空的。我们需要把那些散落在各文件夹的源代码文件,按逻辑添加到工程里。点击工具栏上的“品”字形图标(Manage Project Items),或者右键Target1选择“Manage Project Items”。在这里,我们可以创建不同的“文件组”(Groups),类似于文件夹,来分类管理文件。

我通常创建这样几个组:

  • User:存放用户应用代码。点击“Add Files”,把 User 文件夹下的 main.c, stm32f4xx_it.c, system_stm32f4xx.c 添加进来。别忘了还有那个启动文件 startup_stm32f401xx.s(在ARM编译环境下,.s文件会自动被识别)。
  • StdPeriph_Driver:存放标准外设驱动。添加 Libraries\STM32F4xx_StdPeriph_Driver\src 文件夹下的所有 .c 文件。这里有个技巧:你可以先全选添加,后面再根据 stm32f4xx_conf.h 的配置来屏蔽不用的驱动,减少编译量。
  • CMSIS:存放核心支持文件。添加 Libraries\CMSIS\Device\ST\STM32F4xx\Source\Templates 下的 system_stm32f4xx.c(如果User组已经添加了,这里就不用重复添加,通常放User组就行),以及 Libraries\CMSIS\Include 下的头文件(头文件不需要手动添加,但路径要包含)。

添加完成后,你的工程窗口应该呈现出清晰的结构,每个文件都归位到对应的组里。

4.3 配置魔术棒:最关键的一步

点击工具栏上的“魔术棒”选项(Options for Target)。这里是工程配置的核心。

① Target 标签页:确认芯片型号正确。在“Code Generation”区域,有一个至关重要的选项:Use MicroLIB。我建议勾选它。MicroLIB是Keil为嵌入式系统优化的一个精简版C标准库,比标准的C库小很多,特别适合资源受限的单片机。对于大多数应用,勾选它没有问题。

② Output 标签页:选择输出文件夹为你的 Project 目录下的 Output 子文件夹(可以新建一个),并勾选“Create HEX File”,这样编译后会生成可以下载到芯片的.hex文件。

③ C/C++ 标签页:这是错误和警告的高发区,请仔细配置。

  • Define:在这里输入全局的宏定义。对于STM32F401CCU6,必须输入以下两个(用英文逗号隔开):
    USE_STDPERIPH_DRIVER,STM32F401xx
    
    第一个宏 USE_STDPERIPH_DRIVER 告诉编译器,我们要使用标准外设库。第二个宏 STM32F401xx 精确指定了我们的芯片系列,这会引导编译器包含正确的芯片特定头文件。
  • Include Paths:添加头文件搜索路径。编译器需要知道去哪里找我们代码中 #include 的那些头文件。点击末尾的“...”,添加以下路径(注意是相对于工程文件 .uvprojx 的路径):
    ..\..\User
    ..\..\Libraries\CMSIS\Device\ST\STM32F4xx\Include
    ..\..\Libraries\CMSIS\Include
    ..\..\Libraries\STM32F4xx_StdPeriph_Driver\inc
    
    添加时,点击“New (insert)”图标,然后点“...”去浏览选择对应的文件夹。路径一定要准确,否则会报“找不到头文件”的错误。

④ Debug 标签页:选择你使用的调试器,比如ST-Link、J-Link等。在“Settings”里确认调试接口是SWD(Serial Wire Debug),这是最常用的。

⑤ Utilities 标签页:设置下载编程算法。点击“Settings”,确认“Programming Algorithm”里已经为你选择的芯片型号(STM32F401CC)添加了正确的Flash编程算法(通常是STM32F4xx 512KB Flash)。如果没有,需要点击“Add”添加。

4.4 修改编译器版本与屏蔽无用文件

还记得开头我提到的编译器版本问题吗?现在来检查一下。再次点击“魔术棒”,进入“Target”标签页,看“ARM Compiler”下拉框。如果显示的是“V6.xx”,请把它改为“V5.06 update 7 (build 960)”或类似的AC5版本。这是解决大量莫名错误的关键一步。

最后,为了编译顺利,我们需要“屏蔽”掉一些STM32F401CCU6这颗芯片不支持的外设驱动源文件。因为标准外设库的 src 文件夹包含了F4全系列的外设驱动,但F401是入门款,没有某些高级外设(比如加密模块CRYP、哈希模块HASH)。如果不屏蔽,链接器会因为找不到这些外设对应的寄存器定义而报错。操作方法是:在工程窗口,右键 StdPeriph_Driver 组里那些F401不支持的 .c 文件(例如 stm32f4xx_cryp.c, stm32f4xx_hash.c 等),选择“Options for File...”,在“Properties”标签页下勾选“Exclude from build”。这样,这些文件就不会参与编译了。更优雅的做法是在 stm32f4xx_conf.h 里通过注释掉对应的 #define 来管理,但对于明确的、芯片硬件不支持的外设,直接排除构建更干脆。

5. 验证与第一个程序:点亮一颗LED

所有配置完成,激动人心的时刻到了——编译并下载第一个程序。点击工具栏的“Rebuild”按钮(通常是三个红色箭头那个)。如果一切顺利,你会在下方的“Build Output”窗口看到 0 Error(s), 0 Warning(s) 的提示。如果有错误,请根据错误信息,回头检查上述步骤,最常见的问题是头文件路径错误、宏定义没写、或者启动文件没添加。

编译通过后,连接好你的开发板、ST-Link调试器,点击“Load”按钮(一个向下的箭头)将程序下载到芯片。然后按一下板子的复位键。如果板载有用户LED,现在我们来写个简单代码让它闪烁。打开 User/main.c 文件,在 while(1) 主循环里添加几行代码:

#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_rcc.h"

// 假设LED连接在PA5引脚
#define LED_GPIO_PORT    GPIOA
#define LED_GPIO_PIN     GPIO_Pin_5

void LED_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;

    // 使能GPIOA的时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);

    // 配置PA5为推挽输出
    GPIO_InitStructure.GPIO_Pin = LED_GPIO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(LED_GPIO_PORT, &GPIO_InitStructure);
}

int main(void) {
    // 系统时钟初始化(通常启动文件已调用SystemInit())
    LED_Init();

    while(1) {
        // 点亮LED
        GPIO_SetBits(LED_GPIO_PORT, LED_GPIO_PIN);
        // 简单延时
        for(int i=0; i<1000000; i++);
        // 熄灭LED
        GPIO_ResetBits(LED_GPIO_PORT, LED_GPIO_PIN);
        for(int i=0; i<1000000; i++);
    }
}

记得根据你的实际板子原理图,修改LED连接的引脚和端口。再次编译、下载、复位,你应该能看到LED开始闪烁。当这颗LED被你写的代码点亮并闪烁的那一刻,恭喜你,你已经成功搭建起了属于你自己的STM32开发环境,并且完成了固件库移植最核心、最繁琐的步骤。这个干净的工程模板就是你未来所有项目的基础,以后开发新项目,直接复制这个模板,在 User 文件夹里写你的新代码就行了,省去了重复配置的麻烦。这个过程虽然步骤多,但每一步都有其意义,亲手走一遍,你对STM32工程结构的理解会深刻得多。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐