基于CubeIDE开发笔记|HAL库部分常用函数名称及作用

HAL名带msp的函数功能:MCU Specific Package 单片机的具体方案,即MSP是指和MCU相关的初始化
HAL名带MX前缀的函数:应该是与CubeMX相关(可能是与MX通用的意思)
带Config的一般是配置某种外设或者RCC,EXTI等的参数,一般不是库函数内的。
带IT的一般与中断有关(Interrupt)
小技巧

  • 在某些回调函数判断传过来的参数,像关于定时器中断的回调函数,或者说参数像(TIM_HandleTypeDef *htim)或者(UART_HandleTypeDef *huart)的,若需要判断是那个定时器或者串口可以:
if(htim==(&htim3))

  • 外部中断的回调函数风格则不太一样:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){ if(GPIO_Pin==(GPIO_PIN_5)){ } else if(GPIO_Pin==(GPIO_PIN_12)){ }}

  • 注意:当使用的参数与本函数的参数有关时,该参数可能不需要再取地址,否则会出现地址的地址这种错误
定时器 stm32的TIM定时器HAL库函数的使用
开启PWM
HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel);

HAL_TIM_Base_Init(&htim2) 作用:应用参数基本参数配置对应定时器
CubeIDE中下面的函数很常见,一般这种函数之前会有对应的配置函数,然后在这里判断,如果判断条件成立,即HAL_TIM_Base_Init(&htim2) != HAL_OK,表示之前配置出错,调用错误处理函数Error_Handler();
if (HAL_TIM_Base_Init(&htim2) != HAL_OK) { Error_Handler(); }

HAL_TIM_Base_MspInit(htim) 作用:初始化中断信息(该函数需要重写,CubeMX在配置生成代码时会自动重写该函数)
注意,该函数调用之后会重新设置寄存器的值,可以在 HAL_TIM_Base_Init(&htim2) 之后使能TIM中断,并清理TIM的更新中断寄存器
函数重载与重写的作用
【基于CubeIDE开发笔记|HAL库部分常用函数名称及作用】重写实现:
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle) {if(tim_baseHandle->Instance==TIM2) { /* USER CODE BEGIN TIM2_MspInit 0 *//* USER CODE END TIM2_MspInit 0 */ /* TIM2 clock enable */ __HAL_RCC_TIM2_CLK_ENABLE(); /* TIM2 interrupt Init */ HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(TIM2_IRQn); /* USER CODE BEGIN TIM2_MspInit 1 */ //清理TIM开启时的中断标识 __HAL_TIM_CLEAR_FLAG(tim_baseHandle, TIM_SR_UIF); //添加这条语句解决问题 //htim2.Instance->SR = 0; //这是另一种解决办法 //使能TIM中断 HAL_TIM_Base_Start_IT(tim_baseHandle); /* USER CODE END TIM2_MspInit 1 */ } }

HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *TIM); 作用:开启定时器中断
__HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE); 作用:清理TIM开启时的中断标识
中断处理:
/** * @brief This function handles TIM2 global interrupt. */ void TIM2_IRQHandler(void) { /* USER CODE BEGIN TIM2_IRQn 0 *//* USER CODE END TIM2_IRQn 0 */ HAL_TIM_IRQHandler(&htim2); /* USER CODE BEGIN TIM2_IRQn 1 *//* USER CODE END TIM2_IRQn 1 */ }

一般会调用到下面这个中断处理主函数(看不懂又不敢乱删系列):
/** * @briefThis function handles TIM interrupts requests. * @paramhtim TIMhandle * @retval None */ void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim) { /* Capture compare 1 event */ if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC1) != RESET) { if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC1) != RESET) { { __HAL_TIM_CLEAR_IT(htim, TIM_IT_CC1); htim->Channel = HAL_TIM_ACTIVE_CHANNEL_1; /* Input capture event */ if ((htim->Instance->CCMR1 & TIM_CCMR1_CC1S) != 0x00U) { #if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->IC_CaptureCallback(htim); #else HAL_TIM_IC_CaptureCallback(htim); #endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } /* Output compare event */ else { #if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->OC_DelayElapsedCallback(htim); htim->PWM_PulseFinishedCallback(htim); #else HAL_TIM_OC_DelayElapsedCallback(htim); HAL_TIM_PWM_PulseFinishedCallback(htim); #endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED; } } } // ///* TIM Update event */ if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE) != RESET) { if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_UPDATE) != RESET) { __HAL_TIM_CLEAR_IT(htim, TIM_IT_UPDATE); #if (USE_HAL_TIM_REGISTER_CALLBACKS == 1) htim->PeriodElapsedCallback(htim); #else HAL_TIM_PeriodElapsedCallback(htim); #endif /* USE_HAL_TIM_REGISTER_CALLBACKS */ } } // // }

定时器溢出中断HAL_TIM_PeriodElapsedCallback(htim); 作用:当计数溢出时调用的回调函数,需要用户根据实际需求具体实现(一般定时器使用,输入捕获也可通过该函数记录溢出次数),输入捕获有其特有的跳变检测中断
/** * @briefPeriod elapsed callback in non-blocking mode * @paramhtim TIM handle * @retval None */ __weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { /* Prevent unused argument(s) compilation warning */ UNUSED(htim); /* NOTE : This function should not be modified, when the callback is needed, the HAL_TIM_PeriodElapsedCallback could be implemented in the user file */ }

输入捕获中断HAL_TIM_IC_CaptureCallback
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) { if(TIM4 == htim->Instance) { }}

HAL_TIM_Base_MspDeInit
/** * 函数功能: 基本定时器硬件反初始化配置 * 输入参数: htim_base:基本定时器句柄类型指针 * 返 回 值: 无 * 说明: 该函数被HAL库内部调用 */ void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* htim_base) {if(htim_base->Instance==ADVANCED_TIMx) { /* 基本定时器外设时钟禁用 */ ADVANCED_TIM_RCC_CLK_DISABLE(); } }

HAL_TIM_Base_MspInit
/** * 函数功能: 基本定时器硬件初始化配置 * 输入参数: htim_base:基本定时器句柄类型指针 * 返 回 值: 无 * 说明: 该函数被HAL库内部调用 */ void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base) {if(htim_base->Instance==ADVANCED_TIMx) { /* 基本定时器外设时钟使能 */ ADVANCED_TIM_RCC_CLK_ENABLE(); } }

HAL_TIM_MspPostInit
/** * 函数功能: 定时器硬件初始化配置 * 输入参数: htim:定时器句柄类型指针 * 返 回 值: 无 * 说明: 该函数被GENERAL_TIMx_Init函数调用 */ void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim) { GPIO_InitTypeDef GPIO_InitStruct; if(htim->Instance==GENERAL_TIMx) { /* 定时器通道功能引脚端口时钟使能 */ GENERAL_TIM_GPIO_RCC_CLK_ENABLE(); /* 定时器通道3功能引脚IO初始化 */ GPIO_InitStruct.Pin = GENERAL_TIM_CH3_PIN; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GENERAL_TIM_CH3_PORT, &GPIO_InitStruct); } }

HAL_NVIC_SetPriority(TIM1_UP_TIM16_IRQn, 0, 0); //观察函数名及参数可知,该函数用于设置某中断的优先级 HAL_NVIC_EnableIRQ(TIM1_UP_TIM16_IRQn); //一般会附加下面的函数来使能中断

HAL_GPIO_TogglePin(GPIOx,GPIO_Pin); //翻转对应的IO口,“Toggle”意为“切换”

FLASH HAL_FLASHEx_Erase 作用:擦除Flash相应地址的内容
if (HAL_FLASHEx_Erase(&EraseInitStruct, &PageError) != HAL_OK)//调用擦除函数并检验是否完成擦除 {while (1) { /* Make LED2 blink (100ms on, 2s off) to indicate error in Erase operation */ HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_8); HAL_Delay(100); HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_8); HAL_Delay(2000); } }

HAL_FLASH_Program 作用:对Flash特定区域进行烧写,三个参数分别表示:在指定地址编程的方式,地址,烧写数据
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, Address, DATA_64) == HAL_OK) { Address = Address + 8; /* increment to next double word*/ }

stm32l476 内部flash HAL库操作方法
PWR低功耗 HAL_PWR_EnterSTOPMode 作用:进入停止模式,设置电压调节器为低功耗模式,等待中断唤醒
PWR_EnterSTOPMode(PWR_Regulator,PWR_STOPEntry_WFI);

第一个参数要配置电源低功耗模式
第二个参数是模式,可选择中断唤醒还是事件唤醒,或者两者都要;
退出低功耗的方法是用外部中断方式唤醒(定时中断不是外部中断却能唤醒停止模式)。退出STOPMode后,根据手册说明,会自动选择HSI作为sysclock,因此如果系统之前采用的是非HSI作为Sysclock,则必须重新调用System_init(),对RCC部分进行重新初始化。否则会影响系统性能。
低功耗模式有三种,
1.睡眠模式,( CM3 内核停止,外设仍然运行)此功耗是最高的。
2.停止模式,(所有时钟都停止)此功耗较低,典型大概在20uA左右。
3.待机模式,( 1.8V 内核电源关闭)此功耗最低,典型大概在2uA左右。基于CubeIDE开发笔记|HAL库部分常用函数名称及作用
文章图片

void PWR_EnterSleepMode(uint8_t PWR_SLEEPEntry); //睡眠模式 void PWR_EnterSTOPMode(uint32_t PWR_Regulator, uint8_t PWR_STOPEntry); //停机模式 void PWR_EnterSTANDBYMode(void); //待机模式

#define PWR_Regulator_ON //电源不进低功耗 唤醒基本没延迟 #define PWR_Regulator_LowPower //电源进去低功耗 不过唤醒启动有一点延迟 #define PWR_STOPEntry_WFI //中断唤醒 #define PWR_STOPEntry_WFE //事件唤醒

待机模式下的唤醒结论如下:
1.唤醒形式直接产生闹钟中断就能唤醒。
2.唤醒后不会进入闹钟中断函数
3.唤醒后程序复位,重新执行
停机模式下的唤醒结论如下:
1.唤醒形式产生闹钟中断不一定就唤醒,需要对任何可能的标志位清楚,并且时钟要重新配置。
2.唤醒后进入闹钟中断函数
3.唤醒后程序进入闹钟中断函数,然后再进入原来停机的位置继续运行。没有复位,单片机寄存器里的各种变量值仍然保留!!
通用 assert_param 作用:检查参数1,判断参数1是否为参数2基址中的一个,只要有一个为真则其值为真,否则为假,
定义:
#define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t*)__FILE__, __LINE__))

HAL库似乎比较喜欢使用分流的方式来使用中断,以外部中断为例,都是直接触发后调用同一个中断函数EXTI15_10_IRQHandler(不同中断线不一样),然后在该中断函数中调用所有中断(不需要担心误调用,每个中断函数会有自己的标志位判断):
void EXTI15_10_IRQHandler(void) { /* USER CODE BEGIN EXTI15_10_IRQn 0 */ HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13); /* USER CODE END EXTI15_10_IRQn 0 *//* USER CODE BEGIN EXTI15_10_IRQn 1 */ /* USER CODE END EXTI15_10_IRQn 1 */ }

然后跳到中断处理函数,并判断其中断标志位,然后清除标志位,调用回调函数:
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin) { /* EXTI line interrupt detected */ if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET) { __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin); HAL_GPIO_EXTI_Callback(GPIO_Pin); } }

一般在该函数下面会有一个弱函数:
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)

也就是说,我们可以在 stm32f3xx_it.c 增加 HAL_GPIO_EXTI_Callback() 的实现
弱函数__weak解释
系统滴答定时器 HAL_RCC_GetHCLKFreq 作用:得到系统时钟的某一个参数
应用:(系统时钟为72MHz时)
// HAL_RCC_GetHCLKFreq()/10001ms中断一次,即HAL_Delay函数延时基准为1ms // HAL_RCC_GetHCLKFreq()/10000010us中断一次,即HAL_Delay函数延时基准为10us // HAL_RCC_GetHCLKFreq()/1000000 1us中断一次,即HAL_Delay函数延时基准为1us HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000000); // 配置并启动系统滴答定时器

一般后面会跟着这两句:
/* 系统滴答定时器时钟源 */ HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK); /* 系统滴答定时器中断优先级配置 */ HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);

系统滴答定时器的使用即通过对系统时钟进行分频得到一个定时中断
串口
UART有开关中断的现象,而不是中断一直开着。
1、串口发送/接收函数
HAL_UART_Transmit(); 串口轮询模式发送,使用超时管理机制HAL_UART_Receive(); 串口轮询模式接收,使用超时管理机制HAL_UART_Transmit_IT(); 串口中断模式发送HAL_UART_Receive_IT(); 串口中断模式接收HAL_UART_Transmit_DMA(); 串口DMA模式发送HAL_UART_Transmit_DMA(); 串口DMA模式接收2、串口中断函数HAL_UART_TxHalfCpltCallback(); 一半数据发送完成时调用HAL_UART_TxCpltCallback(); 数据完全发送完成后调用HAL_UART_RxHalfCpltCallback(); 一般数据接收完成时调用HAL_UART_RxCpltCallback(); 数据完全接受完成后调用HAL_UART_ErrorCallback(); 传输出现错误时调用

HAL库中USART需要注意的地方
GPIO口操作 基于CubeIDE开发笔记|HAL库部分常用函数名称及作用
文章图片

ADC(含DMA) ADC采集通过DMA传输
HAL_ADC_Start_DMA 采集固定次数
开启ADC对应的DMA通道,此时,&ADC_Value为内存地址,最后的100是采集次数,如果需要不断得获取新的数值,则需要重新开启转换。
HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC_Value, 100);

连续不断地采集
Mode 改为Circular, Increment Address 的√去掉。
注意:如果是多通道采集的话,Increment Address 的√不能去掉,否则会被多通道数据覆盖。较好选择是创建一个和通道数大小相同的数组,这样依次存放的就是各个通道的ADC测量数值
uint16_t ADC_Value[100],ad1; float adv; //开启 HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC_Value, 100); //本次其实只用到了数组第一位//回调函数,每次DMA转换完成就会被调用一次 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { ad1=ADC_Value[0]; adv = (float)ad1 / 4096 * 3.3; printf("adcV= %f ",adv); }

基于CubeIDE开发笔记|HAL库部分常用函数名称及作用
文章图片

a 激活ADC,启动转换规则组
HAL_ADC_Start(); HAL_ADC_Start_IT()中断模式ADC_EOC_SINGLE_CONV通道转换结束,EOC_SEQ_CONV序列转换结束 HAL_ADC_Start_DMA();

b 关闭ADC,停止转换规则组
HAL_ADC_Stop(); HAL_ADC_Stop_IT() HAL_ADC_Stop_DMA()

C 读取ADC值
HAL_ADC_GetValue()

D 其它
HAL_ADC_PollForConversion()等待转换结束,不适用一下情况:DMA模式且轮询每个转换 HAL_ADC_PollForEvent()// 回调函数,“weak”属性,使用时再在应用代码中实现 ?HAL_ADC_ConvCpltCallback()转换完成后回调,DMA模式下DMA传输完成后调用 ?HAL_ADC_ConvHalfCpltCallback()转换过程中回调 ?HAL_ADC_LevelOutOfWindowCallback() ?HAL_ADC_ErrorCallback()状态函数--返回运行状态获取错误信息 ?HAL_ADC_GetState() ?HAL_ADC_GetError()

如果开启间断模式,每次需要先使用HAL_ADC_Start()(或HAL_ADC_Start_IT(),HAL_ADC_Start_DMA()下文不再赘述)启动转换,需要使用HAL_ADC_PollForConversion()等待转换完成,HAL_ADC_GetState()获取ADC转换状态(若返回值为HAL_OK说明转换完成),转换完成后使用HAL_ADC_GetValue()读取ADC原始值,读取完成后,使用HAL_ADC_Stop()停止转换,如需再次获取ADC数据,需重复执行上述步骤。

    推荐阅读