Logo

郎哥编程

STM32单片机三种开发模式:寄存器、固件库与HAL库

2026-10-30 5

51单片机开发主要采用寄存器模式开发,STM32单片机开发就有了更多选择,有三种开发模式可以选择:寄存器开发模式、固件库开发模式和HAL库开发模式。

1.寄存器开发模式

在51单片机的开发过程中,我们直接配置51单片机的寄存器,控制芯片的工作方式,如中断、定时器等。我们常常要查阅单片机技术手册,看看用到哪些配置位,为了配置某功能,该置1还是置0,这就是寄存器开发模式。

寄存器开发案例

以控制 LED 灯亮灭为例,来看看寄存器编程是怎么实现的。假设我们使用的是 STM32F103 系列单片机,LED 连接到 PA5 引脚。

第一步:使能GPIO时钟

在 STM32 中,通过操作 RCC(复位和时钟控制)寄存器来实现:

// 使能GPIOA时钟

RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;

RCC->APB2ENR‌:指向APB2外设时钟使能寄存器,该寄存器控制着连接到APB2总线上所有外设的时钟开关。

‌RCC_APB2ENR_IOPAEN‌:GPIOA时钟使能位的宏定义,对应寄存器中的第2位。

第二步:配置 PA5 引脚为输出模式

GPIO(通用输入输出)寄存器用于配置引脚的工作模式,这里我们要把 PA5 设置为推挽输出模式,最大速度 50MHz:

// 配置PA5为推挽输出,最大速度50MHz
// 清除PA5相关位
GPIOA->CRL &= ~(0x0F << (5 * 4)); 
// 设置PA5为推挽输出
GPIOA->CRL |= (0x01 << (5 * 4)); 

GPIOA->CRL表示访问 GPIOA 的端口配置低寄存器,&=是按位与操作,用于清除某些位,|=用于设置某些位。这里先清除 PA5 的相关位,再设置为推挽输出模式。

第三步:控制 LED 的亮灭

通过操作 GPIOA 的输出数据寄存器(ODR)来实现:

// 点亮LED
GPIOA->ODR |= (1 << 5);
// 熄灭LED
GPIOA->ODR &= ~(1 << 5);

当GPIOA->ODR |= (1 << 5)时,将 PA5 引脚置为高电平,LED 熄灭(假设 LED 是低电平点亮);当GPIOA->ODR &= ~(1 << 5)时,将 PA5 引脚置为低电平,LED 点亮。

寄存器开发优劣

使用寄存器开发模式,生成的嵌入式程序执行效率高,因为直接对寄存器进行操作,减少了中间层的调用。代码简洁,能够直接操作硬件,对硬件资源的掌控能力强,适合对实时性要求较高的应用场景,如工业控制、航空航天等领域,在这些场景中,每一个毫秒甚至微秒的响应时间都至关重要。

但寄存器开发难度大,需要开发者对单片机的寄存器有深入的了解,需要掌握每个寄存器的功能和配置方法,同时还要理解寄存器之间的逻辑关系。编写代码时需要手动配置大量的寄存器,而且容易出错,调试过程也相对复杂,一旦出错,排查起来也很麻烦。

初学者使用寄存器开发

对学习嵌入式编程的初学者来说,直接操作寄存器能让初学者彻底理解硬件怎么工作,学的扎实。比如怎么控制时钟、配置引脚等。学会寄存器操作后,再学库函数会更轻松,因为你知道底层原理。

但学习门槛高,要记住一堆寄存器的功能和位定义,需要编写大量配置寄存器的代码,容易漏掉关键步骤(如忘记开时钟),当学习困难时,会产生厌学情绪。

2.固件库开发模式

在51单片机的程序开发中,开发者需要直接配置51单片机的寄存器,控制芯片的工作方式,如中断、定时器等。开发者需要查阅51单片机寄存器表,看用到哪些配置位,并对寄存器进行配置,这些都是很繁琐的工作。51单片机寄存器资源有限,因此这些繁琐的寄存器配置工作对开发者影响不大。

STM32芯片外设资源丰富,寄存器数量多且复杂,若采用直接配置寄存器的方式开发,会极大增加开发者开发STM32嵌入式程序的难度,因为开发需要掌握STM32芯片所有寄存器的工作方式和配置方式,这对初学STM32开发者来说是一个巨大的技术挑战。另外使用直接配置寄存器的方式开发STM32嵌入式程序,开发效率低下,程序代码可读性性差,不同STM32芯片的程序难以移植。

ST公司为提高STM32芯片程序的开发效率和可移植性,编写了一组配置寄存器的函数,这些函数通过接口的方式提供给开发者,开发者可以通过接口直接调用这些函数来配置STM32的寄存器,使开发人员可以脱离STM32最底层的寄存器操作,专注于嵌入式应用程序的开发。

STM32提供的寄存器配置函数接口称为STM32固件库,采用STM32固件库的开发方式如下图所示:

STM32固件库开发流程

固件库开发案例

以 GPIO 配置为例,假设还是控制前面提到的连接在 PA5 引脚的 LED 灯。使用固件库时,首先要使能 GPIOA 的时钟,不过这里不是直接操作寄存器了,而是调用固件库函数:

第一步:使能GPIO时钟

// 使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

这里的RCC_APB2PeriphClockCmd是固件库提供的使能 APB2 总线上外设时钟的函数,RCC_APB2Periph_GPIOA表示 GPIOA,ENABLE表示使能。

第二步:配置 PA5 引脚为输出模式

使用固件库中的GPIO_Init函数和GPIO_InitTypeDef结构体来完成:

// 定义GPIO初始化结构体
GPIO_InitTypeDef GPIO_InitStructure;
// 配置PA5引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
// 设置为推挽输出模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
// 设置输出速度为50MHz
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
// 初始化GPIOA
GPIO_Init(GPIOA, &GPIO_InitStructure);

上述代码先定义了一个GPIO_InitTypeDef类型的结构体GPIO_InitStructure,然后设置结构体的各个成员,分别指定要配置的引脚、工作模式和输出速度,最后调用GPIO_Init函数将配置应用到 GPIOA。

第三步:控制 LED 的亮灭

控制 LED 的亮灭,同样使用固件库函数:

// 点亮LED
GPIO_SetBits(GPIOA, GPIO_Pin_5);
// 熄灭LED
GPIO_ResetBits(GPIOA, GPIO_Pin_5);

GPIO_SetBits函数用于将指定引脚设置为高电平,GPIO_ResetBits函数用于将指定引脚设置为低电平 。

固件库编程的优势

虽然使用固件库开发模式点亮LED的代码量确实比直接操作寄存器复杂,但固件库在嵌入式开发中具有多方面的显著优势。

固件库通过封装底层寄存器操作,提供了更直观的API接口。开发者无需记忆复杂的寄存器地址和位定义,只需调用RCC_APB2PeriphClockCmd、GPIO_Init等语义明确的函数即可完成配置。例如,GPIO_Mode_Out_PP直接表明了"推挽输出"模式,代码意图一目了然,极大提升了代码的可读性和可维护性。

固件库有效屏蔽了不同STM32系列芯片的硬件差异。当项目需要从F1系列迁移到F4系列时,固件库代码通常只需少量修改(如时钟总线从APB2改为AHB1),而寄存器操作可能需要完全重写。这种抽象层使得代码在不同型号的STM32芯片间移植更加便捷。

对于初学者或从51单片机转型的开发者,固件库降低了学习曲线。开发者可以专注于功能实现,而不用深入理解每个寄存器的位域含义,减少了因配置错误导致的硬件问题。

3.HAL库开发模式

HAL 库,全称 Hardware Abstraction Layer 库,即硬件抽象层库,提供了硬件抽象层次的API函数库,它为开发者提供了一套统一的、抽象的函数接口。

HAL 库最大的优势在于它的硬件无关性和代码可移植性。因为它提供了统一的 API 接口,使得开发者在不同种类的硬件上进行开发时,无需针对不同硬件的寄存器差异进行大量的代码修改。只要硬件支持 HAL 库所提供的功能,就可以方便地将基于 HAL 库开发的代码从一个硬件平台移植到另一个硬件平台,实现一个工程多个硬件使用。

HAL库旨在提供高级别的硬件抽象接口,简化软件开发并增强代码在不同STM32型号间的可移植性。固件库则提供相对底层的函数,允许开发者更直接地访问和控制硬件寄存器,牺牲部分可移植性以换取更精细的控制权。

HAL库的抽象级别更高,通常更易于学习和使用,特别适合新手和需要快速开发的场景。固件库由于更接近硬件,学习曲线相对陡峭,但能实现更优化的硬件控制。

 HAL 库配合ST公司推出的STM32CubeMX图形化配置工具,允许开发者可以在图形界面中轻松配置 STM32 的各种硬件资源,如 GPIO、USART、SPI 等,简化了开发工作。

以前面的 LED 灯亮灭为例,调用HAL库的完整代码为:

#include "main.h"
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    while (1)
    {
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
        HAL_Delay(1000);
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
        HAL_Delay(1000);
    }
}
void SystemClock_Config(void)
{
    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    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;
    HAL_RCC_OscConfig(&RCC_OscInitStruct);
    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;
    HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0);
}
static void MX_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    __HAL_RCC_GPIOA_CLK_ENABLE();
    GPIO_InitStruct.Pin = GPIO_PIN_5;
    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);
}

对初学者的建议

对于初学者来说,使用寄存器开发模式学习门槛高,要记住一堆寄存器的功能和位定义,需要编写大量配置寄存器的代码,学习过程中遇到困难时,容易产生厌学情绪。固件库开发把复杂的寄存器操作封装成简单易用的函数,让初学者可以轻松上手。初学者先从固件库入手,能够快速建立起对 STM32 开发的基本认知,了解各个外设的基本功能和使用方法。

当初学者对 STM32 的开发有了一定的基础后,再去学习寄存器开发,能够更好地理解每一个操作背后的原理。这种先易后难、逐步深入的学习路径,学习过程会比较平滑,也更容易坚持下去。通过对寄存器的学习,能够加深对硬件底层的理解,这对于后续优化代码、解决复杂问题都有着极大的帮助。

在实际开发中,开发者往往需要在开发效率和性能之间找到一个平衡点。固件库开发在开发效率方面具有明显的优势,它提供的函数库可以让快速实现各种功能,有效缩短了开发周期。

但在一些对性能要求极高的场景中,固件库可能就无法满足需求了。在这种情况下,就需要采用寄存器开发模式,通过直接操作寄存器,可以对硬件进行更精细的控制,优化代码的执行效率,实现更高的性能。

当掌握寄存器开发和固件库开发模式后,再学习HAL库开发就非常简单和容易了。

评论区

请 登录 后发表评论
暂无评论