Freertos學習:03-任務


--- title: rtos-freertos-03-任務 EntryName: rtos-freertos-03-task date: 2020-06-20 09:15:07 categories: tags: - freertos --- **章節概述:**

介紹任務的基本概念,以及如何使用任務。

概述

在RTOS中,任務/進程是一個操作系統的基本概念,作為資源分配與調度的最小單位。

換句話說:使用RTOS的實時應用程序可認為是一系列獨立任務的集合。

每個任務在自己的環境中運行,不依賴於系統中的其它任務或者RTOS調度器。在任何時刻,只有一個任務得到運行,RTOS調度器決定運行哪個任務。調度器會不斷的啟動、停止每一個任務,宏觀看上去就像整個應用程序都在執行。

作為任務,不需要對調度器的活動有所了解,在任務切入切出時保存上下文環境(寄存器值、堆棧內容)是調度器主要的職責。

為了實現任務之間的調度切換,每個任務都需要有自己的堆棧。當任務切出時,它的執行環境會被保存在該任務的堆棧中,這樣當再次運行時,就能從堆棧中正確的恢復上次的運行環境。

任務的特點

  • 簡單
  • 沒有使用限制
  • 支持完全搶占
  • 支持優先級
  • 每個任務都有自己的堆棧,消耗RAM較多
  • 如果使用搶占,必須小心的考慮可重入問題

任務優先級

每個任務都要被指定一個優先級,從0~configMAX_PRIORITIES,configMAX_PRIORITIES定義在FreeRTOSConfig.h中。

低優先級數值代表低優先級。空閑任務(idle task)的優先級為0(tskIDLE_PRIORITY)。

FreeRTOS調度器確保處於最高優先級的就緒或運行態任務獲取處理器,換句話說,處於運行狀態的任務,只有其中的最高優先級任務才會運行。(此時運行的任務總是當前優先級最高的)

任務創建刪除

任務的組成

void vATaskFunction( void *pvParameters )
{
    while(1)
    {
        /*-- 任務代碼放在這里. --*/
    }

    /* 任務不可以調用return進行退出。在較新的FreeRTOS移植包中,如果
    試圖從一個任務中返回,將會調用configASSERT()(如果定義的話)。
    如果一個任務確實要退出函數,那么這個任務應調用vTaskDelete(NULL)
    函數,以便處理一些清理工作。*/
    vTaskDelete(NULL);
}

任務函數返回為void,參數只有一個void類型指針。void類型指針可以向任務傳遞任意類型信息。

任務函數決不應該返回,因此通常任務函數都是一個死循環。

任務由xTaskCreate()函數創建,由vTaskDelete()函數刪除。

所有的任務函數都應該是這樣。

創建任務

BaseType_t xTaskCreate(
    TaskFunction_t pvTaskCode, /* 任務函數入口*/
    const char * const pcName, /* 任務名稱*/
    unsigned short usStackDepth, /* 棧深度*/
    void *pvParameters,
    UBaseType_t uxPriority,
    TaskHandle_t * pvCreatedTask
);

描述:創建新的任務並加入任務就緒列表。

參數解析:

  • pvTaskCode:任務的入口地址
  • pcName:任務的名字,超度不超過configMAX_TASK_NAME_LEN
  • usStackDepth:任務堆棧大小。以字(word)為單位(在32為架構處理器,byte * 4 = byte * CPU地址線 / 8
  • pvParameters:任務參數
  • uxPriority:任務優先級
  • pvCreatedTask:任務的句柄

句柄將在API 調用中對該創建出來的任務進行引用,比如改變任務優先級,或者刪除任務。

如果應用程序中不會用到這個任務的句柄,則pxCreatedTask 可以被設為NULL。

返回值:

成功返回pdPASS,失敗返回錯誤碼(例如errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY)。

如果使用FreeRTOS-MPU(在官方下載包中,為Cortex-M3內核寫了兩個移植方案,一個是普通的FreeRTOS移植層,還有一個是FreeRTOS-MPU移植層。后者包含完整的內存保護),那么推薦使用函數xTaskCreateRestricted()來代替xTaskCreate()。在使用FreeRTOS-MPU的情況下,使用xTaskCreate()函數可以創建運行在特權模式或用戶模式(見下面對函數參數uxPriority的描述)的任務。當運行在特權模式下,任務可以訪問整個內存映射;當處於用戶模式下,任務僅能訪問自己的堆棧。
————————————————
版權聲明:本文為CSDN博主「zhzht19861011」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/zhzht19861011/article/details/50371956

刪除任務

void vTaskDelete( TaskHandle_t xTaskToDelete )

描述:從RTOS內核管理器中刪除一個任務。任務刪除后將會從就緒、阻塞、暫停和事件列表中移除。

在文件FreeRTOSConfig.h中,必須定義宏INCLUDE_vTaskDelete 為1,本函數才有效。

參數解析:

  • xTaskToDelete:被刪除任務的句柄。為NULL表示刪除當前任務。

例子

一個最普通的例子,帶有參數。

/* 創建任務. */
TaskHandle_t demoTaskHandler;

void demoTask( void * pvParameters )
{
    int type;
    type = *((int *)pvParameters);
    while(1)
    {
        printf("demo Task with arg %d\r\n", type);
        
        vTaskDelay(pdMS_TO_TICKS(1000));
    }

    vTaskDelete(NULL);
}

/* 創建任務函數 */
void tasksCreate(void)
{
    int parm = 0x5c;
    xTaskCreate(demoTask, 
                "demo",
                128,
                &parm, 
                1,
                &demoTaskHandler );

    /* 使用句柄刪除任務. */
    if( demoTaskHandler !=NULL )
    {
        vTaskDelete( demoTaskHandler );
    }
}

任務控制

FreeRTOS任務控制API函數主要實現任務延時、任務掛起、解除任務掛起、任務優先級獲取和設置等功能。

延遲

FreeRTOS提供了兩個系統延時函數:相對延時函數vTaskDelay()和絕對延時函數vTaskDelayUntil()。

  • 相對延時是指每次延時都是從任務執行函數vTaskDelay()開始,延時指定的時間結束;
  • 絕對延時是指每隔指定的時間,執行一次調用vTaskDelayUntil()函數的任務。

首先,我們需要明確,任務以固定的頻率執行。

對於相對延時,如果任務不是最高優先級,則任務執行周期更不可測,但問題不大,我們本來也不會使用它作為精確延時;

對於絕對延時函數,如果任務不是最高優先級,則仍然能周期性的將任務解除阻塞,但是解除阻塞的任務不一定能獲得CPU權限,因此任務主體代碼也不會總是精確周期性執行。

如果要想精確周期性執行某個任務,可以使用系統節拍回調函數vApplicationTickHook(),它在系統節拍中斷服務函數中被調用,如果需要使用這個回調函數,那么其中的代碼必須簡潔。

相對延時

 void vTaskDelay( const TickType_t xTicksToDelay )

描述:使任務會進入阻塞狀態。

在文件FreeRTOSConfig.h中,宏INCLUDE_vTaskDelay 必須設置成1,此函數才能有效。

參數解析:

xTicksToDelay:持續時間,單位是系統節拍時鍾周期。

常量portTICK_RATE_MS 用來輔助計算真實時間,此值是系統節拍時鍾中斷的周期,單位是毫秒。

例子:

void vTaskA( voidvoid * pvParameters )    
{    
    /* 
    	阻塞500ms. 
    	注:宏pdMS_TO_TICKS用於將毫秒轉成節拍數,FreeRTOS V8.1.0及以上版本才有這個宏, 
    	如果使用低版本,可以使用 500 / portTICK_RATE_MS 
    */    
    const portTickType xDelay = pdMS_TO_TICKS(500);
    // const portTickType xDelay = 500 / portTICK_RATE_MS;

    for( ;; )    
    {    
        //  ...  
        //  這里為任務主體代碼  
        //  ...  

        /* 調用系統延時函數,阻塞500ms */  
        vTaskDelay( xDelay );    
    }    
} 

說明:

假設有2個任務A、B,但A的優先級最高,還配置了中斷。

任務A每次延時都是從調用延時函數vTaskDelay()開始算起的,延時是相對於這一時刻開始的,所以叫做相對延時函數。

如果執行任務A的過程中(調用vTaskDelay之前)發生中斷,那么中斷回來以后,執行任務A執行的周期就會變長(因為存在中斷的開銷),從而影響任務下一次執行的時間。

因為調用vTaskDelay()到任務解除阻塞的時間不總是固定的並且該任務下一次調用vTaskDelay()函數的時間也不總是固定的(兩次執行同一任務的時間間隔本身就不固定,中斷或高優先級任務搶占也可能會改變每一次執行時間)

所以使用相對延時函數vTaskDelay(),不能准確地周期性的執行任務。

vTaskDelay()指定的延時時間是從調用vTaskDelay()后開始計算的相對時間

絕對延時

周期性任務可以使用絕對延遲以確保自身恆定的頻率執行。

void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, 
                     const TickType_t xTimeIncrement )

描述:任務延時一個指定的時間,每當時間到達,則解除任務阻塞。

在文件FreeRTOSConfig.h中,宏INCLUDE_vTaskDelayUntil 必須設置成1,此函數才有效。

參數解析:

  • pxPreviousWakeTime:指針,指向一個變量,該變量保存任務最后一次解除阻塞的時間。

一般來說,調用函數之前,該變量必須初始化為當前時間。

之后這個變量會在vTaskDelayUntil()函數內自動更新。

  • xTimeIncrement:周期循環時間。當時間等於(*pxPreviousWakeTime + xTimeIncrement)時,任務解除阻塞。

注意:

  • 當調用vTaskSuspendAll()函數掛起RTOS調度器時,不可以使用此函數。
  • 為了確保任務固定時間運行,應該先調用vTaskDelayUntil再執行任務的主體內容。
  • 如果指定的喚醒時間已經達到,vTaskDelayUntil()立刻返回(不會有阻塞)。
  • (可選的)使用vTaskDelayUntil()周期性執行的任務,無論任何原因(比如,任務臨時進入掛起狀態)停止了周期性執行,使得任務少運行了一個或多個執行周期,那么需要重新計算所需要的喚醒時間。這可以通過傳遞給函數的指針參數pxPreviousWake指向的值與當前系統時鍾計數值比較來檢測。

例子:

void vTaskB( voidvoid * pvParameters )    
{    
    static portTickType xLastWakeTime;    
    const portTickType xFrequency = pdMS_TO_TICKS(500);    

    // 使用當前時間初始化變量xLastWakeTime ,注意這和vTaskDelay()函數不同   
    xLastWakeTime = xTaskGetTickCount();    

    for( ;; )    
    {    
        /* 調用系統延時函數,周期性阻塞500ms */          
        vTaskDelayUntil( &xLastWakeTime,xFrequency );    

        //  ...  
        //  這里為任務主體代碼,周期性執行.注意這和vTaskDelay()函數也不同  
        //  ...  

    }    
}

說明:

假設有2個任務A、B,但A的優先級最高,還配置了中斷。

當任務B獲取CPU使用權后,先調用系統延時函數vTaskDelayUntil()使任務進入阻塞狀態。任務B進入阻塞后,其它任務得以執行。FreeRTOS內核會周期性的檢查任務A的阻塞是否達到,如果阻塞時間達到,則將任務A設置為就緒狀態。由於任務B的優先級最高,會搶占CPU,接下來執行任務主體代碼。任務主體代碼執行完畢后,會繼續調用系統延時函數vTaskDelayUntil()使任務進入阻塞狀態,周而復始。

從調用函數vTaskDelayUntil()開始,每隔固定周期,任務B的主體代碼就會被執行一次,即使任務B在執行過程中發生中斷,也不會影響這個周期性,只是會縮短其它任務的執行時間!

所以vTaskDelayUntil被稱為絕對延時函數,可用於周期性的執行任務的主體代碼。

任務優先級

獲取任務優先級

UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );

描述: 獲取指定任務的優先級。

在文件FreeRTOSConfig.h中,宏INCLUDE_uxTaskPriorityGet必須設置成1,此函數才有效。

參數解析:

xTask:指定的任務句柄。NULL表示獲取當前任務的優先級。

返回值:返回指定任務的優先級。

例子:

void vAFunction( void )
{
    xTaskHandle xHandle;
    // 創建任務,保存任務句柄
    xTaskCreate( vTaskCode, "NAME",STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
    // ...
    // 使用句柄獲取創建的任務的優先級
    if( uxTaskPriorityGet( xHandle ) !=tskIDLE_PRIORITY )
    {
        // 任務可以改變自己的優先級
    }
    // ...
    // 當前任務優先級比創建的任務優先級高?
    if( uxTaskPriorityGet( xHandle ) <uxTaskPriorityGet( NULL ) )
    {
        // 當前優先級較高
    }
}

設置任務優先級

  void vTaskPrioritySet( TaskHandle_txTask,
                   UBaseType_tuxNewPriority );

描述:設置指定任務的優先級。如果設置的優先級高於當前運行的任務,在函數返回前會進行一次上下文切換。

在文件FreeRTOSConfig.h中,宏 INCLUDE_vTaskPrioritySet 必須設置成1,此函數才有效。

參數解析:

  • xTask:要設置優先級任務的句柄,為NULL表示設置當前運行的任務。
  • uxNewPriority:要設置的新優先級。

例如:

void vAFunction( void )
{
    xTaskHandlexHandle;
    // 創建任務,保存任務句柄。
    xTaskCreate( vTaskCode, "NAME",STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
    // ...
    // 使用句柄來提高創建任務的優先級
    vTaskPrioritySet( xHandle,tskIDLE_PRIORITY + 1 );
    // ...
    // 使用NULL參數來提高當前任務的優先級,設置成和創建的任務相同。
    vTaskPrioritySet( NULL, tskIDLE_PRIORITY +1 );
}

任務掛起

void vTaskSuspend( TaskHandle_t xTaskToSuspend );

描述:掛起指定任務。被掛起的任務絕不會得到處理器時間,不管該任務具有什么優先級。

調用vTaskSuspend函數是不會累計的:即使多次調用vTaskSuspend ()函數將一個任務掛起,也只需調用一次vTaskResume ()函數就能使掛起的任務解除掛起狀態。

在文件FreeRTOSConfig.h中,宏INCLUDE_vTaskSuspend必須設置成1,此函數才有效。(下同vTaskResume)

參數解析:

xTaskToSuspend:要掛起的任務句柄。為NULL表示掛起當前任務。

例如:

void vAFunction( void )
{
    xTaskHandlexHandle;
    // 創建任務,保存任務句柄.
    xTaskCreate( vTaskCode, "NAME",STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
    // ...
    // 使用句柄掛起創建的任務.
    vTaskSuspend( xHandle );
    // ...
    // 任務不再運行,除非其它任務調用了vTaskResume(xHandle )
    //...
    // 掛起本任務.
    vTaskSuspend( NULL );
    // 除非另一個任務使用handle調用了vTaskResume,否則永遠不會執行到這里
}

恢復掛起的任務

void vTaskResume( TaskHandle_tx TaskToResume );

// 用於恢復一個掛起的任務,用在ISR中。
BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume );

描述:恢復掛起的任務。

在文件FreeRTOSConfig.h中,宏 INCLUDE_vTaskSuspend 必須置1,此函數才有效。(同上vTaskSuspend)

使用xTaskResumeFromISR之前, INCLUDE_xTaskResumeFromISR 必須設置成1,此函數才有效。

FromISR返回值:(由ISR確定是否需要上下文切換)

  • 返回pdTRU:恢復任務后需要上下文切換返回
  • 返回pdFALSE:什么都不用做。

參數解析:

xTaskToResume:要恢復運行的任務句柄。

例如:

xTaskHandlexHandle;               //注意這是一個全局變量

void vAFunction( void )
{
    xTaskHandle xHandle;
    // 創建任務,保存任務句柄
    xTaskCreate( vTaskCode, "NAME",STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
    // ...
    // 使用句柄掛起創建的任務
    vTaskSuspend( xHandle );
    // ...
    //任務不再運行,除非其它任務調用了vTaskResume( xHandle )    
    //...
    // 恢復掛起的任務.
    vTaskResume( xHandle );
    // 任務再一次得到處理器時間
    // 任務優先級與之前相同
}

void vTaskCode( void *pvParameters )
{
    for( ;; )
    {
        // ... 在這里執行一些其它功能

        // 掛起自己
        vTaskSuspend( NULL );

        //直到ISR恢復它之前,任務會一直掛起
    }
}

void vAnExampleISR( void )
{
    portBASE_TYPExYieldRequired;

    // 恢復被掛起的任務
    xYieldRequired = xTaskResumeFromISR(xHandle );

    if( xYieldRequired == pdTRUE )
    {
        // 我們應該進行一次上下文切換
        // 注:  如何做取決於你具體使用,可查看說明文檔和例程
        portYIELD_FROM_ISR();
    }
}

注意:

xTaskResumeFromISR()不可用於任務和中斷間的同步,如果中斷恰巧在任務被掛起之前到達,這就會導致一次中斷丟失(任務還沒有掛起,調用xTaskResumeFromISR()函數是沒有意義的,只能等下一次中斷)。這種情況下,可以使用信號量作為同步機制。

空閑任務

包括:空閑任務(idle task)和 空閑任務回調函數(Idle hook/callback function)。

在FreeRTOS中,回調函數 也稱為 鈎子函數。為了方便理解,以回調函數作為稱呼。

概述

空閑任務是啟動RTOS調度器時由內核自動創建的任務,這樣可以確保至少有一個任務在運行。空閑任務具有最低任務優先級,這樣如果有其它更高優先級的任務進入就緒態就可以立刻讓出CPU。

刪除任務后,空閑任務會釋放RTOS分配給被刪除任務的內存。因此,在應用中使用vTaskDelete()函數后確保空閑任務能獲得處理器時間就很重要了。

除此之外,空閑任務沒有其它有效功能,所以可以被合理的剝奪處理器時間,並且它的優先級也是最低的。

空閑任務回調函數

如果你想將在空閑的時候,運行某些任務,可以有兩種做法:

  • 創建一個具有空閑優先級的任務:簡單靈活但會帶來更多RAM開銷。

    需要配置: configUSE_PREEMPTION configIDLE_SHOULD_YIELD為 1 。

  • 實現一個空閑任務回調函數:因為FreeRTOS必須至少有一個任務處於就緒或運行狀態,因此回調函數不可以調用可能引起空閑任務阻塞的API函數(比如vTaskDelay()或者帶有超時事件的隊列或信號量函數)。

第一種做法比較簡單,我們介紹第二種方法,實現一個空閑任務回調函數。

在FreeRTOS文檔中,有這樣的一段話:

It is possible to add application specific functionality directly into the idle task through the use of an idle hook (or idle callback) function—a function that is called automatically by the idle task once per iteration of the idle task loop.

簡單來講:FreeRTOS允許我們配置一個自定義的函數,使其在每一個空閑任務周期自動調用一次。(因此,稱其為回調函數)

熟悉STM32-CubeMx配置的學者,想想hal庫的各種回調函數,肯定能夠理解這里的章節。

因為原理都是一樣的:處理完成以后調用回調函數。

當其他任務調用了帶阻塞性質的 vTaskDelay() API 函數,會產生大量的空閑時間——在這期間空閑任務會得到執行,因為其他的應用任務均處於阻塞態,最終,空閑任務回調函數也會執行。

通常空閑任務回調函數被用於:

  • 執行低優先級,后台或需要不停處理的功能代碼。
  • 測試出系統處理裕量(空閑任務只會在所有其它任務都不運行時才有機會執行,所以測量出空閑任務占用的處理時間就可以清楚的知道系統有多少富余的處理時間)。
  • 將處理器配置到低功耗模式——提供一種自動省電方法,使得在沒有任何應用功能需要處理的時候,系統自動進入省電模式。
  • 延后一些緊急命令的硬件操作。

例如,在某個高優先級的任務中需要發送串口數據。我們知道,控制某些硬件是需要額外的開銷的,為了不妨礙其他任務的運行,先將數據寫到緩沖區;再在空閑任務中將緩沖區的數據發出去。

使用步驟

1、在FreeRTOSConfig.h頭文件中設置configUSE_IDLE_HOOK為1;

#define configUSE_IDLE_HOOK 1

2、定義一個函數,名字和參數原型如下所示:

void vApplicationIdleHook( void );

3、實現vApplicationIdleHook,不使用阻塞、掛起等api。

空閑任務回調函數的實現限制

空閑任務回調函數必須遵從以下規則:

  1. 絕不能阻塞或掛起。空閑任務只會在其它任務都不運行時才會被執行(除非有應用任務共享空閑任務優先級)。以任何方式阻塞空閑任務都可能導致沒有任務能夠進入運行態!

  2. 如果應用程序用到了 vTaskDelete() API 函數,則空閑回調函數必須能夠盡快返回。因為在任務被刪除后,空閑任務負責回收內核資源。如果空閑任務一直運行在回調函數中,則無法進行回收工作。

任務標簽

在文件FreeRTOSConfig.h中,宏configUSE_APPLICATION_TASK_TAG必須設置為1,此組函數才有效。

一般用於應用程序,RTOS內核不會使用。

設置任務標簽值

void vTaskSetApplicationTaskTag(TaskHandle_t xTask,
                               TaskHookFunction_t pxTagValue );

描述:可以給每個任務分配一個標簽值。

一般用於應用程序,RTOS內核不會使用。

在文件FreeRTOSConfig.h中,宏 configUSE_APPLICATION_TASK_TAG 必須設置為1,此函數才有效。

參數解析:

  • xTask:任務句柄。NULL表示當前任務。
  • pxTagValue:要分配給任務的標簽值。這是一個TaskHookFunction_t類型的函數指針,但也可以給任務標簽分配任意的值。

注:TaskHookFunction_t原型定義:typedef BaseType_t (*TaskHookFunction_t)(void * )

獲取任務標簽值

TaskHookFunction_t xTaskGetApplicationTaskTag( TaskHandle_t xTask );

描述:返回分配給任務的標簽值。

參數解析:

xTask:任務句柄。NULL表示當前任務。

返回值: 返回指定任務的標簽值。

執行任務的應用回調函數

BaseType_t xTaskCallApplicationTaskHook(
    TaskHandle_txTask,
    void*pvParameter );

描述:因為可以為每個任務分配一個標簽值,當這個值是一個TaskHookFunction_t類型函數指針時,相當於應用程序向任務注冊了一個回調函數,而API函數xTaskCallApplicationTaskHook用來調用這個回調函數。

一般這個函數配合RTOS跟蹤回調宏使用

參數解析:

  • xTask:任務句柄。NULL表示當前任務。
  • pvParameter:作為參數傳遞給應用回調函數

例子:

/* 在這個例子中,給任務設置一個整形標簽值。例子中使用了RTOS跟蹤回調宏。*/
void vATask( void *pvParameters )
{
    /* 為自己的標簽分配一個整形值 */
    vTaskSetApplicationTaskTag( NULL, ( void * ) 1 );

    for( ;; )
    {
        /* 任務主體代碼 */
    }
}
/*****************************************************************************/

/*在這個任務中,給任務設置一個函數標簽值。首先定義一個回調函數,這個函數必須聲明為TaskHookFunction_t類型。 */
static BaseType_t prvExampleTaskHook( void * pvParameter )
{
    /* 這里為用戶定義代碼 –可能是記錄數據、更新任務狀態值等。*/

    return 0;
}

/* 將回調函數設置為任務的標簽值。 */
void vAnotherTask( void *pvParameters )
{
    /* 注冊回調函數*/
    vTaskSetApplicationTaskTag( NULL, prvExampleTaskHook );

    for( ;; )
    {
        /* 任務主體代碼 */
    }
}

FreeRTOSConfig.h添加下列代碼。

/* 配合RTOS跟蹤回調宏使用,每當任務切換時,會調用xTaskCallApplicationTaskHook 函數。 */
#define traceTASK_SWITCHED_OUT() xTaskCallApplicationTaskHook(pxCurrentTCB, 0)

線程本地存儲指針

線程本地存儲允許應用程序在任務的控制塊中存儲一些值,每個任務都有自己獨立的儲存空間。

比如,許多庫函數都包含一個叫做errno的全局變量。某些庫函數使用errno返回庫函數錯誤信息,應用程序檢查這個全局變量來確定發生了那些錯誤。在單線程程序中,將errno定義成全局變量是可以的,但是在多線程應用中,每個線程(任務)必須具有自己獨有的errno值,否則,一個任務可能會讀取到另一個任務的errno值。

FreeRTOS提供了一個靈活的機制,使得應用程序可以使用線程本地存儲指針來讀寫線程本地存儲。在文件FreeRTOSConfig.h中,宏configNUM_THREAD_LOCAL_STORAGE_POINTERS指定每個任務線程本地存儲指針數組的大小。API函數vTaskSetThreadLocalStoragePointer()用於向指針數組中寫入值,API函數pvTaskGetThreadLocalStoragePointer()用於從指針數組中讀取值。

設置線程本地存儲指針

void vTaskSetThreadLocalStoragePointer(TaskHandle_t xTaskToSet,
                                       BaseType_t xIndex,
                                       void* pvValue )

描述:從線程本地存儲指針數組中設置值。

參數解析:

  • xTaskToSet:任務句柄。NULL表示當前任務。
  • xIndex:寫入到線程本地存儲數組的索引號,線程本地存儲數組的大小由宏configNUM_THREAD_LOCAL_STORAGE_POINTERS設定,該宏在文件FreeRTOSConfig.h中。
  • pvValue:寫入到指定索引地址的數據值

讀取線程本地存儲指針

void* pvTaskGetThreadLocalStoragePointer(
    TaskHandle_txTaskToQuery,
    BaseType_txIndex );

描述:從線程本地存儲指針數組中讀取值。

參數解析:

  • xTaskToQuery:任務句柄。NULL表示當前任務。
  • xIndex:寫入到線程本地存儲數組的索引號,線程本篤存儲數組的大小由宏configNUM_THREAD_LOCAL_STORAGE_POINTERS設定,該宏在文件FreeRTOSConfig.h中。

返回值:返回一個指針,這個指針存儲在線程本地存儲指針數組中,數組索引由參數xIndex指定。

例子:

  • 存儲一個整形數
uint32_tulVariable;
 
/* 向當前任務的線程本地存儲數組下標為1的位置寫入一個指向32位常量值的指針。*/
vTaskSetThreadLocalStoragePointer(NULL, 1, ( void * ) 0x12345678 );
 
/*向當前任務的線程本地存儲數組下標為0的位置寫入一個指向32整形值的指針*/
ulVariable= ERROR_CODE;
vTaskSetThreadLocalStoragePointer(NULL, 0, ( void * ) ulVariable );
 
/*從當前任務的線程本地存儲數組下標為5的位置讀取出一個指針並賦值給32位整形變量。*/
ulVariable= ( uint32_t ) pvTaskGetThreadLocalStoragePointer( NULL, 5 );
  • 存儲結構體
typedefstruct
{
    uint32_t ulValue1;
    uint32_t ulValue2;
}xExampleStruct;
 
xExampleStruct*pxStruct;
 
/*為結構體分配內存*/
pxStruct= pvPortMalloc( sizeof( xExampleStruct ) );
 
/*為結構體成員賦值*/
pxStruct->ulValue1= 0;
pxStruct->ulValue2= 1;
 
/*向當前任務的線程本地存儲數組下標為0的位置寫入一個指向結構體變量的指針*/
vTaskSetThreadLocalStoragePointer(NULL, 0, ( void * ) pxStruct );
 
/*從當前任務的線程本地存儲數組下標為0的位置讀取出一個結構體指針*/
pxStruct= ( xExampleStruct * ) pvTaskGetThreadLocalStoragePointer( NULL, 0 );

超時狀態

任務因為等待某事件而進入阻塞狀態,通常情況下任務會設置一個等待超時周期。如果在等待事件超時,任務會退出阻塞狀態。

想象一個這樣的應用,某任務等待一個事件而進入阻塞狀態,但是事件遲遲不發生,超時后任務退出阻塞狀態繼續執行任務。假如任務等待的事件仍然沒有發生,則任務又會阻塞在該事件下。只要任務等待的事件一直不發生,這個任務進入阻塞然后超時退出阻塞,再進入阻塞的循環就會一直存在。

是不是可以設定一個總超時時間,只要總阻塞時間大於這個總超時時間,則可以結束這個任務或進行相應記錄?freeRTOS提供了兩個API函數來完成這個功能,這就是vTaskSetTimeOutState()和xTaskCheckForTimeOut()。

vTaskSetTimeOutState()函數用於設置初始條件,之后調用xTaskCheckForTimeOut()函數檢查任務總阻塞時間是否超過總超時時間,如果沒有超過,則調整剩余的超時時間計數器。

設置超時狀態

void vTaskSetTimeOutState( TimeOut_t *const pxTimeOut );

描述:設置初始條件

參數解析:

  • pxTimeOut:用來保存確定超時是否發生的必要信息的結構體。

超時檢測

BaseType_t xTaskCheckForTimeOut(TimeOut_t * const pxTimeOut,
                                 TickType_t* const pxTicksToWait );

描述:檢查任務總阻塞時間是否超過總超時時間

參數解析:

  • pxTimeOut:指向一個結構體的指針。該結構體保存確定超時是否發生的必要信息。需要先使用vTaskSetTimeOutState初始化該結構體。
  • pxTicksToWait:指向的變量保存總超時時間。

返回值:

  • pdTRUE:總超時發生
  • pdFALSE:總超時未發生

例子:

/* 函數用於從RX緩沖區中接收uxWantedBytes字節數據,RX緩沖區由UART中斷填充。如果RX緩沖區沒有足夠的數據,則任務進入阻塞狀態,直到RX緩沖區有足夠數據或者發生超時。如果超時后仍然沒有足夠的數據,則任務會再次進入阻塞狀態,xTaskCheckForTimeOut()函數用於重新計算總超時時間以確保總阻塞狀態時間不超過MAX_TIME_TO_WAIT。如果總阻塞狀態時間大於了總超時時間,則不管RX緩沖區是否有充足數據,都將這些數據讀出來。
 */
size_t xUART_Receive( uint8_t *pucBuffer, size_t uxWantedBytes )
{
    size_t uxReceived = 0;
    TickType_t xTicksToWait = MAX_TIME_TO_WAIT;
    TimeOut_t xTimeOut;

    /* 初始化結構體變量xTimeOut。*/
    vTaskSetTimeOutState( &xTimeOut );

    /* 無限循環,直到緩沖區包含足夠的數據或者阻塞超時發生。*/
    while( UART_bytes_in_rx_buffer(pxUARTInstance ) < uxWantedBytes )
    {
        /* RX緩沖區沒有足夠多的數據,表示任務已經進入過一次阻塞狀態。調用API函數xTaskCheckForTimeOut檢查總阻塞時間是否超過總超時時間,如果沒有,則調整剩余的總超時時間。*/
        if( xTaskCheckForTimeOut( &xTimeOut,&xTicksToWait ) != pdFALSE )
        {
            /* 如果總阻塞時間大於總超時時間,則退出這個循環 */
            break;
        }

        /* 在等待了xTicksToWait個系統節拍周期后,向接收中斷發出通知,需要更多數據。
*/
        ulTaskNotifyTake( pdTRUE, xTicksToWait );
    }

    /*從RX緩沖區讀取uxWantedBytes個字節並放到pucBuffer緩沖區。*/
    uxReceived = UART_read_from_receive_buffer(pxUARTInstance,  pucBuffer,  uxWantedBytes );

    return uxReceived;
}

附錄:任務和協程(Co-routines)

應用程序可以使用任務也可以使用協程,或者兩者混合使用,但是任務和協程使用不同的API函數,因此在任務和協程之間不能使用同一個隊列或信號量傳遞數據。

通常情況下,協程僅用在資源非常少的微處理器中,特別是RAM非常稀缺的情況下。

附錄:FreeRTOS MPU模塊

MPU是Cortex-M的選配件,拿STM32F1來說,STM32F10X_XL系列的芯片才具有這個MPU存儲保護單元,而其他STM32F1芯片沒有。

LPC17xx包括存儲器保護單元(MPU)。這允許整個內存映射(包括Flash、RAM和外圍設備)細分為若干區域,以及要分別分配給每個區域的訪問權限。
區域是地址范圍由起始地址和大小組成。
FreeRTOS MPU是一個FreeRTOS Cortex-M3端口,包括集成MPU支持。它允許額外的功能,並包括一個稍微擴展的API,但在其他方面與標准Cortex-M3端口兼容。

使用FreeRTOS MPU將始終:
保護內核免受任務的無效執行。
保護內核使用的數據不受任務的無效訪問。
保護Cortex-M3核心資源的配置,如SysTick定時器。
確保所有任務堆棧溢出一發生就被檢測到。
另外,在應用程序級別,可以確保任務獨立於自己的任務並使內存空間和外圍設備受到保護,不受意外修改。

FreeRTOS MPU通過隱藏MPU底層計算層面而提供一個簡單的針對MPU的接口,然而給一個不允許數據隨意訪問的環境進行應用編程是一個很大的挑戰
————————————————
版權聲明:本文為CSDN博主「weixin_39344546」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_39344546/article/details/104209929

附錄: 回調函數

在FreeRTOS中,回調函數(callback function) 也稱為 鈎子函數(hook function)。為了方便理解,以回調函數作為稱呼。

HOOK:鈎子、鈎住;在操作系統中可以視為處理機制的一部分。

FreeRTOS中支持一些回調函數,只需要在FreeRTOSConfig.h中進行配置即可。

可以在FreeRTOS源碼中,找到對應的宏。

  • configUSE_IDLE_HOOK:是否使用空閑任務回調函數。
  • configUSE_TICK_HOOK:是否使用TICK滴答回調函數。

使用 configUSE_TICK_HOOK 時需要 定義xTaskIncrementTick函數

提示:xTaskIncrementTick函數是在PendSV_Handler中斷函數中被調用的。因此,vApplicationTickHook()函數執行的時間必須很短才行。

  • configCHECK_FOR_STACK_OVERFLOW:是否定義棧溢出回調函數

這個配置比較重要,特別對於復雜的系統設計,代碼量比較大那種工程,使用該功能,可以幫你分析是否有內存越界的情況。

  • configUSE_MALLOC_FAILED_HOOK:是否定義內存分配失敗回調函數

創建任務、信號量、隊列等都需要耗費系統堆棧,如果我們對系統總共分配堆棧不夠多,在創建多個任務或隊列時容易分配失敗,這個時候就起到一個提示作用。

  • configUSE_DAEMON_TASK_STARTUP_HOOK:是否定義守護進程回調函數

還需要配置configUSE_TIMERS為1。void vApplicationDaemonTaskStartupHook( void );


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM