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
HAL 库追求 “跨型号兼容性、易用性、封装完整性”,LL 库追求 “轻量、高效、底层直接控制”。 STM32CubeMX 支持 HAL HAL库
执行效率较低由于抽象层级高,函数调用开销较大,可能影响实时性。
适合原型开发快速验证功能,后续可优化为LL库或直接操作寄存器。
LL库
执行效率高接近寄存器操作,适合对性能要求严格的场景。
代码体积更小减少不必要的函数调用和条件编译,适合资源受限的STM32F1。 均支持 STM32F1 GPIO 的所有核心功能 LL 库 执行效率高,无冗余延迟 推荐使用HAL库以利用STM32CubeMX等工具提升开发效率 HAL 库通过 GPIO_InitTypeDef 结构体封装所有配置参数,调用 HAL_GPIO_Init() 一次性完成配置,无需关注寄存器细节。 实现方式不同 两者的 API 设计风格、抽象程度、易用性、代码风格 存在显著差异。
STM32F0的LL库:无额外处理,引脚定义和操作逻辑与HAL库类似
STM32F1的LL库:因GPIO寄存器模式耦合,需附加信息区分配置,导致操作复杂化 从HAL库入手,快速掌握STM32开发流程。 LL库设计 接近HAL库 优先选择HAL库 HAL 库的 GPIO 函数位于stm32f1xx_hal_gpio.h/.c
LL 库的 GPIO 函数位于stm32f1xx_ll_gpio.h/.c HAL 库 API 统一,CubeMX 可视化配置,无需关注底层细节 HAL库:
使用结构体配置GPIO参数,代码可读性强。
初始化函数封装了时钟使能、模式配置等步骤。
LL库:
通过单独的函数调用配置每个参数。
需要手动使能外设时钟。 HAL库的API设计尽量保持跨STM32系列的一致性,代码移植性较好。 LL库的API可能因STM32系列不同而略有差异 STM32F1系列的LL库和HAL库在GPIO操作上存在显著差异,主要体现在引脚定义、寄存器操作方式及初始化逻辑等方面。
页:
[1]
2