STM32HAL库 中断中不能使用HAL_Delay()函数的原因及解决方案
HAL库中断中不能使用延时函数的原因和两种解决方案。
文章目录
本文默认读者具有中断的基础知识
1. 追溯问题原因
1.1 按键消抖
给大家举个最常见的例子,大家入门单片机的学习中,在中断章节时学习按键中断控制LED灯的闪烁的案例屡见不鲜,但是一般初学者不会直接使用hal库进行学习,都是从标准库或者直接对寄存器进行开发,后两者相对于hal库易于让初学者理解底层原理,但是开发效率低,移植性差。
按键消抖是为了消除机械按键自身的物理特性引发的抖动产生的影响。抖动不消除可能会多次进入中断从而对一些数据产生影响。
当我们在中断中使用HAL_Delay()函数进行按键消抖时,当中断触发条件发生时,跳入到存在HAL_Delay()函数的延时中断中,会出现死机的情况,这是因为中断优先级的缘故,中断优先级高的能抢占cpu的资源。
1.2 systick系统滴答定时器原理
systick timer系统滴答定时器是一个24位向下计数的定时器。我们可以根据使用的是外部时钟还是内部时钟来更改systick重装载寄存器的值来控制延时基本单位。cubemx生成的代码默认延时最小单位是ms,也就是每毫秒产生一次systick滴答定时器中断。
1.3 优先级
我使用的开发板中断优先级分组为4,4位全部都给到抢占优先级。

使用cubeMX生成的代码默认systick系统滴答定时器的抢占优先级为15,为最低优先级。
将TICK_INT_PRIORITY通过HAL_InitTick()函数传入到HAL_NVIC_SetPriority()函数进行systick系统滴答定时器的优先级设置。

自己查找在cubemx生成的systick系统滴答定时器的代码步骤:main函数 -> HAL_Init()初始化函数 -> 找到HAL_InitTick()函数(TICK_INT_PRIORITY的定义在同一文件中直接进行跳转) -> 进入到HAL_InitTick()函数 就可以看到关于systick系统滴答定时器的优先级设置了。
1.4 “病灶”所在
说了前面的那些无关紧要的,其实一句话就可以说明白:“优先级高的会抢占cpu资源,低优先级不允执行”。
在自定义的中断中,加入HAL_Delay()函数进行延时,当前自定义高优先级中断没有执行完,不允许低优先级中断执行,进入到HAL_Delay()函数中,但是HAL_GetTick()获取到的uwTick的值不会变,因为还在自定义高优先级中断中,systick中断得不到cpu资源,uwTick的值不会自增,就会一直卡在HAL_Delay()函数中的while循环中造成死机。
2. 解决方案
2.1 改变抢占优先级
将systick的抢占优先级变高,比自定义中断的抢占优先级高即可,抢占优先级和子优先级都相同行不通,相同抢占优先级和不同子优先级没事过,感兴趣的可以试一下。
这种办法不予推荐,滴答定时器的中断级别高,进出频繁,抢占大量cpu资源的同时,会影响其他中断的响应。
操作办法:进入HAL_Init()函数中,找到HAL_InitTick()函数,传入的参数即为优先级(不同开发板或许不同),更改该值(比自定义优先级高即可),这样死机问题就可以解决。
2.2 自定义定时器
自定义定时器产生时基,设置计时标志位用来计时,假如说生成10ms的时基,假如说计时标志位从0加到10,就产生了100ms延时。但是还是要保证该中断优先级最高和2.1处理方法原理上是一样的。
这样做的好处不会因为单核cpu的限制,影响其他实时性要求高的事件的处理。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //假设10ms时基
{
if(tim_cnt_flag == 1) tim_cnt_flag++;
}
//A事件触发监听
void A_trigger_process() //如果A事件发生,开始计时
{
tim_cnt_flag = 1;
}
//A事件
void A_process()
{
if(tim_cnt_flag == 11) //如果成立,处理事件。事件触发到现在已经发生了10个时基周期,相当于延时100ms,其他时间不会处理A事件,不会出现因为HAL_delay()的while()死循环占用cpu资源。
{
tim_cnt_flag = 0;
//处理A事件
}
}
2.3 按键消抖,长按,短按
按键处理理想逻辑:
按下—>释放;
但是因为机械按键的特性开始和结束都得消抖,加上按一次执行一次、长按、短按等要求。
所以我们的处理逻辑是:
开始按下—>按下消抖—>按下—>等待弹起—>弹起—>弹起消抖—>释放;
如果是实现长按,添加一个长按状态,在等待状态之后设置长按状态。
if(按键按下){
if(是否是释放状态){ //开始按下
进入消抖状态,开始消抖计时
}
else if(是否是消抖状态){ //按下消抖
if(当前时间-消抖计时>=消抖时长){
消抖完成,进入按下状态
}
}
else if(是否是按下状态){ //等待弹起状态
等待释放状态
}
else if(等待按下状态){ //设置长按
if(长按条件){
按键长按
}
}
}
else{//没有按下
if(是否是等待释放或者按下状态){ //弹起
进入消抖状态,开始消抖计时
}
else if(是否是消抖状态){ //弹起消抖
if(当前时间-消抖计时>=消抖时长){
消抖完成,按键释放
}
}
}
这里以按键实际代码为例子:
//按键的四种状态
enum{
key_released = 0U,
key_pressed,
key_wait_released,
key_reduction,
};
void key_state_gain()
{
keys_volt = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0);
//开始按下>>>消抖>>>按下>>>消抖>>>释放
if(keys_volt == 0)
{
if(keys_state == key_released){
keys_state = key_reduction;
key_redu = HAL_GetTick();
}
else if(keys_state == key_reduction){
if(HAL_GetTick() - key_redu>=10){
keys_state = key_pressed;
}
}
else if(keys_state == key_pressed){
keys_state = key_wait_released;
}
}
else{
if(keys_state == key_pressed || keys_state == key_wait_released)
{
keys_state = key_reduction;
key_redu = HAL_GetTick();
}
else if(keys_state == key_reduction){
if(HAL_GetTick() - key_redu>=10){
keys_state = key_released;
}
}
}
}
更多推荐




所有评论(0)