STM32使用中断屏蔽寄存器BASEPRI保护临界段+中断分组+抢占/响应优先级概念

如果某些代码段不允许被中断打断,那么这段代码就必须用关中断的方式给保护起来,在UCOS中可以看到,一般保护方式有3种:

(1)关闭中断(总中断或者指定的几个中断),执行临界段,开启中断(总中断或者指定的几个中断)。这个方法的弊端有两个:①执行完临界段之后中断总是打开的,即使在关闭中断之前,中断明明没有打开;②会干扰一些重要的中断的执行,例如systick,高精度定时器等。

(2)把中断状态压栈,关中断,执行临界段,把中断状态出栈。这个方法挺好,唯一的不足就是,有些编译器不支持,即使用内嵌汇编也不行,请参考别人的博客《ucos中的三种临界区管理机制》

(3)用局部变量保存中断状态,关闭中断(总中断或者指定的几个中断),执行临界段,从局部变量中读出中断状态并恢复。这时目前最可靠的一种方式。实现起来也很简单。

 

考虑到某些中断格外重要,甚至在临界段执行期间我们仍需要这个中断的正常响应,那么我们再使用方法(3)时就不能直接关闭总中断,而是只关闭指定的那些中断。例如,我有一个变量cnt(4字节)用于在串口接收中断中统计收到的字节数,那么我们在main调用链中读cnt的值时,就必须保护起来,以防读cnt读到一半(2字节)时,恰好发生了串口中断,等从中断出来,cnt的值已经变了,这时main调用链继续读cnt的另一半时,读出的cnt已经是个不可预期的值了。对于这种情况,我们只需在读cnt时关闭串口中断就行了。

上述场景算是个简单的情形,假如我们写了一个通用库,例如串口缓冲区管理、fifo、软定时器模块等,这些模块库,不止一个中断在用它们,难道,我进入临界段之前,要把这些中断状态一个个保存,然后一个个关闭,执行完临界段再一个个恢复吗?显然不会,好在STM32提供了中断屏蔽寄存器,我们可以把这一堆必须要屏蔽的中断,优先级设置的低一些,把另一些不允许关闭的中断优先级设置的高一些,然后通过BASEPRI这个寄存器把优先级低于n的(也即,优先级号>=n,因为优先级和优先级号是反着的,0代表最高优先级)的中断一口气全屏蔽掉,这样我们在进入临界段前,只保存BASEPRI的值就好了。FreeRTOS中也是这么做的。

 

STM32的优先级分组配置如下:

STM32使用中断屏蔽寄存器BASEPRI保护临界段+中断分组+抢占/响应优先级概念_第1张图片

所谓抢占优先级,指的是“可嵌套”的优先级,又叫“主优先级”,也即“抢占优先级高”的中断可以把“抢占优先级低”的中断给打断。
响应优先级,指的是“不可嵌套”的优先级,又叫“副优先级”,也即,主优先级相同的两个中断,“副优先级高”的那个,只能比“副优先级低”的那个优先响应,但不能打断。

STM32支持几个主优先级,几个副优先级?这个不是绝对的,它们的数目依赖于中断分组方式,由上图可见,总共有5种分组方式,以方式1为例,主优先级只占1bit,副优先级占3bit,那么我们在给中断设置优先级时,只能把这个中断的主优先级设置为0或1,因为主优先级只有1bit,这个中断的副优先级可以设置为0~7之间的一个数,因为副优先级有3bit。

选择分组方式很简单,库函数都提供好了:
NVIC_PriorityGroupConfig(XXXXX);//设置系统中断优先级分组方式
其中XXXXX代表分组方式,可以在5个宏中选其一,这就是上图中的5种分组方式:
STM32使用中断屏蔽寄存器BASEPRI保护临界段+中断分组+抢占/响应优先级概念_第2张图片

下面看一个给串口中断设置中断优先级的例子:

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;		//子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ中断请求使能
NVIC_Init(&NVIC_InitStructure);		

 

前文提到的中断屏蔽寄存器BASEPRI,屏蔽的是主优先级,关于这个寄存器的描述,不在STM32的手册中,而是在Cortex M0/1/2/3/4的手册中,请根据你的STM32型号查找,我的是F407,我就查Cortex M4的手册:
STM32使用中断屏蔽寄存器BASEPRI保护临界段+中断分组+抢占/响应优先级概念_第3张图片

 

由上图我们可以发现,无论把BASEPRI设置为多少,都无法屏蔽主优先级为0的中断。

还要注意,这个寄存器最多只能屏蔽1~7号中断,如果我们设置中断分组为主2、副2,那么主优先级号就只可能是0~3共4个,要想用BASEPRI屏蔽2、3号主优先级,那么你不能把BASEPRI字段设置为2,而是要把BASEPRI字段的高2位设置为2!!!
如果设置分组时,把主优先级设置为1 bit,那么要想屏蔽1号中断,应该把BASEPRI字段的高1bit设置为1。相信朋友们已经看懂了,BASEPRI设置屏蔽号时,总是高bit位有效,那么具体是高几bit?其实就等于你所设置的主优先级的bit数。
几乎所有寄存器的字段的值都是从低位计算,唯独这个寄存器搞特殊,大家记住就行了。

 

读/写这个寄存器(注意,需要特权模式),CM4提供了库函数来操作它,头文件是"core_cmFunc.h",函数名原型:  __STATIC_INLINE void __set_BASEPRI(uint32_t basePri);//设置
__STATIC_INLINE uint32_t  __get_BASEPRI(void);//读取

别的平台可以在工程中自行搜索BASEPRI,查找对应的库函数,如果找不到就自己写一个得了,就是两个很简单的汇编函数:
 

inline void set_BASEPRI(uint32_t basePri)
{
  register uint32_t __regBasePri         __ASM("basepri");
  __regBasePri = (basePri & 0xff);
}


inline uint32_t  get_BASEPRI(void)
{
  register uint32_t __regBasePri         __ASM("basepri");
  return(__regBasePri);
}

 

 

 

最后,我们来看看保护临界段的具体步骤:

(1)在初始化中设置好中断分组,假设我设置为主2 bit、副2bit,

(2)

①uint32_t  basePriBak = __get_BASEPRI();//备份中断屏蔽寄存器
②__set_BASEPRI(X << 6);//屏蔽所有主优先级号>=x的中断(X根据你的需要来设置)
③........//执行临界段
④__set_BASEPRI(basePriBak );//恢复中断状态

就这些了,很简单,可以把上述代码封装成宏以便使用。当然,如果想更简单一点,甚至可以不用保存BASEPRI的状态,直接在进入临界段前__set_BASEPRI(X << 6);,退出临界段后取消所有的屏蔽__set_BASEPRI(0)即可。

为什么这句代码中__set_BASEPRI(X << 6);是左移6?因为我把中断分组设置成了主优先级占2bit,也即BASEPRI的有效位为高2bit,也即bit7和bit6,所以要左移6。

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(stm32/单片机)