HAL库文件结构:
HAL驱动文件:
外设驱动API文件和头文件:包含了常见主要的通用API,其中ppp表示外设名称,如adc、usart、gpio、irda等;
- stm32f0xx_hal_ppp.c
- stm32f0xx_hal_ppp.h
外设驱动扩展API文件和头文件:包含指定的API和内部不同实现以覆盖通用API的新定义API接口函数,其中ppp表示外设名称;
- stm32xx_hal_ppp_ex.c
- stm32xx_hal_ppp_ex.h
初始化HAL库文件、包含DBGMCU(调试接口)、Remap(重映射)和SysTick的TimeDelay;
- stm32xx_hal.c
- stm32xx_hal.h
自带的相应库函数例子:包含相应外设的初始化和去初始化;
- stm32xx_hal_msp_template.c
- stm32xx_hal_conf_template.h
通用HAL资源定义:包含通用定义声明、枚举、结构和宏定义;
- stm32xx_hal_def.h
用户应用文件:
用于在main函数前初始化系统时钟,包含SystermInit()函数,但不会在StartUp时配置相同时钟(与标准库不同的地方);
- system_stm32f0xx.c
包含reset handler处理函数、中断向量、并允许调整堆栈大小:
- startup_stm32f0xx.s
EWARM工具链文件,用以调整堆栈大小以适应应用程序的要求;
- stm32f0xx_flash.icf
用户自定义外设初始化文件:包括初始化和去初始化,包含主例程和回调;
- stm32f0xx_hal_msp.c
用户自定义驱动文件:允许用户自定义HAL驱动,可以使用默认配置而无需修改;
- stm32f0xx_hal_conf.h
异常处理和外设中断服务文件:会在SysTick_Handler()函数中反复调用HAL_IncTick()以实现延时;
- stm32f0xx_it.c/.h
主函数:调用HAL_Init()函数、在Debug模式下使用的assert_failed()时间检测函数、系统时钟配置函数、外设HAL初始化和应用代码;
- main.c/.h
通过STM32CubeMX配置的工程,已经默认做好如下的配置:
- HAL初始化完成;
- SysTick中断服务实现HAL_Delay()延时功能;
- 系统时钟配置为器件最大频率的时钟;
HAL数据结构:
每一个HAL驱动都遵循以下数据结构:
- 外设句柄结构Peripheral handle structures
- 初始化和配置结构Initialization and configuration structures
- 特殊的过程结构Specific process structures
Peripheral handle structures:
PPP_HandlerTypeDef *handler是HAL驱动程序中实现的主要结构;它处理外设模块配置、注册、嵌入外围设备所需要的所有结构和变量;
该句柄结构主要用于:
- 可以初始化多个实例(可以使用相同的结构定义和配置多个相同外设,如USART1、USART2、USART3),使每个初始化的外设都有相同的完整的结构;
- 外围进程互通,管理进程之间的共享数据资源,如全局变量、DMA句柄结构、状态机;
- 存储,用于管理对应的初始化HAL外设驱动程序中的全局变量;
外设句柄结构举例:
typedef struct { USART_TypeDef *Instance; /* USART registers base address */ USART_InitTypeDef Init; /* Usart communication parameters */ uint8_t *pTxBuffPtr;/* Pointer to Usart Tx transfer Buffer */ uint16_t TxXferSize; /* Usart Tx Transfer size */ __IO uint16_t TxXferCount;/* Usart Tx Transfer Counter */ uint8_t *pRxBuffPtr;/* Pointer to Usart Rx transfer Buffer */ uint16_t RxXferSize; /* Usart Rx Transfer size */ __IO uint16_t RxXferCount; /* Usart Rx Transfer Counter */ DMA_HandleTypeDef *hdmatx; /* Usart Tx DMA Handle parameters */ DMA_HandleTypeDef *hdmarx; /* Usart Rx DMA Handle parameters */ HAL_LockTypeDef Lock; /* Locking object */ __IO HAL_USART_StateTypeDef State; /* Usart communication state */ __IO HAL_USART_ErrorTypeDef ErrorCode;/* USART Error code */ }USART_HandleTypeDef;
多实例特性意味着应用程序的所有API都是可重入的,因此会避免使用全局变量,当子例程递归调用时,如果子例程依赖全局变量保持不变但是该变量在循环调用时发生改变,则可能会造成子例程无法重入;
因此要遵守:
可重入代码区域不应包含任何静态或全局的非常量数据,可重入函数则可以使用全局数据;例如在整个中断服务函数中使用硬件状态全局变量,会造成硬件状态的易失性;使用静态全局变量期间不应发生中断或其他的信号响应,并因尽量只应用在对其本身读-修改-写的过程中;
可重入代码不会修改自己的代码;
当外设使用DMA全双工同时管理多个外设时,每个进程的DMA接口句柄都会更新对应的外设PPP_HandleTypeDef;即每个可以使用DMA的外设句柄handler中都包含了DMA_HandleTypeDef;
对于共享(所有外设和系统配置都可以使用的)和系统外围设备,没有句柄或实例对象;包括GPIO、SYSTICK、NVIC、PWR、RCC、FLASH;
外设句柄结构的定义:在外设驱动头文件stm32f0xx_hal_ppp.h中定义;其名称通常都是PPP_HandleTypeDef;
这些结构通常都是用来初始化子模块和子实例;
特定的进程结构:具体的流程结构使用特定的流程(通用API),通常也是定义在外设驱动头文件中;
API分类:
通用API,存在于所有通用的HAL驱动程序中;
HAL_StatusTypeDef HAL_ADC_Init(ADC_HandleTypeDef* hadc); HAL_StatusTypeDef HAL_ADC_DeInit(ADC_HandleTypeDef *hadc); 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); void HAL_ADC_IRQHandler(ADC_HandleTypeDef* hadc);
扩展API,存在于扩展外设库文件中,有两类;
第一种是同于特定系列的扩展API;
HAL_StatusTypeDef HAL_ADCEx_Calibration_Start(ADC_HandleTypeDef* hadc, uint32_t SingleDiff); uint32_t HAL_ADCEx_Calibration_GetValue(ADC_HandleTypeDef* hadc, uint32_t SingleDiff);
第二种是用于特定型号的API;
#if defined(STM32F042x6) || defined(STM32F048xx) || defined(STM32F072xB) || defined(STM32F078xx) || \ defined(STM32F091xC) || defined(STM32F098xx) #endif /* STM32F042x6 || STM32F048xx || STM32F072xB || STM32F078xx || */ /* STM32F091xC || STM32F098xx */
HAL驱动规则:
HAL_API命名规则:下面这个表可以仔细看看;
其中PPP是外设模式,而不是指外设本身;
MODE指的是过程模式,是轮循、中断或DMA模式;
FEATURE指的是实现功能,如Start、Stop;
HAL通用命名规则:
以下外围设备其初始化不需要提供句柄handler和实例对象instance object;GPIO、SYSTICK、NVIC、RCC、FLASH
处理中断和特定时钟配置的宏在每个外设/模块驱动头文件中定义;
NVIC和SYSTICK是ARMCortex的两个核心功能,与这些功能相关的API位于stm32f0xx_hal_cortex.c中;
从寄存器中读取状态位或标志时,它由位移值构成,且通常返回的宽度为32位;
在初始化HAL_PPP_Init() 的API中,Init函数在修改句柄字段之前会检查句柄PPP_HandleTypeDef内容是否为空;
HAL_PPP_Init(PPP_HandleTypeDef) if(hppp == NULL) { return HAL_ERROR; }
宏定义分为两类:
条件宏定义;
#define ABS(x) (((x) > 0) ? (x) : -(x))
伪代码宏(多指令宏);
#define __HAL_LINKDMA(__HANDLE__, __PPP_DMA_FIELD_, __DMA_HANDLE_) \ do{ \ (__HANDLE__)->__PPP_DMA_FIELD_ = &(__DMA_HANDLE_); \ (__DMA_HANDLE_).Parent = (__HANDLE__); \ } while(0)
HAL中断处理程序和回调函数
除了API,HAL外设驱动还包含:
HAL_PPP_IRQHandler()外设中断处理函数;
用户定义回调函数,系统默认的回调函数定义为weak属性,一旦用户自己定义了回调函数会覆盖系统默认的回调函数;
有三种类型的回调函数:
外围系统初始化/去初始化回调:HAL_PPP_MspInit()、HAL_PPP_MspDeInit();
处理完整进程的回调函数:HAL_PPP_ProcessCpltCallback;
错误处理回调函数:HAL_PPP_ErrorCallback;
HAL通用APIs:
通用的API由四个方面组成:
初始化和去初始化:
- 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 ()
HAL扩展APIs:
扩展API通常是特定系列或同一系列中特定功能或覆盖已修改的API,扩展功能通常由stm32f0xx_hal_ppp_ex.c/h文件构成;
HAL程序由五种不同的方式处理特定的IP功能:
- 添加部分特定的功能:将新的API添加到stm32f0xx_hal_ppp_ex.c扩展文件中,并命名为HAL_PPPEx_Function();
- 添加一系列的功能:操作同添加部分特定功能相似;
- 添加新的外围设备:在stm32f0xx_hal_newppp.c中添加新的可用外围设备,同时在stm32f0xx_hal_conf.h中包含这个新外围设备的宏;
#define HAL_NEWPPP_MODULE_ENABLED
- 更新已有的通用API:想要覆盖一个在stm32f0xx_hal_ppp.c中已经存在的API函数,则在stm32f0xx_hal_ppp_ex.c扩展文件中使用相同名称的定义,因为通用API定义位weak,所以编译器将通过新定义的函数覆盖原始例程;
- 升级已存在的数据结构:通过采用不同的器件宏定义来重新定义数据结构PPP_InitTypeDef;
#if defined (STM32F072xB) typedef struct { (…) }PPP_InitTypeDef; #endif /* STM32F072xB */
文件包含模型:
在这其中stm32f0xx_hal.h是连接整个HAL库源和用户源的唯一头文件;
其中文件包含关系如下图所示:其基本的包含关系是
HAL库文件或main文件—stm32f0xx_hal.h—stm32f0xx_hal_conf.h—HAL库头文件
由于相应的外设功能需要在外设模块选择中添加相应的功能宏,在配置文件stm32f0xx_hal_conf.h中可以找到;
HAL共有配置:
HALStatus:除了布尔函数和IRQ处理程序,几乎所有的HAL_API都使用HAL状态,它返回当前API操作的状态;
Typedef enum { HAL_OK = 0x00, HAL_ERROR = 0x01, HAL_BUSY = 0x02, HAL_TIMEOUT = 0x03 } HAL_StatusTypeDef;
HALLocked:锁定状态防止意外的共享数据修改和读写;
typedef enum { HAL_UNLOCKED = 0x00, /*!*/ HAL_LOCKED = 0x01 /*!< Resources locked */ } HAL_LockTypeDef;
除了共有的资源,stm32f0xx_hal_def.h文件还调用CMSIS库中的stm32f0xx.h文件来获取所有外设的数据结构和地址映射:
外设寄存器和位定义的声明;
用于访问外设寄存器硬件的宏(读写寄存器.etc);
CommonMarco:
- NULL和HAL_MAX_DELAY宏定义;
#define HAL_MAX_DELAY 0xFFFFFFFF
- 将PPP外设链接到DMA结构指针的宏:__HAL_LINKDMA();
#define __HAL_LINKDMA(__HANDLE__, __PPP_DMA_FIELD_, __DMA_HANDLE_) \ do{ \ (__HANDLE__)->__PPP_DMA_FIELD_ = &(__DMA_HANDLE_); \ (__DMA_HANDLE_).Parent = (__HANDLE__); \ } while(0)
HAL配置:
配置文件stm32f0xx_hal_conf.h允许用户自定义配置参数和定义;
示例配置文件stm32f0xx_hal_conf_template.h中开启了所有的HAL库定义,并将时钟配置为最大时钟数值;
HAL外围设备处理:
时钟Clock:
两个主要的功能配置时钟:
HAL_RCC_OscConfig (RCC_OscInitTypeDef *RCC_OscInitStruct)
用来配置和使能时钟源,如HSE、HSI、LSE、LSI、PLL;
HAL_RCC_ClockConfig (RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency)
用来选择SystemClock系统时钟、配置AHB和APB时钟分频、配置Flash等待状态的数量、HCLK时钟更改时更新SysTick配置;
某些外设时钟不是从SystemClock系统时钟中派生的(USB、RTC),这种情况下,时钟配置由stm32f0xx_hal_rcc_ex.c中定义的扩展API执行:
HAL_RCCEx_PeriphCLKConfig(RCC_PeriphCLKInitTypeDef *PeriphClkInit)
提供额外的RCC_HAL驱动程序功能:
HAL_RCC_DeInit()时钟去启动功能,将时钟配置返回到复位状态;
获取时钟功能,并允许检索各种时钟配置(system clock、HCLK、PCLKn);
MCO和CSS配置功能;
在stm32f0xx_hal_rcc.h和stm32f0xx_hal_rcc_ex.h中定义了一组宏;它们允许在RCC块寄存器上执行基本操作,例如外设时钟门控/复位控制:
__PPP_CLK_ENABLE/__PPP_CLK_DISABLE to enable/disable the peripheral clock __PPP_FORCE_RESET/__PPP_RELEASE_RESET to force/release peripheral reset __PPP_CLK_SLEEP_ENABLE/__PPP_CLK_SLEEP_DISABLE to enable/disable the peripheral clock during low power (Sleep) mode.
GPIOs:
GPIO HAL API主要包含:
HAL_GPIO_Init()/HAL_GPIO_DeInit()
HAL_GPIO_ReadPin()/HAL_GPIO_WritePin()
HAL_GPIO_TogglePin ()
除了标准GPIO模式(输入、输出、模拟)外,引脚模式还可以配置成带有中断IT和事件生成EVENT的EXTI模式;
此模式需要从stm32f0xx_it.c中调用HAL_GPIO_EXTI_IRQHandler()并实现回调函数HAL_GPIO_EXTI_Callback();
下表是介绍了GPIO_InitTypeDef结构可以配置的参数列表:
可以参考GPIO的一些基础配置:
配置GPIO为输出PP模式,控制LED灯;
GPIO_InitStruct.Pin = GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM; HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
配置GPIO作为下降沿触发外部中断模式;
GPIO_InitStructure.Mode = GPIO_MODE_IT_FALLING; GPIO_InitStructure.Pull = GPIO_NOPULL; GPIO_InitStructure.Pin = GPIO_PIN_0; HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
配置复用模式为串口USART1模式;
GPIO_InitStruct.Pin = GPIO_PIN_9; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = GPIO_AF4_USART1; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
Cortex NVIC和SysTick时钟
HAL库在stm32f0xx_hal_cortex.c中给了处理NVIC和SysTick的APIs,这些包含的APIs有:
- HAL_NVIC_SetPriority()
- HAL_NVIC_EnableIRQ()/HAL_NVIC_DisableIRQ()
- HAL_NVIC_SystemReset()
- HAL_SYSTICK_IRQHandler()
- HAL_NVIC_GetPendingIRQ() / HAL_NVIC_SetPendingIRQ ()/ HAL_NVIC_ClearPendingIRQ()
- HAL_SYSTICK_Config()
- HAL_SYSTICK_CLKSourceConfig()
- HAL_SYSTICK_Callback()
PWR:
PWR HAL驱动程序处理电源管理,所有的STM32系列共享的功能如下:
PVD配置,启动/禁用和中断处理:
HAL_PWR_PVDConfig()
HAL_PWR_EnablePVD() / HAL_PWR_DisablePVD()
HAL_PWR_PVD_IRQHandler()
HAL_PWR_PVDCallback()
Wakeup唤醒引脚配置:
HAL_PWR_EnableWakeUpPin() / HAL_PWR_DisableWakeUpPin()
低功耗模式配置:
HAL_PWR_EnterSLEEPMode()
HAL_PWR_EnterSTOPMode()
HAL_PWR_EnterSTANDBYMode()
备份的域配置:
HAL_PWR_EnableBkUpAccess()/ HAL_PWR_DisableBkUpAccess()
EXTI:
EXTI不被视为独立外围设备,而是其他外围设备使用的服务,因此没有EXTI的API;
但每个外围HAL驱动程序实现关联的EXTI配置,EXTI功能在其头文件中表现为宏;
连接到GPIO的前16条EXTI线在GPIO驱动中进行管理,GPIO_InitTypeDef结构允许将IO配置为外部中断IT或外部事件EVENT;
其内部连接到PVD、RTC、USB和COMP的EXTI线路通过库定义好的宏配置外设HAL驱动;
EXTI中断API:将EXTI中断线连接到内部外设;
PPP_EXTI_LINE_FUNCTION
外设中断使能:
__HAL_PPP_EXTI_ENABLE_IT
__HAL_PPP_EXTI_DISABLE_IT
获取EXTI中断状态:
__HAL_PPP_EXTI_GET_FLAG
__HAL_PPP_EXTI_CLEAR_FLAG
生成EXTI中断事件:
__HAL_PPP_EXTI_GENERATE_SWIT
开启EXTI中断线事件:
__HAL_PPP_EXTI_ENABLE_EVENT
__HAL_PPP_EXTI_DISABLE_EVENT
如果选择了EXTI模式,则用户必须从stm32f0xx_it.c文件中调用HAL_PPP_FUNCTION_IRQHandler()来实现HAL_PPP_FUNCTIONCallback()回调函数;
DMA:
DMA HAL驱动程序允许启用和配置外设连接到DMA通道(内部FLASH和SRAM除外),对于给定的HAL_DMA_Init()则可以配置以下参数:
- 传输方向
- 源和目标的数据格式
- 循环、正常或外设流模式
- 通道优先级
- 源和目标的递增模式
- FIFO模式或其阈值
- 源和目标的突发模式
有两种可以定义的模式:
轮询模式
- 使用HAL_DMA_Start()来配置源和目标地址以及要传输的数据长度,来启动DMA;
- 使用HAL_DMA_PollForTransfer()来获取当前传输的结果,可以根据这个判断来配置应用程序的超时设置;
中断模式
- 使用HAL_NVIC_SetPriority()来配置DMA的中断优先级
- 使用HAL_NVIC_EnableIRQ()来使能DMAIRQ处理函数
- 使用HAL_DMA_Start_IT()来配置DMA的源和目标地址以及要传输的数据长度来使能DMA传输;
- 使用HAL_DMA_IRQHandler()子程序来在DMA_IRQHandler()中调用;
- 当数据传输完成时,执行HAL_DMA_IRQHandler()函数并且可以通过定制XferCpltCallback和XferErrorCallback来调用用户函数;
获取状态来确保进行有效的DMA管理:
HAL_DMA_GetState()获取DMA状态;
HAL_DMA_GetError()获取DMA错误标志;
HAL_DMA_Abort()终止当前操作;
最常用的DMA中断
DMA通道使能:
__HAL_DMA_ENABLE: enables the specified DMA Channels.
__HAL_DMA_DISABLE: disables the specified DMA Channels.
获取/清除DMA中断标志:
__HAL_DMA_GET_FLAG: gets the DMA Channels pending flags.
__HAL_DMA_CLEAR_FLAG: clears the DMA Channels pending flags.
DMA中断使能:
__HAL_DMA_ENABLE_IT: enables the specified DMA Channels interrupts.
__HAL_DMA_DISABLE_IT: disables the specified DMA Channels interrupts.
检查DMA中断源是否开启:
__HAL_DMA_GET_IT_SOURCE: checks whether the specified DMA channel interrupt has occurred or not.
在DMA模式下使用外设,应在HAL_PPP_MspInit()回调中完成DMA的初始化,同时将DMA句柄与PPP句柄相关联;
只有在内存到内存传输的情况下,用户应用程序才需要初始化DMA通道回调;但是,当使用外设到内存的传输时,这些回调会通过调用使用DMA的进程API函数自动初始化;
如何使用HAL驱动:
HAL使用模型:
HAL初始化:
HAL全局初始化:除了外设初始化和去初始化,还在stm32f0xx_hal.c提供了以下的API来初始化HAL内核:
HAL_Init()
初始化数据和指令缓存、预取队列集;
设置Systick时钟每1s发生一次中断(基于HSI时钟);
调用HAL_MspInit()用户回调函数(该函数也被设定为弱空函数)执行系统级初始化(时钟、GPIO、DMA、中断);
HAL_DeInit()
复位所有的外设;
调用HAL_MspDeInit()用户回调函数;
HAL_GetTick()
用来获取SysTick的计数值来处理外设驱动的超时;
HAL_Delay()
使用SysTick来定时1ms;
使用HAL_Delay()必须要注意,此函数基于SysTickISR中递增的变量提供准确的延时,如果外设调用HAL_Delay()来实现延时,则SysTick中断必须具有比外设中断更高的优先级,否则将阻止调用ISR;
系统时钟初始化:
系统时钟的配置可以在main代码前完成,也可以用户自己在代码中定义;
static void SystemClock_Config(void) { RCC_ClkInitTypeDef RCC_ClkInitStruct; RCC_OscInitTypeDef RCC_OscInitStruct; /* Enable HSE Oscillator and Activate PLL with HSE as source */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PREDIV = RCC_PREDIV_DIV1; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL6; if (HAL_RCC_OscConfig(&RCC_OscInitStruct)!= HAL_OK) { Error_Handler(); } /* Select PLL as system clock source and configure the HCLK, PCLK1 clocks dividers */ RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1); RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1)!= HAL_OK) { Error_Handler(); } }
HAL MSP初始化过程
通过HAL_PPP_Init()初始化外设,同时也会通过HAL_PPP_MspInit()初始化硬件资源;
MSP回调执行与各个外设不同的附加硬件资源:RCC、GPIO、NVIC、DMA;
并且所有带句柄Handler的HAL驱动程序都包含两个函数:
void __weak HAL_PPP_MspInit(PPP_HandleTypeDef *hppp)
void __weak HAL_PPP_MspDeInit(PPP_HandleTypeDef *hppp)
MSP回调在每个外设驱动程序中被声明成空函数,用户可以通过使用它来设置低级初始化或省略使用自己的初始化例程;
HAL MSP回调函数在stm32f0xx_hal_msp.c中实现,stm32f0xx_hal_msp_template.c文件在HAL文件夹中,这个文件由STM32CubeMX工具生成并进一步修改;
在文件stm32f0xx_hal_msp.c中包含以下函数:前两个是全局初始化、后两个是外设初始化;
当一个或多个外设需要在运行时去初始化DeInit并且给定外设的低级资源需要被另一个外设释放和使用时,在HAL_PPP_MspDeInit()和HAL_PPP_MspInit()中定义,但是HAL_MspInit()和HAL_MspDeInit()可以保持不变;
HAL IO操作
具有内部数据处理(如发送、接收、写入和读取)的HAL外设功能通常都具有三种数据处理模式:
轮询模式
中断模式
DMA模式
轮询模式:当阻塞模式下的数据处理完成后,HAL返回进程状态;
当返回HAL_OK状态时,认为操作已经完成,否则返回错误信息;也可以通过HAL_PPP_GetState()来获取更多的状态;
数据在循环内部处理,超时(ms毫秒)以防止进程挂起;
具体实现如下:
判断接收的数据是否有误;
判断数据读取是否超时;
所有的数据处理无误后返回完成标志位;
HAL_StatusTypeDef HAL_PPP_Transmit ( PPP_HandleTypeDef * phandle, uint8_t pData, int16_tSize,uint32_tTimeout) { if((pData == NULL ) || (Size == 0)) { return HAL_ERROR; } (…) while (data processing is running) { if( timeout reached ) { return HAL_TIMEOUT; } } (…) return HAL_OK; }
中断模式:
HAL在开始数据处理并启用适当的中断来返回过程状态,操作的结果由声明为弱函数的回调指示,回调也可以由用户定义以实时通知过程完成,也可以使用HAL_PPP_GetState()获取进程状态;
在中断模式中有四个函数:
HAL_PPP_Process_IT():启动IT过程;
HAL_PPP_IRQHandler():全局的外设中断;
__weak HAL_PPP_ProcessCpltCallback ():用于在处理流程完成后的回调函数;
__weak HAL_PPP_ProcessErrorCallback():用于在出现错误信息后的回调函数;
要在中断模式下使用进程,需要在用户文件中调用HAL_PPP_Process_IT(),需要在stm32f0xx_it.c中调用HAL_PPP_IRQHandler()中断函数;
HAL_PPP_ProcessCpltCallback()初始被声明为弱函数,用户可以在应用层重新定义该函数;
具体应用举例:
main.c file: UART_HandleTypeDef UartHandle; int main(void) { /* Set User Parameters */ UartHandle.Init.BaudRate = 9600; UartHandle.Init.WordLength = UART_DATABITS_8; UartHandle.Init.StopBits = UART_STOPBITS_1; UartHandle.Init.Parity = UART_PARITY_NONE; UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; UartHandle.Init.Mode = UART_MODE_TX_RX; UartHandle.Init.Instance = USART1; HAL_UART_Init(&UartHandle); HAL_UART_SendIT(&UartHandle, TxBuffer, sizeof(TxBuffer)); while (1); } void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) { } void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { } stm32f0xx_it.cfile: extern UART_HandleTypeDef UartHandle; void USART1_IRQHandler(void) { HAL_UART_IRQHandler(&UartHandle); }
DMA模式:
在DMA模式下,HAL功能在通过DMA开始数据处理之后以及启用适当的DMA中断后返回过程状态;操作的结束由声明为弱函数的回调指示,并且可以由用户定制以实时通知过程完成;用户还可以通过HAL_PPP_GetState()函数获取进程状态;
对于DMA模式,在驱动程序中声明了三个函数:
HAL_PPP_Process_DMA():开启DMA
HAL_PPP_DMA_IRQHandler():PPP外设中断函数定义
__weak HAL_PPP_ProcessCpltCallback():数据处理完成后调用的回调函数
__weak HAL_PPP_ErrorCpltCallback():发生错误了调用的回调函数
要使用DMA模式,需要在用户文件中调用HAL_PPP_Process_DMA(),并将HAL_PPP_DMA_IRQHandler()中断处理函数放在stm32f0xx_it.c中;
其初始化在HAL_PPP_MspInit()回调函数中实现,用户还应将DMA句柄Handler与PPP外设句柄Handler相关联;
要在外设中添加DMA句柄Handler如下:
typedef struct { PPP_TypeDef *Instance; /* Register base address */ PPP_InitTypeDef Init; /* PPP communication parameters */ HAL_StateTypeDef State; /* PPP communication state */ (…) DMA_HandleTypeDef *hdma; /* associated DMA handle */ } PPP_HandleTypeDef;
DMA初始化流程如下:(以USART为例)
int main(void) { /* Set User Parameters */ UartHandle.Init.BaudRate = 9600; UartHandle.Init.WordLength = UART_DATABITS_8; UartHandle.Init.StopBits = UART_STOPBITS_1; UartHandle.Init.Parity = UART_PARITY_NONE; UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; UartHandle.Init.Mode = UART_MODE_TX_RX; UartHandle.Init.Instance = UART1; HAL_UART_Init(&UartHandle); (..) } void HAL_USART_MspInit (UART_HandleTypeDef * huart) { static DMA_HandleTypeDef hdma_tx; static DMA_HandleTypeDef hdma_rx; (…) __HAL_LINKDMA(UartHandle, DMA_Handle_tx, hdma_tx); __HAL_LINKDMA(UartHandle, DMA_Handle_rx, hdma_rx); (…) }
HAL_PPP_ProcessCpltCallback()函数在驱动程序中声明为弱函数,这意味着用户可以在应用程序代码中再次声明它,而不用修改驱动程序中的功能;
在main.c中:
UART_HandleTypeDef UartHandle; int main(void) { /* Set User Paramaters */ UartHandle.Init.BaudRate = 9600; UartHandle.Init.WordLength = UART_DATABITS_8; UartHandle.Init.StopBits = UART_STOPBITS_1; UartHandle.Init.Parity = UART_PARITY_NONE; UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; UartHandle.Init.Mode = UART_MODE_TX_RX; UartHandle.Init.Instance = USART1; HAL_UART_Init(&UartHandle); HAL_UART_Send_DMA(&UartHandle, TxBuffer, sizeof(TxBuffer)); while (1); } void HAL_UART_TxCpltCallback(UART_HandleTypeDef *phuart) { } void HAL_UART_TxErrorCallback(UART_HandleTypeDef *phuart) { }
在stm32f0xx_it.c中:
extern UART_HandleTypeDef UartHandle; void DMAx_IRQHandler(void) { HAL_DMA_IRQHandler(&UartHandle.DMA_Handle_tx); }
HAL_USART_TxCpltCallback()和HAL_USART_ErrorCallback()应该通过使用以下语句在HAL_PPP_Process_DMA()函数中链接到DMA传输完成回调和DMA传输错误回调:
HAL_PPP_Process_DMA (PPP_HandleTypeDef *hppp, Params….) { (…) hppp->DMA_Handle->XferCpltCallback = HAL_UART_TxCpltCallback ; hppp->DMA_Handle->XferErrorCallback = HAL_UART_ErrorCallback ; (…) }
超时和错误管理:
超时管理:
超时通常用于以轮询模式运行的API;它定义了阻塞过程应该等待直到返回错误的延迟;下面提供了一个示例:
HAL_StatusTypeDef HAL_DMA_PollForTransfer(DMA_HandleTypeDef *hdma, uint32_t CompleteLevel, uint32_t Timeout)
超时定义的值大小:
PS:HAL_MAX_DELAY is defined in the stm32fxxx_hal_def.h as 0xFFFFFFFF.
在某些情况下,固定超时用于系统外围设备或内部HAL驱动程序进程,在这些情况下,超时具有相同的含义并以相同的方式使用;
获取当前计数值并赋予超时上限;
在处理程序完成后再次获取计数值,判断是否超时,返回超时状态;
#define LOCAL_PROCESS_TIMEOUT 100 HAL_StatusTypeDef HAL_PPP_Process(PPP_HandleTypeDef) { (…) timeout = HAL_GetTick() + LOCAL_PROCESS_TIMEOUT; (…) while(ProcessOngoing) { (…) if(HAL_GetTick() >= timeout) { /* Process unlocked */ __HAL_UNLOCK(hppp); hppp->State= HAL_PPP_STATE_TIMEOUT; return HAL_PPP_STATE_TIMEOUT; } } (…) }
以下示例显式轮询函数中如何使用超时:
HAL_PPP_StateTypeDef HAL_PPP_Poll (PPP_HandleTypeDef *hppp, uint32_t Timeout) { (…) timeout = HAL_GetTick() + Timeout; (…) while(ProcessOngoing) { (…) if(Timeout != HAL_MAX_DELAY) { if(HAL_GetTick() >= timeout) { /* Process unlocked */ __HAL_UNLOCK(hppp); hppp->State= HAL_PPP_STATE_TIMEOUT; return hppp->State; } } (…) }
错误管理:
通过检查以下的参数来确定错误;
有效参数:使用的参数要是有效的,并且已经定义的,否则系统很可能陷入未定义状态,在使用之前检查这些参数;
HAL_StatusTypeDef HAL_PPP_Process(PPP_HandleTypeDef* hppp, uint32_t *pdata, uint32 Size) { if ((pData == NULL ) || (Size == 0)) { return HAL_ERROR; } }
有效句柄:PPP外围句柄是最重要的参数,因为它保留了PPP驱动程序的重要参数;始终在HAL_PPP_Init()函数的开头检查它;
HAL_StatusTypeDef HAL_PPP_Init(PPP_HandleTypeDef* hppp) { if (hppp == NULL) //the handle should be already allocated { return HAL_ERROR; } }
超时错误:发生超时错误时使用以下语句:while(正在进行);
{ timeout = HAL_GetTick() + Timeout; while (data processing is running) { if(timeout) { return HAL_TIMEOUT;} }
当外设发生错误时,HAL_PPP_Process ()返回一个HAL_ERROR错误状态,HAL PPP外设驱动程序实现HAL_PPP_GetError ()允许检索错误;
HAL_PPP_ErrorTypeDef HAL_PPP_GetError (PPP_HandleTypeDef *hppp);
在所有的外设句柄中,定义了HAL_PPP_ErrorTypeDef来存储最后一个错误代码;
typedef struct { PPP_TypeDef * Instance; /* PPP registers base address */ PPP_InitTypeDef Init; /* PPP initialization parameters */ HAL_LockTypeDef Lock; /* PPP locking object */ __IO HAL_PPP_StateTypeDef State; /* PPP state */ __IO HAL_PPP_ErrorTypeDef ErrorCode; /* PPP Error code */ (…) /* PPP specific parameters */ } PPP_HandleTypeDef;
在返回错误之前,始终更新错误状态和外围设备的全局状态;
PPP->State = HAL_PPP_READY; /* Set the peripheral ready */ PP->ErrorCode = HAL_ERRORCODE ; /* Set the error code */ _HAL_UNLOCK(PPP) ; /* Unlock the PPP resources */ return HAL_ERROR; /*return with HAL error */
HAL_PPP_GetError()必须在错误回调中的中断模式下使用:
void HAL_PPP_ProcessCpltCallback(PPP_HandleTypeDef *hspi) { ErrorCode = HAL_PPP_GetError (hppp); /* retreive error code */ }
运行时间检查:
HAL通过检查所有HAL驱动程序函数的输入值来实现运行时故障检测;运行时检查是通过使用assert_parammacro实现的;该宏用于具有输入参数的所有HAL驱动程序函数;它允许验证输入值是否在参数允许值范围内;
要启用运行时检查,请使用assert_parammacro,并在stm32f0xx_hal_conf.hfile中取消注释定义USE_FULL_ASSERT;
void HAL_UART_Init(UART_HandleTypeDef *huart) { (..) /* Check the parameters */ assert_param(IS_UART_INSTANCE(huart->Instance)); assert_param(IS_UART_BAUDRATE(huart->Init.BaudRate)); assert_param(IS_UART_WORD_LENGTH(huart->Init.WordLength)); assert_param(IS_UART_STOPBITS(huart->Init.StopBits)); assert_param(IS_UART_PARITY(huart->Init.Parity)); assert_param(IS_UART_MODE(huart->Init.Mode)); assert_param(IS_UART_HARDWARE_FLOW_CONTROL(huart->Init.HwFlowCtl)); (..) /** @defgroup UART_Word_Length * @{ */ #define UART_WORDLENGTH_8B ((uint32_t)0x00000000) #define UART_WORDLENGTH_9B ((uint32_t)USART_CR1_M) #define IS_UART_WORD_LENGTH(LENGTH) (((LENGTH) == UART_WORDLENGTH_8B) || \ ((LENGTH) == UART_WORDLENGTH_9B))
如果传递给assert_param宏的表达式为false,则调用theassert_failed函数并返回源文件的名称和失败的调用的源行号;如果表达式为true,则不返回任何值;assert_parammacro在stm32f0xx_hal_conf.h中实现:
/* Exported macro ------------------------------------------------------------*/ #ifdef USE_FULL_ASSERT /** * @brief The assert_param macro is used for function's parameters check. * @param expr: If expr is false, it calls assert_failed function * which reports the name of the source file and the source * line number of the call that failed. * If expr is true, it returns no value. * @retval None */ #define assert_param(expr) ((expr)?(void)0:assert_failed((uint8_t *)__FILE__, __LINE__)) /* Exported functions --------------------------------------*/ void assert_failed(uint8_t* file, uint32_t line); #else #define assert_param(expr)((void)0) #endif /* USE_FULL_ASSERT */
assert_failed函数在main.c文件或任何其他用户C文件中实现:
#ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t* file, uint32_t line) { /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* Infinite loop */ while (1) { } }
由于引入了开销运行时检查,因此建议在应用程序代码开发和调试期间使用它,并将其从最终应用程序中删除以改进代码大小和速度;