顯示具有 FreeRTOS 標籤的文章。 顯示所有文章
顯示具有 FreeRTOS 標籤的文章。 顯示所有文章

2015/01/05

FreeRTOS Low Power Support

1. Introduction


在 FreeRTOS 裡, Power Saving 相關的工作通常會放在 idle task 裡面作, 它的作法是限制periodic tick interrupt, 這樣可以減少 period task的耗電

實際作法是, 它停止 periodic tick interrupt, 然後在 restart periodic tick interrupt實調整 RTOS tick count

在 tick interrupt 停止的時候, microcontroller 就可以進 deep power saving state, 直到有interrupt發生

2. Idle Task


idle task 放在 task.c
static portTASK_FUNCTION( prvIdleTask, pvParameters )

裡面有 infinite for loop 不停地執行一些工作, 包括:

  1. 檢查有沒有 task 已經被刪除
  2. 檢查 preemption 相關的工作
  3. 呼叫 idle hook funtion
  4. Power Saving Task

其中有兩個 hook function 會因為 power saving 的機制受到影響

  • vApplicationIdleHook : 在 idle task 裡每次呼叫
  • vApplicationTickHook : 在 idle task 裡, 並且只有在 sytem tick 增加時才呼叫
這兩個 hook function 都是直接 hard code 實作, 預設是空的, 為了避免影響其它task, 這兩個hook function 裡面不可以擺可能會 blocking 的 task

3. Power Saving


底下是 idle task 裡的 power saving code:
/* This conditional compilation should use inequality to 0, not equality
to 1.  This is to ensure portSUPPRESS_TICKS_AND_SLEEP() is called when
user defined low power mode implementations require
configUSE_TICKLESS_IDLE to be set to a value other than 1. */
#if ( configUSE_TICKLESS_IDLE != 0 )
{
TickType_t xExpectedIdleTime;

 /* It is not desirable to suspend then resume the scheduler on
 each iteration of the idle task.  Therefore, a preliminary
 test of the expected idle time is performed without the
 scheduler suspended.  The result here is not necessarily
 valid. */
 xExpectedIdleTime = prvGetExpectedIdleTime();

 if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
 {
  vTaskSuspendAll();
  {
   /* Now the scheduler is suspended, the expected idle
   time can be sampled again, and this time its value can
   be used. */
   configASSERT( xNextTaskUnblockTime >= xTickCount );
   xExpectedIdleTime = prvGetExpectedIdleTime();

   if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )
   {
    traceLOW_POWER_IDLE_BEGIN();
    portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
    traceLOW_POWER_IDLE_END();
   }
   else
   {
    mtCOVERAGE_TEST_MARKER();
   }
  }
  ( void ) xTaskResumeAll();
 }
 else
 {
  mtCOVERAGE_TEST_MARKER();
 }
}
#endif /* configUSE_TICKLESS_IDLE */

它第一步先計算預計會idle的時間 xExpectedIdleTime, 如果這個時間大於 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 才真的 sleep, 不然的話, idle時間太短又跑去sleep會造成額外的 effort

接著 vTaskSuspendAll(), 這裡將 uxSchedulerSuspended 加 1, 於是task scheduler就被停止

然後重新計算 idle time, 接著進入sleep
traceLOW_POWER_IDLE_BEGIN();
portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );
traceLOW_POWER_IDLE_END();

最後 xTaskResumeAll(), 將 uxSchedulerSuspended 減 1, 將所有應該要切入 ready state 的 task 喚醒, 最後將 system tick 加回來

4. portSUPPRESS_TICKS_AND_SLEEP


portSUPPRESS_TICKS_AND_SLEEP() 這個macro根據不同的MCU而有不同的實作, 在GCC, IAR, Keil都有default的實作

通常 SysTick 跟 Cortex-M Microcontrollers Clock 是一樣的 frequency, 這種情況下, 這兩個值是一樣的, configSYSTICK_CLOCK_HZ 以及 configCPU_CLOCK_HZ

如果 SysTick 比 Core Clock還慢的話, 就要定義 configSYSTICK_CLOCK_HZ 這個值

其中sleep的機制會呼叫到Cortex-M的 function call
/* Sleep until something happens.  configPRE_SLEEP_PROCESSING() can
set its parameter to 0 to indicate that its implementation contains
its own wait for interrupt or wait for event instruction, and so wfi
should not be executed again.  However, the original expected idle
time variable must remain unmodified, so a copy is taken. */
xModifiableIdleTime = xExpectedIdleTime;
configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
if( xModifiableIdleTime > 0 )
{
 __DSB();
 __WFI();
 __ISB();
}
configPOST_SLEEP_PROCESSING( xExpectedIdleTime );

其中會有個 pre & post processing, 中間sleep的部份取決於不同MCU而有不同的code

DSP 表示 Data Synchronization Barrier, 它將 processor目前做到一半的工作做完
WFI 表示 Wait For Interrupt, 它讓 processor 進 suspend, 直到底下其中一個發生:

  • non-masked interrupt 發生
  • 被 PRIMASK mask 的 interrupt 被 pending
  • Debug Entery 的 request

ISB 表示 Instruction Synchronization Barrier, 它將 processor 的pipe line清空, 這樣才不會拿到之前未執行的 code 來執行




2014/11/25

FreeRTOS Interrupt Management

1. Introduction


關於處理event, 有幾個要考慮的點

  • 要怎麼偵測event? 通常會用 interrupt, 但也可以用polling的方式
  • 如果使用 interrupt, 要在 ISR (Interrupt service routine) 裡面處裡多少事情? 相對地要在ISR外面處理多少事情? 是否 ISR 裡面的事情要愈少愈好?
  • ISR 裡的 code 要如何跟外面的 code 溝通? 尤其是 asynchronous 的code
FreeRTOS 沒有規定 event handle要怎麼實作, 但提供了一些interrupt API
在這些API裡, 只有 FromISR 結尾的 function 才可以在 ISR 裡呼叫, 以及以 FROM_ISR 結尾的 macro 可以在ISR裡使用

2. Binary Semaphores used for Synchronization


整個概念如下圖, 當 interrupt 發生時, ISR 解開 semaphore 並讓 handler task 變成 ready state, 然後 ISR return, 這時 handler task 有最高的 priority 並執行, 當它執行完, 等下一個 semaphore, 進入block state, 並讓低 priority 的 task 執行



在使用 semaphore 時, 分成 take & give, 實作上, 它就像是長度為1的queue, 它一樣要設定 block time, 相關的限制也相同

2.1 Create Semaphore


void vSemaphoreCreateBinary( xSemaphoreHandle xSemaphore );

xSemaphoreHandle 是 semaphore 的型態, 在使用它之前必需先 create

2.2 Take Semaphore


portBASE_TYPE xSemaphoreTake( xSemaphoreHandle xSemaphore, portTickType xTicksToWait );

如果把 xTicksToWait 設成 portMAX_DELAY, 表示要永遠等下去

xSemaphoreTake 不能在 ISR 裡面使用

可能的回傳值 :
  • pdPASS : 成功拿到 semaphore, 如果 block time > 0, 表示在 timeout 前拿到 semaphore
  • pdFALSE : 沒能在 block time timeout 前拿到 semaphore

2.3 Give Semaphore in ISR


portBASE_TYPE xSemaphoreGiveFromISR( xSemaphoreHandle xSemaphore,
                                     portBASE_TYPE *pxHigherPriorityTaskWoken
                                     );

如果有 high priority task 在等這個 semaphore, 然後呼叫 xSemaphoreGiveFromISR 造成目前的 task 被 scheduler switch out, 那麼會把 pxHigherPriorityTaskWoken 這個 pointer 裡面的值設成 pdTRUE, 表示要處裡目前要被 switch out 的 task 的 context switch

可能的回傳值 :

  • pdPASS : 成功
  • pdFALSE : 如果目前的 semaphore 已經被 give 了, 會回傳失敗

2.4 sample code


static void vPeriodicTask( void *pvParameters )
{
    for( ;; )
    {
        /* 等 500ms 準備送出 sofeware interrupt */
        vTaskDelay( 500 / portTICK_RATE_MS );

        /* 送出 interrupt, 前後夾 log */
        vPrintString( "Periodic task - About to generate an interrupt.\r\n" );
        __asm{ int 0x82 } /* This line generates the interrupt. */
        vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
    }
}

static void vHandlerTask( void *pvParameters )
{
    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ;; )
    {
        /* take semaphore */
        xSemaphoreTake( xBinarySemaphore, portMAX_DELAY );
        vPrintString( "Handler task - Processing event.\r\n" );
    }
}

static void __interrupt __far vExampleInterruptHandler( void )
{
    static portBASE_TYPE xHigherPriorityTaskWoken;

 /* 先設成 false, 這樣如果有 task 被switch out時會設成ture, 才能分辨 */
    xHigherPriorityTaskWoken = pdFALSE;

    /* give semaphore */
    xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken );
    if( xHigherPriorityTaskWoken == pdTRUE )
    {
        /* 做 context switch, 其中 portSWITCH_CONTEXT() 是 Open Watcom Dos 的port,
           其它平台可能有不同的 port */
        portSWITCH_CONTEXT();
    }
}

int main( void )
{
    /* 建 semaphore */
    vSemaphoreCreateBinary( xBinarySemaphore );

    /* 設定 interrupt handler */
    _dos_setvect( 0x82, vExampleInterruptHandler );

    if( xBinarySemaphore != NULL )
    {
        /* 建立要take semaphore 的 task, 它的 priority 是 3 */
        xTaskCreate( vHandlerTask, "Handler", 1000, NULL, 3, NULL );

        /* 建立定期發出 software interrupt 的 task */
        xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, 1, NULL );

        vTaskStartScheduler();
    }
    /* it should never reach here */
    for( ;; );
}

3. Counting semaphores


使用 binary semaphore 的缺點是, 在 ISR unblock binary semaphore之後, 並且讓handle task執行時, 如果這時又再有一個 interrupt, 這時候 handle task也只會做最初的那次, counting semaphore 可以解決這種情況

可以將 counting semaphore 想成是長度為n的queue, 只是我們不在乎裡面的data

通常 counting semaphore 用在兩個用途: 記錄次數, 與管理資源

底下是它的 api

xSemaphoreHandle xSemaphoreCreateCounting( unsigned portBASE_TYPE uxMaxCount,
                                           unsigned portBASE_TYPE uxInitialCount );

其中 uxMaxCount 是最大值, uxInitialCount是初始值, 成功的話回傳 semaphore handle


4. Use queue in ISR


在 ISR 裡使用 queue, 有相對應的 function:

portBASE_TYPE xQueueSendToFrontFromISR( xQueueHandle xQueue,
                                        void *pvItemToQueue
                                        portBASE_TYPE *pxHigherPriorityTaskWoken
                                        );

portBASE_TYPE xQueueSendToBackFromISR( xQueueHandle xQueue,
                                       void *pvItemToQueue
                                       portBASE_TYPE *pxHigherPriorityTaskWoken
                                       );

BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue,
                                 void *pvBuffer,
                                 BaseType_t *pxHigherPriorityTaskWoken
                                 );

這些其實就是 xQueueSendToFront(), xQueueSendToBack() 以及 xQueueReceive()

4.1 Sample code


static void vIntegerGenerator( void *pvParameters )
{
    portTickType xLastExecutionTime;
    unsigned portLONG ulValueToSend = 0;
    int i;

    /* Initialize the variable used by the call to vTaskDelayUntil(). */
    xLastExecutionTime = xTaskGetTickCount();
    for( ;; )
    {
        /* 每 200ms block */
        vTaskDelayUntil( &xLastExecutionTime, 200 / portTICK_RATE_MS );

        /* 寫入queue 5次, 這些值將從ISR讀出 */
        for( i = 0; i < 5; i++ )
        {
            xQueueSendToBack( xIntegerQueue, &ulValueToSend, 0 );
            ulValueToSend++;
        }
        /* 觸發 software interrupt 0x82 */
        vPrintString( "Generator task - About to generate an interrupt.\r\n" );
        __asm{ int 0x82 } /* This line generates the interrupt. */
        vPrintString( "Generator task - Interrupt generated.\r\n\r\n\r\n" );
    }
}

static void __interrupt __far vExampleInterruptHandler( void )
{
    static portBASE_TYPE xHigherPriorityTaskWoken;
    static unsigned long ulReceivedNumber;

    /* 宣告成 static const 確保它們不是 ISR 產生的 */
    static const char *pcStrings[] = {"St0\r\n", "St1\r\n", "St2\r\n", "St3\r\n"};
    xHigherPriorityTaskWoken = pdFALSE;

    /* Loop until the queue is empty. */
    while( xQueueReceiveFromISR( xIntegerQueue, &ulReceivedNumber, &xHigherPriorityTaskWoken ) != errQUEUE_EMPTY )
    {
        /* Mask最後兩個bit, 然後把對應的String放進queue裡 */
        ulReceivedNumber &= 0x03;
        xQueueSendToBackFromISR( xStringQueue, &pcStrings[ ulReceivedNumber ], &xHigherPriorityTaskWoken );
    }

    /* 如果寫入 queue 造成 high priority task 變成 ready, 就要做context switch */
    if( xHigherPriorityTaskWoken == pdTRUE )
    {
        /* NOTE: 這個context switch 是給 Open Watcom DOS port 使用, 其它平台有不同的port */
        portSWITCH_CONTEXT();
    }
}

static void vStringPrinter( void *pvParameters )
{
    char *pcString;
    for( ;; )
    {
        /* Block on the queue to wait for data to arrive. */
        xQueueReceive( xStringQueue, &pcString, portMAX_DELAY );
        vPrintString( pcString );
    }
}

int main( void )
{
    /* 建queue */
    xIntegerQueue = xQueueCreate( 10, sizeof( unsigned long ) );
    xStringQueue = xQueueCreate( 10, sizeof( char * ) );

    /* 設定 interrupt handler. */
    _dos_setvect( 0x82, vExampleInterruptHandler );

    /* low priority period task */
    xTaskCreate( vIntegerGenerator, "IntGen", 1000, NULL, 1, NULL );

    /* High priority interrupt handler */
    xTaskCreate( vStringPrinter, "String", 1000, NULL, 2, NULL );

    vTaskStartScheduler();
    for( ;; );
}







2014/09/24

FreeRTOS queue management

1. Introduction


queue 用於 task 之間的溝通, 每個queue能放有限的item
item的大小稱為size, item的數量稱為length
當 queue 被建立時, length & size都會設定好

通常 queue 使用 FIFO, 資料從tail寫入, 從head讀出, (但也有可能從head寫入)
寫入的時候, 會copy data到queue裡面
讀出的時候, 會copy data出來, 並且queue的那一份會刪掉

queue 本身就像個物件, 會自行管理queue的相關功能,
所以多個task可以同時寫入同一個queue, 也可以多個task讀出同一個queue
queue通常有多個writer, 但不常有多個reader
( 這裡的writer指的是嘗試寫入queue的接口, reader指的是嘗試從queue讀出的接口 )

每當 task 嘗試從 queue裡讀出data, 需要設定 block time, 這是因為如果 queue 是空的, task就會進入block state, 直到 bloke time timeout 而回到 ready state, 或者是有其它 task / interrupt 寫入data到queue裡, 這樣也會讓 task 回到 ready state

如果 queue 有多個 reader, 並且有多個 task 都在等空的 queue, 那麼當有新的 data 進來, 只會有一個 task變成 ready state, queue會選出priority最高的task, 如果有多個task有一樣的priority, 那麼會選等最久的

當 task 嘗試寫入 data 到 queue裡, 也需要設定 block time, 這是因為如果 queue 滿了, task就會進入 block state, 直到 block time timeout 而回到 ready state, 或者這期間 queue 騰出空間而讓task回到 ready state

如果 queue 有多個 writer, 並且有多個 task 嘗試寫入滿的 queue, 那麼當空間騰出來時, priority最高的task會變成ready state, 如果有多個 task 有一樣的 priority, 那麼會選等最久的

2. using a queue


2.1 create a queue


xQueueHandle xQueueCreate( unsigned portBASE_TYPE uxQueueLength,
                           unsigned portBASE_TYPE uxItemSize
                           );

其中

  • uxQueueLength : max queue length
  • uxItemSize : item size, 單位是 byte

如果 RAM size 不夠,就會回傳 NULL,
如果成功, 回傳 queue handle

2.2 send data to queue


portBASE_TYPE xQueueSendToBack( xQueueHandle xQueue,
                                const void * pvItemToQueue,
                                portTickType xTicksToWait
                                );

xQueueSendToBack() 會把 item 放到 queue 的後面 (tail), xQueueSend() 跟 xQueueSendToBack() 是同樣的function

portBASE_TYPE xQueueSendToFront( xQueueHandle xQueue,
                                 const void * pvItemToQueue,
                                 portTickType xTicksToWait
                                 );

xQueueSendToFromt 把 data 送到 queue 的前端 (head)

其中它的參數 :

  • xTicksToWait : 如果想讓 send 一直等下去, 可以把 xTicksToWait 設成 portMAX_DELAY, 如果不想等, 就把 xTicksToWait 設成 0

可能的回傳值 :

  • pdPASS : 表示成功寫入, 即使task是在block time timeout之前寫入也算是成功寫入
  • errQUEUE_FULL : 失敗, 如果 xTicksToWait > 0, 那麼表示在 block time timeout之前都沒辦法寫入

2.3 receive data from queue


portBASE_TYPE xQueueReceive( xQueueHandle xQueue,
                             const void * pvBuffer,
                             portTickType xTicksToWait
                             );
xQueueReceive 從 queue head 讀一個 item, 並把 queue 裡的 item 刪掉

portBASE_TYPE xQueuePeek( xQueueHandle xQueue,
                          const void * pvBuffer,
                          portTickType xTicksToWait
                          );

xQueuePeek 從 queue head 讀一個item, 但不會刪掉 item

可能的回傳值 :

  • pdPASS : 表示成功讀出, 即使 task 在 block time timeout 之前讀出也算成功讀出
  • errQUEUE_EMPTY : 失敗, 如果 xTicksToWait > 0, 表示在 block time timeout 之前沒辦法讀出

2.4 query queue


unsigned portBASE_TYPE uxQueueMessagesWaiting( xQueueHandle xQueue );

uxQueueMessagesWaiting 可以拿到目前 queue 裡的 items 數量

要注意的是, 這個不能在interrupt裡面使用,
interrupt應該使用 uxQueueMessagesWaitingFromISR()

3. sample code


/* 宣告一個queue */
xQueueHandle xQueue;

static void vSenderTask( void *pvParameters )
{
    long lValueToSend;
    portBASE_TYPE xStatus;

    /* 我們把要寫入 queue 的資料從 task 的 parameter 帶入, 型態為 long */
    lValueToSend = ( long ) pvParameters;

    for( ;; )
    {
        /* 把data寫入queue, 第1個參數是要寫入的queue,
           第2個參數是要寫入的item pointer, 第3個參數是block time */
        xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );
        if( xStatus != pdPASS )
        {
            vPrintString( "Could not send to the queue.\r\n" );
        }

        /* taskYIELD() 通知 scheduler switch 到別的task */
        taskYIELD();
    }
}

static void vReceiverTask( void *pvParameters )
{
    long lReceivedValue;
    portBASE_TYPE xStatus;
    const portTickType xTicksToWait = 100 / portTICK_RATE_MS;

    for( ;; )
    {
        /* 檢查 queue 裡面的 item 數量 */
        if( uxQueueMessagesWaiting( xQueue ) != 0 )
        {
            vPrintString( "Queue should have been empty!\r\n" );
        }

        /* 從 queue 裡讀出data, 第1個參數是要讀出data的queue,
           第2個參數是放 data 的 pointer, 第3個參數是 block time */
        xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );
        if( xStatus == pdPASS )
        {
            /* 成功讀出 data, 把它印出來 */
            vPrintStringAndNumber( "Received = ", lReceivedValue );
        }
        else
        {
            /* 100ms內沒有讀出data */
            vPrintString( "Could not receive from the queue.\r\n" );
        }
    }
}

int main( void )
{
    /* 建 queue, 設定成長度為5個item, item大小為 sizeof(long) */
    xQueue = xQueueCreate( 5, sizeof( long ) );
    if( xQueue != NULL )
    {
        /* 建2個 task, 分別對 queue 寫入 data, 
           data 來自於 task 的 parameter, 也就是100和200 */
        xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
        xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );

        /* 建1個 task 從 queue 讀出 data, 它的priority比較高, 
           但是在 xQueueReceive(), 如果 queue 裡沒 data, 它就會進 block state */
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );

        vTaskStartScheduler();
    }
    else
    {
        /* 建 queue 失敗 */
    }

    /* we should never reach here */
    for( ;; );
}

4. store compound data


有時候對 receiver task 來說, 它需要知道 sender 是誰, 我們可以把 data type 設計成 struct, 並且裡面加上 sender information

另一種情況是, 如果我們要在 queue 裡放大的 data, 改成用指向該 data 的 pointer 會比較好, 這樣可以避免在寫入和讀出時 copy 大量的 data, 但是要注意 memory 的 allocation & release,




2014/09/22

FreeRTOS task management

1. Task function


在 FreeRTOS 裡, 每條thread都稱作 task, 實作成C function, 回傳值是void, 並且parameter為void pointer, 像這樣
void ATaskFunction( void *pvParameters );

每個task通常會一直執行不會停 (所以應該有個loop在裡面), FreeRTOS 不准有"return", 也不准執行到最後一行

如果我們不需要某個task, 我們應該刪除這個task

底下是 task function可能的實作

void ATaskFunction( void *pvParameters )
{
    /* 如同一般的function來宣告變數 */
    int iVariableExample = 0;

    /* 實作一個 infinite loop */
    for( ;; )
    {
        /* task實際上要做的事 */
    }
    /* 如果task在loop中break出來, 那麼task必需在到function end之前刪除,
       比如說用 vTaskDelete( NULL ), 裡面的NULL表示是刪目前的task */
    vTaskDelete( NULL );
}

task function 可以拿來建多個task, 每個task都是獨立的instance, 有自己的stack

2. Task state


我們先簡單的把task分成Running / Not Running
當 task 從 Not Running 變成 Running, 稱作 task in
相反地稱作 task out

3. Create Task


建task用到 xTaskCreate() API, 它的prototype如下

portBASE_TYPE xTaskCreate( pdTASK_CODE pvTaskCode,
                           const signed portCHAR * const pcName,
                           unsigned portSHORT usStackDepth,
                           void *pvParameters,
                           unsigned portBASE_TYPE uxPriority,
                           xTaskHandle *pxCreatedTask
);
( task.h )

底下是參數的說明:

  • pvTaskCode : 放task function的function pointer
  • pcName : task的名字, 在FreeRTOS不會用到它, 這只是用於debug用途, 名字長度限制為configMAX_TASK_NAME_LEN
  • usStackDepth : 每個 task 被放進 stack 的時候, usStackDepth表示它需要的最大stack space, usStackDepth以byte為單位, 比如說, stack size = 32 bit, usStackDepth = 100, 則會用到100*4=400 bytes stack space, 如果task只是設計成idle task, 那麼可以用這個值 configMINIMAL_STACK_SIZE
  • pvParameters : task function的參數, 型態為 (void *)
  • uxPriority : task 的優先權, 0最低, (configMAX_PRIORITIES – 1)最高
  • pxCreatedTask : 這個task的reference, 用於動態調整這個task的priority, 或是刪掉這個task

可能的回傳值 :

  • pdTRUE : 表示成功
  • errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY : 表示RAM不夠了
底下是sample code:

void vTask1( void *pvParameters )
{
    const char *pcTaskName = "Task 1 is running\r\n";
    volatile unsigned long ul;

    /* As per most tasks, this task is implemented in an infinite loop. */
    for( ;; )
    {
        /* Print out the name of this task. */
        vPrintString( pcTaskName );

        /* Delay for a period. */
        for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
        {
            /* busy delay */
        }
    }
}

void vTask2( void *pvParameters )
{
    const char *pcTaskName = "Task 2 is running\r\n";
    volatile unsigned long ul;

    /* As per most tasks, this task is implemented in an infinite loop. */
    for( ;; )
    {
        /* Print out the name of this task. */
        vPrintString( pcTaskName );

        /* Delay for a period. */
        for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
        {
            /* busy delay */
        }
    }
}

int main( void )
{
    /* 建tasks1, 通常需要檢查回傳值, 這裡先略過 */
    xTaskCreate( vTask1,   /* task function pointer, 通常用function name就可以 */
                 "Task 1", /* Task name */
                 1000,     /* Stack depth - 一般的例子不會用到這麼多 */
                 NULL,     /* 不帶parameter */
                 1,        /* priority */
                 NULL );   /* 不需要task handle */

    /* task2跟task1是差不多的 */
    xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );

    /* 啟動scheduler, 讓tasks開始執行 */
    vTaskStartScheduler();

    /* 正常來說不會執行到這邊, 因為scheduler會執行tasks,
       執行到這邊可能是 heap memory 不夠 */
    for( ;; );
}

實際執行結果會看到task1 & task2兩個不停地task in/out

前個例子是把兩個task在main裡面create, 我們也可以把task放在task裡面create

void vTask1( void *pvParameters )
{
    const char *pcTaskName = "Task 1 is running\r\n";
    volatile unsigned long ul;

    /* 執行到這邊表示Scheduler已經開始執行task1 */
    xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, NULL );

    for( ;; )
    {
        /* Print out the name of this task. */
        vPrintString( pcTaskName );

        /* Delay for a period. */
        for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
        {
            /* busy delay */
        }
    }
}

因為task1 & task2很像, 所以改寫成一個就好

void vTaskFunction( void *pvParameters )
{
    char *pcTaskName;
    volatile unsigned long ul;

    /* 把要印出來的字串經由parameter帶進來, 再轉成 (char *) */
    pcTaskName = ( char * ) pvParameters;

    for( ;; )
    {
        /* 把perameter帶進來的字串印出來 */
        vPrintString( pcTaskName );

        /* Delay for a period. */
        for( ul = 0; ul < mainDELAY_LOOP_COUNT; ul++ )
        {
            /* busy delay*/
        }
    }
}

/* 要帶給task的字串 */
static const char *pcTextForTask1 = “Task 1 is running\r\n”;
static const char *pcTextForTask2 = “Task 2 is running\t\n”;

int main( void )
{
    /* Create task 1 */
    xTaskCreate( vTaskFunction,         /* function pointer */
                 "Task 1",              /* task name */
                 1000,                  /* stack depth */
                 (void*)pcTextForTask1, /* 參數, 把要印的字串轉成(void *) */
                 1,                     /* priority */
                 NULL );                /* task handle */

    /* Create task2, 參數不一樣 */
    xTaskCreate( vTaskFunction, "Task 2", 1000, (void*)pcTextForTask2, 1, NULL );

    /* Start scheduler */
    vTaskStartScheduler();

    for( ;; );
}

4. Task priority


priority可以動態的改, 最高不會超過configMAX_PRIORITIES (defined in FreeRTOSConfig.h)
我們可以改configMAX_PRIORITIES, 但改的愈高也就耗掉更多的RAM, 所以通常會保留default值

為了選出下一個要執行的task, scheduler應該要在task執行的片段最後面執行
而 tick interrupt 就是讓scheduler達到這個目的
time slice的常度定義在 configTICK_RATE_HZ (FreeRTOSConfig.h)
舉例來說, 如果 configTICK_RATE_HZ =100Hz, 那麼time slice = 10ms

要注意的是, FreeRTOS的時間是以tick當單位, 就連 portTICK_RATE_MS 也是以tick當單位


每次選task, 都會選priority最高的task, 如果有一樣的priority則會輪流

如果我們把剛剛的例子改一下priority的部份

int main( void )
{
    /* task1 的 priority 是 1 */
    xTaskCreate( vTaskFunction, "Task 1", 1000, (void*)pcTextForTask1, 1, NULL );

    /* task2 的 priority 是 2 */
    xTaskCreate( vTaskFunction, "Task 2", 1000, (void*)pcTextForTask2, 2, NULL );

    vTaskStartScheduler();
    return 0;
}

執行結果會看到:
Task 2 is running
Task 2 is running
Task 2 is running
Task 2 is running
Task 2 is running
.....
結果Task 1因為priority比Task 2, 所以每次選都選不到它造成starvation


5. Not running state


為了避免starvation造成某些task永遠跑不到, 可以使用event driven
這意味著在選擇task的時候, scheduler沒有選priority最高的task

5.1 Blocked State


當task在等某個event的時候, 稱它在 Blocked State
task等的event分兩種 :

  • Temporal event : 在等delay或是確切的時間
  • Synchronization event : 這個 event 由其它 task 或 interrupt 產生

task 也可以同時等這兩種, 比如說, 在10s內等待data

5.2 Suspended State


當 task 在 suspended state 的時候, 就不會被scheduler選上
進suspend : vTaskSuspend()
離開suspend : vTaskResume() or xTaskResumeFromISR()
但大多時候不太會用到suspend

5.3 Ready State


當 task為 Not running, 並且不是Blocked/Suspended State, 就稱作 Ready State

5.4 State Transition Diagram



5.4 vTaskDelay()


之前的例子, 用的是busy delay, 所以Task 2實際上並沒有suspend
我們可以用 vTaskDelay() 來改寫這個 delay 讓 Task 2 進 suspend

void vTaskFunction( void *pvParameters )
{
    char *pcTaskName;

    pcTaskName = ( char * ) pvParameters;
    for( ;; )
    {
        vPrintString( pcTaskName );

        /* 等250 ms, 參數是ticks, 可以利用portTICK_RATE_MS把milliseconds轉成ticks */
        vTaskDelay( 250 / portTICK_RATE_MS );
    }
}

實際結果是:
Task 2 is running
Task 1 is running
Task 2 is running
Task 1 is running
Task 2 is running
Task 1 is running
.....

因為有足夠的時間, 所以雖然Task2的priority比較高, 但它執行完就suspend, 讓Task 1在Task 2 suspend的期間成為唯一的task可以執行, 然後suspend

5.5 vTaskDelayUntil()


vTaskDelayUntil() 跟vTaskDelay()很像, 差別是vTaskDelayUntil()決定多少個tick之後要被離開blocked state, 適合用在需要定時執行的task

這是它的prototype

void vTaskDelayUntil( portTickType * pxPreviousWakeTime, portTickType xTimeIncrement );

其中它的參數:

  • pxPreviousWakeTime : 這個pointer會更新上一次離開block state的時間
  • xTimeIncrement : 週期, 單位為ticks, 一樣可以用 portTICK_RATE_MS 轉換成ms
我們再改寫剛剛的例子

void vTaskFunction( void *pvParameters )
{
    char *pcTaskName;
    portTickType xLastWakeTime;

    pcTaskName = ( char * ) pvParameters;

    /* xLastWakeTime 需要初始化成目前的tick count */
    xLastWakeTime = xTaskGetTickCount();

    for( ;; )
    {
        vPrintString( pcTaskName );

        /* 自 xLastWakeTime 的時間等 250ms 離開 block state */
        vTaskDelayUntil( &xLastWakeTime, ( 250 / portTICK_RATE_MS ) );
    }
}

6. Idle task


Processor通常都需要執行些什麼, 如果task都進suspend, 那裡它會執行idle task
idle task 是在 scheduler 一開始的時候產生, 擁有priority 0, 做的事情就跟busy loop差不多

6.1 Idle Task Hook Function


我們也可以加 callback function 在 idle task 裡
通常用於:

  • 執行低優先權的工作, 或只需要背景處理的工作
  • 計算剩下的processing capacity
  • 進low power mode

6.2 Limitation of Idle Task


不可以block / suspend
如果 callback function 呼叫 vTaskDelete() 來刪除某個task, 那麼會馬上跳回這個task, 讓這個task能夠釋放已用的資源

6.3 Usage of callback of Idle Task


callback function的prototype如下

void vApplicationIdleHook( void );

使用的時候要注意 USE_IDLE_HOOK 的config要設成1 (FreeRTOSConfig.h)

底下是sample code:

/* 儲存 ilde task 裡的 loop 次數 */
unsigned long ulIdleCycleCount = 0UL;

/* Idle hook function只能叫做 vApplicationIdleHook() */
void vApplicationIdleHook( void )
{
    ulIdleCycleCount++;
}

void vTaskFunction( void *pvParameters )
{
    char *pcTaskName;

    pcTaskName = ( char * ) pvParameters;
    for( ;; )
    {
        /* 印出 task name 以及 ulIdleCycleCount */
        vPrintStringAndNumber( pcTaskName, ulIdleCycleCount );

        /* Delay 250 ms*/
        vTaskDelay( 250 / portTICK_RATE_MS );
    }
}

執行結果像這樣:
Task 2 is running
uIdleCycleCOunt = 0
Task 1 is running
uIdleCycleCOunt = 0
Task 2 is running
uIdleCycleCOunt = 3869504
Task 1 is running
uIdleCycleCOunt = 3869504
Task 2 is running
uIdleCycleCOunt = 8564623
Task 1 is running
uIdleCycleCOunt = 8564623
......

7. Changing task priority


更改priority :

void vTaskPrioritySet( xTaskHandle pxTask, unsigned portBASE_TYPE uxNewPriority );

pxTask 是 task handle, uxNewPriority 是新的 priority

取得priority :

unsigned portBASE_TYPE uxTaskPriorityGet( xTaskHandle pxTask );

底下是 sample code

/* 拿來存task2 handle */
xTaskHandle xTask2Handle;

void vTask1( void *pvParameters )
{
    unsigned portBASE_TYPE uxPriority;

    uxPriority = uxTaskPriorityGet( NULL );
    for( ;; )
    {
        vPrintString( "Task1 is running\r\n" );

  /* 把 Task 2 的 priority 設成 Task 1 多 1 */
        vTaskPrioritySet( xTask2Handle, ( uxPriority + 1 ) );
    }
}

void vTask2( void *pvParameters )
{
    unsigned portBASE_TYPE uxPriority;

    uxPriority = uxTaskPriorityGet( NULL );
    for( ;; )
    {
        vPrintString( "Task2 is running\r\n" );

        /* 把 Task 2 的 priority 降 2 */
        vTaskPrioritySet( NULL, ( uxPriority - 2 ) );
    }
}

int main( void )
{
    xTaskCreate( vTask1, "Task 1", 1000, NULL, 2, NULL );
    xTaskCreate( vTask2, "Task 2", 1000, NULL, 1, &xTask2Handle );

    vTaskStartScheduler();
    for( ;; );
}

因為Task 2的priority在一開始比Task 1少1,
所以這個sample裡, Scheduler會讓Task 1執行 (並把Task 2的priority設成比Task 1大1)
在下一輪, Scheduler發現Task 2的priority比Task 1大, 就執行Task 2 (並降priority比Task 1小1)
執行結果就是Task 1/2輪流地執行

8. Deleting Task


用 vTaskDelete() API 來刪掉task, 被刪的task就再也不會進到 Running state
而 idle task 負責釋放被刪掉的task的資源, 注意不可大量用 vTaskDelete() 造成 idle task starve
並且要注意只有 kernel 分配給 task 的資源會自動釋放, 但如果是 task 主動要的資源則要在刪除前釋放

底下是 prototype

void vTaskDelete( xTaskHandle pxTaskToDelete );

底下是sample code

/* 存 Task 2 handle */
xTaskHandle xTask2Handle;

void vTask2( void *pvParameters )
{
    vPrintString( "Task2 is running and about to delete itself\r\n" );
    vTaskDelete( xTask2Handle );
}

void vTask1( void *pvParameters )
{
    const portTickType xDelay100ms = 100 / portTICK_RATE_MS;
    for( ;; )
    {
        vPrintString( "Task1 is running\r\n" );

        xTaskCreate( vTask2, "Task 2", 1000, NULL, 2, &xTask2Handle );

        vTaskDelay( xDelay100ms );
    }
}

int main( void )
{
    xTaskCreate( vTask1, "Task 1", 1000, NULL, 1, NULL );
    vTaskStartScheduler();
    for( ;; );
}

Task 1會create Task 2, Task 2在執行完後就會自刪, 所以執行結果就是Task 1/2輪流執行