分享到plurk 分享到twitter 分享到facebook

版本 ee0b9701d7b905732f78d07b7da89b27f3a94f74

FreeRTOS

協作者

  • 2015 年春季
    • 蕭奕凱<https://github.com/fbukevin>, 張瑋豪<https://github.com/parkson1128>, 余誌偉<https://github.com/peter1yu>, 王皓昱<https://github.com/larrywhy>, 洪煒凱<https://github.com/WaiKevin>_
  • 2014 年春季
    • 梁穎睿<https://github.com/TheKK>, 李奇霖<https://github.com/Shinshipower>, 方威迪<https://github.com/waynew30777>, 陳盈伸<https://github.com/shin21>

共筆

  • 2015 年春季
    • GitHub<https://github.com/Justinsanity/freertos-basic>_
  • 2014 年春季
    • Link<https://hackpad.com/FreeRTOSV8.0.0-PU3awKuzHz6#:h=%3Chardware-interfacing%3E>_
    • GitHub<https://github.com/TheKK/myFreeRTOS>_

目錄

  • FreeRTOS 架構<#FreeRTOS 架構>_
  • 原始碼<#原始碼>_
  • 命名規則<#命名規則>_
  • Run FreeRTOS on STM32F4-Discovery<#Run FreeRTOS on STM32F4-Discovery>_
  • 任務<#任務>_
  • Ready list 的資料形態<#Ready list 的資料形態>_
  • 通訊<#通訊>_
  • 排程<#排程>_
  • 中斷處理<#中斷處理>_
  • 硬體驅動原理<#硬體驅動原理>_
  • 效能評估<#效能評估>_
  • 測試環境架設<#測試環境架設>_
  • 問題討論<#問題討論>_

FreeRTOS 架構

官方網站:http://www.freertos.org/<http://www.freertos.org/>_

FreeRTOS 是一個相對其他作業系統而言較小的作業系統。最小化的 FreeRTOS 核心僅包括 3 個 .c 文件(tasks.c、queue.c、list.c)和少數標頭檔,總共不到 9000 行程式碼,還包括了註解和空行。一個典型的編譯後 binary(二進位碼)小於 10 KB。

FreeRTOS 的程式碼可以分為三個主要區塊:任務、通訊和硬體界面。

  • 任務 (Task): FreeRTOS 的核心程式碼約有一半是用來處理多數作業系統首要關注的問題:任務,任務是擁有優先權的用戶所定義的 C 函數。task.c 和 task.h 負責所有關於建立、排程和維護任務的繁重工作。

  • 通訊 (Communication): 任務很重要,不過任務間可以互相通訊則更為重要!它帶出了 FreeRTOS 的第二項議題:通訊。FreeRTOS 核心程式碼大約有 40% 是用來處理通訊的。queue.c 和 queue.h 負責處理 FreeRTOS 的通訊,任務和中斷(interrupt)使用佇列(佇列,queue)互相發送數據,並且使用 semaphore 和 mutex 來派發 critical section 的使用信號。

  • 硬體界面:有近 9000 行的程式碼組成基本的 FreeRTOS,這部份是與硬體無關的(hardware-independent),同一份程式碼在不同硬體平台上的 FreeRTOS 都可以運行。大約有 6% 的 FreeRTOS 核心代碼,在與硬體無關的 FreeRTOS 核心和與硬體相關的程式碼間扮演著墊片(shim<http://en.wikipedia.org/wiki/Shim_%28computing%29>)的角色。我們將在下個部分討論與硬體相關的程式碼。[#]

.. [#] 用 cloc<http://cloc.sourceforge.net/>_ 統計 FreeRTOS 8.0.0 的 include/ *.c portable/GCC/ARM_CM4F/ 等目錄,可得不含註解、空白行的行數為 6566,而統計與平台有關的部份 (portable/GCC/ARM_CM4F/ 目錄),則是 435 行。計算可得: 435 / 6566 = 0.06625 = 6%,與描述相符,但原本的 9000 行是指含註解的說法 (實際為 8759 行)

原始碼

  • 官方下載:https://sourceforge.net/projects/freertos<https://sourceforge.net/projects/freertos>_

  • 最新版本:v8.2.1

  • 核心程式碼:(Source)

    • tasks.c:主要掌管 task 的檔案

    • queue.c:管理 task 間 communication (message queue 的概念)

    • list.c:提供系統與應用實作會用到的 list 資料結構

      - 選擇性檔案:timer.c、croutine.c (co-routine)、event_groups.c
  • 與硬體相關的檔案:以 ARM Cortext-M3 為例,可在 Source/portable/GCC/ARM_CM3 中找到

    • portmacro.h:定義了與硬體相關的變數,如資料型態定義,以及與硬體相關的函式呼叫名稱定義(以 portXXXXX 命名)等,統一各平臺的函式呼叫
    • port.c:定義了包含與硬體相關的程式碼實作
    • FreeRTOSConfig.h:包含 clock speed, heap size, mutexes 等等都在此定義 (v8.2.1 以前可於此目錄中找到,v8.2.1 的該檔案移至 Source/include 且更名為 FreeRTOS.h)

命名規則

  • 變數
    • char 類型:以 c 為字首
    • short 類型:以 s 為字首
    • long 類型:以 l 為字首
    • float 類型:以 f 為字首
    • double 類型:以 d 為字首
    • Enum 變數:以 e 為字首
    • portBASE_TYPE 或其他(如 struct):以 x 為字首
    • pointer 有一個額外的字首 p , 例如 short 類型的 pointer 字首為 ps
    • unsigned 類型的變數有一個額外的字首 u , 例如 unsigned short 類型的變數字首為 us
  • 函式:以回傳值型態與所在檔案名稱為開頭(prefix)
    • vTaskPriority() 是 task.c 中回傳值型態為 void 的函式
    • xQueueReceive() 是 queue.c 中回傳值型態為 portBASE_TYPE 的函式
    • 只能在該檔案中使用的 (scope is limited in file) 函式,以 prv 為開頭 (private)

Run FreeRTOS on STM32F4-Discovery

任務

任務 (task) 是在 FreeRTOS 中執行的基本單位,每個 task 都是由一個 C 函數所組成,意思是你需要先定義一個 C 的函數,然後再用 xTaskCreate() 這個 API 來建立一個 task,這個 C 函數有幾個特點,它的返回值必須是 void,其中通常會有一個無限迴圈,所有關於這個 task 的工作都會在迴圈中進行,而且這個函數不會有 return,FreeRTOS 不允許 task 自行結束(使用 return 或執行到函數的最後一行)

Task 被建立出來後,它會配置有自己的堆疊空間和 stack variable(就是 function 中定義的變數)

一個典型的 task function 如下:

.. code-block:: c

void ATaskFunction(void *pvParameters)
{
   int i = 0;   // 每個用這個函數建立的 task 都有自己的一份 i 變數

   while(1)
   { /* do something here */ }

   /* 
    * 如果你的 task 就是需要離開 loop 並結束
    * 需要用 vTaskDelete 來刪除自己而非使用 return 或自然結束(執行到最後一行)
    * 這個參數的 NULL 值是表示自己 
    */
   vTaskDelete(NULL);
}

Task 的狀態

.. image:: /tskstate.gif

  • Ready:準備好要執行的狀態
  • Running:正在由 CPU 執行的狀態
  • Block:等待中的狀態(通常是在等待某個事件)
  • Suspended:等待中的狀態(透過 API 來要求退出排程)

每一種狀態 FreeRTOS 都會給予一個 list 儲存(除了 running)

建立 task 的函數

.. code-block:: c

portBASE_TYPE xTaskCreate( pdTASK_CODE pvTaskCode,
                           const signed portCHAR * const pcName,
                           unsigned portSHORT usStackDepth,
                           void *pvParameters,
                           unsigned portBASE_TYPE uxPriority,
                           xTaskHandle *pxCreatedTask );
  • pvTaskCode:就是我們定義好用來建立 task 的 C 函數
  • pcName:任意給定的 task name,這個名稱只被用來作識別,不會在 task 管理中被採用
  • usStackDepth:堆疊的大小
  • pvParameters:要傳給 task 的參數陣列,也就是我們在 C 函數宣告的參數
  • uxPriority:定義這個任務的優先權,在 FreeRTOS 中,0 最低,(configMAX_PRIORITIES – 1) 最高
  • pxCreatedTask:handle<http://zh.wikipedia.org/wiki/%E5%8F%A5%E6%9F%84>_,是一個被建立出來的 task 可以用到的識別符號

Ready list 的資料形態

FreeRTOS 使用 ready list 去管理準備好要執行的 tasks,而 ready list 的資料儲存方式如下圖

.. image:: /freertos-figures-full-ready-list-2.png

OS 會在進行 context switch 時選出下一個欲執行的 task

下面是在 ready list 中依照優先權選取執行目標的程式部分,FreeRTOS 的優先權最小爲 0,數字越大則優先權越高

task.c<https://github.com/TheKK/myFreeRTOS/blob/master/tasks.c#L284>_ (taskSELECT_HIGHEST_PRIORITY_TASK)

.. code-block:: c

while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopReadyPriority ] ) ) )
{
    configASSERT( uxTopReadyPriority );
    --uxTopReadyPriority;
}
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopReadyPriority ] ) );

include/list.h<https://github.com/TheKK/myFreeRTOS/blob/master/include/list.h#L268>_

.. code-block:: c

#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )
{
    List_t * const pxConstList = ( pxList );
    /* Increment the index to the next item and return the item, ensuring */
    /* we don't return the marker used at the end of the list.  */
    ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; 
    if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )  \
    {
        ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;
    }
    ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;
}
  • Task Control Block (TCB)的資料結構(tasks.c<https://github.com/TheKK/myFreeRTOS/blob/master/tasks.c#L120>_)

.. code-block:: c

/* In file: tasks.c */
typedef struct tskTaskControlBlock
{
    volatile portSTACK_TYPE *pxTopOfStack;                  /* 指向 task 記憶體堆疊最後一個項目的位址,這必須是 struct 中的第一個項目 (有關 offset) */
    xListItem    xGenericListItem;                          /* 用來記錄 task 的 TCB 在 FreeRTOS ready 和 blocked queue 的位置 */
    xListItem    xEventListItem;                            /* 用來記錄 task 的 TCB 在 FreeRTOS event queue 的位置 */
    unsigned portBASE_TYPE uxPriority;                      /* task 的優先權 */
    portSTACK_TYPE *pxStack;                                /* 指向 task 記憶體堆疊的起始位址 */
    signed char    pcTaskName[ configMAX_TASK_NAME_LEN ];   /* task 被建立時被賦予的有意義名稱(為了 debug 用)*/
	
    #if ( portSTACK_GROWTH > 0 )
    portSTACK_TYPE *pxEndOfStack;                           /* stack overflow 時作檢查用的 */
    #endif
	
    #if ( configUSE_MUTEXES == 1 )
    unsigned portBASE_TYPE uxBasePriority;                  /* 此 task 最新的優先權 */
    #endif
} tskTCB;

pxTopOfStack , pxEndOfStack:記錄 stack 的大小

uxPriority , uxBasePriority:前者記錄目前的優先權 ,後者記錄原本的優先權(可能發生在 Mutex)

xGenericListItem , xEventListItem:當一個任務被放入 FreeRTOS 的一個列表中,FreeRTOS 在 TCB 中插入指向這個任務的 pointer 的地方

xTaskCreate() 函數被呼叫的時候,一個任務會被建立。FreeRTOS 會為每一個任務分配一個新的 TCB target,用來記錄它的名稱、優先權和其他細節,接著配置用戶所請求的 HeapStack 空間(假設有足夠使用的記憶體),並在 TCB 的 pxStack 成員中記錄 Stack 的記憶體起始位址。

爲了便於排程,創造新 task 時,stack 中除了該有的資料外,還要加上『空的』 register 資料(第一次執行時理論上 register 不會有資料),讓新 task 就像是被 context switch 時選的 task 一樣,依照前述變數的命名規則,下面是實作方式

portable/GCC/ARM_CM4F/port.c<https://github.com/TheKK/myFreeRTOS/blob/master/portable/GCC/ARM_CM4F/port.c#L216>_

.. code-block:: c

/* In file: port.c */
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void  *pvParameters )
{

/* Simulate the stack frame as it would be created by a context switch
    interrupt. */

    /* Offset added to account for the way the MCU uses the stack on entry/exit
    of interrupts, and to ensure alignment. */
    pxTopOfStack--;

    *pxTopOfStack = portINITIAL_XPSR;	/* xPSR */
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) pxCode;	/* PC */
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) portTASK_RETURN_ADDRESS;	/* LR */

    /* Save code space by skipping register initialisation. */
    pxTopOfStack -= 5;	/* R12, R3, R2 and R1. */
    *pxTopOfStack = ( StackType_t ) pvParameters;	/* R0 */

    /* A save method is being used that requires each task to maintain its
    own exec return value. */
    pxTopOfStack--;
    *pxTopOfStack = portINITIAL_EXEC_RETURN;

    pxTopOfStack -= 8;	/* R11, R10, R9, R8, R7, R6, R5 and R4. */

    return pxTopOfStack;
}

在 TCB 完成初始化後,要把該 TCB 接上其他相關的 list,這個過程中必須暫時停止 interrupt 功能,以免在 list 還沒設定好前就被其他的 task 中斷設定

而 ARM Cortex-M4 處理器在 task 遇到中斷時,會將 register 的內容 push 進該 task 的 stack 的頂端,待下次執行時再 pop 出去,以下是在 port.c 裡的實作

portable/GCC/ARM_CM4F/port.c<https://github.com/TheKK/myFreeRTOS/blob/master/portable/GCC/ARM_CM4F/port.c#L450>_

.. code-block:: c

/* In file: port.c */
void xPortPendSVHandler( void )
{
	/* This is a naked function. */

	__asm volatile
	(
	"	mrs r0, psp						\n" // psp: Process Stack Pointer
	"	isb							\n"
	"								\n"
	"	ldr	r3, pxCurrentTCBConst				\n" /* Get the location of the current TCB. */
	"	ldr	r2, [r3]					\n"
	"								\n" // tst used "and" to test.
	"	tst r14, #0x10						\n" /* Is the task using the FPU context?  If so, push high vfp registers. */
	"	it eq							\n"
	"	vstmdbeq r0!, {s16-s31}					\n"
	"								\n" // stmdb: db means "decrease before"
	"	stmdb r0!, {r4-r11, r14}				\n" /* Save the core registers. */
	"								\n"
	"	str r0, [r2]						\n" /* Save the new top of stack into the first member of the TCB. */
	"								\n"
	"	stmdb sp!, {r3}						\n"
	"	mov r0, %0 						\n"
	"	msr basepri, r0						\n"
	"	bl vTaskSwitchContext					\n"
	"	mov r0, #0						\n"
	"	msr basepri, r0						\n"
	"	ldmia sp!, {r3}						\n" // r3 now is switched to the higher priority task
	"								\n"
	"	ldr r1, [r3]						\n" /* The first item in pxCurrentTCB is the task top of stack. */
	"	ldr r0, [r1]						\n" // this r0 is "pxTopOfStack"
	"								\n"
	"	ldmia r0!, {r4-r11, r14}				\n" /* Pop the core registers. */
	"								\n"
	"	tst r14, #0x10						\n" /* Is the task using the FPU context?  If so, pop the high vfp registers too. */
	"	it eq							\n"
	"	vldmiaeq r0!, {s16-s31}					\n"
	"								\n"
	"	msr psp, r0						\n"
	"	isb							\n"
	"								\n"
	#ifdef WORKAROUND_PMU_CM001 /* XMC4000 specific errata workaround. */
		#if WORKAROUND_PMU_CM001 == 1
	"			push { r14 }				\n"
	"			pop { pc }				\n"
		#endif
	#endif
	"								\n"
	"	bx r14							\n"
	"								\n" //number X must be a power of 2. That is 2, 4, 8, 16, and so on...
	"	.align 2						\n" //on a memory address that is a multiple of the value X
	"pxCurrentTCBConst: .word pxCurrentTCB	\n"
	::"i"(configMAX_SYSCALL_INTERRUPT_PRIORITY)
	);
}

Interrupt 的實作,是將 CPU 中控制 interrupt 權限的暫存器(basepri)內容設爲最高,此時將沒有任何 interrupt 可以被呼叫,該呼叫的函數名稱為 ulPortSetInterruptMask()

portable/GCC/ARM_CM4F/port.c<https://github.com/TheKK/myFreeRTOS/blob/master/portable/GCC/ARM_CM4F/port.c#L419>_

.. code-block:: c

__attribute__(( naked )) uint32_t ulPortSetInterruptMask( void )
{
        __asm volatile
        (
                "        mrs r0, basepri        \n"
                "        mov r1, %0             \n"
                "        msr basepri, r1        \n"
                "        bx lr                  \n"
                :: "i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY ) : "r0", "r1"
        );

        /* This return will not be reached but is necessary to prevent compiler
        warnings. */
        return 0;

藉此 mask 遮罩掉所有的 interrupt (所有優先權低於 configMAX_SYSCALL_INTERRUPT_PRIORITY 的 task 將無法被執行)

參照:http://www.freertos.org/RTOS-Cortex-M3-M4.html<http://www.freertos.org/RTOS-Cortex-M3-M4.html>_

通訊

  • Queue

    .. image:: /qu.png

使用 Queue 來傳送 data(variable 或是 pointer,但是用 deep copy 一份到 queue 裡,而不是用 pointer 指向 variable) Queue的結構如下

queue.c<https://github.com/TheKK/myFreeRTOS/blob/master/portable/GCC/ARM_CM4F/port.c#L125>_

.. code-block:: c

/* In file: queue.c */
typedef struct QueueDefinition{

  signed char *pcHead;                    /* Points to the beginning of the queue 
                                             storage area. */

  signed char *pcTail;                    /* Points to the byte at the end of the 
                                             queue storage area. One more byte is 
                                             allocated than necessary to store the 
                                           queue items; this is used as a marker. */

  signed char *pcWriteTo;                 /* Points to the free next place in the 
                                             storage area. */

  signed char *pcReadFrom;                /* Points to the last place that a queued 
                                             item was read from. */

  xList xTasksWaitingToSend;              /* List of tasks that are blocked waiting 
                                             to post onto this queue.  Stored in 
                                             priority order. */

  xList xTasksWaitingToReceive;           /* List of tasks that are blocked waiting 
                                             to read from this queue. Stored in 
                                             priority order. */

  volatile unsigned portBASE_TYPE uxMessagesWaiting;/* The number of items currently
                                                       in the queue. */

  unsigned portBASE_TYPE uxLength;                  /* The length of the queue 
                                                       defined as the number of 
                                                       items it will hold, not the 
                                                       number of bytes. */

  unsigned portBASE_TYPE uxItemSize;                /* The size of each items that 
                                                       the queue will hold. */

} xQUEUE;

由於queue可以被許多個task進行寫入資料(即send data),當queue放滿了但卻還有task要進行寫入時,則使用xTasksWaitingToSend這個list來追蹤這些被block住的task(等待寫入資料到queue裡的task),每當有一個item從queue中被移除,xTasksWaitingToSend就會被檢查是否有task在此list,並且會在這些被block的task中挑出一個priority最高的task,將他解除block然後進行寫入資料的動作,若這些task的priority都一樣高,那將會挑出等待最久的task。

相同的,許多task進行讀取資料(即receive data)亦是如次,若queue沒有任何item在裡面,而同時還有好幾個task想要讀取資料,則這些task會被插在xTasksWaitingToReceive裡,xTasksWaitingToReceive這個list用來追蹤這些被block住的task(等待從queue中讀取資料的task)。

Queue在FreeRTOS的應用方式如下:

/CORTEX_M4F_STM32_DISCOVERY/main.c<https://github.com/TheKK/myFreeRTOS/blob/exti/CORTEX_M4F_STM32_DISCOVERY/main.c#L213>_

.. code-block:: c

/*file: ./CORTEX_M4F_STM32F407ZG-SK/main.c*/
/*line:47*/
xQueueHandle MsgQueue;
/*line:214*/
void QTask1( void* pvParameters )
{
        uint32_t snd = 100;

        while( 1 ){
                xQueueSend( MsgQueue, ( uint32_t* )&snd, 0 );  
                vTaskDelay(1000);
        }
}

void QTask2( void* pvParameters )
{
        uint32_t rcv = 0;
        while( 1 ){
                if( xQueueReceive( MsgQueue, &rcv, 100/portTICK_RATE_MS ) == pdPASS  &&  rcv == 100)
                {  
                        STM_EVAL_LEDToggle( LED3 );
                }
        }
}

關於QUEUE的操作函式定義在./queue.h

FreeRTOS也可使用Queue來實作Semaphores 和 Mutexes:

Semaphores按照對變數的定義

semaphore用來讓一個task喚醒喚醒(wake)另一個task做事,例如:producer和consumer

Mutexes

mutex用來對共享資源做互斥存取
  • 實作semaphore

N-element semaphore,只需同步uxMessagesWaiting,且只需關心有多少queue entries被佔用,其中uxItemSize為0,item和data copying是不需要的。

需要 ARM Cortex-M4F 特有的機制,才能實做 semaphore,其機制為:在存取uxMessagesWaiting時必須一次只能一個task去做更改(進、出入critical section),要防止一次兩個task進入修改的機制如下:

portable/GCC/ARM_CM4F/port.c<https://github.com/TheKK/myFreeRTOS/blob/master/portable/GCC/ARM_CM4F/port.c#L399>_

.. code-block:: c

/* In file: port.c */
void vPortEnterCritical( void ) 
{ 
    portDISABLE_INTERRUPTS(); 
    uxCriticalNesting++; 
    __asm volatile( "dsb" ); 
    __asm volatile( "isb" ); 
} 

/*-----------------------------------------------------------*/ 

void vPortExitCritical( void ) 
{ 
    configASSERT( uxCriticalNesting ); 
    uxCriticalNesting--; 
    if( uxCriticalNesting == 0 ) 
    { 
        portENABLE_INTERRUPTS(); 
    } 
}
  • 實作mutex

因為pcHead和pcTail不需要,所以overloadind來達到較好的使用率:

queue.c<https://github.com/TheKK/myFreeRTOS/blob/master/portable/GCC/ARM_CM4F/port.c#L105>_

.. code-block:: c

    /* Effectively make a union out of the xQUEUE structure. */
    #define uxQueueType           pcHead
    #define pxMutexHolder         pcTail

uxQueueType若為0,表示this queue is being used for a mutex

pxMutexHolder用來實作priority inheritance

mutex 和 semaphore 的差異,請參見這篇短文: http://embeddedgurus.com/barr-code/2008/01/rtos-myth-1-mutexes-and-semaphores-are-interchangeable/

另外: http://embeddedgurus.com/barr-code/2008/03/rtos-myth-3-mutexes-are-needed-at-the-task-level/

  • 生產者與消費者

使用FreeRTOS的semaphore和mutex來實作生產者與消費者問題:

.. code-block:: c

SemaphoreHandle_t xMutex = NULL;
SemaphoreHandle_t empty = NULL;
SemaphoreHandle_t full = NULL;
xQueueHandle buffer = NULL;
long sendItem = 1;
long getItem = -1;

void Producer1(void* pvParameters){
        while(1){
                // initial is 10, so producer can push 10 item
                if( xSemaphoreTake(empty, portMAX_DELAY) == pdTRUE ){

                        if( xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE ){
                        /******************** enter critical section ********************/
                                xQueueSend( buffer, &sendItem, 0 );
                                USART1_puts("add item, buffer = ");
                                itoa( (long)uxQueueSpacesAvailable(buffer), 10);
                                sendItem++;
                        /******************** exit critical section ********************/
                                xSemaphoreGive(xMutex);
                        }
                        // give "full" semaphore
                        xSemaphoreGive(full);
                }
                vTaskDelay(90000);
        }
}

void Consumer1(void* pvParameters){
        while(1){
                // initial is 0 so consumer can't get any item
                if( xSemaphoreTake(full, portMAX_DELAY) == pdTRUE ){

                        if( xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE ){
                        /******************** enter critical section ********************/
                                xQueueReceive( buffer, &getItem, 0 );
                                USART1_puts("get: ");
                                itoa(getItem, 10);
                        /******************** exit critical section ********************/
                                xSemaphoreGive(xMutex);
                        }
                        // give "empty" semaphore
                        xSemaphoreGive(empty);
                }
                vTaskDelay(80000);
        }
}

排程

  • 基本概念

    .. image:: /suspending.gif

RTOS中除了由kernel要求task交出CPU控制權外,各task也能夠選擇自行交出CPU控制權,舉凡

Delay( sleep ): 停止運行一段特定時間

/CORTEX_M4F_STM32_DISCOVERY/main.c<https://github.com/TheKK/myFreeRTOS/blob/exti/CORTEX_M4F_STM32_DISCOVERY/main.c#L177>_

.. code-block:: c

void Task2( void* pvParameters )
{
	while( 1 ){
		vTaskDelay( 1000 );
		itoa(iii, 10);
		iii = 0;
	}
}

wait( block ): 等待取得某資源

.. code-block:: c

void QTask2( void* pvParameters )
{
	uint32_t rcv = 0;
	while( 1 ){
		if( xQueueReceive( MsgQueue, &rcv, 100/portTICK_RATE_MS ) == pdPASS  &&  rcv == 100)
		{  
			STM_EVAL_LEDToggle( LED3 );
		}
	}
}

使用vTaskDelay(),會將該list item從ready list移除並放入DelayList或是OverflowDelayList中(看現在的systick加上欲等待的systick有無overflow而定),但不會在呼叫vTaskDelay()後馬上交出CPU使用權,而是在下一次的systick interrput做交換

使用xQueueReceive(),等待時間非portMAX_DELAY時,會被放入event list中等待資源。若等待時間爲portMAX_DELAY,則放入suspend list中等待資源

  • RTOS tick

    .. image:: /TickISR.gif

portable/GCC/ARM_CM4F/port.c<https://github.com/TheKK/myFreeRTOS/blob/master/portable/GCC/ARM_CM4F/port.c#L506>_

實作細節

.. code-block:: c

void xPortSysTickHandler( void )                                                                                                                                                           
{
    /* The SysTick runs at the lowest interrupt priority, so when this interrupt
    executes all interrupts must be unmasked.  There is therefore no need to
    save and then restore the interrupt mask value as its value is already
    known. */
    ( void ) portSET_INTERRUPT_MASK_FROM_ISR();
    {
        /* Increment the RTOS tick. */
        if( xTaskIncrementTick() != pdFALSE )
        {
            /* A context switch is required.  Context switching is performed in
            the PendSV interrupt.  Pend the PendSV interrupt. */
            portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
        }
    }
    portCLEAR_INTERRUPT_MASK_FROM_ISR( 0 );
}

.. code-block:: c

BaseType_t xTaskIncrementTick( void )                                                                                                                                                      
{
    TCB_t * pxTCB;
    TickType_t xItemValue;
    BaseType_t xSwitchRequired = pdFALSE;

    traceTASK_INCREMENT_TICK( xTickCount );
    if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
    {
        ++xTickCount;

        {
            const TickType_t xConstTickCount = xTickCount;

            if( xConstTickCount == ( TickType_t ) 0U )
            {
                taskSWITCH_DELAYED_LISTS();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }

            if( xConstTickCount >= xNextTaskUnblockTime )
            {
                for( ;; )
                {
                    if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
                    {
                        xNextTaskUnblockTime = portMAX_DELAY;
                        break;
                    }
                    else
                    {
                        pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
                        xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xGenericListItem ) );

                        if( xConstTickCount < xItemValue )
                        {
                            xNextTaskUnblockTime = xItemValue;
                            break;
                        }
                        else
                        {
                            mtCOVERAGE_TEST_MARKER();
                        }

                        ( void ) uxListRemove( &( pxTCB->xGenericListItem ) );

                        if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
                        {
                            ( void ) uxListRemove( &( pxTCB->xEventListItem ) );
                        }
                        else
                        {                                                                                                                                                                  
                            mtCOVERAGE_TEST_MARKER();
                        }

                        prvAddTaskToReadyList( pxTCB );

                        #if (  configUSE_PREEMPTION == 1 )
                        {
                            {
                                xSwitchRequired = pdTRUE;
                            }
                            else
                            {
                                mtCOVERAGE_TEST_MARKER();
                            }
                        }
                        #endif /* configUSE_PREEMPTION */
                    }
                }
            }
        }

        #if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
        {
            if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
            {
                xSwitchRequired = pdTRUE;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
    }
    else
    {
        ++uxPendedTicks;
    }

    #if ( configUSE_PREEMPTION == 1 )
    {
        if( xYieldPending != pdFALSE )
        {
            xSwitchRequired = pdTRUE;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }

    return xSwitchRequired;
}
  • Starvation

FreeRTOS為MultiLevel Queue,若priority高的task霸佔CPU,對於priority較低的task則無法執行,便會發生Starvation(低優先權很長一段時間都無法獲得CPU執行)

中斷處理

On way to achieve this goal consists in the creation of tasks waiting for an interrupt to occur with a semaphore, and let this safer portion of code actually handle the interrupt. An ISR “gives” a semaphore and unblock a ’Handler” task that is able to handler the ISR, making the ISR execution much shorter

  • Nested Vectored Interrupt Controller(NVIC)

    • 能夠定義1~240種interrupt (FreeRTOS on ARM Cortex-M4原始定義了107個interrupt)

      • Cortex-M4只提供240個Interrupt Priority Registers(IPR)去記錄
    • 可自定義的interrput優先度,從0~255(0爲最大優先度,255爲最小)

      • PreemptionPriority: 4bits

      • SubPriority: 4bits

    • 處理器(硬體實作)在interrupt發生時會“自動”將當前狀態stack起來,interrput結束後再unstack回來,藉此減少interrput latency

    • 只需要將資料放入Software Trigger Interrupt Register(STIR),就能夠觸發interrupt

  • External Interrupt(EXTI)

各interrupt handler的排序和名稱定義(並非實作內容)放置在startup_stm32f429_439xx.s中,實作則需要在其他地方宣告 FreeRTOS使用者可以定義的外部中斷通路爲EXTI_Line0至EXTI_Line15,不過EXTI_Line1015和EXTI_Line59被設定爲同一外部中斷通路(表示Line10和Line15會導向同一個handler),如果Line10和Line15需要有不同的任務,則要在EXTI_Line10_15的handler內做觸發來源的判斷

file:startup_stm32f429_439xx.s line:158

CORTEX_M4F_STM32_DISCOVERY/startup/startup_stm32f4xx.s<https://github.com/TheKK/myFreeRTOS/blob/exti/CORTEX_M4F_STM32_DISCOVERY/startup/startup_stm32f4xx.s#L82>_

.. code-block:: c

  .word     EXTI0_IRQHandler                  /* EXTI Line0                   */                        
  .word     EXTI1_IRQHandler                  /* EXTI Line1                   */                          
  .word     EXTI2_IRQHandler                  /* EXTI Line2                   */                          
  .word     EXTI3_IRQHandler                  /* EXTI Line3                   */                          
  .word     EXTI4_IRQHandler                  /* EXTI Line4                   */ 

  .word     EXTI9_5_IRQHandler                /* External Line[9:5]s          */     

  .word     EXTI15_10_IRQHandler              /* External Line[15:10]s        */                           
  • EXTI使用前必須:

    • 和GPIO連接(作爲觸發來源)

    • 設定EXTI

      • 設定哪條Line

      • 模式( Interrupt, Event )

      • 被觸發的條件( Rising, Falling, Rising&falling )

      • LineCmd( ENABLE表示設定該通道,DISABLE表示關閉該通道)

    • 設定NVIC

      • IRQ_Channel

      • PreemptionPriority,SubPriority

      • ChannelCmd( 同LineCmd的意義 )

EXTI在FreeRTOS的實作如下:file:./CORTEX_M4F_STM32F407ZG-SK/main.c line:106

CORTEX_M4F_STM32_DISCOVERY/main.c<https://github.com/TheKK/myFreeRTOS/blob/exti/CORTEX_M4F_STM32_DISCOVERY/main.c#L106>_

.. code-block:: c

    /* Configure PA0 pin as input floating */
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* Connect EXTI Line0 to PA0 pin */
    SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);

    /* Configure EXTI Line0 */
    EXTI_InitStructure.EXTI_Line = EXTI_Line0;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

    /* Enable and set EXTI Line0 Interrupt to the lowest priority */
    NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

硬體驅動原理

  • 簡介
    • 參考 STM32Cube_FW_F4_V1.1.0/Projects/STM32F429I-Discovery/Examples/GPIO/GPIO_EXTI/readme.txt

能夠讓開發者藉由改變記憶體特定位置的資料內容,來控制各種周邊

或者,藉由外部的輸入,來改變記憶體內容,讓硬體得知其變化,來做反應

使用GPIO前,必須預先設定行爲和細節,以stm32f429i_discovery.c內的LED初始作業來看

Utilities/STM32F429I-Discovery/stm32f429i_discovery.c<https://github.com/TheKK/myFreeRTOS/blob/exti/Utilities/STM32F429I-Discovery/stm32f429i_discovery.c#122>_

.. code-block:: c

/* In file: stm32f429i_discovery.c */
void STM_EVAL_LEDInit(Led_TypeDef Led)                                                                                                                          
{
  GPIO_InitTypeDef  GPIO_InitStructure;

  /* Enable the GPIO_LED Clock */
  RCC_AHB1PeriphClockCmd(GPIO_CLK[Led], ENABLE);

  /* Configure the GPIO_LED pin */
  GPIO_InitStructure.GPIO_Pin = GPIO_PIN[Led];
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIO_PORT[Led], &GPIO_InitStructure);
}

使用者開啟使用到的GPIO之bus的clock,然後使用GPIO_InitStructure(struct資料型態)來儲存相關設定,並交與GPIO_Init()去做記憶體的設定

  • 底層實作

用上面LED初始的過程當舉例,其中接觸到硬體設定的function爲RCC_AHB1PeriphClockCmd()和GPIO_Init()

先從RCC_AHB1PeriphClockCmd()開始討論

.. code-block:: c

/* In file: stm32f4xx.h */
typedef struct
{
...
__IO uint32_t AHB1ENR;       /*!< RCC AHB1 peripheral clock register,                          Address offset: 0x30 */
...
} RCC_TypeDef;

#define RCC                 ((RCC_TypeDef *) RCC_BASE)
#define RCC_BASE              (AHB1PERIPH_BASE + 0x3800)
#define AHB1PERIPH_BASE       (PERIPH_BASE + 0x00020000)
#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region

/* In file: stm32f4xx_rcc.h */
#define RCC_AHB1Periph_GPIOA             ((uint32_t)0x00000001)

/* In file: stm32f4xx_rcc.c */
void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState)
{
  /* Check the parameters */
  assert_param(IS_RCC_AHB1_CLOCK_PERIPH(RCC_AHB1Periph));

  assert_param(IS_FUNCTIONAL_STATE(NewState));
  if (NewState != DISABLE)
  {
    RCC->AHB1ENR |= RCC_AHB1Periph;
  }
  else
  {
    RCC->AHB1ENR &= ~RCC_AHB1Periph;
  }
}   

不難發現,GPIO周邊的設定在實作上相當單純,即在預先定義好的記憶體區段上,按照設計者定義之設定將參數寫在該處

值得一提的是,GPIO的初始化和設定皆是在執行時期進行,即使在程式運作中依然能夠重新定義甚至關閉周邊,讓MCU的使用更為彈性

GPIO_Init()的執行也脫離不了寫入資料至記憶體一事,但設定項目更多,設定過程也相對複雜

GPIO_Init()

CORTEX_M4F_STM32_DISCOVERY/Libraries/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_gpio.c<https://github.com/TheKK/myFreeRTOS/blob/exti/CORTEX_M4F_STM32_DISCOVERY/Libraries/STM32F4xx_StdPeriph_Driver/src/stm32f4xx_gpio.c#202>_

.. code-block:: c

/* In file: stm32f4xx_gpio.c */
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
  uint32_t pinpos = 0x00, pos = 0x00 , currentpin = 0x00;

  /* Check the parameters */
  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));
  assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));
  assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));
  assert_param(IS_GPIO_PUPD(GPIO_InitStruct->GPIO_PuPd));

  /* ------------------------- Configure the port pins ---------------- */
  /*-- GPIO Mode Configuration --*/
  for (pinpos = 0x00; pinpos < 0x10; pinpos++)
  {
    pos = ((uint32_t)0x01) << pinpos;
    /* Get the port pins position */
    currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;

    if (currentpin == pos)
    {
      GPIOx->MODER  &= ~(GPIO_MODER_MODER0 << (pinpos * 2));
      GPIOx->MODER |= (((uint32_t)GPIO_InitStruct->GPIO_Mode) << (pinpos * 2));
  
      if ((GPIO_InitStruct->GPIO_Mode == GPIO_Mode_OUT) || (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_AF))
      {
        /* Check Speed mode parameters */
        assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed));
  
        /* Speed mode configuration */
        GPIOx->OSPEEDR &= ~(GPIO_OSPEEDER_OSPEEDR0 << (pinpos * 2));
        GPIOx->OSPEEDR |= ((uint32_t)(GPIO_InitStruct->GPIO_Speed) << (pinpos * 2));

        /* Check Output mode parameters */
        assert_param(IS_GPIO_OTYPE(GPIO_InitStruct->GPIO_OType));

        /* Output mode configuration*/
        GPIOx->OTYPER  &= ~((GPIO_OTYPER_OT_0) << ((uint16_t)pinpos)) ;
        GPIOx->OTYPER |= (uint16_t)(((uint16_t)GPIO_InitStruct->GPIO_OType) << ((uint16_t)pinpos));
      }

      /* Pull-up Pull down resistor configuration*/
      GPIOx->PUPDR &= ~(GPIO_PUPDR_PUPDR0 << ((uint16_t)pinpos * 2));
      GPIOx->PUPDR |= (((uint32_t)GPIO_InitStruct->GPIO_PuPd) << (pinpos * 2));
    }
  }
}

效能評估

  • Context switch

(context switch是指taskA要交出CPU使用權給taslB時,將taslA當前的state和register內資料存放至記憶體內,將先前taskB的state從記憶體讀取至register內的過程)

我們想得知FreeRTOS的context switch時間,並想出一個測試方法: CORTEX_M4F_STM32_DISCOVERY/main.c<https://github.com/shin21/myFreeRTO-1/blob/ContextSwitchTest/CORTEX_M4F_STM32F407ZG-SK/main.c#L178>_

.. image:: /embedded/test1contextSwitch.jpg 1. 首先建立task1和task2,其中task2的priority大於task1的priority。task2先執行時,馬上就進行vTaskDelay使task2移至block狀態1秒,這時就會發生context switch,換成task1執行,這1秒的時間,task1不斷的進行i++,直到1秒結束後,回到task2執行,再由task2印出i值,並把i重新設0,此為一個週期。此動作可得到i在一秒時可跑至多少,設一秒可跑至k值。

  1. 設定一個task3其priority高於task2,讓task3執行vTaskDelay 300秒,當300秒結束後,會中斷task1所執行的i++。再由task3印出i值,設其為final_i,k值與final_i值的差額,即為context switch的總時間。

下圖為隨機挑出45個i值做成圖表,其中平均i值為:4280015

.. image:: /embedded/test2contextSwitch.jpg 接著我們測出的final_i值,平均為:3913853,故可得到 (4280015 - 3913853)/ 4280015 = 0.0855 (秒)

0.0855秒代表在300秒的測試內的所有context switch時間之總和

而因為一個週期(第一個步驟)會經過2個context switch(上圖),我們測300內共有600個context switch,故我們測出每個context switch約為:0.0855 / 600 = 142.5(us)  

  • interrupt latency

我們的架構為是手動設定一個external interrupt,發生在BUTTON_USER按下時,下面程式是我們的實作:

CORTEX_M4F_STM32_DISCOVERY/main.c<https://github.com/TheKK/myFreeRTOS/blob/exti/CORTEX_M4F_STM32_DISCOVERY/main.c#L106>_

.. code-block:: c

i = 0;
while( STM_EVAL_PBGetState( BUTTON_USER ) ){
    i++;
}

當BUTTON_USER按下後,會先執行i++直到interruptHandler處理interrupt,讀i值即可得知interrupt latency,而實作結果發現i依舊為0。  

  • IPC(Inter-Process Communication) throughput

測試程式,在第167行可以改要執行的時間<https://github.com/TheKK/myFreeRTOS/blob/IPC-ThroughPut/CORTEX_M4F_STM32_DISCOVERY/main.c#L167>_

SysTick最小只能設到1 / 100000 (十萬分之一)秒

若設到1 / 1000000 (百萬分之一)秒,則會連將data copy至queue裡都來不及執行

環境設置:

  1. SysTick為1 / 100000 (十萬分之一)秒

  2. Queue的length為10000個

  3. Queue的ItemSize為uint32_t

  • 測試單向(send)

若使用一個task只執行send data的話,在100 SysTicks時間內可以丟入約740個,在1000 SysTicks時間內可以丟入約7500個,

則1 SysTick內平均send 7.5個,故throughput約為:7.5 * 100000 * 4 = 3 (Mbytes/s)

  • 測試雙向(send與receive)

若加入一個task來receive data,且priority和send data的priority相同

1000 SysTicks下可以接收到2962個,

則1 SysTick平均接收2.962個,故throughput約為:2.962 * 100000 * 4 = 1.185(Mbytes/s)

  • 測試把每個ItemSize做變動

若每個ItemSize為uint16_t,則throughput約為:2.893 * 100000 * 2 = 0.579(Mbytes/s)

若每個ItemSize為uint64_t,則throughput約為:2.823 * 100000 * 8 = 2.258(Mbytes/s)

.. image:: /embedded/IPC-test.png

以上三者比較,在uint64_t時有最好的throughput,且snd和rcv相差最小。

  • realtime capability

測試環境架設

.. image:: /embedded/pingpong.jpg

安裝

1. 請先安裝st-link以及openOCD,可參考此頁 

2. git clone https://github.com/TheKK/myFreeRTOS.git

3. git checkout game

4. make

5. 將stm32 f4 - discovery接上電腦

6. make flash

7. done

解說

  • In CORTEX_M4F_STM32F407ZG-SK/game/game.c

.. image:: /embedded/table1.jpg

  • In CORTEX_M4F_STM32F407ZG-SK/game/game.c

.. image:: /embedded/table2.jpg

在main function中,首先初始化有用上的GPIO裝置,再來呼叫函式xTaskCreate()來建立欲排程的task,xTaskCreate()的輸入參數如下

.. code-block:: c

BaseType_t xTaskCreate( 
    TaskFunction_t pvTaskCode,         /* 欲排程的函式名稱 */
    const char * const pcName,          /* 在FreeRTOS中顯示的名稱,方便debug用 */
    unsigned short usStackDepth,     /* stack可以容納之變數的數量(不是設定stack的大小) */
    void *pvParameters,                       /* 建立task時的額外參數 */
    unsigned BaseType_t uxPriority,  /* 優先權 */
    TaskHandle_t *pvCreatedTask     /* 該task的參照,可用以檢查是否建立成功 */
);

最後,呼叫vTaskStartScheduler()正式開始排程

問題討論

**Q1:進入Suspend 的條件

如果一個task會被block很久或者是會有一段時間用不到那就會被丟到suspend狀態。 情境:設有一個taskPrint這個task只做print資料,而有好幾個taskOperator負責做運算,若運算要很久,則可以把taskPrint先丟入suspend狀態中,直到所有運算皆完成後,再喚醒taskPrint進入ready狀態,最後將資料print出來。

vTaskSuspend使用範例:

.. code-block:: c

 void vAFunction( void )

 {

     TaskHandle_t xHandle;

     // Create a task, storing the handle.

     xTaskCreate( vTaskCode, "NAME", STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );

     // ... 

     // If xHandle will wait for a long time.

     // Use the handle to suspend the created task.

     vTaskSuspend( xHandle );

     // ...

     // The created task will not run during this period, unless

     // another task calls vTaskResume( xHandle ).

     //...

     // Suspend ourselves.

     vTaskSuspend( NULL );

     // We cannot get here unless another task calls vTaskResume

     // with our handle as the parameter.

 }

*Q2:Suspend相關程式碼

FreeRTOS提供vTaskSuspend()和vTaskResume()這兩個API來提供我們可以讓task進入suspend狀態。FreeRTOS還有另一個API為vTaskSuspendAll()而主要用途為當某一個task在執行時,某期間內可以讓scheduler被suspend,防止context switch發生,實作方法為控制uxSchedulerSuspended的變數,vTaskSuspendAll()讓uxSchedulerSuspended+1 進入suspendall的狀態 當程式碼那段執行完時再用vTaskresumeall讓uxSchedulerSuspended-1這用法再很多task裡會用到

例如:vTaskDelay,vTaskDelayUntil為了讓Tasks能順利接上DelayedList而不被中斷,當scheduler被suspend時,context switch會被pending,而在scheduler被suspend的情況下,interrupt不可更改TCB的xStateListItem。而PendingReadyList的用法也是當scheduler被suspend時若這種時候interrupt要解除一個task的block狀態的話,則interrupt需將此task的event list item放至xPendingReadyList中,好讓scheduler解除suspend時,可將xPendingReadyList內的task 放入ready list裡。

Q3:Priority範圍且定義在哪裡

在./CORTEX_M4F_STM32F407ZG-SK/FreeRTOSconfig.h裡

.. code-block:: c

#define configMAX_PRIORITIES ( 5 )

Q4:為什麼要用doubly linked list

因為doubly linked list在插入新ITEM時擁有常數的時間複雜度O(1),而Singly linked list則是O(n)

Q5:為什麼FREERTOS在FORK之後是回傳一個STRUCT 而不是PID

追朔了xTaskCreate的程式碼,發現他是執行 xTaskGenericCreate這個function,而xTaskGenericCreate是在function裡malloc完成TCB之後,返回值有兩個:

  • pdPASS
  • errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY

資料型態為BaseType_t,宣告在portmacro.h裡:

.. code-block:: c

typedef long BaseType_t;

所以他的回傳值用途:回傳告知在malloc memory時是否成功。

而linux使用回傳PID的原因在於parent使用wait()來等待child,當child執行結束後會呼叫exit(),parent即可以clean up child process。若parent沒有使用wait()的話,會造成parent可能已經先結束了,這樣造成child變成zombie。

FreeRTOS的task create:

.. code-block:: c

xTaskCreate( vTaskCode, "NAME", STACK_SIZE, &ucParameterToPass, tskIDLE_PRIORITY, &xHandle );

其中Handle存的是新創的TCB這個structure的位址,將來要刪除此task的話可以用如下方法:

.. code-block:: c

/* Use the handle to delete the task. */
if( xHandle != NULL 
{
    vTaskDelete( xHandle );
}

而Linux的parent和child為相同的位址空間,若回傳為child的位址,將來parent要把child刪除時,便也把自己給刪除了…所以linux使用的是PID而不是structure的位址。

Q6:STACK位置的排列,如何存放

存放順序:

xPSR

PC : Program counter 內容存放處理器要存取的下一道指令位址 LR : link rigisiter :保存副程式的返回位址 R12 : Intra-Procedure-call scratch register R3 : parameters R2 : parameters R1 : parameters R0 : parameters

portINITIAL_EXEC_RETURN : 每個task要自己維護自己的返回值

  • R11
  • R10
  • R9
  • R8
  • R7
  • R6
  • R5
  • R4

註:xPSR:Composite of the 3 PSRs,

APSR-Application Program Status Register–condition flags

(存negative、zero、carry、overflow等)

IPSR-Interrupt Program Status Register–Interrupt/Exception No.

(存目前發生Exception的ISR Number )

EPSR-Execution Program Status Register

(存Thumb state bit 和 execution state bits(If-Then (IT) instruction和Interruptible-Continuable Instruction (ICI) field))

Q7:LR(Link Rigisiter) 的用處

當一個task A 執行被中斷時(可能system tick 或是高優先權的Task出現)用來紀錄Task A執行到哪裡的位置,當其他程式執行完時,能返回繼續成行Task A

Q8:為什麼是R12 R3 R2 R1 要預留起來?

R0~R3用來暫存Argument 的 scratch rigister (4個register的原因是為了handle values larger than 32 bits)

R0 R1 亦可暫存 subroutine 的result值

R12 : 作為The Intra-Procedure-call scratch register.

而為什麼是這幾個,因為叫方便使用

R12(IP)用法:

.. code-block:: c

mov     ip, lr
bl      lowlevel_init
mov     lr, ip

先將lr暫存存入ip

bl跳至其他branch的地方

branch結束後使用lr跳回第三行,將ip存回lr

P.S. 關於veneer: ARM 能支援 32-bit 和 16-bit 指令互相切換(THUMB 是 ARM 的 16-bit 指令集),其中切換的程式段叫 veneer

Q9:誰把New Task 接到 Ready List

.. code-block:: c

GDB Trace result                                                                                                                                                                           

Breakpoint 1, xTaskGenericCreate (pxTaskCode=0x80003b1 <GameTask>, pcName=0x800ea84 "GameTask", usStackDepth=128, pvParameters=0x0, uxPriority=1, pxCreatedTask=0x0, puxStackBuffer=0x0, 
xRegions=0x0) at /home/kk/myPrograms/embedded/myFreeRTOS/tasks.c:516
516        {
(gdb) next
520                configASSERT( pxTaskCode );
(gdb) 
516        {
(gdb) 
520                configASSERT( pxTaskCode );
(gdb) 
521                configASSERT( ( ( uxPriority & ( ~portPRIVILEGE_BIT ) ) < configMAX_PRIORITIES ) );
(gdb) 
525                pxNewTCB = prvAllocateTCBAndStack( usStackDepth, puxStackBuffer );
(gdb) 
572                        prvInitialiseTCBVariables( pxNewTCB, pcName, uxPriority, xRegions, usStackDepth );
(gdb) 
551                                pxTopOfStack = pxNewTCB->pxStack + ( usStackDepth - ( uint16_t ) 1 );
(gdb) 
572                        prvInitialiseTCBVariables( pxNewTCB, pcName, uxPriority, xRegions, usStackDepth );
(gdb) 
551                                pxTopOfStack = pxNewTCB->pxStack + ( usStackDepth - ( uint16_t ) 1 );
(gdb) 
572                        prvInitialiseTCBVariables( pxNewTCB, pcName, uxPriority, xRegions, usStackDepth );
(gdb) 
551                                pxTopOfStack = pxNewTCB->pxStack + ( usStackDepth - ( uint16_t ) 1 );
(gdb) 
552                                pxTopOfStack = ( StackType_t * ) ( ( ( portPOINTER_SIZE_TYPE ) pxTopOfStack ) & ( ( portPOINTER_SIZE_TYPE ) ~portBYTE_ALIGNMENT_MASK  ) ); /*lint !e923 MISRA exception.  Avoiding casts between pointers and integers is not practical.  Size differences accounted for using portPOINTER_SIZE_TYPE type. */
(gdb) 
572                        prvInitialiseTCBVariables( pxNewTCB, pcName, uxPriority, xRegions, usStackDepth );
(gdb) 
584                                pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
(gdb) 
588                        if( ( void * ) pxCreatedTask != NULL )
(gdb) 
602                        taskENTER_CRITICAL();
(gdb) 
604                                uxCurrentNumberOfTasks++;
(gdb) 
605                                if( pxCurrentTCB == NULL )
(gdb) 
604                                uxCurrentNumberOfTasks++;
(gdb) 
605                                if( pxCurrentTCB == NULL )
(gdb) 
604                                uxCurrentNumberOfTasks++;
(gdb) 
605                                if( pxCurrentTCB == NULL )
(gdb) 
609                                        pxCurrentTCB =  pxNewTCB;
(gdb) 
611                                        if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
(gdb) 
(gdb) 
616                                                prvInitialiseTaskLists();
(gdb) 
645                                uxTaskNumber++;
(gdb) 
655                                prvAddTaskToReadyList( pxNewTCB );
(gdb) 
645                                uxTaskNumber++;
(gdb) 
655                                prvAddTaskToReadyList( pxNewTCB );
(gdb) 
645                                uxTaskNumber++;
(gdb) 
655                                prvAddTaskToReadyList( pxNewTCB );
(gdb) 
645                                uxTaskNumber++;
(gdb) 
655                                prvAddTaskToReadyList( pxNewTCB );
(gdb) 
645                                uxTaskNumber++;
(gdb) 
650                                        pxNewTCB->uxTCBNumber = uxTaskNumber;
(gdb) 
655                                prvAddTaskToReadyList( pxNewTCB );
(gdb)                                                                                                                                                                                      
660                        taskEXIT_CRITICAL();
(gdb) 
670                        if( xSchedulerRunning != pdFALSE )
(gdb) 
657                                xReturn = pdPASS;
(gdb) 
690        
  • prvInitialiseTaskLists(void)

只有在list未被初始化時,才會被執行。預設會建立pxReadyTasksLists,xDelayedTaskList1,xDelaye traceMOVED_TASK_TO_READY_STATEdTaskList2,xPendingReadyList,依照使用者設定可以選擇是否建立xTasksWaitingTermination和xSuspendedTaskList

  • prvAddTaskToReadyList( pxNewTCB )

將pxNewTCB接上pxReadyTasksLists,prvAddTaskToReadyList()程式碼如下

.. code-block:: c

#define prvAddTaskToReadyList( pxTCB ) \
        traceMOVED_TASK_TO_READY_STATE( pxTCB ) \
        taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \
        vListInsertEnd( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xGenericListItem ) )
  • traceMOVED_TASK_TO_READY_STATE

.. code-block:: c

#ifndef traceMOVED_TASK_TO_READY_STATE
        #define traceMOVED_TASK_TO_READY_STATE( pxTCB )   
#endif

自定義函式,無預設定義。Debug用

  • taskRECORD_READY_PRIORITY

.. code-block:: c

#define taskRECORD_READY_PRIORITY( uxPriority  ) \
{ \
    if( ( uxPriority ) > uxTopReadyPriority ) \
    { \
        uxTopReadyPriority = ( uxPriority ); \
    } \
} /* taskRECORD_READY_PRIORITY */

檢查目前task的priority是否高於“當前最高優先權”。如果是,將更新當前最高優先權。

  • vListInsertEnd

.. code-block:: c

void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t * const pxIndex = pxList->pxIndex;
                                                                                                               
        /* Insert a new list item into pxList, but rather than sort the list,
        makes the new list item the last item to be removed by a call to
        listGET_OWNER_OF_NEXT_ENTRY(). */
        pxNewListItem->pxNext = pxIndex;
        pxNewListItem->pxPrevious = pxIndex->pxPrevious;
        pxIndex->pxPrevious->pxNext = pxNewListItem;
        pxIndex->pxPrevious = pxNewListItem;

        /* Remember which list the item is in. */
        pxNewListItem->pvContainer = ( void * ) pxList;
        ( pxList->uxNumberOfItems )++;
}

將pxNewListItem插入至pxList的最後面

Q10:arm conditional code?

conditional code用法為附加在某些條件指令之後,用來定義指令執行的代碼

.. image:: /embedded/condition.png

Q11:Thumb state bit? execution state bit?

EPSR-Execution Program Status Register內有存Thumb state bit 和 execution state bits, 其中execution state bits包含兩個重疊的區域:**

  1. If-Then (IT) instruction

  2. Interruptible-Continuable Instruction (ICI) field)

  • about IT

IT(If - Then)指令由緊連IT的1~4條後續指令所組成(IT block)。 http://web.eecs.umich.edu/~prabal/teaching/eecs373-f10/readings/ARMv7-M_ARM.pdf p.148~p.149 IT instruction example:

.. code-block:: c

if (R4 == R5)
{
  R7 = R8 + R9;
  R7 /= 2;
}
else
{
  R7 = R10 + R11;
  R7 *= 2;
}

converts to http://wiki.csie.ncku.edu.tw/embedded/freertos

.. code-block:: c

CMP R4, R5
ITTEE EQ
ADDEQ R7, R8, R9    ; if R4 = R5, R7 = R8 + R9
ASREQ R7, R7, #1    ; if R4 = R5, R7 /= 2
ADDNE R7, R10, R11  ; if R4 != R5, R7 = R10 + R11
LSLNE R7, R7, #1    ; if R4 != R5, R7 *=2
  • about ICI

多暫存器(multy register)讀取(LDM)和寫入(STM)是可以中斷的,ICI用來保存該執行過程中,下一個暫存器的編號。

Q12:R0~R3 , R12 , LR 這些對應到function call是哪裡?

Q13:R4~R11用在甚麼時候?

.. image:: /embedded/coreReg.png

Q14:接續Q5,問FreeRTOS設計概念,回去看OS的fork部分

摘自且翻譯恐龍書八版P110~112: * 父程序(parent process)產生子程序(child process),這些新的程序被產生(fork())後,會形成程序樹(tree of processes)。

.. image:: /embedded/ProcessTree.jpg

  • 一般而言,一個程序會需要一些資源(resource),子程序可以直接獲得資源或是子程序被限制在父程序的資源裡。「限制子程序在父程序資源裡」可以防止因為產生太多子程序而發生超載(overloading)。

  • 典型的方法是,在呼叫fork()之後,父程序和子程序其中一個可以使用exec()來呼叫一個新的程式,取代自己的記憶體空間,這個方法的好處是,父、子程序可以跑不同的程式並且還可以做溝通(communicate)。

Q15:VFP有幾個暫存器

ARM 浮點數架構 (VFP,全名Vector Floating-Point)為對浮點運算的操作提供的硬體支援。

.. image:: /embedded/VFP.png

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0439b/Chdhfiah.html

上面表格式VFP會用到的REG

另外FPU擁有獨立的暫存器有32個(32bit)(S0~S31)

所以在PORT.C void xPortPendSVHandler( void )

.. code-block:: c

"    tst r14, #0x10                        \n" /* Is the task using the FPU context?  If so, push high vfp registers. */ 
"    it eq                                \n" 
"    vstmdbeq r0!, {s16-s31}                \n" 

就會把HIGH的部份堆起來(藉由STMDB達成)

Q16:!是什麼(組語)

Note that the exclamation mark in ARM assembly code means that the index operation is performed before applying the real instruction.

For example, str r2, [r3, #-4]! means: store r2 value to the ptr {r3-4} and r3 = r3 -4.

指令集架構:

.. code-block:: c

ADD R0, R0, #1                     ; R0←R0+1
STR  R0, [R1]                      ; R0→[R1]  //將R0的值傳送到以R1的值為位址的記憶體中
ADD R0, R1, R2 LSL #3              ; R0←R1 + R2*8  //R2中的運算元左移3位元
LDMIA  R0, {R1, R2, R3}            ; [R0]→R1
                                   ; [R0+4]→R2
                                   ; [R0+8]→R3
比較三種:
LDR R0, [R1, #8]                   ; R0←[R1+8]
LDR R0, [R1], #8                   ; R0←[R1]
                                   ; R1←R1+8
LDR R0, [R1, #8]!                  ; R0←[R1+8]
                                   ; R1←R1+8
  • 關於STMxx和LDMxx指令

.. image:: /embedded/stm.png

Q17:attribute(( naked )) naked是幹嘛˙?

function經過compiler compile後都會在function entry和exit加入一些code,如save used registers,add return code.

但是如果不想要compiler加上這些code,就可以在function宣告時加上attribut : naked

.. code-block:: c

void funA(void) __attribute__ ( (naked))
{
...
 asm("ret");
}

Q18: vPortEnterCritical 程式碼為何能確保一次只有一個task進入critical section uxCriticalNesting++ ?

.. code-block:: c

funcA(){
    vPortEnterCritical();
    ...
    funcB();
    ...
    vPortExitCritical();
}
funcB(){
    vPortEnterCritical();
    ...
    vPortExitCritical();
}

Q19 :schedule 那邊[ 在一般非即時作業系統上,通常每個task都會分到相同的CPU使用時間,RTOS則不盡然,後續將提到相關資訊 除了由kernel要求task交出CPU控制權外,各task也能夠選擇自行交出CPU控制權,舉凡]作更正

更新在上面內容

Q20:關於 xTaskIncrementTick() 程式碼

更新在上面內容

Q21: queue實作是不是cycle?

是,參考

.. code-block:: c

pxQueue->pcWriteTo += pxQueue->uxItemSize;                                                                                                                                     
                if( pxQueue->pcWriteTo >= pxQueue->pcTail ) /*lint !e946 MISRA exception justified as comparison of pointers is the cleanest solution. */
                {
                        pxQueue->pcWriteTo = pxQueue->pcHead;
                }
                else
                {
                        mtCOVERAGE_TEST_MARKER();
                }

參考資料

  • The Architecture of Open Source Applications: FreeRTOS<http://www.aosabook.org/en/freertos.html>_
    • 簡體中文翻譯<http://www.ituring.com.cn/article/4063>_
  • Study of an operating system: FreeRTOS</embedded/FreeRTOS_Melot.pdf>_
  • FreeRTOS 即時核心實用指南</embedded/FreeRTOS-manual-zh.pdf>_