1.简单了解HAL库与标准库的一些区别
(1)句柄
句柄(handle),有多种意义,其中一种是指程序设计,也可指Windows编程。现在大部分都是指程序设计/程序开发这类。
- 第一种解释:句柄是一种特殊的智能指针 。当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄。
第二种解释:整个Windows编程的基础。一个句柄是指使用的一个唯一的整数值,即一个4字节(64位程序中为8字节)长的数值,来标识应用程序中的不同对象和同类中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。应用程序能够通过句柄访问相应的对象的信息,但是句柄不是指针,程序不能利用句柄来直接阅读文件中的信息。如果句柄不在I/O文件中,它是毫无用处的。 句柄是Windows用来标志应用程序中建立的或是使用的唯一整数,Windows大量使用了句柄来标识对象。
STM32的标准库中,句柄是一种特殊的指针,通常指向结构体
在STM32的标准库中,假设我们要初始化一个外设(这里以USART为例),我们首先要初始化他们的各个寄存器。在标准库中,这些操作都是利用固件库结构体变量+固件库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_xxx_MspInit()作为回调,被 HAL_xxx_Init()函数所调用。当我们需要移植程序到 STM32F1平台的时候,我们只需要修改 HAL_xxx_MspInit 函数内容而不需要修改 HAL_xxx_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_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回调函数,在函数中实现想要的功能。
(比如这里我们只需要对这接收到的五个字节进行处理,完全不用再去手动清除标志位等操作)