FreeRTOS study notes - FreeRTOS interrupt configuration and critical section

1. Cortex-M interrupt

1.1 Introduction to Interrupts

Interrupts are a very common feature of microcontrollers. Interrupts are generated by hardware. When an interrupt is generated, the CPU will interrupt the current process and turn to handle the interrupt service. The MCU of the Cortex-M core provides a nested interface for interrupt management. Vectored Interrupt Controller (NVIC).

The NVIC of Cotex-M3 supports up to 240 IROs (interrupt requests), 1 non-maskable interrupt NMI, 1 Svstick (tick timer) timer interrupt and multiple system exceptions.

For more information about interrupts, see the blogger's STM32 Quick Notes column interrupts.

1.2 Definition of Priority Grouping

When multiple interrupts come, which interrupt the processor should respond to is determined by the priority of the interrupt. High-priority interrupts (with small priority numbers) must be responded first, and high-priority interrupts can preempt low-level interrupts. Priority interrupts, this is interrupt nesting. Some interrupts of the Cortex-M processor have fixed priorities, such as reset, NMI, and HardFault. The priorities of these interrupts are negative, and the priority is the highest.

The Cortex-M processor has three fixed priorities and 256 programmable priorities, with a maximum of 128 preemption levels, but the actual number of priorities is determined by the chip manufacturer. However, most chips will be simplified in design, so that the number of priorities actually supported will be less, such as 8 levels, 16 levels, 32 levels, etc. For example, STM32 only has 16 levels of priority.

移植FreeRTOS时,STM32的中断分组配置为4,也就是全部都是抢占优先级。有 0~15 共 16 个优先级。因为FreeRTOS 的中断配置没有处理亚优先级这种情况,所以只能配置为组 4,直接就 16 个优先级

2. Special registers for interrupt masking

When porting FreeRTOS on STM32, you need to focus on the three registers PRIMASK , FAULTMASK and BASEPRI .

2.1 PRIMASK register

Sometimes it is necessary to temporarily mask all interrupts and perform some tasks with strict timing requirements . At this time, the PRIMASK register can be used . PRIMASK is used to disable all exceptions and interrupts except NMI and HardFalut. This register can be accessed through the MRS and MSR

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

Or you can use the CPS (modify processor state) instruction to modify the value of the PRIMASK register to realize the interrupt switch

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

在使用库函数进行开发时,在sys.c文件中定义了相关函数。

//关闭所有中断
void INTX_DISABLE(void)
{
    
    		  
	__ASM volatile("cpsid i");
}
//开启所有中断
void INTX_ENABLE(void)
{
    
    
	__ASM volatile("cpsie i");		  
}

2.2 FAULTMASK Register

FAULTMASK is more ruthless than PRIMASK, it can even shield HardFaut, the usage method is similar to PRIMASK, it should be noted that FAULTMASK will be automatically cleared when exiting .

2.3 BASEPRI register

在FreeRTOS中,并不会使用上面的两个寄存器来开关中断,而是使用BASEPRI寄存器。Because in a more sophisticated design, it is necessary to control the shielding of interrupts more delicately. For example, only shield interrupts whose priority is lower than a certain threshold—— . 优先级在数字上大于等于某一个数This priority value as a threshold is stored in the BASEPRI register. Writing 0 to BASEPRI will stop masking interrupts .

任务优先级数值越大,优先级越大。但是中断优先级,数值越大,优先级越低。

For example, we want to shield interrupts whose priority is not higher than 0X60

MOV R0, #0X60
MSR BASEPRI, R0

If you need to cancel BASEPRI's masking of interrupts, write 0 directly to the BASEPRI register

MOV R0, #0
MSR BASEPRI, R0

In FreeRTOS, the function to turn on and off the interrupt is

portENABLE_INTERRUPTS();   // 打开中断
portDISABLE_INTERRUPTS();   // 关闭中断
#define portDISABLE_INTERRUPTS()				vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS()					vPortSetBASEPRI( 0 )

To turn off the interrupt, the method is to write a value to the BASEPRI register, which is ulNewBASEPRI, which is configMAX_SYSCALL_INTERRUPT_PRIORITY.

/*-----------------------------------------------------------*/

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
	}
}
/*-----------------------------------------------------------*/
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY			15                      //中断最低优先级
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY	5                       //系统可管理的最高中断优先级
#define configKERNEL_INTERRUPT_PRIORITY 		( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

The manageable interrupt priority of the system set here is 5, which means that interrupts with a priority lower than 5 (5~15) will be masked.

Turning on the interrupt means directly writing 0 to the BASEPRI register.

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
	}
}

通过上面的介绍可知,0~4的中断优先级不属于FreeRTOS管理。在0~4优先级的中断服务函数中不能使用FreeRTOS的AP函数。比如调用在中断中解挂任务的函数。以“FromISR”结尾的API函数是在中断服务函数中使用的API函数,这些都不能在0~4的优先级的中断服务函数中使用。

Interrupt configuration result graph

3. Critical section code

The critical section code is also called the critical section, which refers to the code section that must run completely and cannot be interrupted. For example, the initialization of some peripherals requires strict timing and cannot be interrupted during the initialization process. FreeRTOS needs to turn off the interrupt when entering the critical section code, and turn on the interrupt after processing the critical section code. The FreeRTOS system itself has a lot of critical section codes. These codes are protected by critical section codes. When we write our own user programs, we also need to add critical section code protection in some places.

FreeRTOS has 4 functions related to critical section code protection, taskENTER_CRITICAL(), taskEXIT_CRITICAL(), taskENTER_CRITICAL_FROM_ISR(), taskEXIT_CRITICAL_FROM ISR(). These four functions are actually macro definitions, which are defined in the task.h file. The difference between these four functions is that the first two are task-level critical section code protection, and the latter two are interrupt-level critical section code protection.

#define taskENTER_CRITICAL()		portENTER_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()

#define taskEXIT_CRITICAL()			portEXIT_CRITICAL()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

3.1 Task-level critical section code protection

taskENTER_CRITICAL() and taskEXIT_CRITICAL() are task-level critical code protections. One is to enter the critical section, and the other is to exit the critical section. These two functions are used in pairs. The usage of task-level critical code protection is as follows

void taskcritical_test(void)
{
    
    
    while(1)
    {
    
    
        taskENTER_CRITICAL();   // 进入临界区

		// 临界区代码

		taskEXIT_CRITICAL();   // 退出临界区
		 vTaskDelay(1000);
	}
}

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

3.2 Interrupt-level critical section code protection

The function taskENTER_CRITICAL_FROM_ISR() and askEXIT_CRITICAL_FROM_ISR() interrupt level critical segment code protection is used in the interrupt service routine, and the priority of this interrupt must be lower than configMAX_SYSCALL_INTERRUPT_PRIORITY. The reason has already been said before. These two functions are defined as follows in the file task.h.

Interrupt-level critical code protection is used as follows

// 定时器3中断服务函数
void TIM3_IRQHandler(void)
{
    
    
	if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)   // 溢出中断
	{
    
    
		status_value=taskENTER_CRITICAL_FROM_ISR();

		// 临界区代码

		taskEXIT_CRITICAL_FROM_ISR(status_value);
	}
	TIM_ClearITPendingBit(TIM3,TIM_IT_Update);   // 清除中断标志位
}

4. Interruption test

Here is a test of what was introduced in the second part. FreeRTOS can shield interrupts with priority 5~15, but it cannot shield interrupts with priority 0 to 4. We enable two interrupts, one is a timer interrupt with an interrupt priority of 6. In the interrupt service function of the timer, the state of LED1 is reversed. The other is the external interrupt of WK UP, the interrupt priority is 3. In the interrupt service function of the external interrupt, flip the state of LED2. Use the keys KEY0 and KEY1 to control the interrupt switch. Test the status of external interrupts and timer interrupts after the interrupt mask is enabled.

4.1 Interrupt test program design

The timer configuration program and interrupt service function are as follows

/*
 *==============================================================================
 *函数名称:TIM2_Iint
 *函数功能:初始化定时器2
 *输入参数:per:自动重装载值;psc:预分频系数
 *返回值:无
 *备  注:无
 *==============================================================================
 */
void TIM2_Iint (u16 per,u16 psc)
{
    
    
	// 结构体定义
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);   // 使能TIM2时钟
	
	TIM_TimeBaseInitStructure.TIM_Period = per;   // 自动装载值
	TIM_TimeBaseInitStructure.TIM_Prescaler = psc;   // 分频系数
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;   // 不分频
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;   // 设置向上计数模式
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);   // 开启定时器中断
	TIM_ClearITPendingBit(TIM2,TIM_IT_Update);   // 使能更新中断
	
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;   // 定时器中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=6;   // 抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;   // 子优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   // IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	
	
	TIM_Cmd(TIM2,ENABLE);   // 使能定时器	
}

// TIM2中断服务函数
void TIM2_IRQHandler(void)   // TIM2中断
{
    
    
	// 产生更新中断
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
	{
    
    
		Med_Led_StateReverse(LED0);   // LED状态翻转
	}
	
	TIM_ClearITPendingBit(TIM2, TIM_IT_Update);   // 清除TIM2更新中断标志
}

The external interrupt configuration function and interrupt service function are as follows

/*
 *==============================================================================
 *函数名称:Exit_Init
 *函数功能:初始化外部中断
 *输入参数:无
 *返回值:无
 *备  注:无
 *==============================================================================
 */
void Exit_Init (void)
{
    
    
	NVIC_InitTypeDef NVIC_InitStructure;
	EXTI_InitTypeDef  EXTI_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);   // 开启AFIO时钟

	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);   //选择GPIO管脚用作外部中断线路
	
	//EXTI0 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;   //EXTI0中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;   //抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;   //子优先级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   //IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	   //根据指定的参数初始化VIC寄存器
	
	EXTI_InitStructure.EXTI_Line=EXTI_Line0;   // EXIT0
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;   // 中断
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;   // 上升沿触发
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;   // 使能
	EXTI_Init(&EXTI_InitStructure);
}
/*
 *==============================================================================
 *函数名称:EXTI0_IRQHandler
 *函数功能:外部中断0中断服务函数
 *输入参数:无
 *返回值:无
 *备  注:无
 *==============================================================================
 */

void EXTI0_IRQHandler(void)
{
    
    
	// 如果EXIT0中断标志位被置1
	if(EXTI_GetITStatus (EXTI_Line0)==1)
	{
    
    
		Med_Led_StateReverse(LED1);   // LED状态翻转
	}
	EXTI_ClearITPendingBit (EXTI_Line0);   // 清除中断标志位
}

The main.c program is as follows

//任务优先级
#define START_TASK_PRIO		1
//任务堆栈大小	
#define START_STK_SIZE 		120  
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task (void *pxCreatedTask);

//任务优先级
#define KEY_TASK_PRIO		2
//任务堆栈大小	
#define KEY_STK_SIZE 		120  
//任务句柄
TaskHandle_t KEYTask_Handler;
//任务函数
void key_task (void *pxCreatedTask);

int main(void)
{
    
     	 
	Med_Mcu_Iint();   // 系统初始化
							
	//创建开始任务
	xTaskCreate((TaskFunction_t )start_task,            //任务函数
							(const char*    )"start_task",          //任务名称
							(uint16_t       )START_STK_SIZE,        //任务堆栈大小
							(void*          )NULL,                  //传递给任务函数的参数
							(UBaseType_t    )START_TASK_PRIO,       //任务优先级
							(TaskHandle_t*  )&StartTask_Handler);   //任务句柄 
							
	vTaskStartScheduler();          //开启任务调度
}

void start_task (void *pxCreatedTask)
{
    
    
	taskENTER_CRITICAL();   // 进入临界区
	
	//创建按键任务
	xTaskCreate((TaskFunction_t )key_task,            //任务函数
							(const char*    )"key_task",          //任务名称
							(uint16_t       )KEY_STK_SIZE,        //任务堆栈大小
							(void*          )NULL,                  //传递给任务函数的参数
							(UBaseType_t    )KEY_TASK_PRIO,       //任务优先级
							(TaskHandle_t*  )&KEYTask_Handler);   //任务句柄
	
	// 开始任务只需要执行一次,执行完成后删除即可
	vTaskDelete(StartTask_Handler); //删除开始任务
							
	taskEXIT_CRITICAL();   // 退出临界区
}

void key_task (void *pxCreatedTask)
{
    
    
	u8 keyValue = 0;   // 获取返回键值
	
	while (1)
	{
    
    
		keyValue = Med_KeyScan();   // 按键扫描
		
		// KEY0按下
		if (keyValue == 2)
		{
    
    
			printf ("关闭中断!\r\n");
			portDISABLE_INTERRUPTS();   // 关闭中断
		}
		// KEY1按下
		else if (keyValue == 3)
		{
    
    
			printf ("开启中断!\r\n");
			portENABLE_INTERRUPTS();   // 开启中断
		}
	}
}

4.2 Interrupt test results

Press the key KEY0 to disable the interrupt, the timer interrupt will be masked, and LED1 will stop flashing. Press KEY1, when the interrupt is turned on, the timer interrupt resumes, and LED1 starts to flash. Even if the interrupt is turned off, the external interrupt can still control the status of LED2 to flip. 但是出现了一个异常现象。在关闭中断后,定时器中断被屏蔽,LE1不再闪烁。但是,当按下WK UP,触发外部中断时,定时器中断又恢复了.

修改程序,将外部中断的优先级设置为7。在一开始初始化系统后屏蔽中断。发现外部中断依旧可以触发。

Guess you like

Origin blog.csdn.net/qq_45217381/article/details/131557545
Recommended