目录
HAL库驱动框架简述
HAL库外设设计思想
HAL库和Cube MX相结合
一、对外设的封装——句柄结构体
二、外设初始化
初始化结构体
初始化的逻辑
三、外设使用逻辑
通用接口函数
初始化函数
I/O操作函数
控制函数
状态参数
扩展接口函数
总结
补充:HAL库使用主线
HAL库驱动框架简述
参考于:HAL库学习笔记-10 HAL库外设驱动框架概述_hal库的外设句柄_Q-Stark的博客-CSDN博客
HAL库外设设计思想
HAL库借鉴面向对象的设计思想,将外设驱动封装为对象。
采用此种开发方式有以下特点:
- 屏蔽底层硬件:只需了解相关接口函数的功能和参数要求即可
- 提高开发效率:开发难度较小,开发周期较短,后期的维护升级、以及硬件平台的移植等工作量小
- 程序执行效率:由于考虑了程序的稳健性、扩充性和可移植性,程序代码比较繁琐和臃肿,执行效率较低
HAL库和Cube MX相结合
一、对外设的封装——句柄结构体
围绕着芯片设计的外设多种多样,功能也越来越多,为了能够统一管理这些外设,HAL库设计了统一的外设句柄数据类型xxx_HandleTypeDef(PPP代表外设名称)。如定时器句柄:
/**
* @brief TIM Time Base Handle Structure definition
*/
typedef struct
{
TIM_TypeDef *Instance; /*!< Register base address */
TIM_Base_InitTypeDef Init; /*!< TIM Time Base required parameters */
HAL_TIM_ActiveChannel Channel; /*!< Active channel */
DMA_HandleTypeDef *hdma[7]; /*!< DMA Handlers array This array is accessed by a @ref DMA_Handle_index */
HAL_LockTypeDef Lock; /*!< Locking object */
__IO HAL_TIM_StateTypeDef State; /*!< TIM operation state */
} TIM_HandleTypeDef;
保护锁是HAL库提供的一种安全机制,以避免对外设的并发访问。
二、外设初始化
由上述的句柄结构体可知,我们需要定义一个外设句柄指针,并向其中填充参数,其中最重要的就是初始化参数,为此HAL库为不同的外设定义了不同的初始化结构体,且相同外设的不同功能也有不同的初始化结构,如定时器,有时基单元初始化结构体、输入初始化结构体和输出初始化结构体等,分别用于输入捕获和输出比较等不同功能。
初始化这一步骤使用CubeMX配置,可自动生成初始化代码,大大减少了开发难度。如下的初始化函数代码即由CubeMX自动生成的,带有MX前缀。
初始化结构体
代码如下(示例):
/**
* @brief TIM Time base Configuration Structure definition
*/
typedef struct
{
uint32_t Prescaler;
uint32_t CounterMode;
uint32_t Period;
uint32_t ClockDivision;
uint32_t RepetitionCounter;
uint32_t AutoReloadPreload;
} TIM_Base_InitTypeDef;
/**
* @brief TIM Input Capture Configuration Structure definition
*/
typedef struct
{
uint32_t ICPolarity;
uint32_t ICSelection;
uint32_t ICPrescaler;
uint32_t ICFilter;
} TIM_IC_InitTypeDef;
/**
* @brief TIM Output Compare Configuration Structure definition
*/
typedef struct
{
uint32_t OCMode;
uint32_t Pulse;
uint32_t OCPolarity;
uint32_t OCNPolarity;
uint32_t OCFastMode;
uint32_t OCIdleState;
uint32_t OCNIdleState;
} TIM_OC_InitTypeDef;
初始化的逻辑
如我们在串口笔记中讲到的串口初始化过程,在HAL_PPP_Init()初始化函数中,将句柄结构中的初始化参数存入寄存器,即进行相关参数的传入赋值,然后调用HAL_PPP_MspInit()函数完成具体的时钟、引脚等资源初始化,完成围绕具体MCU的配置;MSP函数调用完成后,回到HAL_PPP_Init()函数调用现场,根据返回值情况进入下一步,最后完成外设初始化。
以串口为例:
代码如下(示例):
UART_HandleTypeDef huart1;
DMA_HandleTypeDef hdma_usart1_rx;
DMA_HandleTypeDef hdma_usart1_tx;
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
Error_Handler();
}
}
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(uartHandle->Instance==USART1)
{
/* USART1 clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART1 DMA Init */
/* USART1_RX Init */
hdma_usart1_rx.Instance = DMA1_Channel5;
hdma_usart1_rx.Init.Request = DMA_REQUEST_2;
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_rx.Init.Mode = DMA_NORMAL;
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);
/* USART1_TX Init */
hdma_usart1_tx.Instance = DMA1_Channel4;
hdma_usart1_tx.Init.Request = DMA_REQUEST_2;
hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_tx.Init.Mode = DMA_NORMAL;
hdma_usart1_tx.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(uartHandle,hdmatx,hdma_usart1_tx);
/* USART1 interrupt Init */
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
}
}
三、外设使用逻辑
通用接口函数
初始化函数
通过上述的初始化步骤完成调用。
I/O操作函数
根据不同的功能使用,设计了三种不同的编程模型:轮询、中断和DMA。
以后缀区分,入口参数均为外设句柄的指针,其中轮询模式还需要传入超时时间参数。三种不同编程模型的具体实现可参考串口的三篇笔记。
控制函数
可以在使用中,动态的调节外设的参数,如中断及时钟。
状态参数
可以清除和查询一些标志位,获取外设的运行状态以及出错信息。
扩展接口函数
设计扩展接口函数可以兼顾STM32各产品系列的特有功能和扩展功能,兼顾同一个产品系列中不同芯片的特有功能。通过单独定义后续为ex的文件来实现。如stm32fxxx_hal_xxx_ex.h和stm32fxxx_hal_xxx_ex.c
总结
HAL库外设的使用步骤总结如下:
1、定义并填充xxx外设句柄结构体。
2、如果遵循HAL库规范,通过HAL_xxx_MspInit()函数,实现外设底层资源的初始化,包括但不限于GPIO、时钟、DMA、中断等资源的初始化。
3、调用HAL库的对应外设初始化函数,形如:HAL_xxx_Init()。
4、初始化完成,开始使用外设。
5、使用方法具体查看对应外设的HAL库驱动包中的说明:
##### How to use this driver #####
补充:HAL库使用主线
外设初始化
外设使用