STM32 HAL库组成概述

STM32 HAL库概述

##

(一)HAL库设计思想

什么是HAL(Hardware Abstraction Layer)?

from 百度百科:

硬件抽象层是位于操作系统内核与硬件电路之间的接口层,其目的在于将硬件抽象化。它隐藏了特定平台的硬件接口细节,为操作系统提供虚拟硬件平台,使其具有硬件无关性,可在多种平台上进行移植。从软硬件测试的角度来看,软硬件的测试工作都可分别基于硬件抽象层来完成,使得软硬件测试工作的并行进行成为可能。

编程方式发展史:在没有库函数编程时,通常采用配置寄存器的方式进行片上外设的配置,这种通过操作寄存器的编程方式直接且直观,但是要求编程人员可以清晰地记忆相关寄存器的名称以及要求大量的位运算,再编程时要翻阅datasheet进行查阅,操作繁杂于是库函数编程应运而生,研发人员将寄存器进行二次封装,将一些外设的寄存器配置封装为结构体,用户在使用时直接更改结构体变量就可以完成对底层寄存器的配置,这就是标准外设库(Standard Peripheral Libraries),但是ST公司给出的标准外设库对每一款芯片都不同,在代码移植上很困难,于是HAL库应运而生,HAL库可以更好地确保跨STM32产品的最大可移植性。该库提供了一整套一致的中间件组件,如RTOS,USB,TCP / IP和图形等。相比标准外设库,HAL库表现出更高的抽象整合水平,HAL API集中关注各外设的公共函数功能,这样便于定义一套通用的用户友好的API函数接口,从而可以轻松实现从一个STM32产品移植到另一个不同的STM32系列产品。

ST公司针对不同外设构造了不同的数据类型以及操作这些数据类型的接口函数,开发人员只需要用这些数据类型定义对应的变量,并写入对应的配置参数,然后调用初始化函数完成外设的初始化、调用控制函数控制外设的行为、调用状态函数判断外设的状态。
##

(二)HAL库文件结构

HAL库文件可以分为以下两大类

库文件:存放在Drivers/STM32Fxxx_HAL_Driver文件夹下

stm32f2xx_hal_ppp.c/.h(ppp指外设名称,例如uart/gpio/i2c等等)
主要的外设或者模块的驱动源文件,包含了该外设的通用API

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

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

用户级别文件:大部分存放在Application/User/Core文件夹下

stm32f2xx_hal_msp.c
只有.c没有.h。它包含用户应用程序中使用的外设的MSP初始化和反初始化(主程序和回调函数)。

system_stm32f2xx.c(存放在Drivers/CMSIS文件夹下
此文件主要包含SystemInit()函数,该函数在刚复位及跳到main之前的启动过程中被调用。它不再启动时配置系统时钟(与标准库相反)。时钟的配置在用户文件中使用HAL API来完成。

startup_stm32f2xx.s(存放在Application/MDK-ARM文件夹下
芯片启动文件,主要包含堆栈定义,中断向量表等。

stm32f2xx_it.c/.h
中断服务函数的相关实现。

ppp.c/h
外设初始化以及相关实现

main.c/.h
主函数

##

(三)HAL库用户代码处理

句柄 HandleTypedef
HAL库将外设全部封装成了一个ppp_HandleTypedef(ppp为外设名称),例如:

UART_HandleTypedef—>串口句柄

下面截取stm32f1xx.hal.h文件中的UART_HandleTypedef作为例子:(ps:此处不对结构体中的变量做相应的解释,相关详细解释见 STM32 UART/USART 调试心得 )

typedef struct __UART_HandleTypeDef
{
    
    
  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              */

  __IO uint16_t                 TxXferCount;      /*!< UART Tx Transfer Counter           */

  uint8_t                       *pRxBuffPtr;      /*!< Pointer to UART Rx transfer Buffer */

  uint16_t                      RxXferSize;       /*!< UART Rx Transfer size              */

  __IO uint16_t                 RxXferCount;      /*!< UART Rx Transfer Counter           */

  __IO HAL_UART_RxTypeTypeDef ReceptionType;      /*!< Type of ongoing reception          */

  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    gState;           /*!< UART state information related to global Handle management
                                                       and also related to Tx operations.
                                                       This parameter can be a value of @ref HAL_UART_StateTypeDef */

  __IO HAL_UART_StateTypeDef    RxState;          /*!< UART state information related to Rx operations.
                                                       This parameter can be a value of @ref HAL_UART_StateTypeDef */

  __IO uint32_t                 ErrorCode;        /*!< UART Error code                    */

从UART_HandleTypedef这个例子中可以看出,该结构体涵盖了UART与MCU有关的所有部分,如接收数据缓冲区指针等,这些变量都是与MCU有密切关系的。

另外,HAL库ppp.c/h文件中的 MX_PPP_Init()函数中也有一个结构体变量,这里我们引入uart.c文件中的MX_USART2_UART_Init()作为例子,与上述UART_HandleTypedef中的结构体变量做对比。

void MX_USART2_UART_Init(void)
{
    
    

  /* USER CODE BEGIN USART2_Init 0 */

  /* USER CODE END USART2_Init 0 */

  /* USER CODE BEGIN USART2_Init 1 */

  /* USER CODE END USART2_Init 1 */
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 115200;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    
    
    Error_Handler();
  }
  /* USER CODE BEGIN USART2_Init 2 */

  /* USER CODE END USART2_Init 2 */
}

不难发现,MX_USART2_UART_Init()中出现的结构体成员变量与MCU无关,只是规定了波特率、字长、停止位等通信协议,这些变量可在任何MCU上直接使用,这部分初始化称之为抽象初始化(相关HAL库初始化的解释说明见下文)


MSP函数-单片机的具体解决方案(MCU Specific Package)
MSP主要指和MCU相关的初始化,HAL库中对外设的初始化主要有两部分:
1.抽象初始化:主要指与MCU无关的外设初始化,如上文MX_USART2_UART_Init(),主要是对通信协议做了初始化。
2.承载初始化:主要指与MCU有关的外设初始化,外设不仅需要通信协议这样的初始化,外设的功能实现还需要借助MCU来完成,故外设初始化中包含了MSP_Init(),其执行的功能是为外设分配管脚等,为外设功能的实现提供硬件条件。
此处引入uart.c文件中的HAL_UART_MspInit(UART_HandleTypeDef*uartHandle)作为例子来说明HAL库的承载初始化

void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
    
    

  GPIO_InitTypeDef GPIO_InitStruct = {
    
    0};
  if(uartHandle->Instance==USART1)
  {
    
    
  /* USER CODE BEGIN USART1_MspInit 0 */

  /* USER CODE END USART1_MspInit 0 */
    /* USART1 clock enable */
    __HAL_RCC_USART1_CLK_ENABLE();         //USART 1的时钟使能

    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**USART1 GPIO Configuration
    PB6     ------> USART1_TX
    PB7     ------> USART1_RX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_6;       //USART1的引脚分配
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    __HAL_AFIO_REMAP_USART1_ENABLE();

  /* USER CODE BEGIN USART1_MspInit 1 */

  /* USER CODE END USART1_MspInit 1 */
  }
  else if(uartHandle->Instance==USART2)
  {
    
    
  /* USER CODE BEGIN USART2_MspInit 0 */

  /* USER CODE END USART2_MspInit 0 */
    /* USART2 clock enable */
    __HAL_RCC_USART2_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART2 GPIO Configuration
    PA2     ------> USART2_TX
    PA3     ------> USART2_RX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_3;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* USART2 DMA Init */
    /* USART2_RX Init */
    hdma_usart2_rx.Instance = DMA1_Channel6;            // USART2 DMA初始化
    hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma_usart2_rx.Init.Mode = DMA_CIRCULAR;
    hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW;
    if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK)
    {
    
    
      Error_Handler();
    }

    __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart2_rx);

    /* USART2 interrupt Init */
    HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(USART2_IRQn);
  /* USER CODE BEGIN USART2_MspInit 1 */

  /* USER CODE END USART2_MspInit 1 */
  }
}

从HAL_UART_MspInit(UART_HandleTypeDef*uartHandle)的代码片段可以看出,MspInit()主要包含了MCU对外设ppp的硬件资源分配,如时钟使能、引脚配置等,这是与MCU密切相关的,是MCU为外设功能实现提供的硬件基础,故这部分初始化我们称之为MSP(单片机的具体解决方案),可以看作是HAL库的承载初始化


回调函数

回调函数
回调函数是一个通过指针调用的函数。如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就将该函数称之为回调函数。
回调函数不是由该函数的实现方法直接调用的,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行的响应。

这里我们以串口中断为例介绍HAL库对中断的封装(关于中断的详细介绍查看:STM32 NVIC 调试心得

第一个重要函数,存放在stm32fxxx.it.c文件中
void USART2_IRQHandler(void)
{
    
    
  /* USER CODE BEGIN USART2_IRQn 0 */

  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */

 
  /* USER CODE END USART2_IRQn 1 */
}

用过中断的同学都知道这个函数,但大部分都将注意力集中在HAL_UART_IRQHandler(&huart2);这个HAL库的中断接口函数上,于是有了第一个问题——>是谁调用了USART2_IRQHandler()这个函数呢?这就需要了解MCU对中断的响应机制(详细说明见STM32 NVIC 调试心得),当中断发生时,微控制器暂停当前运行的程序,由内部的硬件机制跳转执行USART2_IRQHandler(),下面我们再看HAL_UART_IRQHandler(&huart2);中的相关代码

void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
    
    
  uint32_t isrflags   = READ_REG(huart->Instance->SR);
  uint32_t cr1its     = READ_REG(huart->Instance->CR1);
  uint32_t cr3its     = READ_REG(huart->Instance->CR3);
  uint32_t errorflags = 0x00U;
  uint32_t dmarequest = 0x00U;

  /* If no error occurs */
  errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
  if (errorflags == RESET)
  {
    
    
    /* UART in mode Receiver -------------------------------------------------*/
    if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
    {
    
    
      UART_Receive_IT(huart);
      return;
    }
  }

  /* If some errors occur */
  if ((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET)))
  {
    
    
    /* UART parity error interrupt occurred ----------------------------------*/
    if (((isrflags & USART_SR_PE) != RESET) && ((cr1its & USART_CR1_PEIE) != RESET))
    {
    
    
      huart->ErrorCode |= HAL_UART_ERROR_PE;
    }

    /* UART noise error interrupt occurred -----------------------------------*/
    if (((isrflags & USART_SR_NE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    {
    
    
      huart->ErrorCode |= HAL_UART_ERROR_NE;
    }

    /* UART frame error interrupt occurred -----------------------------------*/
    if (((isrflags & USART_SR_FE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    {
    
    
      huart->ErrorCode |= HAL_UART_ERROR_FE;
    }

    /* UART Over-Run interrupt occurred --------------------------------------*/
    if (((isrflags & USART_SR_ORE) != RESET) && (((cr1its & USART_CR1_RXNEIE) != RESET) || ((cr3its & USART_CR3_EIE) != RESET)))
    {
    
    
      huart->ErrorCode |= HAL_UART_ERROR_ORE;
    }

    /* Call UART Error Call back function if need be --------------------------*/
    if (huart->ErrorCode != HAL_UART_ERROR_NONE)
    {
    
    
      /* UART in mode Receiver -----------------------------------------------*/
      if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
      {
    
    
        UART_Receive_IT(huart);
      }

      /* If Overrun error occurs, or if any error occurs in DMA mode reception,
         consider error as blocking */
      dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR);
      if (((huart->ErrorCode & HAL_UART_ERROR_ORE) != RESET) || dmarequest)
      {
    
    
        /* Blocking error : transfer is aborted
           Set the UART state ready to be able to start again the process,
           Disable Rx Interrupts, and disable Rx DMA request, if ongoing */
        UART_EndRxTransfer(huart);

        /* Disable the UART DMA Rx request if enabled */
        if (HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))
        {
    
    
          CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);

          /* Abort the UART DMA Rx channel */
          if (huart->hdmarx != NULL)
          {
    
    
            /* Set the UART DMA Abort callback :
               will lead to call HAL_UART_ErrorCallback() at end of DMA abort procedure */
            huart->hdmarx->XferAbortCallback = UART_DMAAbortOnError;
            if (HAL_DMA_Abort_IT(huart->hdmarx) != HAL_OK)
            {
    
    
              /* Call Directly XferAbortCallback function in case of error */
              huart->hdmarx->XferAbortCallback(huart->hdmarx);
            }
          }
          else
          {
    
    
            /* Call user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
            /*Call registered error callback*/
            huart->ErrorCallback(huart);
#else
            /*Call legacy weak error callback*/
            HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
          }
        }
        else
        {
    
    
          /* Call user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
          /*Call registered error callback*/
          huart->ErrorCallback(huart);
#else
          /*Call legacy weak error callback*/
          HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
        }
      }
      else
      {
    
    
        /* Non Blocking error : transfer could go on.
           Error is notified to user through user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
        /*Call registered error callback*/
        huart->ErrorCallback(huart);
#else
        /*Call legacy weak error callback*/
        HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */

        huart->ErrorCode = HAL_UART_ERROR_NONE;
      }
    }
    return;
  } /* End if some error occurs */

  /* Check current reception Mode :
     If Reception till IDLE event has been selected : */
  if (  (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE)
      &&((isrflags & USART_SR_IDLE) != 0U)
      &&((cr1its & USART_SR_IDLE) != 0U))
  {
    
    
    __HAL_UART_CLEAR_IDLEFLAG(huart);

    /* Check if DMA mode is enabled in UART */
    if (HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))
    {
    
    
      /* DMA mode enabled */
      /* Check received length : If all expected data are received, do nothing,
         (DMA cplt callback will be called).
         Otherwise, if at least one data has already been received, IDLE event is to be notified to user */
      uint16_t nb_remaining_rx_data = (uint16_t) __HAL_DMA_GET_COUNTER(huart->hdmarx);
      if (  (nb_remaining_rx_data > 0U)
          &&(nb_remaining_rx_data < huart->RxXferSize))
      {
    
    
        /* Reception is not complete */
        huart->RxXferCount = nb_remaining_rx_data;

        /* In Normal mode, end DMA xfer and HAL UART Rx process*/
        if (huart->hdmarx->Init.Mode != DMA_CIRCULAR)
        {
    
    
          /* Disable PE and ERR (Frame error, noise error, overrun error) interrupts */
          CLEAR_BIT(huart->Instance->CR1, USART_CR1_PEIE);
          CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);

          /* Disable the DMA transfer for the receiver request by resetting the DMAR bit
             in the UART CR3 register */
          CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);

          /* At end of Rx process, restore huart->RxState to Ready */
          huart->RxState = HAL_UART_STATE_READY;
          huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;

          CLEAR_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);

          /* Last bytes received, so no need as the abort is immediate */
          (void)HAL_DMA_Abort(huart->hdmarx);
        }
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
        /*Call registered Rx Event callback*/
        huart->RxEventCallback(huart, (huart->RxXferSize - huart->RxXferCount));
#else
        /*Call legacy weak Rx Event callback*/
        HAL_UARTEx_RxEventCallback(huart, (huart->RxXferSize - huart->RxXferCount));
#endif
      }
      return;
    }
    else
    {
    
    
      /* DMA mode not enabled */
      /* Check received length : If all expected data are received, do nothing.
         Otherwise, if at least one data has already been received, IDLE event is to be notified to user */
      uint16_t nb_rx_data = huart->RxXferSize - huart->RxXferCount;
      if (  (huart->RxXferCount > 0U)
          &&(nb_rx_data > 0U) )
      {
    
    
        /* Disable the UART Parity Error Interrupt and RXNE interrupts */
        CLEAR_BIT(huart->Instance->CR1, (USART_CR1_RXNEIE | USART_CR1_PEIE));

        /* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
        CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);

        /* Rx process is completed, restore huart->RxState to Ready */
        huart->RxState = HAL_UART_STATE_READY;
        huart->ReceptionType = HAL_UART_RECEPTION_STANDARD;

        CLEAR_BIT(huart->Instance->CR1, USART_CR1_IDLEIE);
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
        /*Call registered Rx complete callback*/
        huart->RxEventCallback(huart, nb_rx_data);
#else
        /*Call legacy weak Rx Event callback*/
        HAL_UARTEx_RxEventCallback(huart, nb_rx_data);
#endif
      }
      return;
    }
  }

  /* UART in mode Transmitter ------------------------------------------------*/
  if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
  {
    
    
    UART_Transmit_IT(huart);
    return;
  }

  /* UART in mode Transmitter end --------------------------------------------*/
  if (((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
  {
    
    
    UART_EndTransmit_IT(huart);
    return;
  }
}

仔细阅读这部分代码,这部分代码其实就是接收中断、发送中断、异常中断、DMA中断等的再次封装,其实是中断类型判定与跳转执行回调函数的过程,在看HAL库代码时一定要注意ST官方对HAL库函数的解释与说明,以下ST公司是对HAL_UART_IRQHandler()的解释与说明

 @brief  This function handles UART interrupt request.
 @param  huart  Pointer to a UART_HandleTypeDef structure that contains
         the configuration information for the specified UART module.
 @retval None

 @简要说明:该函数处理串口中断请求
 @参数:    指向UART\U HandleTypeDef结构的param huart指针,该结构包含指定UART模块的配置信息。
 @返回值:  无返回值 

仔细阅读完代码后,我们不难发现HAL_UART_IRQHandler()中还调用了 HAL_UARTEx_RxEventCallback(huart, nb_rx_data); HAL_UART_ErrorCallback(huart)等回调函数,在keil中右键点击go to Defination,我们发现这些函数前均有weak关键字

/**
  * @brief  Rx Transfer completed callbacks.
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    
    
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  /* NOTE: This function should not be modified, when the callback is needed,
           the HAL_UART_RxCpltCallback could be implemented in the user file
   */
}

/**
  * @brief  Rx Half Transfer completed callbacks.
  * @param  huart  Pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
__weak void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
{
    
    
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  /* NOTE: This function should not be modified, when the callback is needed,
           the HAL_UART_RxHalfCpltCallback could be implemented in the user file
   */
}

什么是weak关键字呢?其实函数名称前面加上__weak 修饰符,我们一般称这个函数为“弱函数”。
加上了__weak 修饰符的函数,用户可以在用户文件中重新定义一个同名函数,最终编译器编译的时候,会选择用户定义的函数,如果用户没有重新定义这个函数,那么编译器就会执行__weak 声明的函数,并且编译器不会报错。所以我们可以在别的地方定义一个相同名字的函数,而不必也尽量不要修改原始函数,于是这便实现了回调,回调给用户,使中断服务程序最终由用户设计,这种方式增强了代码的逻辑性:中断发生后先由内部硬件托管给HAL,HAL调用用户自己编写的回调函数实现功能,增加了文件的嵌套程度
##

(四)HAL库编程方式

HAL库提供了三种编程方式,分别是轮询模式、中断模式、DMA模式
具体三种方式的编程案例详见STM32 ppp调试经验

写在最后:本文基于以下文章进行二次整理总结,参考了漆强老师《嵌入式系统设计——基于STM32与HAL库》,如有错漏,敬请指正!
HAL库详解
weak关键字解读

猜你喜欢

转载自blog.csdn.net/zxt510001/article/details/125470729