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

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

posted @ 2025-05-01 11:51  apolloyhl  阅读(1999)  评论(0)    收藏  举报