> 技术文档 > 深入剖析 STM32:HAL、标准库、LL 库与寄存器操作_stm32ll库

深入剖析 STM32:HAL、标准库、LL 库与寄存器操作_stm32ll库


深入剖析 STM32:HAL、标准库、LL 库与寄存器操作

引言

        在 STM32 微控制器的开发领域,存在着多种开发方式,在入门STM32的时候,首先都要先选择一种要用的开发方式,不同的开发方式会导致你编程的架构是完全不一样的。一般大多数都会选用标准库和HAL库,而极少部分人会通过直接配置寄存器进行开发。开发方式主要包括 HAL(Hardware Abstraction Layer,硬件抽象层)库、标准库、LL(Low Layer,低层)库以及直接进行寄存器操作。每一种方式都有其独特的特点和适用场景,了解它们之间的区别和联系,能够帮助开发者根据具体项目需求选择最合适的开发方法。

寄存器操作

原理

        寄存器操作堪称最底层的开发方式。在 STM32 里,每个外设都配备了一系列与之对应的寄存器,这些寄存器宛如微控制器的 “控制中心”,开发者可通过直接读写它们来掌控外设的工作模式与状态等。以配置 GPIO 引脚的输出模式为例,就需要直接对 GPIO 端口的相关寄存器进行操作。

        不过,STM32 的寄存器数量繁多,开发者根本无法将它们全部记住。在开发过程中,常常需要频繁翻查芯片的数据手册,这使得直接操作寄存器变得十分费力。然而,仍有一小部分开发者钟情于直接操作寄存器,因为这种方式能让他们更接近硬件原理,真正做到知其然且知其所以然。以下代码是本人通过寄存器模板在STM32F10x系列来编写的,实现让LED灯闪烁和蜂鸣器声响功能。

示例代码

#include \"stm32f10x.h\"​//延时函数void delay(unsigned int i){ while(i--);}​//防止报错void SystemInit(void){​}​//入口函数int main(void){ //led的初始化 //1.打开GPIOB控制器的时钟 //RCC_APB2ENR[3] = 1 RCC_APB2ENR |= (1 << 3); RCC_APB2ENR |= (1 << 6); //2.配置GPIO5-推挽输出,50MHz //GPIO_CRL[23:20] = 0011 GPIOB_CRL &= ~(0xf << 20);//[23:20]=0000 GPIOB_CRL |= (3 << 20);//[23:20]=11 GPIOE_CRL &= ~(0xf << 20);//[23:20]=0000 GPIOE_CRL |= (3 << 20); GPIOB_CRH &= ~(0XF); GPIOB_CRH |= 3; //3.配置PB5,输出高电平 //GPIOB_ODR[5] = 1 GPIOB_ODR |= (1 << 5); GPIOE_ODR |= (1 << 5); GPIOB_ODR &= ~(1 << 8); while(1){ //开灯,打开蜂鸣器 GPIOB_ODR &= ~(1 << 5); GPIOE_ODR |= (1 << 5); GPIOB_ODR |= (1 << 8); //延时 delay(0xfffff); //关灯,关闭蜂鸣器 GPIOB_ODR |= (1 << 5); GPIOE_ODR &= ~(1 << 5); GPIOB_ODR &= ~(1 << 8); //延时 delay(0xfffff); }}
#ifndef __STM32F10X_H#define __STM32F10X_H​//定义宏表示两条总线#define PERIPH_BASE ((unsigned int)0x40000000)#define APB2PERIPH_BASE (PERIPH_BASE+0x10000)#define AHBPERIPH_BASE (PERIPH_BASE+0x20000)​//定义宏表示两个控制器地址#define GPIOB_BASE (APB2PERIPH_BASE+0x0C00)#define GPIOE_BASE (APB2PERIPH_BASE+0x1800)#define RCC_BASE (AHBPERIPH_BASE+0x1000)​//定义宏表示寄存器#define GPIOB_CRL (*(unsigned int*)(GPIOB_BASE+0))#define GPIOB_CRH (*(unsigned int*)(GPIOB_BASE+0x04))#define GPIOB_ODR (*(unsigned int*)(GPIOB_BASE+0x0C))#define RCC_APB2ENR (*(unsigned int*)(RCC_BASE+0x18)) #define GPIOE_CRL (*(unsigned int*)(GPIOE_BASE+0))#define GPIOE_ODR (*(unsigned int*)(GPIOE_BASE+0x0C))​#endif

优缺点

  • 优点:代码执行效率高,占用资源少,开发者能够精确控制硬件的每一个细节,对硬件的理解也更加深入。

  • 缺点:开发难度大,代码的可读性和可维护性较差,开发周期长,尤其是在处理复杂外设时,需要开发者对硬件手册有深入的了解。

标准库

原理

        上面也提到了,STM32有非常多的寄存器,而导致了开发困难,所以ST 公司早期推出的一套用于简化 STM32 开发的库(标准库)。它将寄存器操作进行了封装,提供了一系列的函数和结构体,开发者可以通过调用这些函数来完成对外设的配置和操作,而不需要直接操作寄存器。

示例代码

#include \"stm32f10x.h\"​GPIO_InitTypeDef GPIO_InitStructure;​int main(void){    // 使能GPIOA时钟    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);​    // 配置PA0为推挽输出模式    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    GPIO_Init(GPIOA, &GPIO_InitStructure);​    while (1)   {        // 点亮LED        GPIO_SetBits(GPIOA, GPIO_Pin_0);        for (int i = 0; i < 1000000; i++);​        // 熄灭LED        GPIO_ResetBits(GPIOA, GPIO_Pin_0);        for (int i = 0; i < 1000000; i++);   }}

优缺点

  • 优点:相对于寄存器操作,标准库的代码可读性和可维护性有了很大的提高,开发难度降低,开发周期缩短,适合初学者快速上手。

  • 缺点:随着 STM32 产品线的不断丰富,标准库的维护成本越来越高,ST 公司逐渐停止了对标准库的更新,并且标准库的代码效率相对寄存器操作会有所降低。

HAL 库

原理

        HAL 库是 ST 公司推出的新一代硬件抽象层库,全称就是Hardware Abstraction Layer(抽象印象层)。旨在提供统一的 API 接口,方便开发者在不同系列的 STM32 微控制器之间进行移植。HAL 库对底层硬件进行了高度抽象,开发者只需要关注应用层的逻辑,而不需要过多地了解硬件细节。

示例代码

#include \"stm32f1xx_hal.h\"​GPIO_InitTypeDef GPIO_InitStruct = {0};​void SystemClock_Config(void);static void MX_GPIO_Init(void);​int main(void){    HAL_Init();    SystemClock_Config();    MX_GPIO_Init();​    while (1)   {        // 点亮LED        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);        HAL_Delay(1000);​        // 熄灭LED        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);        HAL_Delay(1000);   }}​void SystemClock_Config(void){    RCC_OscInitTypeDef RCC_OscInitStruct = {0};    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};​    /** Initializes the RCC Oscillators according to the specified parameters    * in the RCC_OscInitTypeDef structure.    */    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;    RCC_OscInitStruct.HSIState = RCC_HSI_ON;    RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)   {        Error_Handler();   }    /** Initializes the CPU, AHB and APB buses clocks    */    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK                                  |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;    RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;    RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;​    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK)   {        Error_Handler();   }}​static void MX_GPIO_Init(void){    /* GPIO Ports Clock Enable */    __HAL_RCC_GPIOA_CLK_ENABLE();​    /*Configure GPIO pin Output Level */    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);​    /*Configure GPIO pin : PA0 */    GPIO_InitStruct.Pin = GPIO_PIN_0;    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;    GPIO_InitStruct.Pull = GPIO_NOPULL;    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);}

优缺点

  • 优点:代码移植性强,开发效率高,提供了丰富的功能函数和中断处理机制,适合快速开发和项目的迭代。

  • 缺点:代码体积大,执行效率相对较低,由于对硬件进行了高度抽象,开发者对硬件的控制不够灵活,在一些对性能要求极高的场景下可能不太适用。

LL 库

原理

        LL 库是在 HAL 库的基础上推出的低层库,它结合了寄存器操作的高效性和 HAL 库的易用性。LL 库提供了一系列的宏和函数,这些函数直接操作寄存器,代码执行效率高,同时又保持了一定的可读性和可维护性。

示例代码

#include \"stm32f1xx_ll_gpio.h\"#include \"stm32f1xx_ll_rcc.h\"​int main(void){    // 使能GPIOA时钟    LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_GPIOA);​    // 配置PA0为推挽输出模式    LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_0, LL_GPIO_MODE_OUTPUT);    LL_GPIO_SetPinOutputType(GPIOA, LL_GPIO_PIN_0, LL_GPIO_OUTPUT_PUSHPULL);    LL_GPIO_SetPinSpeed(GPIOA, LL_GPIO_PIN_0, LL_GPIO_SPEED_FREQ_LOW);​    while (1)   {        // 点亮LED        LL_GPIO_SetOutputPin(GPIOA, LL_GPIO_PIN_0);        for (int i = 0; i < 1000000; i++);​        // 熄灭LED        LL_GPIO_ResetOutputPin(GPIOA, LL_GPIO_PIN_0);        for (int i = 0; i < 1000000; i++);   }}

优缺点

  • 优点:代码执行效率高,占用资源少,同时又具有较好的可读性和可维护性,适合对性能要求较高且需要一定开发效率的项目。

  • 缺点:相对于 HAL 库,LL 库的功能函数没有那么丰富,对于一些复杂的外设操作,可能需要开发者进行更多的底层配置。

总结

        在 STM32 开发中,寄存器操作、标准库、HAL 库和 LL 库各有优劣。寄存器操作适合对硬件性能要求极高、对硬件细节有深入了解的开发者;标准库适合初学者快速上手,但由于其停止更新,在新项目中使用较少;HAL 库适合快速开发和项目移植,但代码体积和执行效率是其短板;LL 库则在性能和开发效率之间取得了较好的平衡。开发者应根据项目的具体需求和自身的技术水平,选择最合适的开发方式。

说明

        网上关于 STM32 标准库、HAL 库的文章多如牛毛,但对于刚入门的小伙伴来说,还是很难直观地搞清楚这些开发方式的区别。作为一个同样在学习路上摸爬滚打的小白,我想用最直白的大白话,结合自己的理解把这些知识分享出来。当然,我也还在学习阶段,如果哪里说得不对,或者大家有不同的看法,非常欢迎在评论区指出!毕竟写博客也是我学习的过程,本人第一次写博客,如果有任何建议,还请各位大佬多多指教~