1. 项目概述:为什么还要在Keil MDK5里手搓一个F4库函数工程
Keil MDK5(也就是大家习惯说的Keil5)里新建一个基于F4的标准外设库工程,这件事说难不难,说简单也真有一堆连环坑。我刚转F4的时候,第一次在Keil5里点开“New uVision Project”,手边资料又是寄存器版又是HAL版,愣是把一个“能点亮LED的最小工程”从下午折腾到天黑。后来做的项目多了再回头看,整个搭建过程的核心逻辑其实很朴素:你只需要把启动文件、系统初始化文件、标准外设库驱动、用户代码这四块东西,按照芯片型号的规则组合到一起,剩下的就是在界面里把路径和宏填对而已。
这篇文章就是要把这套流程完整捋一遍,从工具安装、固件库获取,到工程分组、源文件添加、头文件路径与宏定义配置,再到第一次编译下载和常见报错排查,全部按可复现的顺序写清楚。适合两类人:一类是刚入手STM32F4、之前只玩过51或者F1标准库的同学;另一类是手上有老项目还基于SPL标准库、经常需要从零给新板子搭环境的工程师。内容偏实操,我尽量把为什么这么做也讲明白,不然你只是照着点了菜单,换个芯片照样不会。
1.1 标准外设库和其他开发方式的区别
做STM32开发,现在市面上主流大概是三条路线:寄存器操作、标准外设库(SPL)、HAL库。先明确一个概念,“库函数工程”里的“库函数”,在F4语境下默认指的就是ST官方曾经主推的标准外设固件库,不是HAL。
| 开发方式 | 特点 | 适合场景 |
|---|---|---|
| 寄存器开发 | 直接读写寄存器,效率最高、代码最贴近底层,但可读性差、维护成本高,要求对芯片寄存器手册很熟 | 学习芯片原理、极端优化、资源极度紧张 |
| 标准外设库 | ST官方把外设初始化、读写操作封装成函数,结构体配置方式清晰,代码量适中,资料沉淀极多 | F4经典开发方式、老项目维护、系统学习外设原理 |
| HAL库 | ST当前主推方案,配合图形化配置工具,代码移植性强,但封装层次深,全功能代码量偏大 | 新项目快速开发、跨系列产品、生态工具链完善 |
对我个人来说,F4标准库最大的优势不是“新”,而是它把外设初始化流程拆得很清楚。比如初始化一个串口,你要先配置NVIC,再配置GPIO复用,再配置USART结构体,最后调用初始化函数。每一步对应一个结构体和一两句函数,调试时逻辑特别顺,不像HAL库那样一层套一层,出现诡异问题时反而难定位。
1.2 为什么不用现成模板,而要自己新建
很多开发板厂商都会提供现成的标准库工程模板,直接改一改就能用,那为什么还要自己从零建一遍?我的回答是:用现成模板你通常不知道工程里哪几个文件是必须的,哪一个是启动文件,哪个宏负责定义芯片型号。一旦你换一颗不同子系列的F4芯片,或者换一个操作系统、换一套目录结构,工程就趴窝。
自己从零搭一次模板,虽然第一次比较费时间,但好处是后续所有项目都有了统一骨架。你会在心里建立起“这个工程需要哪些源文件、哪些路径、哪些宏”的清晰认知,以后再遇到编译报错,大概率一眼就能猜中问题在哪。而且标准外设库本身就带一个官方模板工程,但你完全用它的话,目录和分组是官方风格,和很多企业里的工程管理习惯并不一致,所以更推荐按自己的习惯重新组织。
2. 准备工作:工具链、固件库和目录规划
动手建工程之前,先把三样东西备齐:Keil MDK5软件本体、STM32F4器件支持包、F4标准外设库压缩包。工具不全就开工的话,后面会反复遇到“找不到芯片型号”“打不开某个头文件”之类的问题,非常影响心情。
2.1 安装Keil MDK5并确认F4器件包
去Keil官网下载MDK5安装包,安装过程一路Next即可,唯一要注意的是安装路径尽量不要带中文和空格,比如不要装到C盘用户目录里,否则后面一些路径问题会变得很玄学。装完之后默认只有少量基础器件支持,你还需要安装STM32F4系列的Device Family Pack,也就是常说的DFP器件支持包。
DFP有两种装法:一种是在Keil软件里点“Pack Installer”,从Packs标签页里找STMicroelectronics,然后找到STM32F4系列对应的包,点Install;另一种是去官网直接下载DFP安装包,双击安装。装完之后,新建工程选择芯片型号时,才能看到STM32F4系列下的具体型号,否则列表是空的或者只有默认的ARM通用核,根本选不了F407。
提示:通过Pack Installer在线装DFP时,如果网络比较慢,耐心等一会儿,不要中途强退。装完之后可以在“Pack Installer”的Devices页签里检查一下目标型号是否变成绿色可用状态。
2.2 获取STM32F4标准外设库并看懂目录结构
ST官方发布F4标准外设库的最后一个版本是V1.8.0,压缩包名称类似“STM32F4xx_DSP_StdPeriph_Lib_V1.8.0”。你可以在ST官网搜索“STM32F4xx standard peripheral library”下载,也可以在一些嵌入式资料平台找到同样名称的压缩包。注意别下错成HAL库的Cube包,F4标准外设库的压缩包一般会带“StdPeriph_Lib”字样。
解压之后,里面有几个关键目录:
- Libraries:核心,里面分CMSIS和STM32F4xx_StdPeriph_Driver两大块。CMSIS里又有Device/ST/STM32F4xx和Include两层,Device层放的是设备头文件、system_stm32f4xx.c和启动文件模板;Include层放的是CMSIS内核头文件,比如core_cm4.h。
- Project:官方示例工程。这里面的模板工程就是很多人说的“官方库模板”,可以拿来参考对照,但我们后面要新建自己的,所以这里暂时不用管。
- Utilities:官方评估板相关的例程和驱动,一般用不到,留着备查。
对建工程来说,真正有用的文件其实就集中在两个地方:一是Libraries/CMSIS下面的CMSIS Device文件和核心头文件;二是Libraries/STM32F4xx_StdPeriph_Driver下面的inc和src,这里面是所有外设驱动源文件和头文件。在创建工程时,我们需要从CMSIS里复制启动文件和系统文件,从StdPeriph_Driver里按需添加驱动源文件。
2.3 设计一个不容易翻车的工程目录结构
下载、解压固件库之后,不要直接在那个大目录里建工程,否则目录又深又乱。建议先新建一个干净的工程根目录,然后按功能模块划分文件夹。
我常用的F4库函数工程目录结构大致是这样:
STM32F407_Template ├─ User │ ├─ main.c │ └─ stm32f4xx_it.c ├─ CMSIS │ ├─ Startup │ ├─ Include │ └─ Device ├─ StdPeriph_Driver │ ├─ inc │ └─ src ├─ BSP │ ├─ inc │ └─ src ├─ OUTPUT └─ Doc- User:存放用户主函数main.c和中断服务函数stm32f4xx_it.c。
- CMSIS:启动文件、系统初始化源文件、CMSIS内核头文件都归这里。
- StdPeriph_Driver:标准外设库的源文件和头文件。这里需要做的很直接,就是把固件库里Libraries/STM32F4xx_StdPeriph_Driver下的inc和src整个复制过来,后面按需添加源文件进工程。
- BSP:板级外设驱动,比如驱动某个传感器、某个屏幕的代码,按模块放,会让工程更清晰。
- OUTPUT:Keil编译输出文件夹,烧录用的hex文件默认也生成在里面。
- Doc:存放硬件原理图、数据手册等文档,不进Keil工程,只作归档。
工程目录设计这个环节,很多人会跳过,但其实特别重要。后续项目越做越大,如果User、BSP、APP的代码都堆在一个文件夹里,找文件都费劲,更别提交互维护了。你现在多花十分钟建好骨架,后面省下的时间远不止十分钟。
3. 新建工程与芯片型号选择
准备工作完成后,正式开始在Keil MDK5里新建工程。这一部分我会照着实际点击顺序往下讲,每一步都会说明为什么要这样做。
3.1 从New uVision Project开始,选对芯片型号
打开Keil5,依次点击菜单栏Project -> New uVision Project,然后选择工程文件保存位置。我习惯把uvprojx工程文件放在工程根目录,和User、BSP这些文件夹平级,这样做的好处是整体压缩打包时不容易漏。工程名建议和板子强相关,比如STM32F407_Template,不要用“test1”“新建文件夹”这种名字。
保存之后,Keil会弹出芯片选择窗口。在搜索框里输入芯片型号,比如STM32F407ZGT6,然后在右侧列表里选中它。这里的重点在于,你选的型号必须和实际板子上的芯片完全一致,因为芯片型号决定了Keil要加载哪个器件描述文件,也决定了后面的启动文件和编译宏。
选好型号之后,Keil会弹出一个提示框,问你是否要添加启动文件。这个弹窗我建议选择“否”,不添加。理由不是启动文件不重要,而是我想把它放到自己规划好位置的CMSIS分组里,手动添加心里更有数。如果你选“是”,Keil也会帮你加一个启动文件,但文件位置和命名可能和你的目录规划不一致,后期反倒要多花时间调整。
3.2 配置工程分组,把源文件归类到合理位置
新建工程之后,Keil默认会生成一个“Target 1”,第一件事是把它重命名,比如改成“STM32F407”。重命名操作很简单,在Target 1上右键选择Manage,然后改名字。名字不会影响编译结果,但工程数量多的时候,窗口上“Target 1”这种默认名会很让人难受。
接下来右键Target,选择Add Group,依次添加下面几个分组:
- User:放main.c和stm32f4xx_it.c。
- Startup:放启动汇编文件。
- CMSIS:放system_stm32f4xx.c等系统文件。
- StdPeriph_Driver:放标准外设库驱动源文件。
- BSP:先空着,后续开发板外设模块代码放这里。
分组命名的颗粒度,原则是“够用但不过度”。如果你把所有文件都塞进同一个组,几百个文件挤在一起,Keil的项目窗口点开来一团乱麻;但如果一个外设建一个组,配置个串口就加好几个group,管理起来也负担不小。按功能模块分五六个组是比较平衡的方案。
3.3 添加启动文件、系统文件和外设驱动源文件
分组建好之后,开始往里面添加文件,这是整个流程里最容易出错的环节。
先加启动文件。在你的固件库目录Libraries/CMSIS/Device/ST/STM32F4xx/Source/Templates/arm里,你可以找到几个启动文件,常见的有startup_stm32f40_41xxx.s、startup_stm32f427_437xx.s、startup_stm32f401xx.s、startup_stm32f411xe.s等。这里千万别选错:如果是STM32F407,就选startup_stm32f40_41xxx.s;如果是F429,就选startup_stm32f427_437xx.s。启动文件选择错误属于编译能过、运行必死的隐性坑,因为它决定了中断向量表排列和堆栈初始化参数。
然后是CMSIS系统文件。把Libraries/CMSIS/Device/ST/STM32F4xx/Source/Templates/system_stm32f4xx.c复制到我们自己的CMSIS/Device目录下,添加到CMSIS分组。这个文件负责系统时钟初始化函数SystemInit和SystemCoreClock变量,是工程里绝不能缺的一块。
最后是外设驱动源文件。标准外设库的驱动源文件在Libraries/STM32F4xx_StdPeriph_Driver/src目录下。这里有一个非常重要的经验:不要把所有源文件全部添加进工程。因为很多外设驱动源文件之间有交集或者依赖,全加进去不但让编译时间成倍增长,还可能引入不必要的符号。正确做法是按需添加,比如我们想点亮一个LED,用到了GPIO外设和控制时钟的RCC,那就添加stm32f4xx_gpio.c和stm32f4xx_rcc.c这两个文件即可。后续用到串口、定时器、DMA,再回来加对应文件。
4. 关键参数配置:宏定义、头文件路径和编译选项
如果说添加源文件是搭骨架,那这一节的配置就是接血管。骨架少一根还能忍,宏定义或者头文件路径错一处,编译报错能刷屏一整屏。但掌握规律之后,这些都是体力活。
4.1 两个关键宏定义,缺一个都不行
在Keil主界面,点击工具栏上的“魔术棒”图标进入Options for Target,切到C/C++选项卡,在Preprocessor Symbols栏的Define输入框里,填入两个宏:
STM32F40_41xxx, USE_STDPERIPH_DRIVER第一个宏STM32F40_41xxx,作用是告诉stm32f4xx.h芯片是哪个子系列,因为F4家族内部底层寄存器映射并不完全一致,需要用这个宏决定具体包含哪些外设定义。第二个宏USE_STDPERIPH_DRIVER,作用是让设备头文件知道当前工程要用标准外设库的函数接口,从而自动包含对应的配置头文件。
如果你漏掉了第一个宏,最典型的症状是编译时出现一堆“未定义标识符”,因为芯片寄存器结构体根本没被展开。如果你漏掉了第二个宏,很多库函数相关的定义不会被包含,同样会报错。
注意:芯片型号不同,第一个宏的写法也不同。比如F427、F437、F429、F439对应STM32F427_437xx;F401对应STM32F401xx;F411对应STM32F411xE。工程换芯片时,记得同步改这个宏,特别容易漏。
4.2 头文件包含路径,用相对路径减少痛苦
同样在C/C++选项卡里,点Include Paths右侧的省略号按钮,在弹出的对话框里添加头文件搜索路径。一个新建的头文件路径列表照着下面这样配置:
- .\User
- .\CMSIS\Include
- .\CMSIS\Device
- .\StdPeriph_Driver\inc
- .\BSP\inc
这里需要解释清楚三件事。
第一,为什么不需要把StdPeriph_Driver的src目录加进来?因为编译器搜索头文件只找.h文件所在的位置,src里是.c源文件,已经在工程里添加了,编译器会自己编译它们。第二,CMSIS\Device目录应该具体到包含stm32f4xx.h那一层,不同版本固件库层级可能有差异,以实际放置stm32f4xx.h的文件夹为准。第三,建议使用相对路径而不是绝对路径。如果将来整个工程拷贝到另一台电脑、换一个盘符,绝对路径会直接失效,而相对路径只要工程内部结构不变,永远不会出问题。
4.3 编译优化、微库和输出文件设置
还有一个很影响开发效率的选项,就是“魔术棒”里Target选项卡的微库。建议勾选Use MicroLIB。MicroLIB是面向嵌入式场景裁剪过的C运行库,体积比默认的glibc小很多,特别是后面你用printf调试,MicroLIB能显著减少Flash占用。如果你的代码里大量使用浮点格式化输出,或者对标准库依赖很深,那可以考虑不勾选,但做模板阶段勾上好处更多。
优化等级在C/C++选项卡的Optimization里设置,初期调试建议选Level 0(-O0)。优化等级太高,调试时单步执行会跳来跳去,变量值也可能被优化得刷新不出来。等代码稳定要发正式版本了,再调成Level 2或者平衡优化也不迟。
输出文件设置方面,在Output选项卡勾选Create HEX File,方便后续用第三方工具烧录。我记得很多新手朋友把hex生成关掉,下载的时候用烧录器软件才发现没有hex文件可用,白白浪费时间。
下面是整套核心配置的速查对照表:
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| 芯片型号 | STM32F407ZGT6等 | 与实际芯片一致 |
| 启动文件 | startup_stm32f40_41xxx.s | 按子系列选择 |
| Define宏 | STM32F40_41xxx, USE_STDPERIPH_DRIVER | 一个管芯片型号,一个管库函数 |
| Include Paths | User/CMSIS/StdPeriph_Driver/inc等 | 建议写相对路径 |
| 微库MicroLIB | 勾选 | 减小代码体积 |
| 优化等级 | Level 0 | 调试阶段首选 |
| Create HEX File | 勾选 | 生成hex文件 |
5. 编写入口代码并完成首次编译下载
配置好工程之后,就要写一个最小的程序来验证整条链路是否通畅。这一步最能暴露问题,因为很多朋友到这一步才会发现头文件路径没配对,宏没写对,甚至启动文件加错了。
5.1 启动文件在整条链路里干什么
在写代码之前,先简单理解一下启动文件和SystemInit的作用。启动文件是汇编写的,它做了三件关键的事:一是设置初始堆栈指针,把栈顶地址写到SP;二是建立中断向量表,把复位中断、NMI、HardFault等异常入口按顺序排列好;三是调用SystemInit,再跳转到main函数。SystemInit是标准库提供的一个函数,负责把系统时钟从默认状态切到用户期望的工作模式,同时更新全局变量SystemCoreClock。
也就是说,哪怕你的main函数里只有一行while(1),只要启动文件没有配对,芯片上电后都不知道从哪里开始执行。这也是为什么我之前强调,启动文件一定要跟芯片子系列匹配,不要随便拿一个就能混过去。
5.2 编写一个能跑通的最小main函数
先看最简单的点灯例程,这段代码可以验证GPIO基础配置和标准库函数调用流程。
#include "stm32f4xx.h" void Delay(uint32_t count); int main(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOC, &GPIO_InitStructure); while (1) { GPIO_SetBits(GPIOC, GPIO_Pin_13); Delay(1000000); GPIO_ResetBits(GPIOC, GPIO_Pin_13); Delay(1000000); } } void Delay(uint32_t count) { while (count--) { } }这个例程里有两个关键知识点。第一,GPIO外设的时钟是挂在AHB1总线上的,所以要用RCC_AHB1PeriphClockCmd使能对应端口时钟,遗漏这一步,程序运行到GPIO_Init时就会卡住,芯片的GPIO寄存器根本打不开。第二,GPIO_InitTypeDef结构体里每一个字段都要赋值,包括引脚号、模式、输出类型、翻转速度、上下拉配置。虽然带默认值的说法在某些编译器里成立,但标准库的套路是结构体不初始化不要直接用,老老实实每个字段都写上才稳。
5.3 扩展说明:外部晶振配置到168MHz的方法
如果你想用外部8MHz晶振跑168MHz主频,标准库推荐的方式是在main里调用RCC配置函数。下面这段配置代码非常典型,值得放进自己的模板备查:
void SystemClock_Config_168MHz(void) { RCC_OscInitTypeDef RCC_OscInitStruct; RCC_ClkInitTypeDef RCC_ClkInitStruct; RCC_OscInitStruct.OscillatorType = RCC_OscillatorType_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSource_HSE; RCC_OscInitStruct.PLL.PLLM = 8; RCC_OscInitStruct.PLL.PLLN = 336; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; RCC_OscInitStruct.PLL.PLLQ = 7; RCC_OscInit(&RCC_OscInitStruct); RCC_ClkInitStruct.ClockType = RCC_ClockType_SYSCLK | RCC_ClockType_HCLK | RCC_ClockType_PCLK1 | RCC_ClockType_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSource_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_Div1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_Div4; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_Div2; RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_Latency_5); }这段代码背后其实是一整套时钟树逻辑。外部晶振8MHz作为PLL输入,先通过PLLM进行8分频得到1MHz,再通过PLLN倍频336倍得到336MHz,PLLP二分频后就是168MHz系统主频。168MHz再经过AHB分频器得到HCLK,APB1四分频得到42MHz,APB2二分频得到84MHz。这些数值不是随便拍的,APB1总线最高上限就是42MHz,APB2是84MHz,Flash在168MHz下至少需要5个等待周期。你把这几组参数理顺了,以后换不同频率的晶振或者换不同主频限制的芯片,也能自己算出来。
5.4 配置下载器和下载验证
代码编译无错后,还不能高兴得太早,下载环节一样容易出幺蛾子。打开Options for Target,切到Debug选项卡,在右上方的下拉菜单里选择你的调试器,常见的是ST-Link Debugger或者J-Link。选择之后点旁边的Settings,进入连接设置页,把Port选为SWD,速度可以先选默认,如果下载报错再适当降低。
接下来切到Utilities选项卡,确保“Use Debug Driver”处于勾选状态,这样下载时会复用上面配置好的调试器。点击Settings,在Flash Download区域里检查Programming Algorithm列表,确保里面存在目标芯片的Flash算法。一般只要我们选对了芯片型号,Keil会自动带出正确的Flash算法,但如果之前手动改过,这里就值得检查一下。
一切就绪后,把开发板通过ST-Link连接好,点一下DOWNLOAD或者按快捷键F8,看到“Download OK”之类的提示,然后板子上的LED开始闪烁,这时候一个完整的F4库函数工程就算真正建好了。
6. 常见问题与排查技巧
这个环节我必须提前说,新建工程报错不丢人,尤其是第一次手搭标准库工程的人,十有八九会遇到下面这几个问题。关键是要会看报错信息,能自己定位到是哪个环节没配对。
6.1 编译期高频报错与解决办法
| 报错信息 | 原因 | 解决办法 |
|---|---|---|
| error: #5: cannot open source input file "stm32f4xx_gpio.h" | 头文件路径没配或者配错了 | 检查Include Paths是否包含StdPeriph_Driver/inc目录 |
| Error: L6218E: Undefined symbol SystemInit | 没有添加system_stm32f4xx.c,或者添加的文件路径不对 | 在CMSIS分组中添加system_stm32f4xx.c |
| error: #20: identifier "GPIO_TypeDef" is undefined | 宏定义STM32F40_41xxx漏写或拼写错误 | 检查C/C++选项卡里的Define |
| Warning: L6989W: Could not apply fixup | 启动文件与芯片型号不匹配 | 更换对应子系列的启动文件 |
| error: #147: redefinition of xxx | 多个源文件重复定义了某个符号 | 检查是否重复添加了某个.c文件,比如一个源文件在多个分组里出现 |
上面这张表里,出现频率最高的是前两个。我见过的同学里,十个有八个报错卡在头文件路径上,因为Keil的Include Paths设置框不算直观,每次加完路径还要一层层展开确认。其实判断路径是否正确有个笨但实用的方法:在代码里#include一句,然后把鼠标放到尖括号里按右键,选择Open Document,如果能直接打开对应的.h,就说明路径没问题,打不开就是没找到。
6.2 下载调试环节的典型问题
编译通过不等于下载就一定顺利。新手最容易遇到的是“No target connected”或者“No device found on the SWD”之类的错误。这种提示别急着怀疑Keil,先查三样东西:一是ST-Link和板子之间的接线,SWDIO、SWCLK、GND三条线是必须的,少一根都不行;二是调试器有没有被电脑识别,可在设备管理器里看驱动是否正常;三是板子是否上电,有时候单独给板子供电而调试器没供电也会出问题。
另一个高频问题是“Flash Download failed - Target not halted”。这个报错常见于芯片内部有程序在跑,或者供电不稳定导致无法进入下载模式。解决办法通常是:按住板子上的复位键,点击下载,等软件连接上后立刻松开复位键;如果不行,可以把SWD速度从4MHz降到1MHz再试一次,这个操作对很多情况都有奇效。
还有一个很多人忽略的坑,是Keil的Flash Download Settings里没有勾选“Reset and Run”,导致下载成功后程序并没有自动运行。你看到下载界面显示成功了,但板子没反应,就又去排错半天。勾选Reset and Run之后,下载完芯片自动复位并运行,才是正常的嵌入式开发体验。
6.3 我的个人实操经验和小习惯
最后分享几个我自己在建F4库工程时沉淀下来的习惯,算不上什么高深技巧,但确实能避免很多不必要的折腾。
第一,第一版模板务必从零搭一遍。不要直接拿开发板厂商的库工程改一改就完事,那样你永远不知道启动文件放在哪、宏在哪配、为什么这样设置。哪怕你搭完之后发现和厂商模板长得差不多,这个过程也价值巨大。
第二,StdPeriph_Driver源文件不要全量添加。我一直坚持按需添加,比如点灯只加gpio和rcc,用串口再加usart。有人觉得全加进去以后方便,但编译时间会肉眼可见地变慢,而且工程里到处都是不用的全局函数符号,只会增加排查问题的难度。
第三,整个工程尽量用Git管理。建议在工程里添加一个.gitignore,把OUTPUT目录和中间文件排除掉,只提交源码、工程文件和文档。这样做的好处是,当你把某个文件或者某段配置搞坏之后,随时可以回滚到上一个正常状态。建工程的这一套配置,别看只折腾一次,但里面的细节非常多,时间一长很容易忘。把配置过程写进Doc目录,下次要做F4同系列的不同芯片时翻出来看一眼,两分钟就能搞定新工程。
这个模板搭好之后,往后不管是加传感器驱动、加网口协议栈,还是加实时操作系统,本质上都是在这个骨架上做加法。骨架扎得稳,后面就省心很多。