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

您所在的位置:网站首页 屏蔽一段代码的软件有哪些 STM32使用中断屏蔽寄存器BASEPRI保护临界段+中断分组+抢占/响应优先级概念

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

2024-07-09 22:01| 来源: 网络整理| 查看: 265

如果某些代码段不允许被中断打断,那么这段代码就必须用关中断的方式给保护起来,在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支持几个主优先级,几个副优先级?这个不是绝对的,它们的数目依赖于中断分组方式,由上图可见,总共有5种分组方式,以方式1为例,主优先级只占1bit,副优先级占3bit,那么我们在给中断设置优先级时,只能把这个中断的主优先级设置为0或1,因为主优先级只有1bit,这个中断的副优先级可以设置为0~7之间的一个数,因为副优先级有3bit。

选择分组方式很简单,库函数都提供好了: NVIC_PriorityGroupConfig(XXXXX);//设置系统中断优先级分组方式 其中XXXXX代表分组方式,可以在5个宏中选其一,这就是上图中的5种分组方式:

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

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);

下图来自cortex-M4官方手册,主优先级和子优先级的数值,总共有8个bit,每一个中断都可以配置主/子优先级,每个中断的优先级配置都存储在8bit中(见下下图)。在这8个bit中,主优先级是左对齐的,接下里的BASEPRI寄存器也是如此。什么叫左对齐?举两个例子: (1)当把优先级分组设置为4:4时,也即主优先级为高4bit时,如果2号中断的主优先级=1,子优先级=2时,那么下下图的PRI2就=0x12=b_0001_0010. (2)当把优先级分组设置为2:6时,也即主优先级为高2bit时,如果2号中断的主优先级=1,子优先级=2时,那么下下图的PRI2就=0x12=b_0100_0010=b_01_000010.

根据以上两个例子,各位应该能看懂主优先级左对齐的概念了。

 由下图可见,CM4总共可以管理0~239共240个中断。每个中断的主/子优先级都占用8bit。

 

前文提到的中断屏蔽寄存器BASEPRI,屏蔽的是主优先级,关于这个寄存器的描述,不在STM32的手册中,而是在Cortex M0/1/2/3/4的手册中,首先如下图所示根据STM32的型号查询对应的ARM CORTEX型号,我的是F407,对应查找Cortex M4的手册,st官网有个摘抄版,完全版在arm官《Cortex -M4 Devices Generic User Guide Generic User Guide.pdf》:

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

还要注意,如果我们设置中断分组为主2、副2,那么主优先级号就只可能是0~3共4个,要想用BASEPRI屏蔽2、3号主优先级,那么你不能把BASEPRI字段设置为2,而是要把BASEPRI字段的高2位设置为2!!!basepri这个东西屏蔽的是主优先级,他的对齐方式和前文主优先级的左对齐方式是一样的。

读/写这个寄存器(注意,需要特权模式),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 =x的中断(X根据你的需要来设置) ③........//执行临界段 ④__set_BASEPRI(basePriBak );//恢复中断状态

就这些了,很简单,可以把上述代码封装成宏以便使用。当然,如果想更简单一点,甚至可以不用保存BASEPRI的状态,直接在进入临界段前__set_BASEPRI(X



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3