1.简单了解HAL库与标准库的一些区别

(1)句柄

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

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


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

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

    定义的结构体变量USART_InitStructure并不是一个全局变量,而是只在函数内部的局部变量,初始化完成之后,USART_InitStructure就失去了作用。

    而在HAL库中,同样是USART初始化结构体变量,我们要定义为全局变量

    1. UART_HandleTypeDef UART1_Handler;


    查看结构体成员:

    1. typedef struct
    2. {
    3. USART_TypeDef *Instance; /*!< UART registers base address */
    4. UART_InitTypeDef Init; /*!< UART communication parameters */
    5. uint8_t *pTxBuffPtr; /*!< Pointer to UART Tx transfer Buffer */
    6. uint16_t TxXferSize; /*!< UART Tx Transfer size */
    7. uint16_t TxXferCount; /*!< UART Tx Transfer Counter */
    8. uint8_t *pRxBuffPtr; /*!< Pointer to UART Rx transfer Buffer */
    9. uint16_t RxXferSize; /*!< UART Rx Transfer size */
    10. uint16_t RxXferCount; /*!< UART Rx Transfer Counter */
    11. DMA_HandleTypeDef *hdmatx; /*!< UART Tx DMA Handle parameters */
    12. DMA_HandleTypeDef *hdmarx; /*!< UART Rx DMA Handle parameters */
    13. HAL_LockTypeDef Lock; /*!< Locking object */
    14. __IO HAL_UART_StateTypeDef State; /*!< UART communication state */
    15. __IO uint32_t ErrorCode; /*!< UART Error code */
    16. }UART_HandleTypeDef;


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

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

  • 2、还包含过采样、(发送或接收的)数据缓存、数据指针、串口 DMA 相关的变量、各种标志位等等要在整个项目流程中都要设置的各个成员。
    该**UART1_Handler**就被称为串口的句柄,它被贯穿整个USART收发的流程,比如开启中断:
    1. HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);

    比如使用MSP与Callback回调函数:
    1. void HAL_UART_MspInit(UART_HandleTypeDef *huart);
    2. 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_xxx_MspInit()作为回调,被 HAL_xxx_Init()函数所调用。当我们需要移植程序到 STM32F1平台的时候,我们只需要修改 HAL_xxx_MspInit 函数内容而不需要修改 HAL_xxx_Init 入口参数内容。

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

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

  1. void HAL_UART_MspInit(UART_HandleTypeDef *huart);

入口参数仅仅需要一个串口句柄,这样有能看出句柄的方便。

(3)Callback函数

类似于MSP函数,Callback函数主要帮助**用户应用层**的代码编写。

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

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


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

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


HAL_UART_IRQHandler这个函数完成了判断是哪个中断(接收?发送?或者其他?),然后读出数据,保存至缓存区,顺便清除中断标志位等等操作。
比如我提前设置了,串口每接收五个字节,我就要对这五个字节进行处理。
在一开始我定义了一个串口接收缓存区:
在初始化中,我在句柄里设置好了缓存区的地址,缓存大小(五个字节)

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


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

  1. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);


因而我们需要做的就是重写Callback回调函数,在函数中实现想要的功能。
(比如这里我们只需要对这接收到的五个字节进行处理,完全不用再去手动清除标志位等操作)