一、HAL库和标准库的区别
HAL库(硬件抽象层)和标准库(标准外设库)都是意法半导体(ST)为STM32微控制器提供的软件开发库,但它们在设计理念、开发方式和适用场景上存在显著差异。
简单来说,HAL库是ST官方主推的未来方向,而标准库是其早期产品,目前已不再进行新功能开发和维护。
🎯 核心理念与抽象层级
-
标准库 (Standard Peripheral Library):可以看作是手动挡汽车。它对硬件寄存器的封装程度较低,开发者需要更直接地操作和配置外设的寄存器,代码更贴近硬件底层。
-
HAL库 (Hardware Abstraction Layer):更像是自动挡汽车。它提供了更高层次的抽象,通过统一的API接口屏蔽了底层硬件的细节,开发者无需关心复杂的寄存器配置,可以更专注于应用逻辑。
📊 主要区别对比
|
特性维度 |
标准库 (StdPeriph) |
HAL库 (Hardware Abstraction Layer) |
|---|---|---|
|
可移植性 |
差。不同系列的STM32(如F1到F4)代码不通用,移植需大量修改。 |
好。API接口统一,代码在不同系列间(如F1到H7)移植非常方便。 |
|
开发效率 |
较低。需要手动编写大量初始化代码,对硬件细节要求高。 |
高。通常配合STM32CubeMX图形化工具,可自动生成初始化代码框架。 |
|
代码效率 |
高。代码更精简,函数调用层级少,执行效率更高。 |
较低。代码体积更大,函数调用层级深,存在一定的性能开销。 |
|
资源占用 |
少。生成的代码量小,更适合Flash和RAM资源受限的芯片。 |
多。库本身代码量庞大,会占用更多Flash和RAM。 |
|
学习曲线 |
陡峭。需要深入理解芯片参考手册和寄存器。 |
平缓。接口统一且规范,入门相对容易。 |
|
中断处理 |
直接在中断服务函数(ISR)中编写处理逻辑。 |
采用回调函数(Callback)机制,将事件检测与业务逻辑分离。 |
🛠️ 代码风格示例 (以GPIO初始化为例)
-
标准库:配置过程相对繁琐,需要分别使能时钟、配置结构体成员,然后调用初始化函数。
// 1. 使能GPIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置GPIO参数
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;
// 3. 调用初始化函数
GPIO_Init(GPIOA, &GPIO_InitStruct);
-
HAL库:代码结构更清晰,通过统一的初始化函数完成配置。
// 1. 使能GPIO时钟 (使用宏定义)
__HAL_RCC_GPIOA_CLK_ENABLE();
// 2. 配置GPIO参数
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
// 3. 调用统一的HAL初始化函数
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
🤔 如何选择?
选择 HAL 库的场景:
-
新项目开发:尤其是使用STM32F4、H7、L4、U5等新型号芯片时,HAL库是官方唯一推荐的选择。
-
需要快速原型开发:配合STM32CubeMX,可以极大缩短开发周期。
-
代码需要跨平台移植:如果你的项目未来可能更换不同系列的STM32芯片,HAL库能节省大量移植工作。
-
团队协作开发:统一的接口和规范有利于代码的维护和阅读。
选择标准库的场景:
-
维护老旧项目:很多基于STM32F1等早期芯片的现有产品仍在使用标准库。
-
资源极其受限:在Flash或RAM非常小的芯片上,标准库的代码体积优势至关重要。
-
对实时性要求极高:在电机控制等对执行效率有严苛要求的场景,标准库的性能优势更明显。
二、低功耗技术
①睡眠模式:CPU时钟关闭,外设继续运行,任意中断或事件均可唤醒。
唤醒源:任意中断(EXTI、定时器、USART等)。
②停止模式:所有时钟停止(1.2V域时钟关闭),但寄存器和SRAM内容保留。功耗约几十μA。
唤醒源:任意EXTI线、RTC闹钟、USB等。
③待机模式:最低功耗模式(约2μA),内核电源关闭,SRAM和寄存器内容丢失(除备份域外)。唤醒相当于系统复位。
唤醒源:WKUP引脚(PA0)上升沿、RTC闹钟、NRST引脚复位、IWDG复位。
HAL库
1、睡眠模式
#include "stm32f4xx.h"
void Sleep_Mode(void)
{
// 1. 关闭不需要的外设时钟以降低功耗
// RCC->AHB1ENR &= ~(RCC_AHB1ENR_GPIOAEN | ...);
// 2. 进入睡眠模式 (WFI - 等待中断唤醒)
// 注意:需要确保有足够的中断源,否则将永久睡眠
__WFI(); // 或者 __WFE(); (等待事件唤醒)
}
// 示例:利用SysTick中断每1秒唤醒一次
volatile uint32_t uwTick = 0;
void SysTick_Handler(void)
{
uwTick++;
}
int main(void)
{
// 配置SysTick每1ms中断一次
if (HAL_Init() != HAL_OK)
{
Error_Handler();
}
SystemClock_Config(); // 配置系统时钟
// 进入睡眠模式前,可以设置优先级分组,确保唤醒中断可以响应
while (1)
{
// 进入睡眠模式
__WFI(); // 等待中断唤醒
// 被唤醒后继续执行
// 可以添加低功耗唤醒后的处理代码
}
}
2、停止模式
#include "stm32f4xx.h"
void Stop_Mode(void)
{
// 1. 配置唤醒源(如EXTI外部中断)
// 例如配置PA0为EXTI0唤醒源
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发中断
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 使能EXTI0中断
HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
// 2. 进入停止模式
// 清除唤醒标志
__HAL_RCC_WAKEUPSTOP_CLR_FLAG();
// 进入停止模式,使用WFI指令,退出后使用HSI作为系统时钟
HAL_PWR_EnterSTOPMode(PWR_MAINREGULATOR_ON, PWR_STOPENTRY_WFI);
// 3. 唤醒后恢复系统时钟
// 停止模式唤醒后默认使用HSI(16MHz),需要重新配置为原来的时钟(如168MHz)
SystemClock_Config(); // 重新配置系统时钟
}
// 中断服务函数
void EXTI0_IRQHandler(void)
{
if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET)
{
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
// 唤醒后的处理代码
}
}
int main(void)
{
HAL_Init();
SystemClock_Config(); // 配置系统时钟为168MHz
while(1)
{
// 进入停止模式
Stop_Mode();
// 唤醒后执行的任务
// 可以在这里添加需要周期性执行的代码
}
}
3、待机模式
#include "stm32f4xx.h"
void Standby_Mode(void)
{
// 1. 使能电源接口时钟
__HAL_RCC_PWR_CLK_ENABLE();
// 2. 清除待机标志
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
// 3. 使能WKUP引脚唤醒(PA0)
// 注意:PA0必须配置为推挽输入模式
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 使能PA0作为唤醒源(上升沿唤醒)
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1); // PWR_WAKEUP_PIN1对应PA0
// 4. 进入待机模式
HAL_PWR_EnterSTANDBYMode();
// 以下代码不会执行,因为待机模式相当于复位
}
int main(void)
{
HAL_Init();
SystemClock_Config();
// 检测是否从待机模式唤醒
if(__HAL_PWR_GET_FLAG(PWR_FLAG_SB) != RESET)
{
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
// 待机唤醒后的处理(相当于上电复位)
// 可以添加恢复现场或提示信息
}
while(1)
{
// 执行主循环任务...
// 当条件满足时进入待机模式
if(/* 需要进入待机模式的条件 */)
{
Standby_Mode();
}
}
}
标准库
1、睡眠模式
#include "stm32f4xx.h"
void Sleep_Mode(void)
{
// 1. 关闭不需要的外设时钟以降低功耗
// RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, DISABLE);
// RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, DISABLE);
// 等等...
// 2. 进入睡眠模式 (WFI - 等待中断唤醒)
// 注意:需要确保有足够的中断源,否则将永久睡眠
__WFI(); // 或者 __WFE(); (等待事件唤醒)
}
// 示例:利用SysTick中断每1ms唤醒一次
volatile uint32_t uwTick = 0;
void SysTick_Handler(void)
{
uwTick++;
}
int main(void)
{
// 配置SysTick每1ms中断一次
// 系统时钟配置(使用标准库的时钟配置函数)
SystemInit(); // 初始化系统时钟和SysTick
// 配置SysTick中断周期为1ms(假设系统时钟168MHz)
if (SysTick_Config(SystemCoreClock / 1000))
{
while(1); // 配置失败
}
while (1)
{
// 进入睡眠模式
__WFI(); // 等待中断唤醒
// 被唤醒后继续执行
// 可以添加低功耗唤醒后的处理代码
}
}
2、停止模式
#include "stm32f4xx.h"
#include "stm32f4xx_pwr.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_exti.h"
#include "misc.h"
void Stop_Mode(void)
{
// 1. 配置唤醒源(如EXTI外部中断)
// 例如配置PA0为EXTI0唤醒源
GPIO_InitTypeDef GPIO_InitStruct;
EXTI_InitTypeDef EXTI_InitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
// 使能GPIOA时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
// 配置PA0为中断输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 连接EXTI0到PA0
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
// 配置EXTI0为下降沿触发
EXTI_InitStruct.EXTI_Line = EXTI_Line0;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
// 配置NVIC中断优先级
NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
// 2. 进入停止模式
// 使能PWR时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
// 清除唤醒标志
PWR_ClearFlag(PWR_FLAG_WU);
// 进入停止模式,使用WFI指令,退出后使用HSI作为系统时钟
PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI);
// 3. 唤醒后恢复系统时钟
// 停止模式唤醒后默认使用HSI(16MHz),需要重新配置为原来的时钟(如168MHz)
SystemInit(); // 重新初始化系统时钟
}
// 中断服务函数
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0) != RESET)
{
EXTI_ClearITPendingBit(EXTI_Line0);
// 唤醒后的处理代码
}
}
int main(void)
{
// 系统初始化
SystemInit(); // 配置系统时钟为168MHz
while(1)
{
// 进入停止模式
Stop_Mode();
// 唤醒后执行的任务
// 可以在这里添加需要周期性执行的代码
}
}
3、待机模式
#include "stm32f4xx.h"
#include "stm32f4xx_pwr.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
void Standby_Mode(void)
{
// 1. 使能电源接口时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
// 2. 清除待机标志
PWR_ClearFlag(PWR_FLAG_SB);
// 3. 使能WKUP引脚唤醒(PA0)
// 注意:PA0必须配置为输入模式
GPIO_InitTypeDef GPIO_InitStruct;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_DOWN; // 下拉
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 使能PA0作为唤醒源(上升沿唤醒)
PWR_WakeUpPinCmd(ENABLE); // 使能WKUP引脚(PA0)
// 4. 进入待机模式
PWR_EnterSTANDBYMode();
// 以下代码不会执行,因为待机模式相当于复位
}
int main(void)
{
// 系统初始化
SystemInit();
// 使能PWR时钟(用于检测待机标志)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
// 检测是否从待机模式唤醒
if(PWR_GetFlagStatus(PWR_FLAG_SB) != RESET)
{
PWR_ClearFlag(PWR_FLAG_SB);
// 待机唤醒后的处理(相当于上电复位)
// 可以添加恢复现场或提示信息
}
while(1)
{
// 执行主循环任务...
// 当条件满足时进入待机模式
if(/* 需要进入待机模式的条件 */)
{
Standby_Mode();
}
}
}

150

被折叠的 条评论
为什么被折叠?



