Jiangxiaopi 发表于 2025-11-9 15:03

STM32F1 LL 库和HAL 库在GPIO 上的区别

区别
STM32F1 的LL 库在GPIO_PIN 定义上用了特别的处理,和HAL 库不一样,不能按HAL 库或标准库的惯例处理。LL 的GPIO 头文件中Pin 的定义如下:

/** @defgroup GPIO_LL_EC_PIN PIN
* @{
*/
#define LL_GPIO_PIN_0                  ((GPIO_BSRR_BS0<< GPIO_PIN_MASK_POS) | 0x00000001U)/*!< Select pin 0*/
#define LL_GPIO_PIN_1                  ((GPIO_BSRR_BS1<< GPIO_PIN_MASK_POS) | 0x00000002U)/*!< Select pin 1*/
#define LL_GPIO_PIN_2                  ((GPIO_BSRR_BS2<< GPIO_PIN_MASK_POS) | 0x00000004U)/*!< Select pin 2*/
#define LL_GPIO_PIN_3                  ((GPIO_BSRR_BS3<< GPIO_PIN_MASK_POS) | 0x00000008U)/*!< Select pin 3*/
#define LL_GPIO_PIN_4                  ((GPIO_BSRR_BS4<< GPIO_PIN_MASK_POS) | 0x00000010U)/*!< Select pin 4*/
#define LL_GPIO_PIN_5                  ((GPIO_BSRR_BS5<< GPIO_PIN_MASK_POS) | 0x00000020U)/*!< Select pin 5*/
#define LL_GPIO_PIN_6                  ((GPIO_BSRR_BS6<< GPIO_PIN_MASK_POS) | 0x00000040U)/*!< Select pin 6*/
#define LL_GPIO_PIN_7                  ((GPIO_BSRR_BS7<< GPIO_PIN_MASK_POS) | 0x00000080U)/*!< Select pin 7*/
#define LL_GPIO_PIN_8                  ((GPIO_BSRR_BS8<< GPIO_PIN_MASK_POS) | 0x04000001U)/*!< Select pin 8*/
// ...


而HAL 库的定义是:

/** @defgroup GPIO_pins_define GPIO pins define
* @{
*/
#define GPIO_PIN_0               ((uint16_t)0x0001)/* Pin 0 selected    */
#define GPIO_PIN_1               ((uint16_t)0x0002)/* Pin 1 selected    */
#define GPIO_PIN_2               ((uint16_t)0x0004)/* Pin 2 selected    */
#define GPIO_PIN_3               ((uint16_t)0x0008)/* Pin 3 selected    */
#define GPIO_PIN_4               ((uint16_t)0x0010)/* Pin 4 selected    */
#define GPIO_PIN_5               ((uint16_t)0x0020)/* Pin 5 selected    */
#define GPIO_PIN_6               ((uint16_t)0x0040)/* Pin 6 selected    */

// ...




HAL 库的Pin 定义就是简单的pin mask,GPIO_PIN_N = 0x1 << N,和GPIO 里ODR,IDR 等寄存器的操作对应,而LL 库把pin mask 左移了八位,然后在低8 位和高8 位附加了其他信息。

所以,首先,HAL 的pin_mask 是uint16_t,而LL 要用uint32_t。如果自己写了函数接收Pin 作为参数,LL 库的类型可能会不匹配。其次,使用LL 库的Pin 定义时,需要一些额外处理,比如LL 里面给引脚设置高电平的函数:

__STATIC_INLINE void LL_GPIO_SetOutputPin(GPIO_TypeDef *GPIOx, uint32_t PinMask)
{
WRITE_REG(GPIOx->BSRR, (PinMask >> GPIO_PIN_MASK_POS) & 0x0000FFFFU);
}


给BSRR 赋值之前,先要把Pin 右移8 位,也就是把pin mask 挪回去,还要把高8 位的东西去掉。对比HAL 库的写引脚电平函数:

void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
{
/* Check the parameters */
assert_param(IS_GPIO_PIN(GPIO_Pin));
assert_param(IS_GPIO_PIN_ACTION(PinState));

if (PinState != GPIO_PIN_RESET)
{
    GPIOx->BSRR = (uint32_t)GPIO_Pin;
}
else
{
    GPIOx->BRR = (uint32_t)GPIO_Pin;
}
}


直接用Pin 给BSRR 赋值,不需要额外处理。另外,STM32F0 的LL 库也是没有额外处理的:

__STATIC_INLINE void LL_GPIO_SetOutputPin(GPIO_TypeDef *GPIOx, uint32_t PinMask)
{
WRITE_REG(GPIOx->BSRR, PinMask);
}



没去细究pin_mask 里附加的那些东西有什么用,估计是和F1 系列特殊的GPIO 模式寄存器有关。F0 和F4 系列GPIO 寄存器把输出模式、速度、上下拉之类的配置都用单独的寄存器实现,而F1 系列是全部塞进CRH 和CRL 寄存器里,各个模式之间相互耦合,某一个寄存器位的含义取决于其他寄存器位的值;这就给驱动代码编写造成了麻烦,比如,没办法临时将输出切换成输入,而不修改原来配置的输出速度。

影响
如果实际代码中,调用写GPIO 引脚函数时用的参数都是常量,比如:

#define LED_PIN    LL_GPIO_PIN_0
#define LED_PORT   GPIOB

// ...

// 拉高LED 引脚
LL_GPIO_SetOutputPin(LED_PORT, LED_PIN);



这种情况编译器有可能把右移和位运算都优化省略掉,变成常量操作。但是如果引脚参数不是常量,而是用变量传入的,比如结构体:

typedef struct {
        GPIO_TypeDef *port;
        uint32_t pin_mask;
} PinType;

// 多个引脚放进一个数组
const PinType pins[] = {{GPIOB, LL_GPIO_PIN_0}, {GPIOB, LL_GPIO_PIN_1}, {GPIOB, LL_GPIO_PIN_2}};

void 拉高所有引脚() {
        for(int i = 0; i< 3; ++i) {
                LL_GPIO_SetOutputPin(pins.port, pins.pin_mask);
        }
}



此时,对LL 函数的调用难以被优化,引脚操作的效率有所降低。当然效率低一点还是其次,重点是我的代码不兼容STM32F1 的LL 库这种引脚定义ヽ(≧□≦)ノ。除了STM32F1,其他单片机的库代码基本都是HAL 库那样定义的,所以我就默认pin mask 可以用(0x1 << N) 这种方式生成,然而拿这种pin mask 去调用LL 库的函数是没效果的。总之,建议别用STM32F1 的LL 库,实在用不了HAL,GPIO 操作很简单,就自己写点寄存器代码喽。
————————————————
版权声明:本文为CSDN博主「刻BITTER」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Etberzin/article/details/143611283

51xlf 发表于 2025-11-14 20:07

HAL 库追求 “跨型号兼容性、易用性、封装完整性”,LL 库追求 “轻量、高效、底层直接控制”。

everyrobin 发表于 2025-11-14 21:32

STM32CubeMX 支持 HAL

jimmhu 发表于 2025-11-14 21:49

HAL库
执行效率较低由于抽象层级高,函数调用开销较大,可能影响实时性。
适合原型开发快速验证功能,后续可优化为LL库或直接操作寄存器。
LL库
执行效率高接近寄存器操作,适合对性能要求严格的场景。
代码体积更小减少不必要的函数调用和条件编译,适合资源受限的STM32F1。

wengh2016 发表于 2025-11-15 16:14

均支持 STM32F1 GPIO 的所有核心功能

mickit 发表于 2025-11-15 16:36

LL 库        执行效率高,无冗余延迟

minzisc 发表于 2025-11-17 14:03

推荐使用HAL库以利用STM32CubeMX等工具提升开发效率

geraldbetty 发表于 2025-11-17 15:25

HAL 库通过 GPIO_InitTypeDef 结构体封装所有配置参数,调用 HAL_GPIO_Init() 一次性完成配置,无需关注寄存器细节。

sheflynn 发表于 2025-11-17 16:45

实现方式不同            

sesefadou 发表于 2025-11-17 17:49

两者的 API 设计风格、抽象程度、易用性、代码风格​ 存在显著差异。

beacherblack 发表于 2025-11-17 20:29


STM32F0的LL库:无额外处理,引脚定义和操作逻辑与HAL库类似
STM32F1的LL库:因GPIO寄存器模式耦合,需附加信息区分配置,导致操作复杂化

phoenixwhite 发表于 2025-11-17 21:41

从HAL库入手,快速掌握STM32开发流程。

iyoum 发表于 2025-11-18 22:27

LL库设计 接近HAL库            

ingramward 发表于 2025-11-19 16:29

优先选择HAL库            

claretttt 发表于 2025-11-21 17:58

HAL 库的 GPIO 函数位于stm32f1xx_hal_gpio.h/.c
LL 库的 GPIO 函数位于stm32f1xx_ll_gpio.h/.c

youtome 发表于 2025-11-21 19:07

HAL 库        API 统一,CubeMX 可视化配置,无需关注底层细节

chenci2013 发表于 2025-11-21 20:26

HAL库:
使用结构体配置GPIO参数,代码可读性强。
初始化函数封装了时钟使能、模式配置等步骤。
LL库:
通过单独的函数调用配置每个参数。
需要手动使能外设时钟。

lzbf 发表于 2025-11-21 21:33

HAL库的API设计尽量保持跨STM32系列的一致性,代码移植性较好。

sesefadou 发表于 2025-11-21 22:28

LL库的API可能因STM32系列不同而略有差异

updownq 发表于 2025-11-23 12:18

STM32F1系列的LL库和HAL库在GPIO操作上存在显著差异,主要体现在引脚定义、寄存器操作方式及初始化逻辑等方面。
页: [1] 2
查看完整版本: STM32F1 LL 库和HAL 库在GPIO 上的区别