基于STM32的FreeRTOS学习之中断配置和临界段(四)

记录一下,方便以后翻阅~

FreeRTOS的中断配置需要根据MCU具体配置,这需要我们了解MCU架构关于中断的知识。

1. Cortex-M中断

1.1 简介
中断由硬件产生,当中断产生后CPU会中断当前的流程转而去处理中断服务,Cortex-M内核MCU提供了用于中断管理的嵌套向量中断控制器(NVIC)。NVIC最多支持240个中断请求(IRQ)、1个不可屏蔽中断(NMI)、1个滴答定时器中断(Systick)和多个系统异常。

1.2 管理方式
Cortex-M处理器有多个用于管理中断和异常的可编程寄存器,位于NVIC和系统控制块(SCB)中,CMSIS将这些寄存器定义为结构体。打开core_cm3.h,有如下两个结构体:
在这里插入图片描述
在这里插入图片描述
NVIC和SCB位于系统控制空间(SCS)内,SCS的地址从0XE000E000开始,SCB和NVIC的地址在core_cm3.h中定义如下:

在这里插入图片描述
1.3 优先级分组定义
高优先级的中断可以抢占低优先级的中断,这就是中断嵌套。有些中断是固定优先级的,比如复位、NMI、HardFault,这些中断的优先级是负数,即最高优先级。
由于大多数芯片会精简设计,实际优先级数会比较少。STM32只有16级优先级。在设计芯片时会裁掉优先级的几个低端有效位,以减少优先级数,如下图所示:
在这里插入图片描述
上图中,Bit0~Bit4的返回值总是零。对于3个位的情况,可使用的优先级就是8个:0x00、0x20、0x40、0x60、0x80、0xA0、0xC0和0xE0。注意,STM32选择了4位作为优先级!

为了使抢占机能变得可控,Cortex-M处理器把256个优先级按位分为高低两段:抢占优先级和亚优先级,NVIC中有一个寄存器叫“应用程序中断及复位控制寄存器AIRCR”,其中有个位段名为优先级组
在这里插入图片描述
PRIGROUP是优先级分组,把优先级分为两个位段:MSB所在位段(左)对应抢占优先级,LSB所在位段(右)对应亚优先级:
在这里插入图片描述
在msic.h中有定义:
在这里插入图片描述
可以看出有5个分组,如果选择分组4,即NVIC_PriorityGroup_4,那4位优先级都是抢占优先级,没有亚优先级。
由于FreeRTOS的中断配置没有处理亚优先级这种情况,所以必须配置为组4!

1.4 优先级设置
每个外部中断有一个对应的优先级寄存器,每个寄存器占8位,因此最大宽度是8位,最小是3位。4个相邻优先级寄存器组成一个32位寄存器。根据优先级分组设置,优先级可分为高、低两个位段,即抢占优先级和亚优先级。优先级寄存器可按字节访问,也可按半字/字来访问,有意义的优先级寄存器数目由厂商确定:
在这里插入图片描述
在这里插入图片描述
根据四个相邻寄存器可拼成一个32位寄存器,因此地址0xE000_ED20~0xE000_ED23这四个寄存器可拼成一个地址为0xE000ED20的32位寄存器。
FreeRTOS在设置PendSV和SysTick的中断优先级时是直接操作地址0xE000_ED20。

1.5 用于中断屏蔽的特殊寄存器
这里重点关注PRIMASK、FAULTMASK和BASEPRI三个寄存器:
1)PRIMASK和FAULTMASK寄存器
在应用中,需要暂时屏蔽所有的中断,执行一些对时序要求严格的任务,这时要用PRIMASK寄存器,它用于禁止除NMI和HardFalut外的所有异常和中断,汇编编程时用CPS(修改处理器状态)指令修改PRIMASK寄存器:

CPSIE     I;    // 清除PRIMASK, 使能中断
CPSID     I;    // 设置PRIMASK, 禁止中断

PRIMASK寄存器还可以通过MRS和MSR指令访问:

MOVS    R0,   #1
MSR     PRIMASK,     R0  ;  // 将1写入PRIMASK禁止所有中断
MOVS    R0,   #0
MSR     PRIMASK,     R0  ;  // 将0写入PRIMASK以使能中断

FAULTMASK比PRIMASK更厉害,它可以连HardFault都屏蔽掉,FAULTMASK会在退出时自动清零,汇编编程时用CPS指令:

CPSIE   F   ; // 清除FAULTMASK
CPSID   F   ; // 设置FAULTMASK

用MRS和MSR指令:

MOVS   R0,   #1
MSR    FAULTMASK,   R0   ; // 将1写入FAULTMASK禁止所有中断
MOVS   R0,   #0
MSR    FAULTMASK,   R0   ; // 将0吸入FAULTMASK使能中断

2)BASEPRO寄存器
当只要屏蔽优先级低于某个阈值的中断时,可用BASEPRI寄存器,向BASEPRI写0会停止屏蔽中断。若屏蔽优先级不高于0x60的中断,可用如下汇编编程:

MOV   R0,    #0X60
MSR   BASEPRI,  R0

如要取消BASEPRI对中断的屏蔽:

MOV   R0,   #0
MSR   BASEPRI,   R0

FreeRTOS开关中断就是操作BASEPRI寄存器实现的!

2. FreeRTOS中断配置宏

1)configPRIO_BITS
设置MCU使用几位优先级,STM32使用4位,则宏为4!

2)configLIBRARY_LOWEST_INTERRUPT_PRIORITY
设置最低优先级。由于STM32优先级使用4位,且STM32配置为分组4,即4位都是抢占优先级。那么最低优先级即为15。所以此宏为15。不同MCU值不同,要看所用的MCU架构。

3)configKERNEL_INTERRUPT_PRIORITY
设置内核中断优先级,宏定义如下:

#define configKERNEL_INTERRUPT_PRIORITY 		( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

左移(8 - configPRIO_BITS) 位,即左移4位。因为STM32使用4位作为优先级,且该4位是高4位,因此要左移4位!
configKERNEL_INTERRUPT_PRIORITY用来设置PendSV和滴答定时器的中断优先级,在port.c中有如下定义:
在这里插入图片描述
PendSV优先级设置是configKERNEL_INTERRUPT_PRIORITY左移16位,
SysTick优先级设置是configKERNEL_INTERRUPT_PRIORITY左移24位。
因为,PendSV和SysTick的中断优先级操作是0xE000_ED20地址,一次写入的是32位数据,SysTick和PendSV的优先级寄存器对应这个32位数据的最高8位和次高8位,所以是左移16位和左移24位。
PendSV和SysTick优先级在port.c文件里的xPortStartScheduler()函数里设置:

BaseType_t xPortStartScheduler( void )
{
	#if( configASSERT_DEFINED == 1 )
	{
		volatile uint32_t ulOriginalPriority;
		volatile uint8_t * const pucFirstUserPriorityRegister = ( uint8_t * ) ( portNVIC_IP_REGISTERS_OFFSET_16 + portFIRST_USER_INTERRUPT_NUMBER );
		volatile uint8_t ucMaxPriorityValue;
		/* Determine the maximum priority from which ISR safe FreeRTOS API
		functions can be called.  ISR safe functions are those that end in
		"FromISR".  FreeRTOS maintains separate thread and ISR API functions to
		ensure interrupt entry is as fast and simple as possible.
		Save the interrupt priority value that is about to be clobbered. */
		ulOriginalPriority = *pucFirstUserPriorityRegister;
		/* Determine the number of priority bits available.  First write to all possible bits. */
		*pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;
		/* Read the value back to see how many bits stuck. */
		ucMaxPriorityValue = *pucFirstUserPriorityRegister;
		/* Use the same mask on the maximum system call priority. */
		ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;
		/* Calculate the maximum acceptable priority group value for the number of bits read back. */
		ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;
		while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
		{
			ulMaxPRIGROUPValue--;
			ucMaxPriorityValue <<= ( uint8_t ) 0x01;
		}
		/* Shift the priority group value back to its position within the AIRCR register. */
		ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
		ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;
		/* Restore the clobbered interrupt priority register to its original value. */
		*pucFirstUserPriorityRegister = ulOriginalPriority;
	}
	#endif /* conifgASSERT_DEFINED */
	/* Make PendSV and SysTick the lowest priority interrupts. */
	portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;     // PendSV中断优先级设置
	portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;    // SysTick中断优先级设置
	/* Start the timer that generates the tick ISR.  Interrupts are disabled here already. */
	vPortSetupTimerInterrupt();
	/* Initialise the critical nesting count ready for the first task. */
	uxCriticalNesting = 0;
	/* Start the first task. */
	prvStartFirstTask();
	/* Should not get here! */
	return 0;
}

上述代码中,PendSV和SysTick优先级设置是直接向地址portNVIC_SYSPRI2_REG写入优先级数据,portNVIC_SYSPRI2_REG是个宏,在port.c中定义如下:

#define portNVIC_SYSPRI2_REG				( * ( ( volatile uint32_t * ) 0xe000ed20 ) )

可知,portNVIC_SYSPRI2_REG地址是0xE000ED20,同时可知在FreeRTOS中PendSV和SysTick的中断优先级是最低的。

4) configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
设置FreeRTOS系统可管理的最大优先级,也就是BASEPRI寄存器的那个阈值优先级,这里设为5(不强制),即高于5的优先级不归FreeRTOS管理。

5)configMAX_SYSCALL_INTERRUPT_PRIORITY
由configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY左移4位而来。设置好以后,低于此优先级的中断可安全的调用FreeRTOS的API函数,高于此优先级的中断FreeRTOS是不能禁止的,中断服务函数也不能调用FreeRTOS的API函数。
以STM32为例,有16个优先级,0为最高优先级,15为最低优先级,配置如下:
configMAX_SYSCALL_INTERRUPT_PRIORITY == 5;
configKERNEL_INTERRUPT_PRIORITY == 15。
结果如下图:
在这里插入图片描述

3. FreeRTOS开关中断

FreeRTOS开关中断函数为portENABLE_INTERRUPTS()和portDISABLE_INTERRUPTS(),位于portmacro.h中:

#define portDISABLE_INTERRUPTS()				vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS()					vPortSetBASEPRI( 0 )

可以看出开关中断实际上是通过函数vPortRaiseBASEPRI()和vPortSetBASEPRI(0)来实现的:

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
	__asm
	{
		/* Barrier instructions are not used as this function is only used to lower the BASEPRI value. */
		msr basepri, ulBASEPRI
	}
}

static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
	uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
	__asm
	{
		/* Set BASEPRI to the max syscall priority to effect a critical section. */
		msr basepri, ulNewBASEPRI
		dsb
		isb
	}
}

函数vPortSetBASEPRI()是向寄存器BASEPRI写入一个值,portENABLE_INTERRUPTS()是开中断,它传递一个0值给vPortSetBASEPRI(),即开中断。
函数vPortRaiseBASEPRI()是向寄存器BASEPRI写入宏configMAX_SYSCALL_INTERRUPT_PRIORITY,那么优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断就会被屏蔽。

4. 临界段代码

临界段代码也称临界区,指那些必须完整运行,不能被打断的代码段。FreeRTOS在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。FreeRTOS系统本身就有很多临界段代码,这些代码都加了临界段代码保护。
FreeRTOS与临界段代码保护有关的函数有4个:taskENTER_CRITICAL()、taskEXIT_CRITICAL()、taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR(),这四个函数是宏定义,在task.h文件中。前两个是任务级的临界段代码保护,后两个是中断级的临界段代码保护。

4.1 任务级临界段代码保护
taskENTER_CRITICAL()和taskEXIT_CRITICAL()是任务级的临界代码保护,一个是进入临界段,一个是退出临界段,这两个是成对使用的,在task.h文件里如下:

#define taskENTER_CRITICAL()		portENTER_CRITICAL()
#define taskEXIT_CRITICAL()			portEXIT_CRITICAL()

portENTER_CRITICAL()和portSET_INTERRUPT_MASK_FROM_ISR()也是宏定义,在portmacro.h文件里:

#define portENTER_CRITICAL()					vPortEnterCritical()
#define portEXIT_CRITICAL()						vPortExitCritical()

函数vPortEnterCritical()和vPortExitCritical()位于port.c文件中:

void vPortEnterCritical( void )
{
	portDISABLE_INTERRUPTS();
	uxCriticalNesting++;
	/* This is not the interrupt safe version of the enter critical function so assert() if it is being called from an interrupt context.  Only API functions that end in "FromISR" can be used in an interrupt.  Only assert if the critical nesting count is 1 to protect against recursive calls if the assert function also uses a critical section. */
	if( uxCriticalNesting == 1 )
	{ configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 ); }
}

void vPortExitCritical( void )
{
	configASSERT( uxCriticalNesting );
	uxCriticalNesting--;
	if( uxCriticalNesting == 0 )
	{ portENABLE_INTERRUPTS(); }
}

上述代码可知,在进入函数vPortEnterCritical()以后会首先关闭中断,给变量uxCriticalNesting加1,uxCriticalNesting是全局变量,记录临界段嵌套次数。函数vPortExitCritical()是退出临界段调用的,将uxCriticalNesting减1,只有当uxCriticalNesting为0时才会调用函数portENABLE_INTERRUPTS()使能中断。这样,只有所有临界段代码都退出后才会使能中断!

任务级临界段代码保护案例:

void taskcritical_test(void)
{
	while(1)
	{
		taskENTER_CRITICAL();   // 进入临界区
		total_num+=0.01f;
		printf("total_num的值为:%.4f\r\n",total_num);
		taskEXIT_CRITICAL();    // 退出临界区
		vTaskDelay(1000);
	}
}

注意临界区代码一定要精简,因为进入临界区会关闭中断,导致优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断得不到及时的响应!

4.2 中断级临界段代码保护
函数 taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()中断级别临界段代码保护,是用在中断服务程序中的,且这个中断的优先级一定要低于configMAX_SYSCALL_INTERRUPT_PRIORITY!
这两个函数位于task.h文件中:

#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

portSET_INTERRUPT_MASK_FROM_ISR()和portCLEAR_INTERRUPT_MASK_FROM_ISR( x )位于portmacro.h文件中:

#define portSET_INTERRUPT_MASK_FROM_ISR()		ulPortRaiseBASEPRI()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)	vPortSetBASEPRI(x)

vPortSetBASEPRI(x)前面已经说明,ulPortRaiseBASEPRI()位于也portmacro.h文件中:

static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
	uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
	__asm
	{
		/* Set BASEPRI to the max syscall priority to effect a critical section. */
		mrs ulReturn, basepri       // 读出BASEPRI的值,保存在ulReturn中
		msr basepri, ulNewBASEPRI   // 将configMAX_SYSCALL_INTERRUPT_PRIORITY写入到寄存器BASEPRI中。
		dsb
		isb
	}
	return ulReturn;                // 返回ulReturn,退出临界区代码保护时要用此值!
}

中断级临界代码保护案例:

void TIM3_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) // 溢出中断
	{	
		status_value=taskENTER_CRITICAL_FROM_ISR();    // 进入临界区
		total_num+=1;
		printf("float_num的值为:%d\r\n",total_num);
		taskEXIT_CRITICAL_FROM_ISR(status_value);      // 退出临界区
	}
	TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天亮继续睡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值