< 返回

问题18:GetFlagStatus,ClearFlag,GetITStatus,ClearITPendingBit四个函数的区别

本文最后更新时间:2025.4.18

问题解释

在STM32中,有一类寄存器,叫做状态寄存器,状态寄存器中的某一位(简称状态位)可以指示硬件电路的状态(比如定时时间到,ADC转换完成,串口收到数据了等等),我们读取状态位,就可以得知电路的工作状况。

同时,如果这个状态是比较紧急的,我们希望状态位置1了,可以立即申请中断,则此时,状态位就化身为中断标志位。如果开启了对应的中断并配置了NVIC,则状态位/中断标志位为1时,外设就会向CPU申请中断。

例如,下图所示,状态寄存器里的UIF位,表示定时器发生更新,即定时时间到,如果不开启中断,我们可以手动查询此位,来判断定时时间到没到。如果开启了中断,则此位化身为更新中断标志位,此位为1,且开启了定时器更新中断并配置了NVIC,那么定时器就会向CPU申请中断,进而使程序跳转到中断函数执行。

img

UIF为定时器更新状态位

STM32中还有一类寄存器的位,叫做中断使能位,顾名思义,中断使能位为1,表示开启中断,中断使能位为0,表示关闭中断。

例如,下图所示,定时器使能寄存器DIER中,UIE位,表示使能更新中断,此位置1,表示允许更新中断,此位置0,表示禁止更新中断。

img

UIE为定时器更新中断使能位

综上所示,状态位表示设备运行状态,可以通过查询的方式获取状态位的值,如果此状态比较紧急,可以进一步使用中断使能位开启中断,此时状态位化身为中断标志位,为1时,会申请中断。

注意,不同外设的寄存器和库函数的设计可能有区别,但是它们的基本执行流程都是一样的。例如,对于ADC外设,中断使能位位于控制寄存器CR1中;对于外部中断EXTI,中断使能位被中断屏蔽位代替,位于中断屏蔽寄存器IMR中,而状态位被挂起位代替,位于挂起寄存器PR中。这可能是因为每个外设的设计者不一样,所以寄存器的安排和设计也有所区别。

GetFlagStatus,ClearFlag,GetITStatus,ClearITPendingBit这4个函数,就是对状态位和中断使能位的读写函数。

以定时器为例,TIM_GetFlagStatus的程序源码如下:

FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG)
{ 
    ITStatus bitstatus = RESET;  
    /* Check the parameters */
    assert_param(IS_TIM_ALL_PERIPH(TIMx));
    assert_param(IS_TIM_GET_FLAG(TIM_FLAG));
    
    if ((TIMx->SR & TIM_FLAG) != (uint16_t)RESET)
    {
        bitstatus = SET;
    }
    else
    {
        bitstatus = RESET;
    }
    return bitstatus;
}

显而易见,它就是直接读取定时器的状态寄存器SR,返回对应状态位的值,如果指定的状态位不为0,则返回SET(1),否则,返回RESET(0)。例如调用函数TIM_GetFlagStatus(TIM2, TIM_FLAG_Update),则返回SET表示状态位UIF为1,返回RESET表示状态位UIF为0。

TIM_ClearFlag的程序源码如下:

void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG)
{  
    /* Check the parameters */
    assert_param(IS_TIM_ALL_PERIPH(TIMx));
    assert_param(IS_TIM_CLEAR_FLAG(TIM_FLAG));
    
    /* Clear the flags */
    TIMx->SR = (uint16_t)~TIM_FLAG;
}

可以看出,它通过在指定状态位里写0,来清除此状态位,例如调用函数TIM_ClearFlag(TIM2, TIM_FLAG_Update),表示清除UIF状态位。

总结:GetFlagStatus和ClearFlag是对状态位的直接读写函数,GetFlagStatus用于读取状态位,ClearFlag用于清除状态位。

既然GetFlagStatus和ClearFlag就已经包含了读写状态位,那还要GetITStatus和ClearITPendingBit干啥呢?

前面说了,状态位可以直接进行读写,通过查询的方式来获知对应的状态有没有发生,同时,如果配置了中断,状态位还可以进一步去申请中断,此时,我们就有判断中断是否发生的需求了,因此GetITStatus和ClearITPendingBit孕育而生。

注意,状态位为1,并不代表中断一定发生、中断函数一定执行,因为,还要使能中断和配置NVIC,才能触发中断函数执行。没有开启中断的时候,状态位也是可以正常工作的,这时可以使用查询的方式用到状态位。

TIM_GetITStatus的程序源码如下:

ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT)
{
    ITStatus bitstatus = RESET;  
    uint16_t itstatus = 0x0, itenable = 0x0;
    /* Check the parameters */
    assert_param(IS_TIM_ALL_PERIPH(TIMx));
    assert_param(IS_TIM_GET_IT(TIM_IT));
    
    itstatus = TIMx->SR & TIM_IT;
    
    itenable = TIMx->DIER & TIM_IT;
    if ((itstatus != (uint16_t)RESET) && (itenable != (uint16_t)RESET))
    {
        bitstatus = SET;
    }
    else
    {
        bitstatus = RESET;
    }
    return bitstatus;
}

可以看出,它同时读取了状态寄存器SR和中断使能寄存器DIER,判断条件是,如果指定的状态位为1,且中断被使能了,才返回SET(1),否则,就返回RESET(0)。例如调用函数TIM_GetITStatus(TIM2, TIM_IT_Update),只有UIF和UIE同时为1,函数才返回SET,否则,就是返回RESET。

ClearITPendingBit的程序源码如下:

void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT)
{
    /* Check the parameters */
    assert_param(IS_TIM_ALL_PERIPH(TIMx));
    assert_param(IS_TIM_IT(TIM_IT));
    /* Clear the IT pending Bit */
    TIMx->SR = (uint16_t)~TIM_IT;
}

可以说,它的执行逻辑和TIM_ClearFlag完全一样,也是通过在指定状态位里写0,来清除此状态位,或者说叫中断标志位。

总结:GetITStatus和ClearITPendingBit是与中断相关的读写函数,GetITStatus相比较GetFlagStatus,增加了对中断使能位的判断,如果中断没使能,即使状态位为1,返回的也是RESET。ClearITPendingBit意为清除中断挂起标志位,其实也就是清除状态位,内部执行逻辑,与ClearFlag没有区别。

最后,GetFlagStatus和ClearFlag相比较GetITStatus和ClearITPendingBit,它们的参数取值范围可能不同,因为状态位有很多,但并不是所有的状态位都支持申请中断,因此GetITStatus和ClearITPendingBit的参数,只能是涉及中断的标志位,参数取值范围,小于或等于,GetFlagStatus和ClearFlag的参数取值范围。

简单理解:如果只想单纯地查询状态位,用GetFlagStatus和ClearFlag,如果想判断中断有没有发生,用GetITStatus和ClearITPendingBit。

当然,在大多数情况下,GetFlagStatus可以与GetITStatus混用,ClearFlag也可与ClearITPendingBit混用。至于为什么相似的逻辑出两套函数,那可能就要看设计者的意思了。


< 返回