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

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

关键点

深入剖析 STM32:HAL、标准库、LL 库与寄存器操作 引言 ​ 在 STM32 微控制器的开发领域,存在着多种开发方式,在入门STM32的时候,首先都要先选

相关文章