FreeRTOS - critical section and switch interrupts

The following content is reproduced from Avnet Electronics: http://forum.armfly.com/forum.php

1. Critical section

  The critical section of the code is also called the critical section. Once this part of the code starts executing, no interruption is allowed. In order to ensure that the execution of the critical section code is not interrupted, the interrupt must be turned off before entering the critical section, and the interrupt must be turned on immediately after the execution of the critical section code is completed.

There are multiple critical sections in FreeRTOS source code. Like FreeRTOS, uCOS-II and uCOS-III source code has critical section, while RTX source code does not have critical section. In addition, in addition to the critical section brought by the FreeRTOS operating system source code, users also have critical section problems when writing applications, such as the following two:

2. Critical section processing of task code

  The entry and exit of the critical section in the FreeRTOS task code is mainly realized by operating the register basepri. Before entering the critical section, the operation register basepri closes all interrupt priorities less than or equal to the macro definition configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY, so that the critical section code will not be disturbed by interrupts, and the PendSV interrupt and tick timer interrupt that implement the task switching function are the lowest priority level interrupt, so this task will not be interrupted by other high-priority tasks during the execution of critical section code. Re-manipulate the basepri register when exiting the critical section, that is, turn on the interrupt that was turned off (here we do not consider higher priority interrupts that are not managed by FreeRTOS). The FreeRTOS functions for entering and exiting critical sections are as follows:

 It can be seen from the above two functions vPortEnterCritical and vPortExitCritical that entering the critical section and exiting the critical section are implemented by calling the switch interrupt functions portENABLE_INTERRUPTS and portDISABLE_INTERRUPTS. Careful readers will also find that the two functions above operate on the variable uxCriticalNesting. This variable is important for nesting counts of critical sections . Beginners may ask if the direct switch interrupt is not enough here, why do you need to do a nested count? Mainly because the direct switch interrupt method does not support the nested processing of switch interrupts in the code between switch interrupts.

The code is to turn off interrupts. After nesting a critical section code containing switch interrupts, the interrupts are turned on when exiting, so there is a problem. The nested count effectively prevents users from making errors when calling the functions taskENTER_CRITICAL and taskEXIT_CRITICAL nested.

After so many macro definitions, finally came to the final original function. This layer-by-layer method of calling macro definitions in FreeRTOS brings convenience to operations, but it makes it very inconvenient for users to analyze source code. It can be seen from the above source code implementation that the global interrupt of FreeRTOS is realized by operating the register basepri. This register has been explained in detail in "FreeRTOS - Interrupt Priority Configuration", and will not be repeated here.

Example
of use: When using, be sure to use it in pairs

Nested usage example:

 

3. Interrupt service routine critical section processing

  Similar to the processing of the critical section in the task code, the processing of the critical section in the interrupt service routine also has a pair of switch interrupt functions.

  

  通过上面的源码可以看出,中断服务程序里面的临界段代码的开关中断也是通过寄存器 basepri 实现的。初学的同学也许会问,这里怎么没有中断嵌套计数了呢?是的,这里换了另外一种实现方法,通过保存和恢复寄存器 basepri 的数值就可以实现嵌套使用。如果大家研究过 uCOS-II 或者 III 的源码,跟这里的实现方式是一样的,具体看下面的使用举例。

  使用举例:

  使用的时候一定要保证成对使用

 

4 、开 关 中 断 的 实 现

  FreeRTOS 也专门提供了一组开关中断函数,实现比较简单,其实就是前面 第2小节里面临界段进入和退出函数的精简版本,主要区别是不支持中断嵌套。具体实现如下:

从上面的源码可以看出,FreeRTOS 的全局中断开关是通过操作寄存器 basepri 实现的,关于这个寄存器,前面已经讲过,这里不再赘述。

  使用举例:

  使用的时候一定要保证成对使用

  

  

5 、BSP 板 级 支 持 包 中 开 关 中 断 的 特 别 处 理

  前面为大家讲解了 FreeRTOS 临界段的处理方法和开关中断方法,加上了 FreeRTOS 操作系统后,我们实际编写的外设驱动又该怎么修改呢?因为外设驱动编写时,有些地方有用到开关中断操作,这里以教程配套的 STM32F103,F407 和 F429 开发板为例进行说明,这三种开发板的外设驱动的编写架构都是统一的,用户只需将 bsp.h 文件里面的宏定义:

 

临界段开关中断实验,实验现象:

K1键按下 挂起任务VTaskLED 

K2键按下 启动单次定时器中断,50ms后在定时器中断将任务vTaskLED恢复

 

接口消息处理函数:

复制代码
static void vTaskTaskUserIF(void *pvParameters)
{
   while(1)
    {    
            if(ucKeyCode != 0)
            {
                switch( ucKeyCode)
                {
                    /* K1键按下 挂起任务VTaskLED */
                    case 1:
                        taskENTER_CRITICAL();   /* 进入临界区 */        
                                printf("K2键按下,挂起任务vTaskLED\r\n");                        
                      taskEXIT_CRITICAL();      /* 退出临界区 */    
                        vTaskSuspend(xHandleTaskLED1);            
                    
                    ucKeyCode = 0;
                    break;
                    
                    /* K2键按下 启动单次定时器中断,50ms后在定时器中断将任务vTaskLED恢复 */
                    case 2:
                        taskENTER_CRITICAL();   /* 进入临界区 */                    
                        printf("K3键按下,启动单次定时器中断,50ms后在定时器中断将任务vTaskLED恢复\r\n");
                        taskEXIT_CRITICAL();      /* 退出临界区 */
                    
                    BASIC_TIMx_Mode_Config();
                    ucKeyCode = 0;
                    break;                        /* 其他的键值不处理 */
                default:                     
                    break;                    
                }    
                vTaskDelay(20);
            }
        }
}
复制代码

K1键按下,程序执行case1,把原本闪烁的LED灯挂起。

k2键按下,程序执行case2,然后调用定时器初始化结构体,BASIC_TIMx_Mode_Config();

 

复制代码
 void BASIC_TIMx_Mode_Config(void)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    
    // 开启TIMx_CLK,x[6,7] 
  RCC_APB1PeriphClockCmd(BASIC_TIMx_CLK, ENABLE); 
    
    /* 累计 TIM_Period个后产生一个更新或者中断*/        
  //当定时器从0计数到49,即为50次,为一个定时周期
  TIM_TimeBaseStructure.TIM_Period = 50-1;       
    
    //定时器时钟源TIMxCLK = 2 * PCLK1  
  //                PCLK1 = HCLK / 4 
  //                => TIMxCLK=HCLK/2=SystemCoreClock/2=90MHz
    // 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=1MHz
  TIM_TimeBaseStructure.TIM_Prescaler = 90-1;     /* T = 50/1000000 = 50us */
        
    // 初始化定时器TIMx, x[2,3,4,5]
    TIM_TimeBaseInit(BASIC_TIMx, &TIM_TimeBaseStructure);
        
    // 清除定时器更新中断标志位
    TIM_ClearFlag(BASIC_TIMx, TIM_FLAG_Update);
    
    // 开启定时器更新中断
    TIM_ITConfig(BASIC_TIMx,TIM_IT_Update,ENABLE);
    
    // 使能定时器
    TIM_Cmd(BASIC_TIMx, ENABLE);    
}
复制代码

 

定时器开始定时,执行定时器中断函数,

 

复制代码
void  BASIC_TIMx_IRQHandler(void)
{
    if ( TIM_GetITStatus( BASIC_TIMx, TIM_IT_Update) != RESET ) 
    {    
        ulHighFrequencyTimerTicks++; 
        TimeOut++;
        
        TIM_ClearITPendingBit(BASIC_TIMx , TIM_IT_Update);           
    }
    if(TimeOut == 1000) //50us*1000 = 50ms
    {
        TIM_Cmd(BASIC_TIMx, DISABLE);      //不使能定时器,此时定时器不定时
                TimeOut = 0;                                //让TimeOut重新置零
            BaseType_t xYieldRequired;
    UBaseType_t uxSavedInterruptStatus;

    uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();  /* 进入临界区 */
    {
        /* 用户可以在这里添加临界段代码,我们这里暂时未用到 */
    }
    portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus ); /* 退出临界区 */

    /* 恢复挂起任务 */
    xYieldRequired = xTaskResumeFromISR(xHandleTaskLED1);

                /* 退出中断后是否需要执行任务切换 */    
                if( xYieldRequired == pdTRUE )
                {
                 portYIELD_FROM_ISR(xYieldRequired);
                }
    }
    
}
复制代码

 

定时器定时到50ms时,TIM_Cmd(BASIC_TIMx, DISABLE);  //不使能定时器,此时定时器不定时。

等到下次按下K2的时候再调用用定时器初始化结构体,并使能定时器 TIM_Cmd(BASIC_TIMx, ENABLE);   

notice

复制代码
/*===========================================可屏蔽的中断优先级配置====================================================*/
/*
 * 用于配置STM32的特殊寄存器basepri寄存器的值,用于屏蔽中断,当大于basepri值的优先级的中断将被全部屏蔽。basepri只有4bit有效,
 * 默认只为0,即全部中断都没有被屏蔽。configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY配置为:5,意思就是中断优先级大于5的中断都被屏蔽。
 * 当把配置好的优先级写到寄存器的时候,是按照8bit来写的,所以真正写的时候需要经过转换,公式为:
 * ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff),其中的priority就是我们配置的真正的优先级。经过这个公式之后得到的是下面的这个宏:
 * configMAX_SYSCALL_INTERRUPT_PRIORITY
 *
 * 在FreeRTOS中,关中断是通过配置basepri寄存器来实现的,关掉的中断由配置的basepri的值决定,小于basepri值的
 * 中断FreeRTOS是关不掉的,这样做的好处是系统设计者可以人为的控制那些非常重要的中断不能被关闭,在紧要的关头必须被响应。
 * 而在UCOS中,关中断是通过控制PRIMASK来实现的,PRIMASK是一个单1的二进制位,写1则除能除了NMI和硬 fault的所有中断。当UCOS关闭
 * 中断之后,即使是你在系统中设计的非常紧急的中断来了都不能马上响应,这加大了中断延迟的时间,如果是性命攸关的场合,那后果估计挺严重。
 * 相比UCOS的关中断的设计,FreeRTOS的设计则显得人性化很多。
 *
 */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY    2
#define configMAX_SYSCALL_INTERRUPT_PRIORITY     ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325891984&siteId=291194637