目录
一、STM32F10X 系列 HAL 库核心特性
1. 跨系列通用,API 标准化
2. 外设句柄化(Handle),状态化管理
3. 中断 + 回调函数机制,解耦业务逻辑
4. 无缝集成 CubeMX/CubeIDE,自动化代码生成
5. 内置错误处理和基础中间件
6. F10X 专属轻量化优化
二、STM32F10X HAL 库与标准库的异同点
先明确:标准库的核心定位
1. 相同点
2. 核心不同点(分维度对比,重点针对 F10X)
三、STM32F10X HAL 库 / 标准库使用注意事项
(一)HAL 库使用注意事项(F10X 专属,重点避坑)
1. 必须重视 CubeMX 的配置,尤其是时钟树
2. HAL_Delay的依赖与限制
3. 句柄的定义与使用规范
4. 阻塞式 API 与非阻塞式 API 的选择
5. MSP 层代码的管理
6. 中断优先级的配置规则
(二)标准库使用注意事项(F10X 专属,停更后重点)
1. 仅使用官方最终版 V3.5.0,避免兼容问题
2. 手动配置时钟树,需熟悉寄存器
3. 中断服务函数需手动编写,注意入口名
4. 无状态管理,需自行处理外设忙状态
5. 库函数与寄存器可混合使用
6. 移植第三方组件需手动适配
(三)通用注意事项(两者均需遵守)
四、STM32F10X 典型应用详解(HAL 库 + 标准库对比)
应用 1:GPIO 输出 ——LED 闪烁(最基础)
HAL 库实现(CubeMX 配置后)
标准库实现
关键对比
应用 2:GPIO 输入 —— 按键检测(消抖)
HAL 库实现
标准库实现
关键对比
应用 3:UART 串口通信 ——printf 重定向(最常用)
HAL 库实现
标准库实现
关键对比
应用 4:定时器定时中断 ——1s 定时翻转 LED
HAL 库实现
标准库实现
关键对比
五、总结与选型建议
1. 核心知识点回顾
2. 选型建议(基于 F10X 系列)
选 HAL 库的场景
选标准库的场景
3. 最终建议
STM32F10X 系列HAL 库和标准库的核心内容,包括 HAL 库特性、两者异同、使用注意事项,以及结合 F10X 的典型应用详解。首先明确核心定位:标准库是 ST 为 F10X 量身定制的传统底层库,贴近寄存器;HAL 库是 ST 主推的跨系列通用库,抽象层更高,适配全系列 STM32,也是目前 ST 官方唯一维护的库(标准库已停更)。
以下内容将围绕F10X 系列专属特性展开,避免泛泛而谈,同时兼顾入门实用性和工程落地性,所有代码均适配 F103C8T6(最主流的 F10X 型号)。
一、STM32F10X 系列 HAL 库核心特性
HAL 库(Hardware Abstraction Layer,硬件抽象层)是 ST 从 2015 年开始推出的新一代驱动库,F10X 作为经典系列,HAL 库对其做了后向兼容适配(并非原生设计,原生为 F4/F7 等高端系列),核心特性结合 F10X 的硬件限制做了裁剪,核心特性如下:
1. 跨系列通用,API 标准化
这是 HAL 库最核心的特性:一套 API 适配几乎所有 STM32 系列(F0/F1/F4/G0/G4/H7 等)。
- 命名规范统一:
HAL_外设名_操作动作(如HAL_GPIO_WritePin、HAL_UART_Transmit),无需为不同系列重新学习 API; - 对 F10X 的特殊适配:屏蔽了 F10X 不支持的高级特性(如 DMA 多缓冲区、硬件互斥锁),保留核心功能,避免冗余。
2. 外设句柄化(Handle),状态化管理
所有外设的配置和状态都封装在专属句柄结构体中(如GPIO_HandleTypeDef、UART_HandleTypeDef),替代标准库的分散变量:
- 句柄包含外设配置参数和运行状态(如 UART 的发送 / 接收状态、错误标志),便于外设的统一管理和中断 /_DMA 的回调触发;
- F10X 的句柄做了轻量化设计,无多余的高端系列字段,减少 RAM 占用。
3. 中断 + 回调函数机制,解耦业务逻辑
HAL 库将外设底层中断处理和用户业务逻辑完全分离:
- 底层中断服务函数(如
USART1_IRQHandler)由 HAL 库实现,仅做状态更新和回调调用; - 用户只需重写弱回调函数(如
HAL_UART_RxCpltCallback),在其中编写业务逻辑,无需修改中断服务函数,降低开发难度。
4. 无缝集成 CubeMX/CubeIDE,自动化代码生成
HAL 库是 ST 官方工具链(CubeMX/CubeIDE)的唯一适配库:
- CubeMX 通过图形化配置完成 F10X 的时钟树、外设引脚、中断、DMA 等配置,自动生成 HAL 库的初始化代码,无需手动编写底层初始化;
- 生成的代码分模块管理(如
main.c、stm32f1xx_hal_msp.c),用户只需在指定区域编写业务代码,避免误改底层初始化。
5. 内置错误处理和基础中间件
- 所有 HAL 库 API 均返回错误码(
HAL_StatusTypeDef:HAL_OK/HAL_ERROR/HAL_BUSY/HAL_TIMEOUT),便于问题排查; - 内置轻量中间件(如 SPI/I2C 的外设驱动、UART 的串口收发),同时兼容 ST 的 CMSIS 标准和 RTOS(FreeRTOS),支持线程安全(F10X 基础版)。
6. F10X 专属轻量化优化
针对 F10X 系列Flash/RAM 资源有限的特点(如 C8T6:64K Flash、20K RAM),HAL 库做了裁剪:
- 移除高端系列的冗余功能(如外设时钟精细门控、硬件 CRC 加速的高级接口);
- 核心 API 无冗余嵌套,执行效率接近标准库,仅抽象层带来少量资源开销。
二、STM32F10X HAL 库与标准库的异同点
先明确:标准库的核心定位
STM32F10X 标准库(官方最终版为STM32F10x_StdPeriph_Lib_V3.5.0)是 ST 为 F10X 系列量身打造的传统驱动库,2016 年起正式停更,无后续维护和 BUG 修复。其核心是对寄存器的轻量级封装,无跨系列设计,所有 API 均为 F10X 专属。
1. 相同点
两者均为 ST 官方提供的驱动库,底层均基于 STM32F10X 的寄存器操作,核心目标一致:屏蔽底层寄存器细节,简化外设开发,具体相同点:
- 均基于CMSIS 标准(ARM Cortex-M3 内核标准),兼容内核层 API(如
NVIC_EnableIRQ、__enable_irq()); - 均支持 F10X 全系列外设(GPIO/UART/TIM/I2C/SPI/ADC 等),覆盖所有基础和常用功能;
- 均需先使能外设时钟(如 GPIOA 时钟、USART1 时钟),遵循 F10X 的时钟树规则;
- 均支持中断、轮询、DMA三种外设操作方式;
- 均提供官方例程和参考手册,便于快速上手。
2. 核心不同点(分维度对比,重点针对 F10X)
| 对比维度 | HAL 库(F10X 适配版) | 标准库(F10X V3.5) |
|---|---|---|
| 设计理念 | 跨系列通用,硬件抽象层高,面向工程化开发 | F10X 专属,轻量封装,面向底层寄存器学习 |
| API 特性 | 命名统一、跨系列兼容,无 F10X 专属 API | 命名为 F10X 定制,无跨系列兼容(如 F4 无法使用) |
| 外设管理 | 句柄化(Handle),配置 + 状态一体化封装 | 分散的配置结构体,无统一状态管理 |
| 初始化方式 | 主要通过 CubeMX图形化自动生成,手动编写少 | 完全手动编写初始化代码,需熟悉寄存器配置 |
| 中断处理 | 底层中断由 HAL 实现,用户重写回调函数 | 需手动编写中断服务函数,自行处理状态 |
| 工具链集成 | 无缝集成 CubeMX/CubeIDE,支持自动化开发 | 仅支持 MDK-ARM/Keil、IAR,无官方图形化工具 |
| 维护状态 | ST 官方持续维护,更新 BUG 修复和新特性 | 2016 年停更,无后续维护,存在已知 BUG |
| 资源占用 | 稍高(Flash+5%~20%,RAM+10%~30%),轻量化适配后可忽略 | 极低,接近直接寄存器操作,无抽象层开销 |
| 学习曲线 | 入门快(无需懂寄存器),深入需理解句柄 / 回调 | 入门慢(需熟悉 F10X 寄存器),深入后更灵活 |
| 工程扩展性 | 跨系列扩展极快(如 F1 迁 F4,仅需 CubeMX 重新配置) | 跨系列扩展几乎重写(无 API 兼容) |
| 中间件支持 | 内置轻量中间件,兼容 FreeRTOS/LWIP 等 | 无内置中间件,需手动移植第三方组件 |
三、STM32F10X HAL 库 / 标准库使用注意事项
(一)HAL 库使用注意事项(F10X 专属,重点避坑)
1. 必须重视 CubeMX 的配置,尤其是时钟树
F10X 的 HAL 库初始化代码由 CubeMX 生成,时钟树配置错误是最常见的问题:
- F103C8T6 的外部晶振(HSE)通常为 8MHz,需配置 PLL 倍频为 72MHz(F10X 最大主频),否则会导致
HAL_Delay、定时器定时不准确; - 务必在 CubeMX 中开启外设时钟(如 GPIOA、USART1),HAL 库不会自动使能外设时钟,未开启会导致外设无响应。
2.HAL_Delay的依赖与限制
HAL_Delay是 HAL 库的基础延时函数,依赖 SysTick 定时器(滴答定时器),使用时注意:
HAL_Delay的时基为 1ms,由SysTick_Handler中断驱动,不能在中断服务函数中调用HAL_Delay(中断优先级低于 SysTick,会导致死等);- 若项目中重写了
SysTick_Handler,需保留HAL_IncTick()调用,否则HAL_Delay失效; - F10X 的 SysTick 定时器若被其他功能占用(如 RTOS 的时基),需重新配置 HAL 库的时基源(修改
stm32f1xx_hal.c中的HAL_InitTick)。
3. 句柄的定义与使用规范
- 外设句柄建议定义为全局变量(或 static 全局),若定义为局部变量,中断 /_DMA 的回调函数将无法访问句柄,导致状态更新失败;
- 句柄初始化后禁止手动修改内部参数,需通过 HAL 库 API 修改(如
HAL_UART_MspConfig),否则会导致外设工作异常。
4. 阻塞式 API 与非阻塞式 API 的选择
HAL 库的外设操作 API 分阻塞式和中断 / DMA 非阻塞式,F10X 资源有限,需合理选择:
- 阻塞式 API(如
HAL_UART_Transmit、HAL_GPIO_TogglePin):简单易用,但会阻塞主循环,适合简单场景(如 LED 闪烁、单次串口发送); - 非阻塞式 API(如
HAL_UART_Transmit_IT、HAL_UART_Transmit_DMA):不阻塞主循环,适合多任务场景,但需通过回调函数处理完成事件,注意 F10X 的 DMA 通道数量有限(如 C8T6 有 7 个 DMA 通道)。
5. MSP 层代码的管理
CubeMX 生成的stm32f1xx_hal_msp.c是MSP 层(MCU Support Package),负责外设底层硬件初始化(如引脚映射、时钟使能、中断优先级配置):
- 禁止手动修改 MSP 层自动生成的代码,否则重新生成 CubeMX 代码时会被覆盖;
- 若需自定义硬件初始化,在 MSP 层的回调函数中编写(如
HAL_UART_MspInit),CubeMX 会保留用户添加的代码。
6. 中断优先级的配置规则
F10X 的 NVIC 为Cortex-M3 内核,支持 4 位抢占优先级 + 0 位响应优先级(或 2 位抢占 + 2 位响应,由NVIC_PriorityGroupConfig配置):
- HAL 库中通过
HAL_NVIC_SetPriority配置中断优先级,需先在 CubeMX 中配置优先级分组(如 Priority Group 2),否则优先级配置无效; - 回调函数的执行依赖中断,需合理设置外设中断优先级(如 UART 中断优先级高于定时器中断),避免中断嵌套异常。
(二)标准库使用注意事项(F10X 专属,停更后重点)
1. 仅使用官方最终版 V3.5.0,避免兼容问题
标准库有多个版本,V3.5.0 是 F10X 的最终官方版,支持 F10X 全系列(低密 / 中密 / 高密),低版本存在较多 BUG,且无后续修复,务必使用该版本。
2. 手动配置时钟树,需熟悉寄存器
标准库无图形化工具,时钟树需手动通过寄存器 / 库函数配置(如RCC_DeInit、RCC_HSEConfig、RCC_PLLConfig):
- 配置 PLL 倍频时,需注意 F10X 的PLL 最大输出频率为 72MHz,超过会导致芯片死机;
- 外设时钟需手动使能(如
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE)),忘记使能是最常见的错误。
3. 中断服务函数需手动编写,注意入口名
标准库无统一的中断处理层,需手动编写外设中断服务函数,函数名必须与启动文件(startup_stm32f10x_md.s)中的中断向量表一致:
- 例如 USART1 的中断服务函数名必须为
USART1_IRQHandler,否则中断无法触发; - 中断服务函数中需自行判断中断标志位(如
USART_GetITStatus(USART1, USART_IT_RXNE)),处理完成后需手动清除标志位(如USART_ClearITPendingBit),否则会重复触发中断。
4. 无状态管理,需自行处理外设忙状态
标准库无外设状态封装,使用中断 / DMA 时,需自行定义全局变量标记外设忙状态(如u8 uart1_tx_busy = 0),避免在忙状态下重复触发操作,导致数据丢失。
5. 库函数与寄存器可混合使用
标准库是寄存器的轻量级封装,支持库函数与寄存器直接混合使用(如GPIOA->ODR ^= GPIO_Pin_5替代GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_Toggle)),适合对执行效率要求高的场景。
6. 移植第三方组件需手动适配
标准库无内置中间件,移植 FreeRTOS/LWIP/SSD1306 等组件时,需手动适配 F10X 的底层驱动(如串口、SPI),无官方适配模板,开发效率低。
(三)通用注意事项(两者均需遵守)
- 引脚复用配置:使用 UART/SPI/I2C 等复用外设时,需先配置引脚为复用功能模式(F10X 为 GPIO_Mode_AF_PP),否则外设无法正常工作;
- DMA 通道映射:F10X 的 DMA 通道与外设是固定映射(如 USART1_TX 对应 DMA1_Channel4),需严格按照数据手册配置,避免通道错配;
- 低功耗模式的处理:进入睡眠 / 停机模式前,需关闭未使用的外设时钟,清除中断标志位,否则会导致唤醒异常;
- Flash/RAM 资源限制:F10X 的中密型号(如 C8T6)资源有限,避免编写冗余代码,标准库可通过条件编译裁剪无用功能,HAL 库可在 CubeMX 中关闭未使用的外设,减少资源占用。
四、STM32F10X 典型应用详解(HAL 库 + 标准库对比)
选取 F10X 最常用的 4 个典型应用,均基于STM32F103C8T6(8MHz HSE,72MHz 主频),给出核心代码片段和关键说明,省略重复的初始化框架(如 CubeMX 生成的 HAL 库框架、标准库的系统初始化框架)。
应用 1:GPIO 输出 ——LED 闪烁(最基础)
需求:PA5 引脚接 LED(共阳,低电平点亮),实现 1s 闪烁,使用轮询方式。
HAL 库实现(CubeMX 配置后)
- CubeMX 配置:PA5 设为GPIO_Output,推挽输出、上拉、高速;时钟树配置 8MHz HSE→PLL 72MHz;
- 核心代码(
main.c的while(1)中):
#include "stm32f1xx_hal.h" // CubeMX自动生成的LED句柄,全局定义在gpio.c中 extern GPIO_HandleTypeDef hgpioA; int main(void) { // HAL库系统初始化:时钟、SysTick、NVIC等(CubeMX自动生成) HAL_Init(); // 外设时钟初始化(CubeMX自动生成) SystemClock_Config(); // GPIO初始化(CubeMX自动生成) MX_GPIO_Init(); while (1) { // PA5置低:点亮LED(HAL库统一API) HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); HAL_Delay(1000); // 1ms延时,HAL库内置 // PA5置高:熄灭LED HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); HAL_Delay(1000); } }标准库实现
- 核心初始化 + 业务代码:
#include "stm32f10x.h" #include "stm32f10x_gpio.h" #include "stm32f10x_rcc.h" // 自定义延时函数(基于SysTick,1ms) void Delay_ms(u32 ms) { u32 i; SysTick_Config(72000); // 72MHz主频,SysTick每1ms中断一次 for(i=0; i<ms; i++) { while(!(SysTick->CTRL & (1<<16))); // 等待计数完成 } SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; // 关闭SysTick } int main(void) { // 1. 使能GPIOA时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 2. 配置PA5为推挽输出 GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 高速 GPIO_Init(GPIOA, &GPIO_InitStruct); // 3. 初始状态:熄灭LED(置高) GPIO_SetBits(GPIOA, GPIO_Pin_5); while(1) { GPIO_ResetBits(GPIOA, GPIO_Pin_5); // 点亮LED Delay_ms(1000); GPIO_SetBits(GPIOA, GPIO_Pin_5); // 熄灭LED Delay_ms(1000); } }关键对比
- HAL 库:无需手动配置 GPIO 结构体,CubeMX 自动生成,使用统一的
HAL_GPIO_WritePin,延时直接用HAL_Delay; - 标准库:需手动配置 GPIO 结构体、使能时钟,自定义延时函数,使用F10X 专属的
GPIO_SetBits/ResetBits。
应用 2:GPIO 输入 —— 按键检测(消抖)
需求:PB0 引脚接按键(上拉输入,按下为低电平),实现按键消抖,按下后翻转 PA5 的 LED 状态。
HAL 库实现
- CubeMX 配置:PB0 设为GPIO_Input,上拉模式;PA5 为 GPIO_Output;
- 核心代码(
main.c):
extern GPIO_HandleTypeDef hgpioA; extern GPIO_HandleTypeDef hgpioB; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); u8 key_flag = 0; // 按键标志位 while (1) { // 检测PB0是否为低电平(按键按下) if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET) { HAL_Delay(20); // 软件消抖 if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET && key_flag == 0) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // 翻转LED key_flag = 1; } } else { HAL_Delay(20); key_flag = 0; // 按键释放,清除标志 } } }标准库实现
// 复用应用1的Delay_ms和LED初始化 int main(void) { // 1. 使能GPIOB时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 2. 配置PB0为上拉输入 GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入 GPIO_Init(GPIOB, &GPIO_InitStruct); // 3. LED初始化(同应用1) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_SetBits(GPIOA, GPIO_Pin_5); u8 key_flag = 0; while(1) { if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0) // 检测低电平 { Delay_ms(20); if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0 && key_flag == 0) { GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)!GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_5)); key_flag = 1; } } else { Delay_ms(20); key_flag = 0; } } }关键对比
- HAL 库:使用
HAL_GPIO_ReadPin读取输入电平,HAL_GPIO_TogglePin直接翻转输出,API 更简洁; - 标准库:使用
GPIO_ReadInputDataBit读取输入,需手动通过位运算翻转输出,无专门的翻转库函数。
应用 3:UART 串口通信 ——printf 重定向(最常用)
需求:配置 USART1(PA9-TX、PA10-RX),波特率 9600,8N1,实现printf串口打印,接收串口数据并回显。
HAL 库实现
- CubeMX 配置:PA9 设为GPIO_AF_PP(复用推挽)、PA10 设为GPIO_IPU(上拉输入);USART1 波特率 9600,8N1,开启接收中断;
- 核心代码:
#include "stdio.h" extern UART_HandleTypeDef huart1; // CubeMX自动生成的UART句柄 u8 uart1_rx_buf[1] = {0}; // 接收缓冲区(单字节) // 重定向printf到USART1(HAL库阻塞式发送) int fputc(int ch, FILE *f) { HAL_UART_Transmit(&huart1, (u8*)&ch, 1, 100); // 阻塞发送1字节,超时100ms return ch; } // USART1接收完成回调函数(重写弱函数) void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART1) { HAL_UART_Transmit(&huart1, uart1_rx_buf, 1, 100); // 回显接收到的字节 // 重新开启中断接收,等待下一个字节 HAL_UART_Receive_IT(&huart1, uart1_rx_buf, 1); } } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_USART1_UART_Init(); // 开启USART1中断接收(单字节) HAL_UART_Receive_IT(&huart1, uart1_rx_buf, 1); printf("HAL库UART1初始化完成,等待数据...\r\n"); while (1) { // 主循环无业务,由中断处理接收 } }标准库实现
#include "stdio.h" #include "stm32f10x_usart.h" // 重定向printf到USART1 int fputc(int ch, FILE *f) { while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 等待发送缓冲区空 USART_SendData(USART1, (u8)ch); return ch; } // USART1中断服务函数(必须与启动文件一致) void USART1_IRQHandler(void) { u8 rx_data; if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) // 接收非空中断 { rx_data = USART_ReceiveData(USART1); // 读取接收到的字节 USART_SendData(USART1, rx_data); // 回显 USART_ClearITPendingBit(USART1, USART_IT_RXNE); // 清除中断标志 } } // USART1初始化函数 void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART_InitStruct; NVIC_InitTypeDef NVIC_InitStruct; // 1. 使能GPIOA和USART1时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); // 2. 配置PA9(TX)为复用推挽,PA10(RX)为上拉输入 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOA, &GPIO_InitStruct); // 3. 配置USART1:9600,8N1 USART_InitStruct.USART_BaudRate = 9600; USART_InitStruct.USART_WordLength = USART_WordLength_8b; USART_InitStruct.USART_StopBits = USART_StopBits_1; USART_InitStruct.USART_Parity = USART_Parity_No; USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; USART_Init(USART1, &USART_InitStruct); // 4. 配置NVIC:USART1中断优先级 NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级2 NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; // 响应优先级0 NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); // 5. 开启USART1和接收非空中断 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); USART_Cmd(USART1, ENABLE); } int main(void) { // 系统时钟初始化(72MHz) SystemInit(); // USART1初始化 USART1_Init(); printf("标准库UART1初始化完成,等待数据...\r\n"); while(1) { // 主循环无业务 } }关键对比
- HAL 库:无需手动编写中断服务函数,只需重写接收完成回调函数,开启中断后需重新触发才能继续接收,API 统一;
- 标准库:需手动编写中断服务函数,自行判断 / 清除中断标志位,手动配置 NVIC 优先级,所有步骤均需手写,更贴近底层。
应用 4:定时器定时中断 ——1s 定时翻转 LED
需求:配置 TIM2(通用定时器),实现 1s 定时中断,中断中翻转 PA5 的 LED 状态(72MHz 主频,预分频器 7199,自动重装值 9999,计数频率 10kHz,定时 1s)。
HAL 库实现
- CubeMX 配置:TIM2 设为Internal Clock(内部时钟),预分频器 7199,自动重装值 9999,开启更新中断;NVIC 中开启 TIM2 中断,优先级分组 2;
- 核心代码:
extern TIM_HandleTypeDef htim2; // CubeMX自动生成的定时器句柄 extern GPIO_HandleTypeDef hgpioA; // TIM2更新中断回调函数(重写弱函数) void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM2) { HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // 翻转LED } } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM2_Init(); // 开启TIM2定时中断(更新中断) HAL_TIM_Base_Start_IT(&htim2); while (1) { // 主循环无业务,由定时器中断处理 } }标准库实现
c
运行
#include "stm32f10x_tim.h" // TIM2中断服务函数 void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) // 更新中断 { GPIO_ToggleBits(GPIOA, GPIO_Pin_5); // 翻转LED(需提前初始化PA5) TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 清除中断标志 } } // TIM2初始化函数(1s定时中断) void TIM2_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; NVIC_InitTypeDef NVIC_InitStruct; // 1. 使能TIM2时钟(APB1,最大36MHz) RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 2. 配置TIM2:预分频7199,自动重装9999,向上计数 TIM_TimeBaseInitStruct.TIM_Period = 9999; // 自动重装值 TIM_TimeBaseInitStruct.TIM_Prescaler = 7199; // 预分频器 TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct); // 3. 配置NVIC:TIM2中断优先级 NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 3; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); // 4. 开启TIM2更新中断和定时器 TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); TIM_Cmd(TIM2, ENABLE); } int main(void) { // 1. LED初始化(PA5,同应用1) RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); // 2. TIM2初始化 TIM2_Init(); while(1) { // 主循环无业务 } }关键对比
- HAL 库:CubeMX 自动计算定时器参数,只需开启中断并重写更新回调函数,无需处理中断服务函数和标志位;
- 标准库:需手动配置定时器结构体、NVIC 优先级,编写中断服务函数,自行判断 / 清除更新中断标志位,所有步骤均需手写。
五、总结与选型建议
1. 核心知识点回顾
- HAL 库:跨系列通用、CubeMX 图形化开发、句柄化管理、回调函数中断、官方持续维护,是工程化开发的首选,F10X 适配版做了轻量化优化,资源占用可忽略;
- 标准库:F10X 专属、轻量贴近寄存器、无图形化工具、需手动编写所有代码,适合底层学习和超小资源项目,但已停更,无官方支持;
- 两者底层均基于寄存器,F10X 的 HAL 库执行效率与标准库差距极小,仅抽象层带来少量资源开销,完全满足 F10X 的应用场景。
2. 选型建议(基于 F10X 系列)
选 HAL 库的场景
- 实际工程项目开发,追求开发效率、可维护性和跨系列扩展性;
- 新手入门,不想深入寄存器,希望快速实现功能;
- 需使用 ST 官方工具链(CubeMX/CubeIDE)或移植 RTOS / 中间件;
- 项目可能后续迁移到其他 STM32 系列(如 F4/G4)。
选标准库的场景
- 底层学习,希望理解 STM32F10X 的寄存器结构和外设工作原理;
- 超小资源项目(如 F100F4,16K Flash),对资源占用要求极致;
- 维护老项目(基于标准库开发的现有项目),无需重新开发。
3. 最终建议
优先选择 HAL 库:无论是新手入门还是工程开发,HAL 库都是目前的最优解,ST 已将所有资源投入到 HAL 库和 Cube 生态,标准库仅作为历史产物存在。对于 F10X 系列,HAL 库的轻量化适配完全满足其应用需求,且能让你掌握 STM32 的通用开发方法,为后续学习高端系列打下基础。