[其他ST产品] STM32 HAL库详解

[复制链接]
4270|65
 楼主| 原来是wjc 发表于 2023-8-27 16:20 | 显示全部楼层 |阅读模式
1. HAL库文件结构

对于开发人员而言,首先要清楚 HAL 库的文件结构。

根据文件类型可认为以下两大类:

  1. 库文件:
  2.         stm32f2xx_hal_ppp.c/.h                        // 主要的外设或者模块的驱动源文件,包含了该外设的通用API
  3.         stm32f2xx_hal_ppp_ex.c/.h                // 外围设备或模块驱动程序的扩展文件。这组文件中包含特定型号或者系列的芯片的特殊API。以及如果该特定的芯片内部有不同的实现方式,则该文件中的特殊API将覆盖_ppp中的通用API。
  4.         stm32f2xx_hal.c/.h                                // 此文件用于HAL初始化,并且包含DBGMCU、重映射和基于systick的时间延迟等相关的API
  5.         其他库文件
  6. 用户级别文件:
  7.         stm32f2xx_hal_msp_template.c        // 只有.c没有.h。它包含用户应用程序中使用的外设的MSP初始化和反初始化(主程序和回调函数)。使用者复制到自己目录下使用模板。
  8.         stm32f2xx_hal_conf_template.h        // 用户级别的库配置文件模板。使用者复制到自己目录下使用
  9.         system_stm32f2xx.c                                // 此文件主要包含SystemInit()函数,该函数在刚复位及跳到main之前的启动过程中被调用。 **它不在启动时配置系统时钟(与标准库相反)**。 时钟的配置在用户文件中使用HAL API来完成。
  10.         startup_stm32f2xx.s                                // 芯片启动文件,主要包含堆栈定义,终端向量表等
  11.         stm32f2xx_it.c/.h                                // 中断服务函数的相关实现
  12.         main.c/.h                                                //


 楼主| 原来是wjc 发表于 2023-8-27 16:20 | 显示全部楼层
与我们密切相关的有

主函数: main.c/.h
MSP初始化: stm32f2xx_hal_msp_template.c
中断服务函数: stm32f2xx_it.c/.h
 楼主| 原来是wjc 发表于 2023-8-27 16:20 | 显示全部楼层
2. HAL库用户代码
HAL 库对底层进行了抽象,在此结构下,用户代码处理可分为三大部分:

句柄
MSP
回调函数
关于这三点,也可参考这里进行理解,
 楼主| 原来是wjc 发表于 2023-8-27 16:20 | 显示全部楼层
1、关于句柄

HAL库在结构上,对每个外设抽象成了一个称为ppp_HandleTypeDef的结构体,其中ppp就是每个外设的名字。

与标准库中的结构体相比,HAL 库中的结构体包含了其可能出现的所有定义,比如用户想要使用ADC,只要定义一个ADC_HandleTypeDef的全局变量,针对不同的应用场景配置不同结构体成员就可以满足使用要求。
 楼主| 原来是wjc 发表于 2023-8-27 16:21 | 显示全部楼层
2、MSP
MSP(MCU Specific Package,单片机的具体方案),是指和MCU相关的初始化

初始化函数包含的内容可分为两部分:

一部分是与MCU无关的,通信协议;
一部分是与MCU相关的,引脚功能。
这里可以结合博主关于串口的说明USART and UART进行理解。
 楼主| 原来是wjc 发表于 2023-8-27 16:21 | 显示全部楼层
以串口为例,在 MX_USART1_UART_Init(void) 函数中初始化串口的波特率、停止位、奇偶校验等,这部分代码是与串口协议相关的,并未涉及到具体的引脚,因此与 MCU 是无关的,是抽象的。
 楼主| 原来是wjc 发表于 2023-8-27 16:21 | 显示全部楼层
  1. static void MX_USART1_UART_Init(void)
  2. {

  3.   huart1.Instance = USART1;
  4.   huart1.Init.BaudRate = 115200;  
  5.   huart1.Init.WordLength = UART_WORDLENGTH_8B;
  6.   huart1.Init.StopBits = UART_STOPBITS_1;
  7.   huart1.Init.Parity = UART_PARITY_NONE;
  8.   huart1.Init.Mode = UART_MODE_TX_RX;
  9.   huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  10.   huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  11.   huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  12.   huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  13.   if (HAL_UART_Init(&huart1) != HAL_OK)
  14.   {
  15.     _Error_Handler(__FILE__, __LINE__);
  16.   }
  17.     __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);

  18. }
 楼主| 原来是wjc 发表于 2023-8-27 16:21 | 显示全部楼层
在 stm32f0xx_hal_msp.c 文件中的 HAL_UART_MspInit(UART_HandleTypeDef* huart) 函数中将串口所时用的 MCU 引脚模式进行配置,这部分是与 MCU 具体相关的。
 楼主| 原来是wjc 发表于 2023-8-27 16:21 | 显示全部楼层
在 stm32f0xx_hal_msp.c 文件中的 HAL_UART_MspInit(UART_HandleTypeDef* huart) 函数中将串口所时用的 MCU 引脚模式进行配置,这部分是与 MCU 具体相关的。
 楼主| 原来是wjc 发表于 2023-8-27 16:21 | 显示全部楼层
  1. void HAL_UART_MspInit(UART_HandleTypeDef* huart)
  2. {
  3.   // 关于 GPIO 引脚初始化的流程与上面介绍的“四步”相同
  4.   GPIO_InitTypeDef GPIO_InitStruct;
  5.   if(huart->Instance==USART2)   // USART 2
  6.   {
  7.   /* USER CODE BEGIN USART2_MspInit 0 */

  8.   /* USER CODE END USART2_MspInit 0 */
  9.     /* Peripheral clock enable */
  10.     __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口时钟
  11.   
  12.     /**USART2 GPIO Configuration   
  13.     PA2     ------> USART2_TX
  14.     PA3     ------> USART2_RX
  15.     */
  16.     GPIO_InitStruct.Pin = USART2_TX_Pin|USART2_RX_Pin;  // 收发引脚
  17.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;  // 复用推挽输出
  18.     GPIO_InitStruct.Pull = GPIO_NOPULL;
  19.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  20.     GPIO_InitStruct.Alternate = GPIO_AF1_USART2;  // 复用功能配置
  21.     HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);  // 结构体初始化

  22.     /* USART2 DMA Init */    // DMA 方式收发初始化
  23.     /* USART2_RX Init */
  24.     hdma_usart2_rx.Instance = DMA1_Channel3;
  25.     hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
  26.     hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE;
  27.     hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE;
  28.     hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  29.     hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  30.     hdma_usart2_rx.Init.Mode = DMA_NORMAL;
  31.     hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW;
  32.     if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK)
  33.     {
  34.       _Error_Handler(__FILE__, __LINE__);
  35.     }

  36.     __HAL_DMA1_REMAP(HAL_DMA1_CH3_USART2_RX);

  37.     __HAL_LINKDMA(huart,hdmarx,hdma_usart2_rx);

  38.     /* USART2_TX Init */
  39.     hdma_usart2_tx.Instance = DMA1_Channel4;
  40.     hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
  41.     hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
  42.     hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE;
  43.     hdma_usart2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
  44.     hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
  45.     hdma_usart2_tx.Init.Mode = DMA_NORMAL;
  46.     hdma_usart2_tx.Init.Priority = DMA_PRIORITY_LOW;
  47.     if (HAL_DMA_Init(&hdma_usart2_tx) != HAL_OK)
  48.     {
  49.       _Error_Handler(__FILE__, __LINE__);
  50.     }

  51.     __HAL_DMA1_REMAP(HAL_DMA1_CH4_USART2_TX);

  52.     __HAL_LINKDMA(huart,hdmatx,hdma_usart2_tx);

  53.     /* USART2 interrupt Init */
  54.     HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
  55.     HAL_NVIC_EnableIRQ(USART2_IRQn);
  56.   /* USER CODE BEGIN USART2_MspInit 1 */

  57.   /* USER CODE END USART2_MspInit 1 */
  58.   }
  59. }
 楼主| 原来是wjc 发表于 2023-8-27 16:22 | 显示全部楼层
3、回调函数
在标准库中,串口中断了以后,我们要先在中断中判断是否是接收中断,然后读出数据,顺便清除中断标志位,然后再是对数据的处理。这样如果我们在一个中断函数中写这么多代码,就会显得很混乱。

而在HAL库中,以GPIO为例,进入中断后,直接由HAL库中断函数HAL_GPIO_EXTI_IRQHandler() 进行托管。
 楼主| 原来是wjc 发表于 2023-8-27 16:22 | 显示全部楼层
而该函数主要完成两项任务:

清除中断标志位
调用回调函数处理中断
  1. // EXIT 外部中断服务函数
  2. void EXTI0_1_IRQHandler(void)
  3. {
  4.   HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_10);
  5. }


  6. // HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_10)函数如下
  7. void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
  8. {
  9.   /* EXTI line interrupt detected */
  10.   if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET)
  11.   {
  12.     __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
  13.     HAL_GPIO_EXTI_Callback(GPIO_Pin);
  14.   }
  15. }
 楼主| 原来是wjc 发表于 2023-8-27 16:22 | 显示全部楼层
在回调函数内对中断进行处理,这种方式增强了代码的逻辑性,但也增加了文件的嵌套程度。
 楼主| 原来是wjc 发表于 2023-8-27 16:22 | 显示全部楼层
3. HAL库编程方式
在 HAL 库中对外设模型进行了统一,支持三种编程方式:

轮询模式/阻塞模式
中断方式
DMA模式
 楼主| 原来是wjc 发表于 2023-8-27 16:23 | 显示全部楼层
以IIC为例,三种编程模式对应的函数如下:

1、轮询模式/阻塞模式

  1. HAL_I2C_Master_Transmit();  
  2. HAL_I2C_Master_Receive();  
  3. HAL_I2C_Slave_Transmit();  
  4. HAL_I2C_Slave_Receive()
  5. HAL_I2C_Mem_Write();      
  6. HAL_I2C_Mem_Read();   
  7. HAL_I2C_IsDeviceReady()
 楼主| 原来是wjc 发表于 2023-8-27 16:23 | 显示全部楼层
2、中断模式
  1. HAL_I2C_Master_Transmit_IT();   
  2. HAL_I2C_Master_Receive_IT();  
  3. HAL_I2C_Slave_Transmit_IT()
  4. HAL_I2C_Slave_Receive_IT();   
  5. HAL_I2C_Mem_Write_IT();      
  6. HAL_I2C_Mem_Read_IT()
 楼主| 原来是wjc 发表于 2023-8-27 16:23 | 显示全部楼层
3、DMA模式
  1. HAL_I2C_Master_Transmit_DMA();   
  2. HAL_I2C_Master_Receive_DMA();   
  3. HAL_I2C_Slave_Transmit_DMA();   
  4. HAL_I2C_Slave_Receive_DMA();   
  5. HAL_I2C_Mem_Write_DMA();     
  6. HAL_I2C_Mem_Read_DMA()
Stahan 发表于 2023-9-2 23:43 | 显示全部楼层
感觉HAL里面好多都是判断,逻辑很严谨但是太冗杂了
fengm 发表于 2023-9-5 10:19 | 显示全部楼层
HAL库提供了抽象化的硬件接口,屏蔽了底层硬件差异,使得开发者可以更方便地编写可移植的代码。
linfelix 发表于 2023-9-5 10:59 | 显示全部楼层
STM32 HAL库是一种基于HAL的软件开发库,提供了丰富的驱动程序和API,可以帮助开发者快速设计和调试STM32应用程序。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

87

主题

1250

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部