深入浅出理解STM32 HAL库的底层逻辑
共 18214字,需浏览 37分钟
·
2024-07-13 12:15
前言
STM32三种开发方式
1、直接配置寄存器
2、标准库
3、HAL库
HAL库固件库安装与用户手册
1、首先设置让Cube可以自动联网下载相关固件库选择updater Settings
2、根据芯片选择所需固件
3、寻找用户帮助手册
STM32 HAL库与标准库的区别
1、句柄
-
第一种解释:句柄是一种特殊的智能指针 。当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄。
-
第二种解释:整个Windows编程的基础。一个句柄是指使用的一个唯一的整数值,即一个4字节(64位程序中为8字节)长的数值,来标识应用程序中的不同对象和同类中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。应用程序能够通过句柄访问相应的对象的信息,但是句柄不是指针,程序不能利用句柄来直接阅读文件中的信息。如果句柄不在I/O文件中,它是毫无用处的。句柄是Windows用来标志应用程序中建立的或是使用的唯一整数,Windows大量使用了句柄来标识对象。
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函数
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函数
-
HAL_USART_Init()—>HAL_USART_MspInit() ,先初始化与 MCU无关的串口协议,再初始化与 MCU 相关的串口引脚。
-
在 STM32 的 HAL 驱动中HAL_PPP_MspInit()作为回调,被 HAL_PPP_Init()函数所调用。当我们需要移植程序到 STM32F1平台的时候,我们只需要修改 HAL_PPP_MspInit 函数内容而不需要修改 HAL_PPP_Init 入口参数内容。
void HAL_UART_MspInit(UART_HandleTypeDef *huart);
void USART3_IRQHandler(void) //串口1中断服务程序
{
u8 Res;
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART3); //读取接收到的数据
/*数据处理区*/
}
}
}
void USART1_IRQHandler(void)
{
HAL_UART_IRQHandler(&UART1_Handler); //调用HAL库中断处理公用函数
/***************省略无关代码****************/
}
/*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
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
STM32 HAL库结构
-
1、stm32f4xx.h主要包含STM32同系列芯片的不同具体型号的定义,是否使用HAL库等的定义,接着,其会根据定义的芯片信号包含具体的芯片型号的头文件:
-
2、stm32f4xx_hal.h:stm32f4xx_hal.c/h 主要实现HAL库的初始化、系统滴答相关函数、及CPU的调试模式配置
-
3、stm32f4xx_hal_conf.h :该文件是一个用户级别的配置文件,用来实现对HAL库的裁剪,其位于用户文件目录,不要放在库目录中。
-
4、库文件:stm32f4xx_hal_ppp.c/.h // 主要的外设或者模块的驱动源文件,包含了该外设的通用API
stm32f4xx_hal_ppp_ex.c/.h // 外围设备或模块驱动程序的扩展文件。这组文件中包含特定型号或者系列的芯片的特殊API。以及如果该特定的芯片内部有不同的实现方式,则该文件中的特殊API将覆盖_ppp中的通用API。
stm32f4xx_hal.c/.h // 此文件用于HAL初始化,并且包含DBGMCU、重映射和基于systick的时间延迟等相关的API
-
5、其他库文件
用户级别文件:
stm32f4xx_hal_msp_template.c // 只有.c没有.h。它包含用户应用程序中使用的外设的MSP初始化和反初始化(主程序和回调函数)。使用者复制到自己目录下使用模板。
stm32f4xx_hal_conf_template.h // 用户级别的库配置文件模板。使用者复制到自己目录下使用
system_stm32f4xx.c // 此文件主要包含SystemInit()函数,该函数在刚复位及跳到main之前的启动过程中被调用。它不在启动时配置系统时钟(与标准库相反)。时钟的配置在用户文件中使用HAL API来完成。startup_stm32f4xx.s // 芯片启动文件,主要包含堆栈定义,终端向量表等 stm32f4xx_it.c/.h // 中断处理函数的相关实现
-
6 main.c/.h //
-
初始化/反初始化函数:
HAL_PPP_Init(), HAL_PPP_DeInit()
-
IO 操作函数:
HAL_PPP_Read(),
HAL_PPP_Write(),
HAL_PPP_Transmit(),
HAL_PPP_Receive()
-
控制函数:
HAL_PPP_Set (),
HAL_PPP_Get ().
-
状态和错误:
** HAL_PPP_GetState (),
HAL_PPP_GetError ().
-
注意:
-
处理外设句柄(实现用户功能) -
处理MSP -
处理各种回调函数
1、外设句柄定义
-
多实例支持:每个外设/模块实例都有自己的句柄。因此,实例资源是独立的下面,以ADC为例,外围进程相互通信:该句柄用于管理进程例程之间的共享数据资源。
/**
* @brief ADC handle Structure definition
*/
typedef struct
{
ADC_TypeDef *Instance; /*!< Register base address */
ADC_InitTypeDef Init; /*!< ADC required parameters */
__IO uint32_t NbrOfCurrentConversionRank; /*!< ADC number of current conversion rank */
DMA_HandleTypeDef *DMA_Handle; /*!< Pointer DMA Handler */
HAL_LockTypeDef Lock; /*!< ADC locking object */
__IO uint32_t State; /*!< ADC communication state */
__IO uint32_t ErrorCode; /*!< ADC Error code */
}ADC_HandleTypeDef;
-
GPIO
-
SYSTICK
-
NVIC
-
RCC
-
FLASH
2、 三种编程方式
HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Start_IT(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Stop_IT(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length);
HAL_StatusTypeDef HAL_ADC_Stop_DMA(ADC_HandleTypeDef* hadc);
__HAL_PPP_ENABLE_IT(HANDLE, INTERRUPT):使能一个指定的外设中断
__HAL_PPP_DISABLE_IT(HANDLE, INTERRUPT):失能一个指定的外设中断
__HAL_PPP_GET_IT (HANDLE, __ INTERRUPT __):获得一个指定的外设中断状态
__HAL_PPP_CLEAR_IT (HANDLE, __ INTERRUPT __):清除一个指定的外设的中断状态
__HAL_PPP_GET_FLAG (HANDLE, FLAG):获取一个指定的外设的标志状态
__HAL_PPP_CLEAR_FLAG (HANDLE, FLAG):清除一个指定的外设的标志状态
__HAL_PPP_ENABLE(HANDLE) :使能外设
__HAL_PPP_DISABLE(HANDLE) :失能外设
__HAL_PPP_XXXX (HANDLE, PARAM) :指定外设的宏定义
_HAL_PPP_GET IT_SOURCE (HANDLE, __ INTERRUPT __):检查中断源
3、 三大回调函数
__weak HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
/*Configure the SysTick to have interrupt in 1ms time basis*/
HAL_SYSTICK_Config(SystemCoreClock/1000U);
/*Configure the SysTick IRQ priority */
HAL_NVIC_SetPriority(SysTick_IRQn, TickPriority ,0U);
/* Return function status */
return HAL_OK;
}
__weak void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(hspi);
/* NOTE : This function should not be modified, when the callback is needed,the HAL_SPI_TxCpltCallback should be implemented in the user file
*/
}
-
1、外设系统级初始化/解除初始化回调函数(用户代码的第二大部分:对于MSP的处理):
-
2、处理完成回调函数:HAL_PPP_ProcessCpltCallback*(Process指具体某种处理,如UART的Tx),
__weak void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
HAL_PPP_ErrorCallback
__weak void HAL_SPI_ErrorCallback(SPI_HandleTypeDef hspi)*
-
3、当外设或者DMA出现错误时,触发终端,该回调函数会在外设中断处理函数或者DMA的中断处理函数中被调用
HAL_PPP_ErrorCallback
__weak void HAL_SPI_ErrorCallback(SPI_HandleTypeDef hspi)*
HAL_UART_TxCpltCallback
HAL_UART_TxHalfCpltCallback
HAL库移植使用
-
1、复制stm32f2xx_hal_msp_template.c,参照该模板,依次实现用到的外设的HAL_PPP_MspInit()和 HAL_PPP_MspDeInit。
-
2、复制stm32f2xx_hal_conf_template.h,用户可以在此文件中自由裁剪,配置HAL库。
-
3、在使用HAL库时,必须先调用函数:HAL_StatusTypeDef HAL_Init(void)(该函数在stm32f2xx_hal.c中定义,也就意味着第一点中,必须首先实现HAL_MspInit(void)和HAL_MspDeInit(void))
-
4、HAL库与STD库不同,HAL库使用RCC中的函数来配置系统时钟,用户需要单独写时钟配置函数(STD库默认在system_stm32f2xx.c中)
-
5、关于中断,HAL提供了中断处理函数,只需要调用HAL提供的中断处理函数。用户自己的代码,不建议先写到中断中,而应该写到HAL提供的回调函数中。
-
6、对于每一个外设,HAL都提供了回调函数,回调函数用来实现用户自己的代码。整个调用结构由HAL库自己完成。
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);
-
1、 配置外设句柄 例如,建立UartConfig.c,在其中定义串口句柄 UART_HandleTypeDef huart;接着使用初始化句柄(HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef huart))
-
2、编写Msp 例如,建立UartMsp.c,在其中实现void HAL_UART_MspInit(UART_HandleTypeDef huart) 和 void HAL_UART_MspDeInit(UART_HandleTypeDef* huart)
-
3、实现对应的回调函数 例如,建立UartCallBack.c,在其中实现上文所说明的三大回调函数中的完成回调函数和错误回调函数
参考文档及网文链接
深度长文:STM32是如何软硬件结合,编译后怎么样一步步运行起来的
一份很棒的外设驱动库!基于STM32F4
STM32夺命100问,收藏夹吃灰走起~
干货 | 一文讲透STM32串口DMA收发机制