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

版本 9aeaf35d3ade38525c0750d6e00adb11b33939a9

embedded/freertos

Changes from 9aeaf35d3ade38525c0750d6e00adb11b33939a9 to 2764c7927fe1c1b6c4379c4dea5dc4e782e836f5

---
title: FreeRTOS
toc: no
...

組員
----
* 梁穎睿 / TheKK 
* 李奇霖 / Shinshipower
* 方威迪 / waynew30777 
* 陳盈伸 / shin21 

共筆
----
* `Link<https://hackpad.com/FreeRTOSV8.0.0-PU3awKuzHz6#:h=%3Chardware-interfacing%3E>`_

FreeRTOS架構
-----------

FreeRTOS是一個相對較小的應用程式。最小化的FreeRTOS內核僅包括3個(.c)文件和少數標頭檔,總共不到9000行程式碼,還包括了註解和空行。一個典型的編譯後(二進制)binary小於10KB。

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

●任務:大約有一半的FreeRTOS的核心程式碼用來處理多數作業系統首要關注的問題:任務。任務是給定優先權的用戶定義的C函數。 task.c和task.h完成了所有有關創造,排程,和維護任務的繁重工作。

●通訊:任務很重要,不過任務間可以互相通訊則更為重要!它給我們帶來FreeRTOS的第二項任務:通訊。大約40%的FreeRTOS核心程式碼是用來處理通訊的。 queue.c和queue.h是負責處理FreeRTOS的通訊的。任務和中斷使用隊列互相發送數據,並且使用semaphore和mutex來發送critical section的使用情況。

●硬體界面:接近9000行的程式碼拼湊起基本的FreeRTOS,是和硬體無關的;相同的程式碼都能夠運行,大約有6%的FreeRTOS的核心代碼,在硬體無關的FreeRTOS內核與硬體相關的程式碼間扮演著墊片的角色。我們將在下個部分討論硬體相關的程式碼。

硬體方面:

portmacro.h:

    定義了硬體相關變數,如資料形態定義,以及硬體相關的函式呼叫的名稱定義(以portXXXXX爲名)等,統一各平臺呼叫函式的

port.c:

    定義了包含和硬體相關的程式碼的實作

FreeRTOSConfig.h:

    包含Clock speed, heap size, mutexes等等都在此定義


Task的狀態

.. image:: /Task狀態.png


* Ready : 準備好要執行的狀態
* Running : 正在給CPU執行的狀態
* Block : 等待中的狀態
* Suspended :等待中的狀態

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



* Ready list的資料形態

<<命名規則>>
>>變數
    char類型:以 c 為字首
    short類型:以 s 為字首
    long類型:以 l 為字首
    float類型:以 f 為字首
    double類型:以 d 為字首
    Enum變數:以 e 為字首
    其他(如struct):以 x 為字首
    pointer有一个額外的字首 p , 例如short類型的pointer字首為 ps
    unsigned類型的變數有一個額外的字首 u , 例如unsigned short類型的變數字首為 us 
    
>>Functions
    文件内:以 prv 為字首
    API:以其return類型為字首,按照對變數的定義
    名字:以其所在的文件名開頭。如vTaskDelete即在Task.c文件中名稱


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

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

* Context Switch 時選出下一個欲執行的task

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

.. code-block:: c

    /* In file: Source/tasks.c */
    while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopReadyPriority ] ) ) )
    {
        configASSERT( uxTopReadyPriority );
        --uxTopReadyPriority;
    }
    listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopReadyPriority ] ) );
    
    /*In file: Source/include.h */
    #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

TCB的資料結構:

<<<<<<< edited
.. code-block:: c
=======
 .. code-block:: prettyprint 

    typedef struct tskTaskControlBlock
    {
    volatile portSTACK_TYPE *pxTopOfStack;                  /* Points to the location of
>>>>>>> 9aeaf35d3ade38525c0750d6e00adb11b33939a9

     typedef struct tskTaskControlBlock
    {
<<<<<<< edited
        volatile portSTACK_TYPE *pxTopOfStack;                  /* Points to the location of
                                                                                                   the last item placed on 
                                                                                                   the tasks stack.  THIS 
                                                                                                   MUST BE THE FIRST MEMBER 
                                                                                                   OF THE STRUCT. */

     xListItem    xGenericListItem;                          /* List item used to place 
                                                                                   the TCB in ready and 
                                                                                   blocked queues. */

     xListItem    xEventListItem;                            /* List item used to place 
                                                                                  the TCB in event lists.*/

     unsigned portBASE_TYPE uxPriority;                      /* The priority of the task
                                                                                            where 0 is the lowest 
                                                                                            priority. */

=======
      volatile portSTACK_TYPE *pxTopOfStack;                  /* Points to the location of
                                                             the last item placed on 
                                                             the tasks stack.  THIS 
                                                             MUST BE THE FIRST MEMBER 
                                                             OF THE STRUCT. */

      xListItem    xGenericListItem;                          /* List item used to place 
                                                             the TCB in ready and 
                                                             blocked queues. */
      xListItem    xEventListItem;                            /* List item used to place 
                                                             the TCB in event lists.*/
      unsigned portBASE_TYPE uxPriority;                      /* The priority of the task
                                                             where 0 is the lowest 
                                                             priority. */
>>>>>>> 9aeaf35d3ade38525c0750d6e00adb11b33939a9
      portSTACK_TYPE *pxStack;                                /* Points to the start of 
                                                             the stack. */
                                                                                       the stack. */

      signed char    pcTaskName[ configMAX_TASK_NAME_LEN ];   /* Descriptive name given 
                                                             to the task when created.
                                                             Facilitates debugging 
                                                             only. */
                                                                                                                  to the task when created.
                                                                                                                  Facilitates debugging 
                                                                                                                  only. */

      #if ( portSTACK_GROWTH > 0 )
        portSTACK_TYPE *pxEndOfStack;                         /* Used for stack overflow 
                                                             checking on architectures
                                                             where the stack grows up
                                                             from low memory. */
                                                                                             checking on architectures
                                                                                             where the stack grows up
                                                                                             from low memory. */
      #endif

      #if ( configUSE_MUTEXES == 1 )
        unsigned portBASE_TYPE uxBasePriority;                /* The priority last 
                                                             assigned to the task - 
                                                             used by the priority 
                                                             inheritance mechanism. */
                                                                                                 assigned to the task - 
                                                                                                 used by the priority 
                                                                                                 inheritance mechanism. */
      #endif

    } tskTCB;


pxTopOfStack , pxEndOfStack :紀錄Stack的大小

uxPriority , uxBasePriority :紀錄優先權 ,而後者是紀錄原本的優先權(可能發生再Mutux)

xGenericListItem , xEventListItem : 當一個任務被放入到FreeRTOS的一個列表中 ,被插入pointer的地方

xTaskCreate()函數被調用的時候,一個任務被創建。 FreeRTOS為一個任務新分配一個TCB對象,來記錄它的名稱,優先級,和其他細節,接著分配用戶請求的總的堆棧(假設有足夠使用的內存)和在TCB的pxStack成員中記錄堆內存的開始。

而這段程式碼就是ARM Cortex-M4將剛建造好的好的task將暫存器裡的資訊存入stck裡
 
.. code-block:: c

    StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void  *pvParameters )
<<<<<<< edited
    {
        /* Simulate the stack frame as it would be created by a context switch
        interrupt. */
=======
     {
	/* Simulate the stack frame as it would be created by a context switch
	interrupt. */
>>>>>>> 9aeaf35d3ade38525c0750d6e00adb11b33939a9

	/* Offset added to account for the way the MCU uses the stack on entry/exit
	of interrupts, and to ensure alignment. */
	pxTopOfStack--;
        /* 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 */
        *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 */
        /* 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;
        /* 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. */
        pxTopOfStack -= 8;	/* R11, R10, R9, R8, R7, R6, R5 and R4. */

<<<<<<< edited
        pxTopOfStack;
=======
	return pxTopOfStack;
>>>>>>> 9aeaf35d3ade38525c0750d6e00adb11b33939a9
    }

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

 .. code-block:: c

<<<<<<< edited
    void xPortPendSVHandler( void )
    {	/* This is a naked function. */
        __asm volatile
        (
        "	mrs r0, psp							\n"
        "	isb									\n"
        "										\n"
        "	ldr	r3, pxCurrentTCBConst			\n" /* Get the location of the current TCB. */
        "	ldr	r2, [r3]						\n"
        "										\n"
        "	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 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"
        "										\n"
        "	ldr r1, [r3]						\n" /* The first item in pxCurrentTCB is the task top of stack. */
        "	ldr r0, [r1]						\n"
        "										\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"
        "	.align 2							\n"
        "pxCurrentTCBConst: .word pxCurrentTCB	\n"
        ::"i"(configMAX_SYSCALL_INTERRUPT_PRIORITY)
        );
}
=======
     void xPortPendSVHandler( void )
     {	/* This is a naked function. */
    	__asm volatile
    	(
    	"	mrs r0, psp							\n"
    	"	isb									\n"
    	"										\n"
  	"	ldr	r3, pxCurrentTCBConst			\n" /* Get the location of the current TCB. */
	"	ldr	r2, [r3]						\n"
	"										\n"
	"	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 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"
	"										\n"
	"	ldr r1, [r3]						\n" /* The first item in pxCurrentTCB is the task top of stack. */
	"	ldr r0, [r1]						\n"
	"										\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"
	"	.align 2							\n"
	"pxCurrentTCBConst: .word pxCurrentTCB	\n"
	::"i"(configMAX_SYSCALL_INTERRUPT_PRIORITY)
	);
    }
>>>>>>> 9aeaf35d3ade38525c0750d6e00adb11b33939a9

* 硬體驅動原理
-----------
* 以 `GPIO</embedded/GPIO>`_ 為例
  - 參考 STM32Cube_FW_F4_V1.1.0/Projects/STM32F429I-Discovery/Examples/GPIO/GPIO_EXTI/readme.txt
創造全新task
TCB的資料結構:

效能表現
--------
●context switch
我們想得知FreeRTOS的context switch時間,並想出一個測試方法:

.. 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值。

2. 設定一個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按下時,下面程式是我們的實作:

.. 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

●realtime capability

參考資料
--------
* `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>`_