STM32F1固件库V1.8.0详解:HAL库、BSP与示例工程实战
简介ST官方发布的STM32CubeF1固件包V1.8.0专为STM32F1系列MCU设计覆盖驱动、板级支持与示例工程的完整开发资源。资源共133个文件以74个HAL/LL驱动头文件和34个C源码文件为主体另含HTML说明页、CHM帮助文档、PDF入门指南等压缩包仅2.8MB。包内整合HAL驱动库与CMSIS核心提供STM3210C_EVAL、Nucleo-F103RB等评估板BSP代码与示例工程目录按Drivers、Utilities、Documentation、Third_Party划分可直接导入Keil MDK、IAR、GCC等主流IDE编译运行。已有445人学习下载适合STM32初学者快速上手也适合项目开发中稳定复用ST标准化驱动层。1. 拿到手就能用的STM32F1固件库V1.8.0HAL、BSP与示例一次配齐做STM32F1开发大部分时间不是耗在写逻辑上而是在折腾“工程能不能编译”。一套完整固件库里缺一个CMSIS文件都会让新手摸不着头脑。V1.8.0这个版本把HAL驱动、BSP板级支持、示例工程和文档打包在一起配合STM32Cube生态基本做到解压即用不用东拼西凑。HAL驱动统一外设接口BSP则管理板级差异适合从标准外设库转过来的开发者也适合做课程设计底板的工程模板。之前帮某开发者调一块F103板子他手里混着SPL和HAL两代库文件灯都点不亮。换到V1.8.0完整包后直接用官方模板几分钟就有了可复现的起点。它解决的核心问题不是“教你怎么写代码”而是让你在写业务逻辑之前先有一个配置正确、编译通过、烧录能跑的工程底座。无论你是手工建工程还是用STM32CubeMX生成这套包都值得放在本地库里长期使用。2. 固件库内部结构HAL驱动、BSP与文档的职责划分拿到压缩包先别急着复制到工程目录先把它当一本书翻开看。固件库的目录结构其实就代表了芯片厂商对代码的划分方式CMSIS管内核HAL管外设BSP管板子Projects管示例。把这四个象限先认清楚后面所有操作都不会慌。2.1 解开压缩包后的目录先认清四块核心通用结构类似下面这样具体版本的文件名略有差异. ├── Drivers │ ├── CMSIS │ ├── STM32F1xx_HAL_Driver │ └── BSP ├── Middlewares ├── Projects ├── Utilities └── Readme.html这张目录图里有几个区域是经常要和命令行、IDE窗口打交道的。Drivers/CMSIS是内核抽象层包含Cortex-M3相关的头文件、系统初始化文件以及启动文件这部分不要随意改但Include Path里必须加。Drivers/STM32F1xx_HAL_Driver是整套HAL驱动的源码一切带HAL_前缀的函数都在这常见外设如gpio.c、uart.c、tim.c、dma.c都在这里。Drivers/BSP是板级支持包专门封装LED、按键、串口这类板载外设让你像调库函数一样调板子。Projects下面是各种现成的示例工程这是新手最容易忽略的区域其实它比任何教程都有价值。用表格来划分职责会更直观目录作用你该关心什么Drivers/CMSIS内核头文件、系统启动代码启动文件后缀是hd还是ld要与芯片容量匹配Drivers/STM32F1xx_HAL_Driver外设驱动源码剪裁开关在stm32f1xx_hal_conf.h里Drivers/BSP板级硬件封装需要定义板卡宏否则编译会报错Projects模板和示例工程找最小模板比自己建工程快得多需要注意Projects下的示例工程并不是简单的“Hello World”而是包含了完整IDE工程配置。复制出来直接用能避开Include Path、芯片型号、启动文件这类最容易被新手搞砸的问题。我一般会把某个和手头芯片最接近的工程整体复制一份改改宏定义就开工而不是从空工程开始一根根加文件。2.2 为什么是这个版本HAL库对现代工具链的适配先明确一点STM32F1系列早期的官方库是标准外设库SPL函数风格是USART_SendData、GPIO_SetBits这一类。而HAL库的接口风格是HAL_UART_Transmit、HAL_GPIO_WritePin整体设计思路完全不一样。SPL更贴近寄存器操作直接、高效但每次配置外设都要手动写好几十行寄存器代码而且对DMA、中断回调、RTOS支持很弱。HAL库则是在寄存器外面套了一层抽象把初始化流程收敛到结构体里再把中断事件的回调暴露给用户代码可读性高很多。V1.8.0这个版本在F1系列里属于比较稳定的一个迭代。从它开始HAL库接口和STM32CubeMX生成的代码做到了高度兼容。也就是说你可以先用CubeMX图形化配置引脚和时钟生成一份工程框架再手动往里面塞业务代码两边不会“打架”。这是SPL年代做不到的。维度SPLHALAPI风格直接寄存器操作外设句柄结构体配置中断处理用户自己维护中断标志回调函数机制中间件支持较弱较好地支持RTOS、FATFS、USB等CubeMX生成不支持支持且生成代码可读调试友好度依赖寄存器窗口提供句柄和状态字段所以选V1.8.0并不是因为它“新”而是因为它正好卡在“功能完整”和“兼容性好”的交汇点上。再往后的版本对F1的改进更多是修bug但核心接口没有大变。用V1.8.0你在搜索引擎里能找到最多的资料和踩坑记录这对独立学习很重要。2.3 BSP层到底做了什么从板级差异中解放应用层BSP板级支持包的作用是把“这块板子上LED接在哪个引脚”这样的细节从应用代码里剥离出去。比如某评估板上有个LED硬件上连的是GPIOA的Pin 5高电平点亮。直接用HAL写的话你每次都要记得引脚号还得开GPIO时钟、设置模式。而BSP层会封装成类似BSP_LED_Init和BSP_LED_On这样的函数应用层只需要关心“这是第几号LED”。看一段典型调用#include bsp.h void led_demo(void) { BSP_LED_Init(LED1); BSP_LED_On(LED1); }这段代码里BSP_LED_Init内部做了什么它会打开GPIO时钟、把相应引脚配置为推挽输出、设置初始电平。BSP_LED_On则直接往GPIO数据寄存器写值。好处是如果你的硬件平台换了只要换一套BSP实现上面这段led_demo完全不用动。对做产品原型的人来说这就是板级差异隔离出来的底气。但要注意BSP不是万能黑匣子。你用自己的板子时必须修改BSP驱动里的引脚宏和时钟宏。打开BSP头文件能看到大量“#define LED1_PIN GPIO_PIN_5”这样的定义把每个引脚宏改成你板子实际的连接即可。另外一个坑是编译器的预定义宏BSP头文件通常会根据一个板卡宏选择加载哪一套引脚映射。如果你没定义这个宏编译会直接失败提示“Unknown board”之类。3. 搭一个能跑能调的工程从官方示例到最小HAL工程这一章解决“手里有库但不知道怎么落到IDE里”的问题。两种路径都会讲到一条是用现成的示例工程快速跑起来适合验证环境另一条是手工建一个最小HAL工程适合理解库的组成和依赖关系。两条路不建议只走一条至少都要过一遍。3.1 用现成示例三分钟跑通点灯最快上手的方式是从压缩包的Projects目录里找一个和手头芯片型号最接近的工程。比如手头是STM32F103ZE芯片就找工程名里带“F103ZE”或“F103ZET6”的文件夹。复制到工作区后用搭建好的IDE打开工程文件。操作步骤如下在Projects目录里按板卡文件夹进入找一个名为“GPIO_EXTI”或“LED_Blink”的示例工程。复制整个文件夹到你的工作区不要直接在压缩包里打开。用IDE打开后缀为.uvprojx的工程文件。打开工程选项确认Device型号和你的芯片一致。编译若报错优先检查“Include Paths”是否包含了Drivers下的三个路径。连接调试器直接下载运行。这里面最容易翻车的是工程里的芯片型号和实际不符。比如工程原本写的是F103VET6而你手里是F103RET6两个芯片Flash容量不同启动文件也不同强行烧录轻则程序跑飞重则调试器连接不上。正确的做法是先查芯片丝印再在IDE里把Device型号改对。有些IDE会在改型号时提醒你重新选择启动文件这个是好事别点掉。另外如果下载器是ST-Link注意在Debug选项卡里把Flash Download的Programming Algorithm也改成匹配的型号。很多时候明明编译过了一按下载按钮就报“RDDI-DAP Error”一大半原因是这里选错了芯片算法。3.2 手写最小HAL工程没有IDE模板也能建用官方示例跑通只能算热身手工建工程才是真正理解库结构的机会。以MDK为例新建工程后不要选任何“Device Support Library”而是手动添加固件库源码。一个能点灯的最小工程至少需要以下文件清单文件类型必须包含说明启动文件startup_stm32f103x.s在CMSIS/Device/Support/Startup里系统时钟system_stm32f1xx.c提供SystemInit和SystemCoreClock内核头文件core_cm3.h、stm32f1xx.hCMSIS核心定义HAL驱动stm32f1xx_hal.cHAL_Init和时基相关外设驱动stm32f1xx_hal_gpio.c至少要有GPIO驱动配置文件stm32f1xx_hal_conf.h模板修改而来然后写一个最简main.c用HAL库点亮一颗灯#include stm32f1xx_hal.h int main(void) { HAL_Init(); __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_5; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); while (1) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); HAL_Delay(500); } }这段代码只有一个目标证明你的库、启动文件、时钟和GPIO配置全部是通的。HAL_Init做了三件事初始化FLASH接口、配置SysTick作为时间基准、使能全局中断。GPIO初始化结构体里Mode用的是GPIO_MODE_OUTPUT_PP代表推挽输出这是驱动LED最常用的模式Speed设置为LOW对几mA级别的LED够用了。最后进入主循环用HAL_GPIO_TogglePin翻转灯的状态HAL_Delay实现毫秒延时。注意这段代码没有配置PLL芯片默认使用内部HSI时钟主频8MHz。点灯没问题但串口波特率之类的就别指望准了。接下来就是要把系统时钟配到72MHz。3.3 时钟树设置翻车最多的地方之一STM32F1的时钟树说难不难说简单但也有一堆细节。最常见的外部晶振方案是8MHz晶振经PLL倍频9倍得到72MHz系统时钟。配置代码常写成这样void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct {0}; RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.PLL.PLLState RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL RCC_PLL_MUL9; HAL_RCC_OscConfig(RCC_OscInitStruct); RCC_ClkInitStruct.ClockType RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider RCC_HCLK_DIV1; HAL_RCC_ClockConfig(RCC_ClkInitStruct, FLASH_LATENCY_2); }这段代码把HSE当PLL输入源8MHz经过9倍频后变成72MHz。AHB不分频所以HCLK是72MHz。APB1最高只能到36MHz所以必须二分频APB2可以保持72MHz不变。FLASH等待周期设置为2个周期是因为72MHz下Flash访问至少要两个等待周期。参数照抄通常没问题但有两个地方要特别留意。第一个是HSEPredivValue。部分F1芯片如F105/F107的PLL输入源前有一个预分频器如果你不小心配成了DIV2实际倍频出来就是36MHz还查不到原因。第二个是HSE_VALUE宏这个宏在stm32f1xx.h中定义默认是80000008MHz。如果你板子上焊的是12MHz晶振光把HSE_ON改成RCC_HSE_ON还不够还得把HSE_VALUE改成12000000否则HAL库在计算波特率、延时的时候会按错误时钟算结果就是串口乱码、延时不准。这类跑偏问题几乎都指向时钟树。4. HAL驱动实战GPIO、定时器与串口三件套工程能编译、灯能闪之后就可以开始写真正的外设逻辑了。GPIO、定时器、串口是BSP和大多数应用绕不开的三件套。这一章用三段可运行代码把HAL库的常用姿势讲透。每个代码块后都会解释参数意图和常见变体。4.1 GPIO轮询按键与LED翻转按键和LED是GPIO最典型的输入输出场景。先给出初始化代码void gpio_init(void) { __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct {0}; // 按键接在PC13对地触发所以用上拉输入 GPIO_InitStruct.Pin GPIO_PIN_13; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOC, GPIO_InitStruct); // LED接在PA5推挽输出 GPIO_InitStruct.Pin GPIO_PIN_5; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); }主循环里读取按键状态并翻转LEDwhile (1) { if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) GPIO_PIN_RESET) { HAL_Delay(30); // 简单消抖 if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) GPIO_PIN_RESET) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); while (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) GPIO_PIN_RESET) { // 等待按键松开 } } } }Pull参数选择GPIO_PULLUP是因为按键通常把引脚拉低到地内部上拉可以保证空闲时引脚为高电平。如果板子设计是按键接电源、引脚内部下拉那Pull参数应改成GPIO_PULLDOWN。Mode这里要注意区分GPIO_MODE_OUTPUT_PP推挽输出和GPIO_MODE_OUTPUT_OD开漏输出。驱动LED通常用推挽驱动I2C这种需要“线与”的场合才用开漏。GPIO_Speed可以考虑提升到HIGH如果LED驱动电流大或引脚翻转频率高。低频点灯用LOW没毛病但复用成串口、定时器时建议设置成HIGH。4.2 定时器1ms中断与PWM呼吸灯HAL库用定时器的逻辑是先初始化句柄配置时基再配置通道。下面以TIM3为例实现1ms定时中断TIM_HandleTypeDef htim3; void timer_init(void) { __HAL_RCC_TIM3_CLK_ENABLE(); htim3.Instance TIM3; htim3.Init.Prescaler 72 - 1; htim3.Init.Period 1000 - 1; htim3.Init.CounterMode TIM_COUNTERMODE_UP; htim3.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(htim3); HAL_TIM_Base_Start_IT(htim3, TIM_CHANNEL_ALL); }时钟树配好之后TIM3挂载在APB1上频率72MHz。预分频Prescaler为71也就是72分频得到1MHz计数频率Period设为999表示计数到999后溢出每次溢出间隔1ms。这是72MHz主频下最常见的1ms时基组合。中断回调函数写在别处void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM3) { // 每1ms进来一次 } }如果要做呼吸灯要配置PWM模式。PWM不是用Base初始化而是用OC初始化void pwm_init(void) { __HAL_RCC_TIM3_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_6; // TIM3_CH1 GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); htim3.Instance TIM3; htim3.Init.Prescaler 72 - 1; htim3.Init.Period 2000 - 1; htim3.Init.CounterMode TIM_COUNTERMODE_UP; HAL_TIM_PWM_Init(htim3); }PWM输出频率和分辨率由预分频和周期共同决定。例如预分频72-1计数时钟就是1MHzARR为1999时PWM频率是500Hz占空比分辨率2000级。设置占空比用__HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 1000);在循环里缓慢改变比较值就能看到呼吸效果。这里要注意GPIO_MODE_AF_PP是复用推挽模式不是普通输出。如果你忘了改模式引脚上永远看不到波形。4.3 串口中断接收一帧数据串口是HAL库回调机制最典型的外设。初始化如下UART_HandleTypeDef huart1; void uart_init(void) { __HAL_RCC_USART1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitTypeDef GPIO_InitStruct {0}; GPIO_InitStruct.Pin GPIO_PIN_9 | GPIO_PIN_10; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); huart1.Instance USART1; huart1.Init.BaudRate 115200; huart1.Init.WordLength UART_WORDLENGTH_8B; huart1.Init.StopBits UART_STOPBITS_1; huart1.Init.Parity UART_PARITY_NONE; huart1.Init.Mode UART_MODE_TX_RX; huart1.Init.HwFlowCtl UART_HWCONTROL_NONE; HAL_UART_Init(huart1); HAL_UART_Receive_IT(huart1, rx_data, 1); }发送数据时直接调用HAL_UART_Transmit(huart1, (uint8_t *)hello\r\n, 7, 1000);接收一个字节采用中断方式必须在初始化最后调用HAL_UART_Receive_IT使能接收中断。注意这个函数的第三个参数是“要接收的字节数”如果传1就是每收到一个字节触发一次回调。回调函数实现void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { rx_buffer[rx_index] rx_data; HAL_UART_Receive_IT(huart1, rx_data, 1); // 继续接收 } }很多新手第一次写中断接收会发现只能收到第一个字符原因就在于HAL_UART_Receive_IT是一次性的接收完成后中断被关闭。必须在回调里重新调用才能继续接收下一帧。这里我把rx_data定义成局部或全局变量看你的数据用途如果是全局变量注意加volatile修饰避免编译器优化时读不到最新值。5. 避坑/常见问题用HAL库翻车的那几次HAL库用顺手之后一些坑会反复出现。这里的每一条都是从实际调试现场扒出来的不是理论推演。每一条都按“现象→原因→解决”的顺序写读者可以当查错手册用。5.1 编译与链接期魔鬼藏在配置里现象编译时报“No such file or directory”而且指向的是stm32f1xx_hal_gpio.h这类头文件。或编译时大量“macro redefined”警告缝缝补补加了一堆路径还是报错。原因工程里Include Path没加全比如只加了Drivers/STM32F1xx_HAL_Driver/Inc却没加CMSIS的Include目录。另一类是工程里同时引用了老SPL库和HAL库两个库里存在同名宏或同名函数预处理阶段就会出现宏重定义进入链接阶段则可能报一堆“redefined”。解决以V1.8.0压缩包里的官方示例工程为准把Include Path加上以下四个路径CMSIS核心头文件目录、CMSIS设备目录、HAL驱动Inc目录、HAL驱动Inc/Externals目录。同时检查工程里有没有添加过任何以“stm32f10x_”开头的标准外设库文件如果有全部移除只保留HAL库那一套。经验是不要在现有老工程上打补丁重新从官方模板复制一份更省时。现象编译通过但下载后程序一上电就进入HardFault_Handler。原因启动文件与芯片容量不匹配。STM32F1启动文件分为md中等容量、hd高容量、xl超大容量等。如果你用F103ZET6512KB Flash却用了startup_stm32f10x_md.s向量表偏移不对中断全部错位。解决去工程里确认启动文件后缀。F103C8T664KB用md生F103ZET6和F103RET6用hd生F103ZGT6这类1MB Flash用xl生。改完启动文件后编译再烧录大概率HardFault消失。现象BSP相关源文件一编译就报“#error Unknown board”或者有几十行错误指向某个未知的宏。原因BSP层通常通过一个板卡选择宏来决定编译哪一块硬件映射。比如“U813”这种开发板宏没在IDE里定义BSP头文件就不知道该如何配置引脚于是主动报错。解决在工程选项的C/C选项卡里找到Preprocessor Symbols在Defines栏加上你的板卡标识。具体宏名去Drivers/BSP目录下翻头文件看它#error前面的条件是#if defined哪个宏。照着补上就能编译过。5.2 运行期行为异常的玄学与修复现象调用HAL_Delay(500)实际延时快得离谱或者慢得几乎停在那里。串口波特率也不对示波器看到的波形频率偏了大几十倍。原因九成是因为SystemCoreClock变量和真实时钟不一致。如果工程里实际配置了PLL到72MHz但SystemCoreClock没有更新或者HSE_VALUE写的8MHz但板上晶振是12MHz那么HAL库计算出来的时间基准就全错了。HAL_Delay内部是用SysTick计数器减去到达值实现的时钟基准错了延时自然不准。解决在SystemClock_Config函数末尾手动打印或调试查看SystemCoreClock是否为72000000。如果不是检查stm32f1xx.h里的HSE_VALUE宏再检查RCC配置中PLLMUL。还有一个省事做法从官方示例工程复制system_stm32f1xx.c里面默认覆盖了HSE到PLL的重映射逻辑很多时钟问题会直接消失。现象串口中断接收第一帧数据完整后面只能收到一个字节或完全无响应。原因HAL_UART_Receive_IT的设计是单次模式。当接收中断触发并进入回调时接收通道已经关闭必须由用户在回调里再次调用HAL_UART_Receive_IT来重新打开。如果回调里没写就只会收到第一帧。解决在HAL_UART_RxCpltCallback里每次处理完数据后立刻重新调用一次HAL_UART_Receive_IT(huart1, rx_data, 1)。注意调用前确认全局接收变量没有被业务代码写坏。另外如果开了多个串口回调里要根据huart-Instance区分数据来源避免串口1的数据被存进串口2的缓冲区。6. 进阶把固件库变成你的基础设施如果你已经能用V1.8.0这套包独立建工程、调外设那可以再往前走一步把它和STM32CubeMX、模块裁剪、错误处理结合起来让固件库成为你长期复用的基础设施而不是每次开发都从零折腾的碎石堆。6.1 用CubeMX生成工程再手动合并HAL代码STM32CubeMX能图形化配置引脚、时钟、外设初始参数然后生成HAL工程基础。强烈建议生成的CubeMX工程和V1.8.0包保持一致的库版本。做法是在CubeMX里选择“从本地固件包安装”指定到V1.8.0的压缩包解压目录这样生成的代码路径里引用的库函数和本包完全对齐。生成后不需要再手动写SystemClock_Config和GPIO_InitCubeMX已经帮你写好了。你的业务代码放在main.c中标记“USER CODE BEGIN”和“USER CODE END”之间这样即使你改了引脚定义再重新生成业务代码也不会被覆盖。这个技巧我用了很多年生成代码和手写代码互不干扰。6.2 裁剪HAL库只编译用到的模块HAL库全量编译会拖慢构建时间并且烧写到芯片里也占Flash空间。V1.8.0的stm32f1xx_hal_conf.h里有一排模块使能宏想裁哪个就注释哪个#define HAL_MODULE_ENABLED #define HAL_GPIO_MODULE_ENABLED #define HAL_UART_MODULE_ENABLED #define HAL_TIM_MODULE_ENABLED // #define HAL_SPI_MODULE_ENABLED // #define HAL_I2C_MODULE_ENABLED // #define HAL_DMA_MODULE_ENABLED注释掉后对应的stm32f1xx_hal_spi.c等文件就不会被编译进工程链接时体积能小不少。注意别把用到的模块注释掉否则编译会报“未定义引用”。我一般把常用模块全开着到项目中期不再改外设分配后再集中裁剪一次把编译时间从那几十秒压下来。这套V1.8.0固件库我前前后后用了三年多。后来我养成了一个习惯每个新板子到手不管需求多急都强制先跑一遍官方模板工程确认HAL版本、启动文件和芯片型号匹配再开始写业务代码。这个习惯救过我很多次尤其是深夜调板子到心态崩溃的时候一个能稳定复现的工程就是最好的后悔药。希望帮到你。本文还有配套的精品资源点击获取