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

実験現象

プログラムの実行後、2つの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セーフなFreeRTOSAPI関数は、優先度configMAX_SYSCALL_INTERRUPT_PRIORITY以下の割り込み呼び出しにのみ割り当てる必要があります。

この文は、FreeRTOS API関数が割り込み関数で使用されている場合、もちろん、FromISRサフィックスを使用することが前提であり、割り込みの優先度は、FreeRTOSConfig.hで定義されているマクロ定義configMAX_SYSCALL_INTERRUPT_PRIORITYより高くすることはできないことを意味します。

/* 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を使用すると、予期しない動作が発生します。

これらの2つの段落は、優先順位のグループ化について説明することを意味します。つまり、すべてのビットがプリエンプティブ優先順位であり、2次優先順位はありません。つまり、メイン関数で設定される割り込みグループ化モード4です。


NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);

NVIC_PriorityGroup_3などの別のものと交換すると、プログラムは割り込みに入った後にスタックします。

完全なプロジェクトコードがGitHubに保存されました:https//github.com/xxpcb/FreeRTOS-STM32F407-examples

おすすめ

転載: blog.51cto.com/15060517/2641097