FreeRTOS例程2-任务挂起恢复与使用中断遇到的坑!

任务挂起简单点理解就是现在不需要执行这个任务,让它先暂停,就是挂起。恢复就是从刚才挂起的状态下继续运行。

API函数

任务挂起vTaskSuspend()

函数原型(tasks.c中):


void vTaskSuspend( TaskHandle_t xTaskToSuspend )

参数:

  • xTaskToSuspend:需要挂起的任务句柄

任务恢复vTaskResume()

函数原型(tasks.c中):


void vTaskResume( TaskHandle_t xTaskToResume )

参数:

  • xTaskToSuspend:需要恢复的任务句柄

中断函数中进行任务恢复xTaskResumeFromISR()


BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume )

参数:

xTaskToSuspend:需要挂起的任务句柄

总结:

这几个函数用起来还是很简单的,只需要传入任务的句柄即可。

注意,任务挂起是没有FromISR版本的,所以在中断中貌似就不可以使用任务挂起了。

程序验证

在上个例程的基础上,增加一个按键检测任务和外部中断函数,用来测试任务挂起与恢复。

按键任务


//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上的中断标志位  
}

整个主函数


//*******************************************
//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上的中断标志位  
}

实验现象

程序运行起来后,两个LED任务按照自己的方式闪烁,按下KEY1,LED任务1挂起,即LED保持在常亮或常灭状态,再次按下KEY1,LED任务1恢复,即LED继续闪烁。按下KEY_UP,LED任务2挂起,再按下KEY0,LED任务2恢复。同时串口也会打印相关信息。

注意,中断程序中没有使用延时消抖,所以按下KEY0,从中断恢复任务时,可能会执行多次恢复,(1次挂起)多次恢复目前是没有什么影响的。

注意事项(避免程序卡死)!!!

中断函数中不可以使用vTaskDelay()!

实验中用到了按键作为中断,本想用vTaskDelay(10)进行消抖,结果是程序运行起来后,按下中断的按键,程序卡死,通过调试运行,发现程序死在了这里:

//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 );
    }
}

英文注释的大致意思是:

这不是进入关键函数的中断安全版本,所以assert()如果是从中断上下文中调用的话。只有以“FromISR”结尾的API函数才能在中断中使用。只有在关键嵌套计数为1时才使用assert,以防止assert函数也使用关键部分时出现递归调用。

所以FreeRTOS的API函数只有带FromISR后缀的才能在中断函数中使用,而vTaskDelay()好像也没有FromISR版本,所以就不能使用!推而广之,其它不带FromISR后缀的API函数也不能在中断函数中使用!

另外,中断函数本来就是为了处理紧急情况,在中断函数中延时是不太合理的。

中断函数中必须使用带FromISR后缀的API函数!

这一条和上一条其实是一个意思,实验中在中断函数中对信号量进行释放,使用的是xTaskResumeFromISR()函数,如果改成vTaskResume(),实测发现程序同样会卡死在这里。

中断的优先级不能设置的过高(对应数字过小)!

按键中断的优先级设置:


    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);                         //配置    

第2行的抢占优先级为6是没有问题的,如果改成3,程序在进入按键中断会卡死在这里(port.c文件的末尾):


#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 */

注意里面的几段:

  • 中断优先级级别

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

这句的意思是,如果在中断函数中使用了FreeRTOS的API函数,当然前提也是使用带FromISR后缀的,中断的优先级不能高于宏定义configMAX_SYSCALL_INTERRUPT_PRIORITY,这个宏定义在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) )

即中断优先级设置范围为5~15(0xf)。

当然,如果中断函数中没有使用FreeRTOS的API,那么中断的优先级就不受限制。

  • 中断优先级分组

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

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

这两段意思是在说优先级分组的事,即所有位都是抢占优先级,没有次优先级,即中断分组模式4,也就是在主函数设置的:


NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

如果换成其它的,比如NVIC_PriorityGroup_3,程序进入中断后也会卡死在。

完整工程代码已保存至GitHub:https://github.com/xxpcb/FreeRTOS-STM32F407-examples

猜你喜欢

转载自blog.51cto.com/15060517/2641097

相关文章