目录
一、Cortex-M中断
1、中断简介
Cortex-M内核MCU提供了一个用于中断管理的嵌套向量中断控制器(NVIC),M3、M4的NVIC最多支持240个中断请求(IRQ)、一个不可屏蔽中断(NMI)、一个SysTick定时器中断和多个异常请求。可分为系统异常(编号1-15)和外部中断(编号大于等于16),具体详细如下表单:
2、中断优先级
中断优先级越高(优先级数值越小),中断响应越及时。对于三个系统异常:复位、NMI和硬fault,它们的优先级是固定的,且都是负数,所以它们的优先级是最高的。
每个中断都有一个8位的优先级配置寄存器,最少使用3位(8个优先级),最多使用8位(256个优先级)。具体使用多少位由芯片设计厂商决定,STM32使用的是4位(16个优先级)。
中断优先级又分为抢占优先级与亚优先级。只有高抢占优先级才能打断低抢占优先级的执行。对于同级别的抢占优先级中断,亚优先级高的先执行。用来表达优先级的8位可以分为两部分,高位部分表示抢占优先级,低位部分表示亚优先级。亚优先级至少有一位,因此抢占优先级最多只有128个。
优先级分组配置在控制寄存器的位[10:8],该位写入几就是从第几位开始分组。不使用的位并不影响分组。
3、中断屏蔽寄存器
与中断屏蔽相关的寄存器有三个:PRIMASK, FAULTMASK以及BASEPRI。
(1)PRIMASK和FAULTMASK寄存器
PRIMASK寄存器用于禁止除NMI和HardFalut外的所有异常和中断。
FAULTMASK寄存器用于禁止除NMI外的所有异常和中断。
(2)BASEPRI寄存器(重点)
BASEPRI寄存器用于关闭除复位、NMI和HardFalut外的其他中断。可以设置屏蔽优先级阈值,高于这个阈值的中断将不被屏蔽,低于的将被屏蔽。只需将这个阈值写入BASEPRI寄存器即可,写入0就会停止屏蔽中断。(FreeRTOS的开关中断就是对这个寄存器进行操作的)
二、FreeRTOS的中断
1、相关宏配置
#define configPRIO_BITS 4
//设置MCU使用几位表示优先级,STM32为4位
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0xf
//设置最低优先级,STM32用4位表示优先级,当中断优先级分组为组4时,4位都用来表示抢占优先级,
//所以共有16级,因此该宏为16-1=15。不同的MCU和不同的优先级分组对应该宏都不一样。
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
//设置上面所讲的可屏蔽中断阈值,优先级数值低于该值的不会被屏蔽
#define configKERNEL_INTERRUPT_PRIORITY \
( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
//设置内核中断优先级,将最低优先级数值写入表达优先级的高4位(0xf<<(8-4))
//此宏用来设置PendSV和SysTick定时器中断的优先级port.c中:
#define portNVIC_PENDSV_PRI ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 16UL )
#define portNVIC_SYSTICK_PRI ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 24UL )
//设置PendSV和SysTick定时器中断的优先级,都为最低优先级
#define configMAX_SYSCALL_INTERRUPT_PRIORITY \
( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
//此宏是将可屏蔽中断阈值写入表示优先级高4位,方便将其直接写入到对于寄存器中
2、FreeRTOS开关中断
开中断函数:portDISABLE_INTERRUPTS()
关中断函数:portENABLE_INTERRUPTS()
函数是两个宏:
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI() //关中断
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 ) //开中断
开关中断具体实现函数:
/*关中断*/
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; //阈值优先级的宏
__asm
{
msr basepri, ulNewBASEPRI //写入BASEPRI寄存器中(开启屏蔽中断)
dsb
isb
}
}
/*开中断*/
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
msr basepri, ulBASEPRI //写入BASEPRI寄存器中(传入0停止屏蔽中断)
}
}
三、临界段代码
临界段代码也叫临界区,指那些必须完整运行,不能被打断的代码段。比如某些外设的驱动过程需要严格的时序,过程中不能被打断。进入临界段只需将能打断的中断关闭即可,推出临界段则打开中断即可。任务的切换在SysTick中断和PendSV中断作用下进行,进入临界段这两个中断将被屏蔽,因此不会进行任务切换。
1、进入临界段函数
两个函数都是宏:
#define taskENTER_CRITICAL() portENTER_CRITICAL() //任务级
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR() //中断级
实现函数
void vPortEnterCritical( void ) //任务级
{
portDISABLE_INTERRUPTS(); //关中断函数
uxCriticalNesting++;
if( uxCriticalNesting == 1 )
{
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
}
//中断级函数是关中断的实现函数
2、退出临界段
两个函数都是宏:
#define taskEXIT_CRITICAL() portEXIT_CRITICAL() //任务级
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x ) //中断级
实现函数
void vPortExitCritical( void ) //任务级
{
configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS(); //开中断函数
}
}
//中断级函数是开中断的实现函数
3、用法
在需要进入临界段的程序前调用taskENTER_CRITICAL()进入临界段,程序执行结束后调用taskEXIT_CRITICAL()退出临界段。
注意:调用多少次函数taskENTER_CRITICAL()进入临界段,就要调用多少次函数taskEXIT_CRITICAL()退出临界段。