STM32的HAL库分析及使用

news2025/1/16 1:31:10

STM32的HAL库分析及使用

STM32的三种开发方式

通常新手在入门STM32的时候,首先都要先选择一种要用的开发方式,不同的开发方式会导致你编程的架构是完全不一样的。一般大多数都会选用标准库和HAL库,而极少部分人会通过直接配置寄存器进行开发。

网上关于标准库、HAL库的描述相信是数不胜数。可是一个对于很多刚入门的朋友还是没法很直观的去真正了解这些不同开发发方式彼此之间的区别,所以笔者想以一种非常直白的方式,用自己的理解去将这些东西表述出来,如果有描述的不对的地方或者是不同意见的也可以大家提出。

1、直接配置寄存器

不少先学了51的朋友可能会知道,会有一小部分人或是教程是通过汇编语言直接操作寄存器实现功能的,这种方法到了STM32就变得不太容易行得通了,因为STM32的寄存器数量是51单片机的十数倍,如此多的寄存器根本无法全部记忆,开发时需要经常的翻查芯片的数据手册,此时直接操作寄存器就变得非常的费力了。但还是会有很小一部分人,喜欢去直接操作寄存器,因为这样更接近原理,知其然也知其所以然。

2、标准库

上面也提到了,STM32有非常多的寄存器,而导致了开发困难,所以为此ST公司就为每款芯片都编写了一份库文件,也就是工程文件里stm32F1xx…之类的。在这些 .c .h文件中,包括一些常用量的宏定义,把一些外设也通过结构体变量封装起来,如GPIO口时钟等。所以我们只需要配置结构体变量成员就可以修改外设的配置寄存器,从而选择不同的功能。也是目前最多人使用的方式,也是学习STM32接触最多的一种开发方式,我也就不多阐述了。

3、HAL库

HAL库是ST公司目前主力推的开发方式,全称就是Hardware Abstraction Layer(抽象印象层)。库如其名,很抽象,一眼看上去不太容易知道他的作用是什么。

它的出现比标准库要晚,但其实和标准库一样,都是为了节省程序开发的时期,而且HAL库尤其的有效,如果说标准库把实现功能需要配置的寄存器集成了,那么HAL库的一些函数甚至可以做到某些特定功能的集成。也就是说,同样的功能,标准库可能要用几句话,HAL库只需用一句话就够了。

并且HAL库也很好的解决了程序移植的问题,不同型号的stm32芯片它的标准库是不一样的,例如在F4上开发的程序移植到F3上是不能通用的,而使用HAL库,只要使用的是相通的外设,程序基本可以完全复制粘贴,注意是相通外设,意思也就是不能无中生有,例如F7比F3要多几个定时器,不能明明没有这个定时器却非要配置,但其实这种情况不多,绝大多数都可以直接复制粘贴。

是而且使用ST公司研发的STMcube软件,可以通过图形化的配置功能,直接生成整个使用HAL库的工程文件,可以说是方便至极,但是方便的同时也造成了它执行效率的低下,在各种论坛帖子真的是被吐槽的数不胜数。

HAL库固件库安装与 用户手册

1、首先设置让Cube可以自动联网下载相关固件库选择updater Settings
在这里插入图片描述

设置如下:

在这里插入图片描述

2、根据芯片选择所需固件
版本是向下兼容的,可以直接选择最新版。但如果觉得最新版太大,可以阅读下面的Main Changes.能够支持你目前的芯片就好。

在这里插入图片描述

选好了,点击Install Now就行,过程可能有点长。建议直接官网下载到本地,再安装文件会被下载到如下位置,建议更改此目录,不要选在C盘!!!

在这里插入图片描述

3、寻找用户帮助手册
进入固件所在文件夹,里面包含很多内容。

在这里插入图片描述

比如说 官方提供的开发板程序,每个型号下面都有对应功能的实现,用户手册就在Drivers文件夹下面。

在这里插入图片描述

在这里插入图片描述

STM32 HAL库与标准库的区别

1、句柄
句柄(handle),有多种意义,其中第一种是指程序设计,第二种是指Windows编程。现在大部分都是指程序设计/程序开发这类。

第一种解释:句柄是一种特殊的智能指针 。当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄。

第二种解释:整个Windows编程的基础。一个句柄是指使用的一个唯一的整数值,即一个4字节(64位程序中为8字节)长的数值,来标识应用程序中的不同对象和同类中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。应用程序能够通过句柄访问相应的对象的信息,但是句柄不是指针,程序不能利用句柄来直接阅读文件中的信息。如果句柄不在I/O文件中,它是毫无用处的。句柄是Windows用来标志应用程序中建立的或是使用的唯一整数,Windows大量使用了句柄来标识对象。

STM32的标准库中,句柄是一种特殊的指针,通常指向结构体!

在STM32的标准库中,假设我们要初始化一个外设(这里以USART为例),我们首先要初始化他们的各个寄存器。在标准库中,这些操作都是利用固件库结构体变量+固件库Init函数实现的:

USART_InitTypeDef USART_InitStructure;

USART_InitStructure.USART_BaudRate = bound;//串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式

USART_Init(USART3, &USART_InitStructure); //初始化串口1

可以看到,要初始化一个串口,需要:

1、对六个位置进行赋值
2、然后引用Init函数
USART_InitStructure并不是一个全局结构体变量,而是只在函数内部的局部变量,初始化完成之后,USART_InitStructure就失去了作用。而在HAL库中,同样是USART初始化结构体变量,我们要定义为全局变量。

UART_HandleTypeDef UART1_Handler;

右键查看结构体成员

typedef struct
{
   USART_TypeDef                 *Instance;        /*!< UART registers base address        */
   UART_InitTypeDef              Init;             /*!< UART communication parameters      */
   uint8_t                       *pTxBuffPtr;      /*!< Pointer to UART Tx transfer Buffer */
   uint16_t                      TxXferSize;       /*!< UART Tx Transfer size              */
   uint16_t                      TxXferCount;      /*!< UART Tx Transfer Counter           */
   uint8_t                       *pRxBuffPtr;      /*!< Pointer to UART Rx transfer Buffer */
   uint16_t                      RxXferSize;       /*!< UART Rx Transfer size              */
   uint16_t                      RxXferCount;      /*!< UART Rx Transfer Counter           */  
   DMA_HandleTypeDef             *hdmatx;          /*!< UART Tx DMA Handle parameters      */ 
   DMA_HandleTypeDef             *hdmarx;          /*!< UART Rx DMA Handle parameters      */
   HAL_LockTypeDef               Lock;             /*!< Locking object                     */
   __IO HAL_UART_StateTypeDef    State;            /*!< UART communication state           */
   __IO uint32_t                 ErrorCode;        /*!< UART Error code                    */
}UART_HandleTypeDef;

我们发现,与标准库不同的是,该成员不仅:

1、包含了之前标准库就有的六个成员(波特率,数据格式等),

2、还包含过采样、(发送或接收的)数据缓存、数据指针、串口 DMA 相关的变量、各种标志位等等要在整个项目流程中都要设置的各个成员。

该 UART1_Handler就被称为串口的句柄,它被贯穿整个USART收发的流程,比如开启中断:

HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);

比如后面要讲到的MSP与Callback回调函数:

void HAL_UART_MspInit(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

在这些函数中,只需要调用初始化时定义的句柄UART1_Handler就好。

2、MSP函数
MSP: MCU Specific Package 单片机的具体方案

MSP是指和MCU相关的初始化,引用一下正点原子的解释,个人觉得说的很明白:

我们要初始化一个串口,首先要设置和 MCU 无关的东西,例如波特率,奇偶校验,停止位等,这些参数设置和 MCU 没有任何关系,可以使用 STM32F1,也可以是 STM32F2/F3/F4/F7上的串口。而一个串口设备它需要一个 MCU 来承载,例如用 STM32F4 来做承载,PA9 做为发送,PA10 做为接收,MSP 就是要初始化 STM32F4 的 PA9,PA10,配置这两个引脚。所以 HAL驱动方式的初始化流程就是:

  • HAL_USART_Init()—>HAL_USART_MspInit() ,先初始化与 MCU无关的串口协议,再初始化与 MCU 相关的串口引脚。

  • 在 STM32 的 HAL 驱动中HAL_PPP_MspInit()作为回调,被 HAL_PPP_Init()函数所调用。当我们需要移植程序到 STM32F1平台的时候,我们只需要修改 HAL_PPP_MspInit 函数内容而不需要修改 HAL_PPP_Init 入口参数内容。

在HAL库中,几乎每初始化一个外设就需要设置该外设与单片机之间的联系,比如IO口,是否复用等等,可见,HAL库相对于标准库多了MSP函数之后,移植性非常强,但与此同时却增加了代码量和代码的嵌套层级。可以说各有利弊。

同样,MSP函数又可以配合句柄,达到非常强的移植性:

void HAL_UART_MspInit(UART_HandleTypeDef *huart);

3、Callback函数
类似于MSP函数,个人认为Callback函数主要帮助用户应用层的代码编写。

还是以USART为例,在标准库中,串口中断了以后,我们要先在中断中判断是否是接收中断,然后读出数据,顺便清除中断标志位,然后再是对数据的处理,这样如果我们在一个中断函数中写这么多代码,就会显得很混乱:

void USART3_IRQHandler(void)                 //串口1中断服务程序
{
 u8 Res;
 if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
 {
  Res =USART_ReceiveData(USART3); //读取接收到的数据
  /*数据处理区*/
  }      
     } 
} 

而在HAL库中,进入串口中断后,直接由HAL库中断函数进行托管:

void USART1_IRQHandler(void)                 
{ 
 HAL_UART_IRQHandler(&UART1_Handler); //调用HAL库中断处理公用函数
 /***************省略无关代码****************/ 
}

HAL_UART_IRQHandler这个函数完成了判断是哪个中断(接收?发送?或者其他?),然后读出数据,保存至缓存区,顺便清除中断标志位等等操作。

比如我提前设置了,串口每接收五个字节,我就要对这五个字节进行处理。在一开始我定义了一个串口接收缓存区:

/*HAL库使用的串口接收缓冲,处理逻辑由HAL库控制,接收完这个数组就会调用HAL_UART_RxCpltCallback进行处理这个数组*/
/*RXBUFFERSIZE=5*/
u8 aRxBuffer[RXBUFFERSIZE];

在初始化中,我在句柄里设置好了缓存区的地址,缓存大小(五个字节)

/*该代码在HAL_UART_Receive_IT函数中,初始化时会引用*/
    huart->pRxBuffPtr = pData;//aRxBuffer
    huart->RxXferSize = Size;//RXBUFFERSIZE
    huart->RxXferCount = Size;//RXBUFFERSIZE

则在接收数据中,每接收完五个字节,HAL_UART_IRQHandler才会执行一次Callback函数:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

在这个Callback回调函数中,我们只需要对这接收到的五个字节(保存在aRxBuffer[]中)进行处理就好了,完全不用再去手动清除标志位等操作。

所以说Callback函数是一个应用层代码的函数,我们在一开始只设置句柄里面的各个参数,然后就等着HAL库把自己安排好的代码送到手中就可以了~

综上,就是HAL库的三个与标准库不同的地方之个人见解。个人觉得从这三个小点就可以看出HAL库的可移植性之强大,并且用户可以完全不去理会底层各个寄存器的操作,代码也更有逻辑性。但与此带来的是复杂的代码量,极慢的编译速度,略微低下的效率。看怎么取舍了。

STM32 HAL库结构

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

在这里插入图片描述

1、stm32f4xx.h主要包含STM32同系列芯片的不同具体型号的定义,是否使用HAL库等的定义,接着,其会根据定义的芯片信号包含具体的芯片型号的头文件:

#if defined(STM32F405xx)
  #include "stm32f405xx.h"
#elif defined(STM32F415xx)
  #include "stm32f415xx.h"
#elif defined(STM32F407xx)
  #include "stm32f407xx.h"
#elif defined(STM32F417xx)
  #include "stm32f417xx.h"
#else
 #error "Please select first the target STM32F4xx device used in your application (in stm32f2xx.h file)"
#endif

紧接着,其会包含stm32f4xx_hal.h。

2、stm32f4xx_hal.h:stm32f4xx_hal.c/h 主要实现HAL库的初始化、系统滴答相关函数、及CPU的调试模式配置

3、stm32f4xx_hal_conf.h :该文件是一个用户级别的配置文件,用来实现对HAL库的裁剪,其位于用户文件目录,不要放在库目录中。

接下来对于HAL库的源码文件进行一下说明,HAL库文件名均以stm32f4xx_hal开头,后面加上_外设或者模块名(如:stm32f4xx_hal_adc.c):

4、库文件:stm32f4xx_hal_ppp.c/.h // 主要的外设或者模块的驱动源文件,包含了该外设的通用API

stm32f4xx_hal_ppp_ex.c/.h // 外围设备或模块驱动程序的扩展文件。这组文件中包含特定型号或者系列的芯片的特殊API。以及如果该特定的芯片内部有不同的实现方式,则该文件中的特殊API将覆盖_ppp中的通用API。

stm32f4xx_hal.c/.h // 此文件用于HAL初始化,并且包含DBGMCU、重映射和基于systick的时间延迟等相关的API

5、其他库文件

用户级别文件:

stm32f4xx_hal_msp_template.c // 只有.c没有.h。它包含用户应用程序中使用的外设的MSP初始化和反初始化(主程序和回调函数)。使用者复制到自己目录下使用模板。

stm32f4xx_hal_conf_template.h // 用户级别的库配置文件模板。使用者复制到自己目录下使用

system_stm32f4xx.c // 此文件主要包含SystemInit()函数,该函数在刚复位及跳到main之前的启动过程中被调用。它不在启动时配置系统时钟(与标准库相反)。时钟的配置在用户文件中使用HAL API来完成。startup_stm32f4xx.s // 芯片启动文件,主要包含堆栈定义,终端向量表等 stm32f4xx_it.c/.h // 中断处理函数的相关实现

6 main.c/.h //

根据HAL库的命名规则,其API可以分为以下三大类:

初始化/反初始化函数:

 HAL_PPP_Init(), HAL_PPP_DeInit()

IO 操作函数:

HAL_PPP_Read(),
HAL_PPP_Write(),
HAL_PPP_Transmit(), 
HAL_PPP_Receive()

控制函数:

  HAL_PPP_Set (), 
  HAL_PPP_Get ().

状态和错误:

  ** HAL_PPP_GetState (), 
  HAL_PPP_GetError ().

注意:
目前LL库是和HAL库捆绑发布的,所以在HAL库源码中,还有一些名为 stm32f2xx_ll_ppp的源码文件,这些文件就是新增的LL库文件。使用CubeMX生产项目时,可以选择LL库。

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

处理外设句柄(实现用户功能)
处理MSP
处理各种回调函数
相关知识如下:

1、外设句柄定义
用户代码的第一大部分:对于外设句柄的处理。HAL库在结构上,对每个外设抽象成了一个称为ppp_HandleTypeDef的结构体,其中ppp就是每个外设的名字。*所有的函数都是工作在ppp_HandleTypeDef指针之下。

多实例支持:每个外设/模块实例都有自己的句柄。因此,实例资源是独立的
下面,以ADC为例

外围进程相互通信:该句柄用于管理进程例程之间的共享数据资源。

/** 
 * @brief  ADC handle Structure definition
 */ 
typedef struct
{
 ADC_TypeDef                   *Instance;                   /*!< Register base address */
 ADC_InitTypeDef               Init;                        /*!< ADC required parameters */
  __IO uint32_t                 NbrOfCurrentConversionRank;  /*!< ADC number of current conversion rank */
 DMA_HandleTypeDef             *DMA_Handle;                 /*!< Pointer DMA Handler */
 HAL_LockTypeDef               Lock;                        /*!< ADC locking object */
 __IO uint32_t                 State;                       /*!< ADC communication state */
 __IO uint32_t                 ErrorCode;                   /*!< ADC Error code */
}ADC_HandleTypeDef;

从上面的定义可以看出,ADC_HandleTypeDef中包含了ADC可能出现的所有定义,对于用户想要使用ADC只要定义一个ADC_HandleTypeDef的变量,给每个变量赋好值,对应的外设就抽象完了。接下来就是具体使用了。

当然,对于那些共享型外设或者说系统外设来说,他们不需要进行以上这样的抽象,这些部分与原来的标准外设库函数基本一样。例如以下外设:

GPIO

SYSTICK

NVIC

RCC

FLASH

以GPIO为例,对于HAL_GPIO_Init() 函数,其只需要GPIO 地址以及其初始化参数即可。

2、 三种编程方式
HAL库对所有的函数模型也进行了统一。在HAL库中,支持三种编程模式:轮询模式、中断模式、DMA模式(如果外设支持)。其分别对应如下三种类型的函数(以ADC为例):

HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef* hadc);

HAL_StatusTypeDef HAL_ADC_Start_IT(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Stop_IT(ADC_HandleTypeDef* hadc);

HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length);
HAL_StatusTypeDef HAL_ADC_Stop_DMA(ADC_HandleTypeDef* hadc);

其中,带_IT的表示工作在中断模式下;带_DMA的工作在DMA模式下(注意:DMA模式下也是开中断的);什么都没带的就是轮询模式(没有开启中断的)。至于使用者使用何种方式,就看自己的选择了。

此外,新的HAL库架构下统一采用宏的形式对各种中断等进行配置(原来标准外设库一般都是各种函数)。针对每种外设主要由以下宏:

__HAL_PPP_ENABLE_IT(HANDLE, INTERRUPT): 使能一个指定的外设中断
__HAL_PPP_DISABLE_IT(HANDLE, INTERRUPT):失能一个指定的外设中断
__HAL_PPP_GET_IT (HANDLE, __ INTERRUPT __):获得一个指定的外设中断状态
__HAL_PPP_CLEAR_IT (HANDLE, __ INTERRUPT __):清除一个指定的外设的中断状态
__HAL_PPP_GET_FLAG (HANDLE, FLAG):获取一个指定的外设的标志状态
__HAL_PPP_CLEAR_FLAG (HANDLE, FLAG):清除一个指定的外设的标志状态
__HAL_PPP_ENABLE(HANDLE) :使能外设
__HAL_PPP_DISABLE(HANDLE) :失能外设
__HAL_PPP_XXXX (HANDLE, PARAM) :指定外设的宏定义
_HAL_PPP_GET IT_SOURCE (HANDLE, __ INTERRUPT __):检查中断源
3、 三大回调函数
在HAL库的源码中,到处可见一些以__weak开头的函数,而且这些函数,有些已经被实现了,比如:

__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
 /*Configure the SysTick to have interrupt in 1ms time basis*/
 HAL_SYSTICK_Config(SystemCoreClock/1000U);
 /*Configure the SysTick IRQ priority */
 HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0U);
 /* Return function status */
 return HAL_OK;
}

有些则没有被实现,例如:

__weak void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(hspi);
  /* NOTE : This function should not be modified, when the callback is needed,the HAL_SPI_TxCpltCallback should be implemented in the user file
  */
}

所有带有__weak关键字的函数表示,就可以由用户自己来实现。如果出现了同名函数,且不带__weak关键字,那么连接器就会采用外部实现的同名函数。

通常来说,HAL库负责整个处理和MCU外设的处理逻辑,并将必要部分以回调函数的形式给出到用户,用户只需要在对应的回调函数中做修改即可。HAL库包含如下三种用户级别回调函数(PPP为外设名):

1、外设系统级初始化/解除初始化回调函数(用户代码的第二大部分:对于MSP的处理):

HAL_PPP_MspInit()和 HAL_PPP_MspDeInit**

例如:

__weak void HAL_SPI_MspInit(SPI_HandleTypeDef *hspi)

在HAL_PPP_Init() 函数中被调用,用来初始化底层相关的设备(GPIOs, clock, DMA, interrupt)

2、处理完成回调函数:HAL_PPP_ProcessCpltCallback*(Process指具体某种处理,如UART的Tx),
例如:

__weak void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)

当外设或者DMA工作完成后时,触发中断,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用错误处理回调函数:

HAL_PPP_ErrorCallback

例如:

__weak void HAL_SPI_ErrorCallback(SPI_HandleTypeDef hspi)*

3、当外设或者DMA出现错误时,触发终端,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用
错误处理回调函数:

HAL_PPP_ErrorCallback

例如:

__weak void HAL_SPI_ErrorCallback(SPI_HandleTypeDef hspi)*

当外设或者DMA出现错误时,触发终端,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用。

绝大多数用户代码均在以上三大回调函数中实现。

HAL库结构中,在每次初始化前(尤其是在多次调用初始化前),先调用对应的反初始化(DeInit)函数是非常有必要的。

某些外设多次初始化时不调用返回会导致初始化失败。完成回调函数有多中,例如串口的完成回调函数有

HAL_UART_TxCpltCallback 
HAL_UART_TxHalfCpltCallback

(用户代码的第三大部分:对于上面第二点和第三点的各种回调函数的处理)在实际使用中,发现HAL仍有不少问题,例如在使用USB时,其库配置存在问题。

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中的函数来配置系统时钟,用户需要单独写时钟配置函数(STD库默认在system_stm32f2xx.c中)

5、关于中断,HAL提供了中断处理函数,只需要调用HAL提供的中断处理函数。用户自己的代码,不建议先写到中断中,而应该写到HAL提供的回调函数中。

6、对于每一个外设,HAL都提供了回调函数,回调函数用来实现用户自己的代码。整个调用结构由HAL库自己完成。

例如:

Uart中,HAL提供了

void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);

函数,用户只需要触发中断后,用户只需要调用该函数即可,同时,自己的代码写在对应的回调函数中即可!如下:

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

使用了哪种就用哪个回调函数即可!

基本结构

综上所述,使用HAL库编写程序(针对某个外设)的基本结构(以串口为例)如下:

  • 1、 配置外设句柄 例如,建立UartConfig.c,在其中定义串口句柄 UART_HandleTypeDef huart;接着使用初始化句柄(HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef huart))

  • 2、编写Msp 例如,建立UartMsp.c,在其中实现void HAL_UART_MspInit(UART_HandleTypeDef huart) 和 void HAL_UART_MspDeInit(UART_HandleTypeDef* huart)

  • 3、实现对应的回调函数 例如,建立UartCallBack.c,在其中实现上文所说明的三大回调函数中的完成回调函数和错误回调函数

参考文档及网文链接
ST - Description of STM32F4 HAL and LL drivers.pdf

ST - en.stm32_embedded_software_offering.pdf

作者:ZCShoucsdn 来源:CSDN 原文:https://blog.csdn.net/zcshoucsdn/article/details/55213616

作者:ZCShoucsdn 来源:CSDN 原文:https://blog.csdn.net/zcshoucsdn/article/details/55213616

作者:Error_4O4 来源:CSDN 原文:https://blog.csdn.net/weixin_43186792/article/details/88759321

作者:csdnpapa 来源:CSDN 原文:https://blog.csdn.net/csdnpapa/article/details/79309937

参考原文:《STM32的HAL库分析及使用》
声明:文章转自“网络”,版权归作者所有。如有侵权,请联系我们删除!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/331217.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Mysql第八期 子查询】

文章目录前言1. 需求分析与问题解决1.2 子查询的基本使用1.3 子查询的分类2. 单行子查询2.1 单行比较操作符2.2 代码示例2.5 子查询中的空值问题3. 多行子查询3.1 多行比较操作符3.2 代码示例3.3 空值问题4. 相关子查询4.2 代码示例4.3 EXISTS 与 NOT EXISTS关键字4.4 相关更新…

开发者社区「运营官」招募启动啦!

国内首个聚焦AI3D视觉技术的开发者社区「运营官」招募启动啦&#xff01; 想积累实习经验&#xff0c;却苦于找不到大厂机会&#xff1f; 想进入AI3D视觉行业&#xff0c;却苦于没有知音伯乐&#xff1f; 想积累更多工作经历&#xff0c;却苦于路程奔波、天各一方&#xff1f…

我们的微服务中为什么需要网关?

说起 Spring Cloud Gateway 的使用场景&#xff0c;我相信很多小伙伴都能够脱口而出认证二字&#xff0c;确实&#xff0c;在网关中完成认证操作&#xff0c;确实是 Gateway 的重要使用场景之一&#xff0c;然而并不是唯一的使用场景。在微服务中使用网关的好处可太多了&#x…

MODBUS TCP 转 PROFINET 网关从站快速配置手册

一、本案例是1500PLC通过微硬创新MODBUS TCP 转 PROFINET 网关连接组态王服务器从站快速配置&#xff0c;将 Modbus TCP 设备数据转接入到西门子 PROFINET 网络中 二、设备列表如下&#xff1a; 三、MODBUS TCP 转 PROFINET 网关从站快速配置方法步骤&#xff1a; 第1步&#x…

全网最详细的介绍ChatGPT:包括ChatGPT原理、应用、如何试用以及回答ChatGPT能否让程序员失业

文章目录1. 介绍ChatGPT2. ChatGPT示例3. 试用ChatGPT4. ChatGPT原理5. ChatGPT应用5.1 世界杯问题咨询5.2 写书信&#xff08;情书&#xff09;6. 总结1. 介绍ChatGPT 今天开车去上班的路上&#xff0c;听到电台介绍ChatGPT&#xff0c;此时百度的股价涨幅为25%&#xff0c;当…

谈谈Spring中Bean的生命周期?(让你瞬间通透~)

目录 1.Bean的生命周期 1.1、概括 1.2、图解 2、代码示例 2.1、初始化代码 2.2、初始化的前置方法和后置方法&#xff08;重写&#xff09; 2.3、Spring启动类 2.4、执行结果 2.5、经典面试问题 3.总结 1.Bean的生命周期 1.1、概括 Spring中Bean的生命周期就是Bean在…

Spring Cloud Alibaba+saas企业架构技术选型+架构全景业务图 + 架构典型部署方案

基于Spring Cloud Alibaba 分布式微服务高并发数据平台化(中台)思想多租户saas设计的企业开发架构&#xff0c;支持源码二次开发、支持其他业务系统集成、集中式应用权限管理、支持拓展其他任意子项目。 一、架构技术选型 核心框架 Spring Boot SOA Spring Cloud …

如何搞垮一个测试团队【反向教学,最为致命】

如何搞垮一个测试团队【反向教学&#xff0c;最为致命】 目录&#xff1a;导读 一、QA 二、项目经理 三、产品经理 四、开发人员 五、测试人员 六、组织文化 七、组织战略 要想彻底搞垮一个测试团队并非易事&#xff0c;需要多角色通力配合、多方联动、综合施策&#x…

FFmpeg5.0源码阅读——内存池AVBufferPool

摘要&#xff1a;FFmpeg中大多数数据存储比如AVFrame,AVPacket都是通过AVBufferRef管理的&#xff0c;而承载数据的结构为AVBuffer。本文主要通过FFmpeg源码来分析下FFmpeg中AVBuffer相关的实现。 关键字&#xff1a;AVBuffer、AVBufferPool、AVBufferPool 1. AVBufferRef 1.…

谁说菜鸟不会数据分析,不用Python,不用代码也轻松搞定

作为一个菜鸟&#xff0c;你可能觉得数据分析就是做表格的&#xff0c;或者觉得搞个报表很简单。实际上&#xff0c;当前有规模的公司任何一个岗位如果没有数据分析的思维和能力&#xff0c;都会被淘汰&#xff0c;数据驱动分析是解决日常问题的重点方式。很多时候&#xff0c;…

RS232串口之RTS与CTS作用

RTS与CTS的定义 RTS和CTS用于流控&#xff0c;提供了流控信号&#xff0c;但实际的流控功能还是要在软件实现&#xff0c;就是说即使硬件上RTS和CTS做了连线&#xff0c;但软件没有使用这两个信号&#xff0c;则通信就如无流控状态。 RTS &#xff08;Require ToSend&#xf…

力扣64.最小路径和

文章目录力扣64.最小路径和题目描述方法1&#xff1a;动态规划力扣64.最小路径和 题目描述 给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。 说明&#xff1a;每次只能向下或者向右移动一步…

爱了爱了,这些顶级的 Python 工具包太棒了

Python 语言向来以丰富的第三方库而闻名&#xff0c;今天来介绍几个非常nice的库&#xff0c;有趣好玩且强大&#xff01;推荐好好学习。 文章目录技术交流数据采集AKShareTuShareGoPUPGeneralNewsExtractor爬虫playwright-pythonawesome-python-login-modelDecryptLoginScylla…

「题解」关于sizeof陷阱,无符号整形,变种水仙花数

&#x1f680;&#x1f680;&#x1f680;大家觉不错的话&#xff0c;就恳求大家点点关注&#xff0c;点点小爱心&#xff0c;指点指点&#x1f680;&#x1f680;&#x1f680; sizeof陷阱以及无符号整形 让我们看一下这段代码&#xff1a;​​​​​​​ int main(){ int x …

C语言操作符详解(下)

提示&#xff1a;本篇内容是C语言操作符详解下篇 文章目录前言八、条件表达式九、逗号表达式十、 下标引用、函数调用和结构成员1. [ ] 下标引用操作符2. ( ) 函数调用操作符3.结构成员访问操作符十一、表达式求值1. 隐式类型转换举例说明1举例说明2举例说明32.算数转换3.操作…

三子棋——【保姆级C语言小游戏】

前言&#xff1a;今天七七为大家带来的是C语言中比较简单的小游戏“三子棋” 下面跟着七七一起来学习吧&#xff01; 文章目录游戏整体思路游戏的实现流程游戏的实现菜单的打印创建与初始化棋盘玩家下棋电脑下棋判断输赢代码的整体运行游戏整体思路 我们需要三个文件&#xff…

Nginx 配置文件详细介绍

1、大致说明 Nginx 包含很多配置文件&#xff0c;但是主要配置文件是&#xff1a;/usr/local/nginx/conf/nginx.conf。去掉全部注释后&#xff0c;配置文件的主体结构为&#xff1a; worker_processes 1;events {worker_connections 1024; }http {include mime.types…

勒索病毒整体攻击态势简单分析

声明 本文是学习2018勒索病毒白皮书政企篇. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 勒索病毒整体攻击态势 2018年&#xff0c;勒索病毒攻击特点也发生了变化&#xff1a;2017年&#xff0c;勒索病毒由过去撒网式无差别攻击逐步转向以服务器定…

python(14)--集合

前言 本篇文章学习的是 python 中集合的基础知识。 集合元素的内容是不可变的&#xff0c;常见的元素有整数、浮点数、字符串、元组等。至于可变内容列表、字典、集合等不可以是集合元素。虽然集合不可以是集合的元素&#xff0c;但是集合本身是可变的&#xff0c;可以去增加或…

代码随想录算法训练营第六十四天_第十章_单调栈 | 84. 柱状图中最大的矩形

LeetCode 84. 柱状图中最大的矩形 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。求在该柱状图中&#xff0c;能够勾勒出来的 矩形的最大面积。 视频讲解文章讲解https://programmercarl.com/0084.%E6%9F%B1%E7%8A%…