打印
[应用相关]

STM32HAL库使用详解

[复制链接]
2809|9
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
wowu|  楼主 | 2021-8-4 20:29 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
1. 文档和库规范本
用户手册和固态函数库按照以下章节所描述的规范编写。

1.1缩写
Table1 本文档所有缩写定义

缩写                外设/单元
ADC                模数转换器
BKP                备份寄存器
CAN                控制器局域网模块
DMA                直接内存存取控制器
EXTI                外部中断事件控制器
FLASH        闪存存储器
GPIO                通用输入输出
I2C                内部集成电路
IWDG        独立看门狗
NVIC                嵌套中断向量列表控制器
PWR                电源/功耗控制
RCC                复位与时钟控制器
RTC                实时时钟
SPI                串行外设接口
SysTick        系统嘀嗒定时器
TIM                通用定时器
TIM1                高级控制定时器
USART        通用同步异步接收发射端
WWDG        窗口看门狗

1.2命名规则
固态函数库遵从以下命名规则 PPP表示任一外设缩写,例如:ADC。更多缩写相关信息参阅章节1.1 缩写

系统、源程序文件和头文件命名都以“stm32f10x_hal”作为开头,例如:stm32f10x_hal_conf.h。

常量仅被应用于一个文件的,定义于该文件中;被应用于多个文件的,在对应头文件中定义。所有常量都由英文字母大写书写。寄存器作为常量处理。他们的命名都由英文字母大写书写。

在大多数情况下,他们采用与缩写规范与本用户手册一致。

外设函数的命名以HAL加该外设的缩写加下划线为开头。每个单词的第一个字母都由英文字母大写书写,例如:HAL_I2C_Master_Transmit。在函数名中,以HAL开头加下划线加外设缩写,用以分隔外设缩写和函数名的其它部分。名为HAL_PPP_Init的函数,其功能是根据HAL_StatusTypeDef中指定的参数,初始化外设PPP,例如HAL_I2C_Init.


使用特权

评论回复
沙发
wowu|  楼主 | 2021-8-4 20:30 | 只看该作者
2. HAL函数库文件结构
2.1压缩包描述
STM32Fxx HAL函数库被压缩在一个zip文件中。解压该文件会产生一个文件夹:STM32Cube_FW_F1_V1.0.0,包含如下所示的子文件夹:



STM32 HAL固件库是Hardware Abstraction Layer的缩写,中文名称是:硬件抽象层。HAL库是ST公司为STM32的MCU最新推出的抽象层嵌入式软件,为更方便的实现跨STM32产品的最大可移植性。HAL库的推出,可以说ST也慢慢的抛弃了原来的标准固件库,这也使得很多老用户不满。但是HAL库推出的同时,也加入了很多第三方的中间件,有RTOS,USB,TCP / IP和图形等等。



和标准库对比起来,STM32的HAL库更加的抽象,ST最终的目的是要实现在STM32系列MCU之间无缝移植,甚至在其他MCU也能实现快速移植。


使用特权

评论回复
板凳
wowu|  楼主 | 2021-8-4 20:30 | 只看该作者
2.2HAL 库文件调用结构


使用特权

评论回复
地板
wowu|  楼主 | 2021-8-4 20:30 | 只看该作者
3.HAL的初始化

HAL层被调用前要先运行初始化函数HAL_init(),它包含在针对HAL自身的全局操作操作的源文件hal.c里。

HAL全局结构


使用特权

评论回复
5
wowu|  楼主 | 2021-8-4 20:31 | 只看该作者
4.STM32 HAL库句柄、MSP函数、Callback函数
4.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 ansferBuffer   */
    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就好。


使用特权

评论回复
6
wowu|  楼主 | 2021-8-4 20:31 | 只看该作者
4.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);


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


使用特权

评论回复
7
wowu|  楼主 | 2021-8-4 20:32 | 只看该作者
4.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库的可移植性之强大,并且用户可以完全不去理会底层各个寄存器的操作,代码也更有逻辑性。但与此带来的是复杂的代码量,极慢的编译速度,略微低下的效率。看怎么取舍了。


使用特权

评论回复
8
wowu|  楼主 | 2021-8-4 20:32 | 只看该作者
5.HAL库编程方式
在 HAL 库中对外设模型进行了统一,支持三种编程方式:

轮询模式/阻塞模式
中断方式
DMA模式
以IIC为例,三种编程模式对应的函数如下:

1、轮询模式/阻塞模式

HAL_I2C_Master_Transmit();  
HAL_I2C_Master_Receive();  
HAL_I2C_Slave_Transmit();  
HAL_I2C_Slave_Receive()
HAL_I2C_Mem_Write();      
HAL_I2C_Mem_Read();   
HAL_I2C_IsDeviceReady()


2、中断模式

HAL_I2C_Master_Transmit_IT();   
HAL_I2C_Master_Receive_IT();  
HAL_I2C_Slave_Transmit_IT()
HAL_I2C_Slave_Receive_IT();   
HAL_I2C_Mem_Write_IT();      
HAL_I2C_Mem_Read_IT()


3、DMA模式

HAL_I2C_Master_Transmit_DMA();   
HAL_I2C_Master_Receive_DMA();   
HAL_I2C_Slave_Transmit_DMA();   
HAL_I2C_Slave_Receive_DMA();   
HAL_I2C_Mem_Write_DMA();     
HAL_I2C_Mem_Read_DMA()


使用特权

评论回复
9
wowu|  楼主 | 2021-8-4 20:33 | 只看该作者
6.例程和帮助文档

STM32 HAL库包含大量的库函数的使用例程和库函数API接口帮助文档,在开发过程中如果遇到任何问题可以通过库文件自带的例程和帮助文档获得帮助。

6.1 例程文档结构图


使用特权

评论回复
10
wowu|  楼主 | 2021-8-4 20:33 | 只看该作者
6.2 HAL用户手册


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

96

主题

4119

帖子

1

粉丝