HAL库与标准库对比

📖 精选 ✍️ Jack | 📅 2025-03-11 | 👍 0 | 原帖↗
#知识星球 #立芯嵌入式 #来源/立芯星球 #技术/HAL库 #技术/UART #技术/STM32 #质量/精华

原帖 | Jack | 2025-03-11 21:20 | 👍0 | 阅读约1

HAL库与标准库的区别

1. 句柄

标准库中,要初始化一个外设,首先要初始化他们的各个寄存器。在标准库中,这些操作都是利用固件库结构体变量+固件库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

可以看到,要初始化一个串口,需要对六个位置进行赋值,然后引用Init函数,并且USART_InitStructure并不是一个全局结构体变量,而是只在函数内部的局部变量,初始化完成后,这个结构体就失去了作用。HAL库则不然,同样是初始化USART结构体变量,我们要定义为全局变量。

extern 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;

与标准库不同的是,该成员不仅包含了之前标准库就有的六个成员,还包括了采样、数据缓存、数据指针等相关变量。这个UART_HandleTypeDef就称为串口的句柄(对外设资源的封装,通过它可以方便的配置和操作外设),只需配置一次,整个项目任意处都可以传入它的信息,逻辑上确实比标准库合理。

2. MSP函数

MCU Specific Package :MCU 具体方案

标准库中,我们要初始化一个串口,首先要设置和MCU无关的东西,例如波特率、停止位等,这些参数设置和具体的MCU没有任何关系。而一个串口收发功能需要一个MCU来承载,比如具体方案是PA9发送、PA10接收,另有一个引脚是接地的,这些是和MCU牢牢相关的。

HAL库就是上面这个逻辑,先写和MCU无关的那些理论参数,再写和MCU有关的具体方案。

HAL_USART_Init()—>HAL_USART_MspInit()

比如下面这个usart.c中的代码,这个代码是由STM32 IDE生成的,只需要点点点就可以自动生成如下代码:

/* USART1 init function */

void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

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

  /* USER CODE END USART1_Init 2 */

}

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();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART1 GPIO Configuration
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* USER CODE BEGIN USART1_MspInit 1 */

  /* USER CODE END USART1_MspInit 1 */
  }
}

HAL_USART_MspInit()函数作为回调,被HAL_USART_Init()所调用,当我们需要移植程序到另一款STM32芯片上时,只需要修HAL_USART_MspInit()即可,而不需要修改HAL_USART_Init()函数。可见,HAL库相对于标准库多了MSP函数之后,移植性非常强。

3. Callback(回调)函数

在标准库中,串口中断了以后,我们要先在中断中判断是否接收中断,然后读出数据,顺便清除中断标志位,然后对数据进行处理。

void DEBUG_USART_IRQHandler(void)
{
    uint8_t ucTemp;
    if (USART_GetITStatus(DEBUG_USARTx, USART_IT_RXNE) != RESET)
    {
        ucTemp = USART_ReceiveData(DEBUG_USARTx);  // 读取数据寄存器,自动清除RXNE标志位
        USART_SendData(DEBUG_USARTx, ucTemp);      // 回发数据
    }
}

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

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

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

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

*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

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

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

在这个callback回调函数中,我们只需要对这接收到的5个字节(在aRxBuffer[]中)进行处理即可,不用管清除中断标志位的事。所以说Callback函数是一个应用层代码的函数,我们在一开始只设置句柄里面的各个参数,然后就等着HAL库把自己安排好的代码送到手中就可以了~

如此,可见HAL库的可移植性之强大,并且用户可以完全不去理会底层各个寄存器的操作,代码也很有逻辑性。只不过相比于标准库而言,代码量大、编译速度慢,效率略低。但在未来,这些缺点会被弥补,因为STM已经不再维护标准库,all in HAL库。

总的来说,它们的主要区别就在于句柄不同,比如标准库中的句柄会是临时的,HAL中就是永存的,会封装外设所有关键信息。这些因素最终导致两个库再体积上,兼容性,中间件支持,以及代码运行效率上都有区别。


相关笔记