FreeRTOS routine 2-task suspension recovery and the pits encountered by using interrupts!

Suspending a task. Simply understand that you don't need to perform this task now. Let it suspend first, which means it suspends. Resume is to continue running from the state just suspended.

API function

Task Suspend vTaskSuspend()

Function prototype (in tasks.c):


void vTaskSuspend( TaskHandle_t xTaskToSuspend )

parameter:

  • xTaskToSuspend: The task handle that needs to be suspended

Task resume vTaskResume()

Function prototype (in tasks.c):


void vTaskResume( TaskHandle_t xTaskToResume )

parameter:

  • xTaskToSuspend: The task handle that needs to be restored

Task recovery in the interrupt function xTaskResumeFromISR()


BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume )

parameter:

xTaskToSuspend: The task handle that needs to be suspended

to sum up:

These functions are still very simple to use, you only need to pass in the handle of the task.

Note that there is no FromISR version for task suspension, so it seems that task suspension cannot be used during interrupts.

Program verification

On the basis of the previous routine, a key detection task and external interrupt function are added to test task suspension and resume.

Key task


//key任务函数
void key_task(void *pvParameters)
{
    u8 key;
    static uint8_t flag=0;

    while(1)
    {
        key=KEY_Scan(0);
        switch(key)
        {
            case KEY1_PRES:
                if(!flag)
                {
                    vTaskSuspend(Task1Task_Handler);//挂起任务1
                    printf("1 suspend\r\n");
                }
                else
                {
                    vTaskResume(Task1Task_Handler); //恢复任务1
                    printf("1 resume\r\n");
                }
                flag=~flag;
                break;
            case K_UP_PRES:
                vTaskSuspend(Task2Task_Handler);//挂起任务2
                printf("2 suspend\r\n");
                break;
        }
        vTaskDelay(10);         //延时10ms 
    }
}

Interrupt configuration and interrupt function


//==============中断相关配置
void EXTIX_Init(void)
{
    NVIC_InitTypeDef   NVIC_InitStructure;
    EXTI_InitTypeDef   EXTI_InitStructure;

    //KEY_Init(); //按键对应的IO口初始化

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);//使能SYSCFG时钟

    SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource4);//PE4 连接到中断线4

    /* 配置EXTI_Line4 */
    EXTI_InitStructure.EXTI_Line =  EXTI_Line4;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;     //中断事件
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;               //中断线使能
    EXTI_Init(&EXTI_InitStructure);                         //配置

    NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;        //外部中断4
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x06;//抢占优先级6
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;   //子优先级0
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         //使能外部中断通道
    NVIC_Init(&NVIC_InitStructure);                         //配置       
}

//外部中断4服务程序
void EXTI4_IRQHandler(void)
{
    BaseType_t YieldRequired;

    //vTaskDelay(10);   //消抖-------//中断函数中不可以使用vTaskDelay()!!!
    if(KEY0==0)  
    {           
        //vTaskResume(Task2Task_Handler);//这里必须使用FromISR版本的!!!  
        YieldRequired=xTaskResumeFromISR(Task2Task_Handler);//恢复任务2
        printf("2 resume\r\n");
        if(YieldRequired==pdTRUE)
        {
            /*如果函数xTaskResumeFromISR()返回值为pdTRUE,那么说明要恢复的这个
            任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),所以在
            退出中断的时候一定要进行上下文切换!*/
            portYIELD_FROM_ISR(YieldRequired);
        }
    }        
    EXTI_ClearITPendingBit(EXTI_Line4);//清除LINE4上的中断标志位  
}

Whole main function


//*******************************************
//STM32F407+FreeRTOS 任务挂起与恢复(结合中断)
//File: main.c
//Author: xxpcb(wxgzh:码农爱学习)
//Version: V1.0
//Date: 2020/06/04
//*******************************************

#include "stm32f4xx.h"
#include "led.h"
#include "key.h"
#include "usart.h"

#include "FreeRTOS.h"
#include "task.h"

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

#define TASK1_TASK_PRIO     3
#define TASK1_STK_SIZE      128  
TaskHandle_t Task1Task_Handler;
void task1_task(void *pvParameters);

#define TASK2_TASK_PRIO     4   
#define TASK2_STK_SIZE      128  
TaskHandle_t Task2Task_Handler;
void task2_task(void *pvParameters);

#define KEY_TASK_PRIO       2   
#define KEY_STK_SIZE        128  
TaskHandle_t KeyTask_Handler;
void key_task(void *pvParameters);

void EXTIX_Init(void);

int main(void)
{   
    //设置系统中断优先级分组4(FreeRTOS中的默认方式!)
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

    //初始化LED端口
    LED_Init(); 
    //初始化按键
    KEY_Init(); 
    //初始化外部中断
    EXTIX_Init();
    //串口初始化
    uart_init(115200);

    //创建开始任务
    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 *pvParameters)
{
    taskENTER_CRITICAL();           //进入临界区

    //创建TASK1任务
    xTaskCreate((TaskFunction_t )task1_task,             
                (const char*    )"task1_task",           
                (uint16_t       )TASK1_STK_SIZE,        
                (void*          )NULL,                  
                (UBaseType_t    )TASK1_TASK_PRIO,        
                (TaskHandle_t*  )&Task1Task_Handler);   
    //创建TASK2任务
    xTaskCreate((TaskFunction_t )task2_task,     
                (const char*    )"task2_task",   
                (uint16_t       )TASK2_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK2_TASK_PRIO,
                (TaskHandle_t*  )&Task2Task_Handler); 
    //创建KEY任务
    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();            //退出临界区
}

//task1任务函数
void task1_task(void *pvParameters)
{
    while(1)
    {
        LEDa_Toggle;
        vTaskDelay(500); //延时500ms
    }
}

//task2任务函数
void task2_task(void *pvParameters)
{
    while(1)
    {
        LEDb_ON;
        vTaskDelay(200); //延时200ms
        LEDb_OFF;
        vTaskDelay(800); //延时800ms
    }
}

//key任务函数
void key_task(void *pvParameters)
{
    u8 key;
    static uint8_t flag=0;

    while(1)
    {
        key=KEY_Scan(0);
        switch(key)
        {
            case KEY1_PRES:
                if(!flag)
                {
                    vTaskSuspend(Task1Task_Handler);//挂起任务1
                    printf("1 suspend\r\n");
                }
                else
                {
                    vTaskResume(Task1Task_Handler); //恢复任务1
                    printf("1 resume\r\n");
                }
                flag=~flag;
                break;
            case K_UP_PRES:
                vTaskSuspend(Task2Task_Handler);//挂起任务2
                printf("2 suspend\r\n");
                break;
        }
        vTaskDelay(10);         //延时10ms 
    }
}

//==============中断相关配置
void EXTIX_Init(void)
{
    NVIC_InitTypeDef   NVIC_InitStructure;
    EXTI_InitTypeDef   EXTI_InitStructure;

    //KEY_Init(); //按键对应的IO口初始化

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);//使能SYSCFG时钟

    SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource4);//PE4 连接到中断线4

    /* 配置EXTI_Line4 */
    EXTI_InitStructure.EXTI_Line =  EXTI_Line4;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;     //中断事件
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;               //中断线使能
    EXTI_Init(&EXTI_InitStructure);                         //配置

    NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;        //外部中断4
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x06;//抢占优先级6
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;   //子优先级0
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         //使能外部中断通道
    NVIC_Init(&NVIC_InitStructure);                         //配置       
}

//外部中断4服务程序
void EXTI4_IRQHandler(void)
{
    BaseType_t YieldRequired;

    //vTaskDelay(10);   //消抖-------//中断函数中不可以使用vTaskDelay()!!!
    if(KEY0==0)  
    {           
        //vTaskResume(Task2Task_Handler);//这里必须使用FromISR版本的!!!  
        YieldRequired=xTaskResumeFromISR(Task2Task_Handler);//恢复任务2
        printf("2 resume\r\n");
        if(YieldRequired==pdTRUE)
        {
            /*如果函数xTaskResumeFromISR()返回值为pdTRUE,那么说明要恢复的这个
            任务的任务优先级等于或者高于正在运行的任务(被中断打断的任务),所以在
            退出中断的时候一定要进行上下文切换!*/
            portYIELD_FROM_ISR(YieldRequired);
        }
    }        
    EXTI_ClearITPendingBit(EXTI_Line4);//清除LINE4上的中断标志位  
}

Experimental phenomena

After the program is running, the two LED tasks flash in their own way. Press KEY1, and LED task 1 hangs, that is, the LED remains on or off. Press KEY1 again, and LED task 1 resumes, that is, the LED continues to flash. . Press KEY_UP, LED task 2 is suspended, and then press KEY0, LED task 2 resumes. At the same time, the serial port will also print related information.

Note that the delay debounce is not used in the interrupt program, so when you press KEY0, when the task is resumed from the interrupt, it may execute multiple restorations. (1 suspension) multiple restorations have no effect at present.

Matters needing attention (to avoid program stuck)! ! !

VTaskDelay() cannot be used in interrupt functions!

In the experiment, a button was used as an interrupt. I wanted to use vTaskDelay(10) to eliminate jitter. As a result, after the program was running, I pressed the interrupt button and the program was stuck. After debugging and running, it was found that the program was dead here:

//port.c的429~443行
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 );
    }
}

The general meaning of the English annotation is:

This is not a safe version of the interrupt to enter the critical function, so assert() if it is called from an interrupt context. Only API functions ending with "FromISR" can be used in interrupts. Use assert only when the key nesting count is 1, to prevent recursive calls when the assert function also uses the key part.

Therefore, FreeRTOS API functions can only be used in interrupt functions with the FromISR suffix, and vTaskDelay() does not seem to have a FromISR version, so it cannot be used! By extension, other API functions without the FromISR suffix cannot be used in interrupt functions!

In addition, the interrupt function is meant to handle emergency situations, and the delay in the interrupt function is not reasonable.

The API function with the FromISR suffix must be used in the interrupt function!

This one and the previous one actually have the same meaning. In the experiment, the semaphore was released in the interrupt function, and the xTaskResumeFromISR() function was used. If it was changed to vTaskResume(), the actual test found that the program would also be stuck here.

The priority of the interrupt cannot be set too high (the corresponding number is too small)!

Priority setting of key interrupt:


    NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;        //外部中断4
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x06;//抢占优先级6
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;   //子优先级0
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         //使能外部中断通道
    NVIC_Init(&NVIC_InitStructure);                         //配置    

The preemption priority of the second line is 6 is no problem, if it is changed to 3, the program will be stuck here when entering the key interrupt (the end of the port.c file):


#if( configASSERT_DEFINED == 1 )
    void vPortValidateInterruptPriority( void )
    {
    uint32_t ulCurrentInterrupt;
    uint8_t ucCurrentPriority;

        /* 获取当前正在执行的中断的数量。*/
        ulCurrentInterrupt = vPortGetIPSR();

        /* 中断号是用户定义的中断吗?*/
        if( ulCurrentInterrupt >= portFIRST_USER_INTERRUPT_NUMBER )
        {
            /* 查找中断的优先级。*/
            ucCurrentPriority = pcInterruptPriorityRegisters[ ulCurrentInterrupt ];

            /* 如果一个被分配了高于configMAX_SYSCALL_INTERRUPT_PRIORITY优先级的中断的服务
            例程(ISR)调用了一个ISR安全的FreeRTOS API函数,那么下面的断言将失败。
            ISR安全FreeRTOS API函数必须*仅*被分配优先级在
            configMAX_SYSCALL_INTERRUPT_PRIORITY或以下的中断调用。

            数字上较低的中断优先级数在逻辑上代表较高的中断优先级,因此中断的优先级必须设置为
            等于或数字上*高于* configMAX_SYSCALL_INTERRUPT_PRIORITY。

            使用FreeRTOS API的中断不能保留其缺省优先级为零,因为这是可能的最高优先级,它保证
            高于configMAX_SYSCALL_INTERRUPT_PRIORITY,因此也保证无效。

            FreeRTOS维护单独的线程和ISR API函数,以确保中断条目尽可能快速和简单。

            以下链接提供详细资料:
            http://www.freertos.org/RTOS-Cortex-M3-M4.html
            http://www.freertos.org/FAQHelp.html */
            configASSERT( ucCurrentPriority >= ucMaxSysCallPriority );
        }

        /* 优先级分组:中断控制器(NVIC)允许定义每个中断优先级的比特被分割成定义中断的优先级比特和
        定义中断的次优先级比特。为简单起见,必须将所有位定义为抢占优先位。
        如果不是这样(如果某些位表示次优先级),下面的断言将失败。

        如果应用程序只使用CMSIS库进行中断配置,那么在启动调度程序之前,通过调用NVIC_SetPriorityGrouping(0);
        可以在所有Cortex-M设备上实现正确的设置。但是请注意,一些特定于供应商的外设库假设了非零优先级组设置,
        在这种情况下,使用值为0将导致不可预测的行为。 */
        configASSERT( ( portAIRCR_REG & portPRIORITY_GROUP_MASK ) <= ulMaxPRIGROUPValue );
    }

#endif /* configASSERT_DEFINED */

Pay attention to the paragraphs inside:

  • Interrupt priority level

If an interrupt service routine (ISR) assigned a priority higher than configMAX_SYSCALL_INTERRUPT_PRIORITY calls an ISR-safe FreeRTOS API function, the following assertion will fail. ISR-safe FreeRTOS API functions must only be assigned to interrupt calls with priority configMAX_SYSCALL_INTERRUPT_PRIORITY or below.

This sentence means that if FreeRTOS API functions are used in the interrupt function, of course, the premise is also to use the FromISR suffix, and the priority of the interrupt cannot be higher than the macro definition configMAX_SYSCALL_INTERRUPT_PRIORITY, which is defined in FreeRTOSConfig.h:

/* Cortex-M specific definitions. */
#ifdef __NVIC_PRIO_BITS
    /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
    #define configPRIO_BITS             __NVIC_PRIO_BITS
#else
    #define configPRIO_BITS             4        /* 15 priority levels */
#endif

/* 在调用“设置优先级”函数时可以使用的最低中断优先级 */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY         0xf

/* 可以被任何中断服务程序使用的最高中断优先级,它可以调用来中断安全的FreeRTOS API函数。
不要从任何比这个优先级更高的中断调用中断安全的FREERTOS API函数!(优先级越高,数值越低)*/
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY    5

/* 内核端口层本身使用的中断优先级。这些对所有Cortex-M端口都是通用的,并且不依赖于任何特定的库函数。*/
#define configKERNEL_INTERRUPT_PRIORITY         ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY 不能设置为零 !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY    ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

That is, the interrupt priority setting range is 5~15 (0xf).

Of course, if the FreeRTOS API is not used in the interrupt function, the priority of the interrupt is not limited.

  • Interrupt priority grouping

Priority grouping: The interrupt controller (NVIC) allows the bits that define the priority of each interrupt to be divided into priority bits that define interrupts and sub-priority bits that define interrupts. For simplicity, all bits must be defined as preemption priority bits. If this is not the case (if some bits indicate sub-priority), the following assertion will fail.

If the application only uses the CMSIS library for interrupt configuration, before starting the scheduler, by calling NVIC_SetPriorityGrouping(0); the correct settings can be implemented on all Cortex-M devices. Note, however, that some vendor-specific peripheral libraries assume a non-zero priority group setting, in which case using a value of 0 will cause unpredictable behavior.

These two paragraphs mean to talk about priority grouping, that is, all bits are preemptive priority, there is no secondary priority, that is, interrupt grouping mode 4, which is set in the main function:


NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

If it is replaced with another one, such as NVIC_PriorityGroup_3, the program will get stuck after entering the interrupt.

The complete project code has been saved to GitHub: https://github.com/xxpcb/FreeRTOS-STM32F407-examples

Guess you like

Origin blog.51cto.com/15060517/2641097