STM32之CubeL4(一)---HAL详解与CubeMX使用(GPIO + EXIT示例)

本文详细介绍了STM32CubeL4的HAL库,对比了HAL库与STD库的区别,并探讨了HAL库的结构、API和移植使用。通过CubeMX配置GPIO和外部中断(EXIT)的步骤,展示了HAL库在STM32L4上的应用。同时,文中提供了一个使用HAL库和CubeMX控制GPIO及外部中断的示例,以帮助开发者更好地理解和运用HAL库。
摘要由CSDN通过智能技术生成

一、HAL库与STD库对比

之前使用STM32系列芯片都是基于STD(Standard Peripheral Libraries)标准外设库,标准外设库将相关寄存器以结构体的形式组织起来,使用库函数操作外设有点轻度面向对象编程的感觉,比寄存器操作方便不少。但在不同芯片上使用标准外设库开发的程序可移植性比较差,换个芯片常需要重新做大量的开发工作,为了解决这个问题,ST推出了全新的HAL(Hardware Abstraction Layer)库。

HAL库即硬件抽象层库,相比标准外设库提供了更高层次的抽象,更类似于面向对象编程的风格,将一个外设抽象组织为一个句柄,对该句柄的操作也就相当于对该外设的操作,在不同芯片上使用HAL库开发的程序更方便移植,能大幅减少重复的开发任务。更高层的抽象意味着更低的效率,对于实时性和效率要求更高的场合,ST也提供了LL(Low Layer)库可以达到标准外设库的效率并兼容HAL API,HAL与LL将称为ST主推的库。

HAL库与STD库两者相互独立,互不兼容,几种库的对比如下(参考自博客:STM32 之一 HAL库、标准外设库、LL库):
STD库与HAL库对比
ST不同库的对比
ST为新的标准库HAL和LL注册了一个新商标STMCube™,ST专门为其开发了配套的桌面软件STMCubeMX,开发者可以直接使用该软件进行可视化配置,大大节省开发时间。STMCubeMX的层级框架如下图所示:
STM32Cube框架
从上图不难看出,LL库和HAL库两者相互独立,只不过LL库更底层。而且,部分HAL库会调用LL库(例如:USB驱动)。同样,LL库也会调用HAL库。

HAL库可以更好的确保跨STM32产品的最大可移植性,同时提供了一整套一致的中间件组件,如RTOS,USB,TCP / IP和图形等。LL库与HAL捆绑发布,文档也是和HAL文档在一起的,但可完全抛开HAL库独立使用,也可与HAL库结合使用,在使用STM32CubeMX生成项目时,可以选择LL库(默认选择HAL库)。

二、HAL库详解

2.1 HAL库结构

说到STM32的HAL库,就不得不提STM32CubeMX,其作为一个可视化的配置工具,对于开发者来说,确实大大节省了开发时间。STM32CubeMX就是以HAL库为基础的,且目前仅支持HAL库及LL库!

下面以STM32Cube_FW_L4_V1.14.0(下载地址:https://www.st.com/content/st_com/en/products/embedded-software/mcu-mpu-embedded-software/stm32-embedded-software/stm32cube-mcu-mpu-packages/stm32cubel4.html)为例,官方给出的STM32CubeL4固件包组件体系如下图所示:
STM32CubeL4组件框架
STM32CubeL4固件框架
STM32CubeL4固件包的文件结构如下图所示:
STM32CubeL4固件包文件结构
STM32CubeL4固件包中HAL库的文件包含结构如下图所示(借用了正点原子的图,可以忽略下图中的sys.h文件):
HAL库工程文件包含关系
下面以STM32CubeL4库和STM32L475VET6芯片(基于正点原子的STM32L4潘多拉开发板)为例简单介绍下各个头文件作用,首先是HAL库的主要源码文件如下表所示:
HAL库关键源码文件

  • stm32l4xx.h:是所有stm32l4系列的顶层头文件,主要包含STM32同系列芯片的不同具体型号的定义,是否使用HAL库等的宏定义,其会根据定义的芯片型号包含具体芯片型号的头文件,比如stm32l475xx.h(主要提供STM32L475芯片的寄存器定义声明以及封装内存操作);
  • system_stm32l4xx.c/h:主要声明和定义了系统初始化函数SystemInit以及时钟更新函数SystemCoreClockUpdate:SystemInit函数的作用是进行时钟系统的一些初始化操作以及中断向量表偏移地址设置,并没有设置具体的时钟值,这是与标准库的最大区别,使用标准库时该函数会帮我们设置好系统时钟配置相关的寄存器,使用HAL库时需要我们自己配置具体的时钟值;SystemCoreClockUpdate函数可以在系统时钟配置修改后被调用,以更新全局变量SystemCoreClock;
  • stm32l4xx_it.c/h :主要是一些中断服务函数的定义和声明;
  • stm32l4xx_hal_msp.c:msp全称为MCU Support Package,主要实现了HAL_MspInit和HAL_MspDeInit函数的定义,HAL_MspInit实现MCU级别的硬件初始化配置,其会被上一层的初始化函数HAL_Init和HAL_DeInit所调用,这种机制可以把MCU相关的硬件初始化剥离出来,方便用户代码在不同型号的MCU上移植;
  • startup_stm32l475xx.s:STM32L475芯片的启动文件,主要作用是进行堆栈的初始化、中断向量表以及中断函数的定义等,启动过程可以参考我的博客:实时操作系统RTOS(六)—系统启动与固件移植

从上面的文件包含关系图可以看出,顶层头文件stm32l4xx.h直接或间接包含了其他所有HAL库必要的头文件,所以在我们的用户代码中,只需要包含顶层头文件stm32l4xx.h即可。

在HAL库中很多回调函数前使用了__weak修饰符,可以称之为“弱函数”,加上该修饰符用户可以在自己的代码文件中重定义一个同名函数,最终编译的时候会选择用户定义的函数,如果用户没有重定义该函数则编译器会执行__weak声明的函数而不会报错,可以类比下C++中虚函数的概念。

2.2 HAL库API

从上面HAL库的结构看,供用户调用的API函数定义与声明主要在stm32l4xx_hal_ppp.c/h文件中,其中stm32l4xx_hal.c/h主要定义和声明了通用API函数,stm32l4xx_hal_ppp.c/h则定义和声明了基本外设ppp的操作API,stm32l4xx_hal_ppp_ex.c/h则定义和声明了扩展外设ppp的操作API。打开几个头文件看一下里面的API函数声明,大致可以分为下面几类:

  • 初始化/反初始化函数: HAL_PPP_Init() / DeInit(), HAL_PPP_MspInit() / MspDeInit();
  • I / O 操作函数: HAL_PPP_Read() / Write(), HAL_PPP_Transmit() / Receive();
  • 控制函数: HAL_PPP_SetConfig(), HAL_PPP_GetConfig();
  • 回调函数:HAL_PPP_ProcessCpltCallback(), HAL_PPP_ErrorCallback;
  • 状态和错误: HAL_PPP_GetState (), HAL_PPP_GetError ()。

HAL库最大的特点就是对底层进行了抽象。在此结构下,用户代码的处理主要分为三部分:

  • 处理外设句柄:HAL库对每个外设抽象成了一个ppp_HandleTypeDef的结构体,ppp外设的所有API函数都是对ppp_HandleTypeDef结构体实例化变量的操作,每个外设/模块实例资源相互独立且都有自己的句柄,用户根据自己需求对相关外设句柄进行处理;
  • 处理MSP:用来初始化底层MCU级相关的设备,如GPIOx, Clock, DMA, Interrupt等,该部分也是在不同MCU间移植时需要重新实现的部分;
  • 处理各种回调函数:当外设或者DMA工作完成后触发中断,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用,可以在相应的回调函数中实现用户的控制逻辑。

2.3 HAL库移植使用

HAL库移植使用时主要需以下步骤(参考自博客:STM32 之二 HAL库详解 及 手动移植):

  1. 复制stm32f2xx_hal_msp_template.c,参照该模板,依次实现用到的外设的HAL_PPP_MspInit()和 HAL_PPP_MspDeInit;
  2. 复制stm32f2xx_hal_conf_template.h,用户可以在此文件中自由裁剪,配置HAL库;
  3. 在使用HAL库时,必须先调用函数HAL_StatusTypeDef HAL_Init(void),该函数在stm32f2xx_hal.c中定义,也就意味着第一点中,必须首先实现HAL_MspInit(void)和HAL_MspDeInit(void);
  4. HAL库与STD库不同,HAL库使用RCC中的函数来配置系统时钟,用户需要单独编写并调用时钟配置函数SystemClock_Config(void)(STD库默认在system_stm32f2xx.c中);
  5. 关于中断,HAL提供了中断处理函数,只需要调用HAL提供的中断处理函数,用户自己的代码,不建议先写到中断中,而应该写到HAL提供的回调函数中;
  6. 对于每一个外设,HAL都提供了回调函数,回调函数用来实现用户自己的代码,整个调用结构由HAL库自己完成。例如:UART中HAL提供了void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)函数,用户只需要触发中断后调用该函数即可,自己的代码写在对应的回调函数中,这些回调函数的声明如下(回调函数均实现为__weak弱函数,用户直接按声明重写即可):
// Drivers\STM32L4xx_HAL_Driver\Inc\stm32l4xx_hal_uart.h

void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);

三、CubeMX使用

下面以LED灯的控制为例为例,展示下CubeMX可视化配置工具的使用,以及基于HAL库的基本开发流程,案例中使用的软硬件资源如下:

工具的安装就略去了,需要提醒一点的是STM32CubeMX的运行需要JRE(Java Runtime Environment)的支持。

3.1 GPIO / AFIO配置

CubeMX提供了可视化配置操作,可以通过图形菜单配置GPIO引脚、外设、时钟、中断等;HAL库提供更高层次的抽象可以让我们专注实现用户业务逻辑,减少对寄存器相关的操作。但我们仍需要对各个外设的原理特性有较深的了解,这样才能清楚在CubeMX中如何配置,也才能更有效的利用外设为自己服务。

下面先看看GPIO / AFIO端口位的基本结构(参考自《SMT32L475VE Reference manual.pdf》):
STM32L475 GPIO基本结构
从上图来看,每个I / O端口可以自由编程,通过寄存器配置可实现多种输入、输出、复用模式,比如浮空输入、上拉输入、下拉输入、模拟输入、复用功能输入、开漏输出、推挽输出、复用功能输出等,具体的GPIO / AFIO端口位配置表如下所示:
GPIO端口位配置表
下面简单介绍下各种模式的特点:

  • GP(General Purpose):作为普通的输入 / 输出引脚使用,比如可以驱动LED、产生PWM、驱动蜂鸣器等,也可以接收来自外部的中断信号或事件;
  • PP(Push Pull)Output:推挽结构一般是指两个三极管分别受两互补信号的控制,总是在一个三极管导通的时候另一个截止,可以由 IC控制输出高、低电平;
  • OD(Open Drain) Output:输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行,适合于做电流型的驱动,其吸收电流的能力相对强(一般 20ma 以内);
  • AF(Alternate Function):作为内置外设的输入 / 输出引脚使用,比如AF_PP复用推挽输出常用于USART / SPI外设,AF_OU复用开漏输出常用于IIC外设;
  • PU(Pull Up):内部接通上拉电阻,将不确定的信号通过一个电阻嵌位在高电平VDD;
  • PD(Pull Down):内部接通下拉电阻,将不确定的信号通过一个电阻嵌位在低电平VSS;
  • Floating Input:浮空输入(内部上拉、下拉电阻均不接通),比如用作外部按键输入;
  • Analog Input:应用ADC模拟输入,或者低功耗下省电;

下面是推挽输出与开漏输出的原理对比,正如前面介绍的:推挽输出可以由芯片IC控制端口输出高、低电平,既可以向负载灌电流又可以从负载抽取电流;开漏输出若没有外接上拉电阻只能输出低电平,若外接上拉电阻则可以通过改变外接上拉电源的电压来改变输出电平大小,适合于做电流型的驱动,其吸收电流的能力相对强(一般20ma以内):
推挽输出与开漏输出

每个端口的配置除了输入/输出模式、普通/复用模式外,还有一个重要参数即传输速度,从源码中看到GPIO结构体定义如下:

// Drivers\STM32L4xx_HAL_Driver\Inc\stm32l4xx_hal_gpio.h

/**
  * @brief   GPIO Init structure definition
  */
typedef struct
{
   
  uint32_t Pin;        /*!< Specifies the GPIO pins to be configured.
                           This parameter can be any value of @ref GPIO_pins */

  uint32_t Mode;       /*!< Specifies the operating mode for the selected pins.
                           This parameter can be a value of @ref GPIO_mode */

  uint32_t Pull;       /*!< Specifies the Pull-up or Pull-Down activation for the selected pins.
                           This parameter can be a value of @ref GPIO_pull */

  uint32_t Speed;      /*!< Specifies the speed for the selected pins.
  • 19
    点赞
  • 98
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流云IoT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值