Freertos Task

在過去Cortex-M單晶片開發經驗中,使用過新唐、Renesas、STM32和TI,搭配使用過的開發IDE有IAR、e2studio、STM32CUBE、keilc、和CCS,主要focus在算法/功能的開發,從來沒認真探討過這些MCU的底層基礎建設(HAL、BSP、bootloader、RTOS)以及IDE背後是如何對MCU進行Compile and fLASH(Cross Compile),並且透過Uart在電腦端進行real-time debug,所以這次想透過STM32加上Ubuntu的開源環境,對整個嵌入式系統編譯過程進行學習。

如此未來在開發嵌入式系統時就不會被該產品的IDE綁定,能使用各種opensource的editor如vim、vscode、atom等,此外能對RTOS的系統有更深入的了解。

另外開發經驗中都是以bare-metal的方式,簡單以幾個interrupt priority設定完成整個專案所需功能,但當整個系統越來越複雜non-OS的開發方式肯定是不夠的,考慮到模組化開發需要有分時多工(TDM)的程式架構,所以這次也會透過STM32所提供的Motor Control(MC) SDK中的FreeRTSO範例對MCU底層基礎建設和RTOS概念進行一個完整的學習,並且以RTOS的角度去分析stm32的MCSDK。

DAY1 實驗環境建制

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
我的開發環境

  • 開發板 STM32 NUCLEO G474RE
    附帶st-link v2電路 使用SWD介面對stm32燒錄
  • 開發環境 Mac Sonoma + VSCODE editor
  • Compiler ARM-GCC
  • debugger ST-LINK

在x86電腦中使用gcc提供的arm compiler進行cross compile,compile後會產生16進位檔.hex,再透過openocd燒錄置mcu的memory中。

在單晶片開發時,大部分都採用燒錄後執行程式,並且透過UART吐出數據的方式來debug。不過隨著系統的複雜度上升,這樣的開發方式逐漸會遇到瓶頸。也因此,比較大的晶片例如32位元的,通常會內建JTAG/SWD/SWIM等燒錄介面,搭配OpenOCD+GDB進行debug(簡單來說就是能夠單步執行並且隨時能知道記憶體內容),整體系統架構如下圖所示:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

from Linaro

實做過程

  1. VSCODE C/C++ CMAKE CMAKE TOOLS Cortex-Debug

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →
    !
    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

  2. STM32CUBE
    直接官網下載,我們需要他來產生相應板子的MAKEFILE。

  3. OpenOCD燒錄工具
    OpenOCD is a free-software tool mainly used for on-chip debugging, in-system programming and boundary-scan testing.

sudo apt install openocd
  1. gcc-arm-none-eabi compiler
    直接到官網Arm GNU下載,並利用增加環境變數(.bashrc)的方式進行安裝。

  2. STM32Cube產生Makefile

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

  3. vscode的環境設置參考一下YT(不然main.c會有error)
    vscode環境設置

  4. compile toggle led project 生成.hex檔案

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

    Image Not Showing Possible Reasons
    • The image file may be corrupted
    • The server hosting the image is unavailable
    • The image path is incorrect
    • The image format is not supported
    Learn More →

  5. OpenOCD與STlink連線

openocd -f interface/stlink-v2.cfg -f target/stm32f3x.cfg

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

  • openocd目前support的燒錄器這查看/usr/share/openocd/scripts/interface
  • openocd目前support的chip這查看/usr/share/openocd/scripts/target
    由圖知可用talnet對port4444進行連線
telnet localhost 4444

接下來進行燒錄與reset後程式成功燒進mcu

program /home/tomt/stm32/TEST/build/TEST.hex
reset

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

測試後發現arm gdb工具在ubuntu 22.04 有bug 目前無法使用gdb工具

解決方法

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

DAY2 WHAT IS RTOS?

The type of an operating system is defined by how the scheduler decides which program to run when. For example, the scheduler used in a multi user operating system (such as Unix) will ensure each user gets a fair amount of the processing time. As another example, the scheduler in a desk top operating system (such as Windows) will try and ensure the computer remains responsive to its user.

The scheduler in a Real Time Operating System (RTOS) is designed to provide a predictable (normally described as deterministic) execution pattern. This is particularly of interest to embedded systems as embedded systems often have real time requirements. A real time requirements is one that specifies that the embedded system must respond to a certain event within a strictly defined time (the deadline). A guarantee to meet real time requirements can only be made if the behaviour of the operating system's scheduler can be predicted (and is therefore deterministic).

Traditional real time schedulers, such as the scheduler used in FreeRTOS, achieve determinism by allowing the user to assign a priority to each thread of execution. The scheduler then uses the priority to know which thread of execution to run next. In FreeRTOS, a thread of execution is called a task

From freeRTOS官網的介紹

即時作業系統系統架構

接下來以Motor Control為例子介紹何謂RTOS,基本上RTOS系統可分為Hardware-independent的Thread(Task) & interrupt,跟Hardware-dependent的Kernel,若以Motor Control來講其包括的Task和Kernel舉例如下。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
在freeRTOS中稱Thread為Task

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

from 工研院 嵌入式即時作業系統於運動控制領域之運用

TASK

  1. Medium Frequency Tasks of each motors
    • Motor position/speed control
    • Motor Impedance control
    • AIR condition motor temperature fuzzy control
  2. Safety Task
    • Bus voltage and temperature monitor
  3. Power Factor Correction Task(Control the AC power supply power factor)
  4. User Interface task

Background interrupt

其中核心的中斷只有ADC中斷,會進行FOC運算,更新Motor每個周期所需之PWM,其餘的外部中斷都非常簡單。

  1. ADC interrupt - Motor Current loop(High frequecny ISR)
  2. USART or I2C
  3. Button I/O interrupt
  4. Non-maskable Interrupt(NMI)

KERNEL

  1. HAL driver
    • ADC
    • GPIO
    • TIMER(PWM)
    • RCC
    • I2C UART
  2. BSP
  3. RTOS - freeRTOS
  4. Motor control LIB(FOC)
    • SVPWM
    • MTPA
    • PID controller
    • Sensorless Algorithm
    • 座標轉換
    • ETC..

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
在STM32中把RTOS & Motor control LIB稱作MiddleWares,用詞似乎不準確。

以下是本人對ROTS的理解,RTOS的Real time其注重的是核心background interrupt的即時性,例如freeRTOS中會設定讓Kernel的SystemTick中斷的priority為最低,也就是在FreeRTOS中Kernel的時脈xPortSysTickHandler優先權,所以表示任何在FreeRTOS kernel上執行的軟體Task,都不會打擾核心硬體中斷,例如 : motor current control loop(更新PWM)。

只有在要進行一些核心的程式碼操作時,例如:scheduling、queue等操作時,會強制MASK所有的interrupt,防止在使用FreeRTOS API的情況下,Kernel被打擾造成kernel crush,如此在獲得freeRTOS所提供的好處的同時,還能確保控制系統中重要的中斷(current loop),能夠即時的更新PWM命令去控制馬達。

另外較不重要的外部中斷,例如 : I2C、UART、Button I/O這類的中斷通常非常簡單,會瞬間執行完畢,其目的只是要透過中斷去呼叫使用者針對這個I/O所要做的Task,取代Polling。

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
The priorities between Task and Interrupt
Interrupt priorities are purely a hardware thing. Task priorities are purely a software thing. Tasks have the ability to mask out interrupts, but if interrupts are not masked they will always interrupt a task – even if it is the lowest priority interrupt that is interrupting the highest priority task.

但在freeRTOS的官方文件中也有提到,在使用freeRTOS時對於整理程式的效率不一定會提高,雖然freeRTOS能減少許多非必要的運算(CPU IDLE),但也增加了RTOS tick跟Contex switch的負擔,所以運算效率還是要取決於實際應用,但將程式模塊化方便閱讀、維護與擴充這點確實是freeRTOS的優點。

而如果是在更複雜的機器人控制系統或是無人機系統中,RTOS的使用就是必要的,可以想像機器人控制系統中,會將需要即時處理的FOC運算放在中斷(或直接以ASIC硬體實現),而各種位置、運動軌跡、姿態和視覺的計算則以TASK的方式進行分時多工(TDM),不只模組化方便後須維護,且在各Task之間還提供了插隊機制,可將重要的任務(例如:硬體保護任務)指定較高優先權,例如﹔知名的無人機專案Pixhawk中使用的Nuttx

Why use an RTOS?

RTOS中經常看到的名詞解釋

  • Jiffies
    Jiffies is a unit of time used in computing to represent the duration of a single tick of the system timer interrupt. It's commonly used in the Linux kernel to keep track of the elapsed time since the system was booted. The length of a jiffy can vary depending on the system configuration and can be expressed in either clock cycles or in nanoseconds. When we talk about freertos in Stm32 MCU, freertos using sysmtem tick under the help of HAL to determine the Jiffies.

    • Jitter
  • CONTEXT SWITCH
    A context switch is the process of storing the state of a running task (also known as a "process") and loading the state of a different task. This allows multiple tasks to share a single CPU, as the CPU appears to be executing only one task at a time. In a context switch, the operating system saves the current state of the task that was running, including its register values and program counter, and then restores the state of the task that is to resume execution. Context switches can occur due to various reasons, such as the scheduling of a higher-priority task, a task yielding voluntarily, or the completion of a task. Context switches can have an overhead in terms of performance and should be minimized as much as possible for efficient system operation.

  • POSIX STANDARDS
    POSIX (Portable Operating System Interface) is a family of standards specified by the IEEE for maintaining compatibility between operating systems. The POSIX standards define a common interface for operating system services such as file and process management, user authentication, and inter-process communication, among others.
    POSIX is designed to be a portable interface, meaning that software written to comply with the POSIX standards can be run on any operating system that implements those standards. This allows developers to write software that can run on multiple platforms with minimal changes, increasing compatibility and portability.
    POSIX is widely used in Unix-like operating systems, as well as in some real-time operating systems. It is also used as a basis for many programming APIs and is supported by many programming languages.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
在freeRTOS中好像也能夠設定POSIX標準

Day3 Learning FreeRTOS

透過成大師生整理的NCKU wiki、freertos的官網以及Real-time Operating Systems Book 2來學習freertos。

官方網站: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的通訊,任務(Task)和中斷(interrupt)使用佇列(queue)互相發送數據,並且使用semaphore 和 mutex 來派發 critical section 的使用信號(Defer interrupt)。
  • 硬體界面 (HAL, hardware abstraction layer):有近 9000 行的程式碼組成基本的FreeRTOS,這部份是與硬體無關的(hardware-independent),同一份程式碼在不同硬體平台上的FreeRTOS 都可以運行。大約有 6% 的 FreeRTOS 核心代碼,在與硬體無關的FreeRTOS核心和與硬體相關的程式碼間扮演著墊片(shim)的角色。

資料型態及命名規則

在不同硬體裝置上,通訊埠設定上也不同,定義在portmacro.h標頭檔內,有兩種特殊資料型態portTickType以及portBASE_TYPE。另外FreeRTOS明確的定義變數名稱以及資料型態,不會有unsigned以及signed搞混使用的情形發生。

  • portTickType : 用以儲存tick的計數值,可以用來判斷block次數
  • portBASE_TYPE :定義為架構基礎的變數,隨各不同硬體來應用,如在32-bit架構上,其為32-bit型態,最常用以儲存極限值或布林數。
  • 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)
  • 巨集名稱:巨集在FreeRTOS裡皆為大寫字母定義,名稱前小寫字母為巨集定義的地方
    • portMAX_DELAY : portable.h
    • configUSE_PREEMPTION : FreeRTOSConfig.h
  • 一般巨集回傳值定義pdTRUE 及 pdPASS為1 , pdFALSE 及 pdFAIL 為0。

TASK Status

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
TASK的狀態
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

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

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
Blocked vs Suspended
blocked 是說如果有個 task 將要等待某個目前無法取得的資源(mutex lock or waiting semaphore),則會被設為 blocked 狀態(這是被動的),OS 會呼叫 blocking API 來設定 task 進入 blocked queue。而suspended 與 blocked 的差異在於,suspended 是 task 主動呼叫 API 來要求讓自己進入暫停狀態的,每一種狀態 FreeRTOS 都會給予一個 list 儲存(除了 running)。

一個典型的Task如下

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

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

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

Blocked
Task 的 blocked 狀態通常是 task 進入了一個需要等待某事件發生的狀態,這個事件通常是執行時間到了(例如 systick interrupt)或是同步處理的回應,如果像一開始的 ATaskFunciton() 中使用 while(1){} 這樣的無限迴圈來作等待事件,會占用 CPU 運算資源,也就是 task 實際上是在 running,但又沒做任何事情,占用著資源只為了等待 event,所以比較好的作法是改用 vTaskDelay(),當 task 呼叫了 vTaskDelay(),task 會進入 blocked 狀態,就可以讓出 CPU 資源了。

使用 infinite loop 的執行時序圖


使用 vTaskDelay() 的執行時序圖

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
vTaskDelay():這個函式的參數如果直接給數值,是 ticks,例如 vTaskDelay(250) 是暫停 250 個 ticks 的意思,由於每個 CPU 的一個 tick 時間長度不同,FreeRTOS 提供了 portTICK_RATE_MS 這個巨集常數,可以幫我們轉換 ticks 數為毫秒 (ms),也就是說 vTaskDelay( 250/portTICK_RATE_MS ) 這個寫法,就是讓 task 暫停 250 毫秒(ms)的意思 (v8.2.1 改名為 portTICK_PERIOD_MS)

Suspended
如果一個 task 會有一段時間不會執行,那就可以進入 suspend 狀態。
例如有個 task 叫做 taskPrint,只做 print 資料,而有好幾個 operation 負責做運算,若運算要很久,則可以把 taskPrint 先丟入 suspend 狀態中,直到所有運算皆完成後,再喚醒 taskPrint 進入 ready 狀態,最後將資料 print 出來。

Ready list

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

這邊就想到之前上過Jserv大的Linux核心設計線上課程,其中提到所謂嵌入型的list概念。只要在自定義的結構中加入 struct list_head,就可以搭配 Linux 中一系列的 linked list 操作。這部分的詳細實作可以參考Jserv的教材 lab0-c。細節關於嵌入型list可參考你所不知道的C語言linked list篇

//TASK CONTROL BLOCK(TCB) Data struct
/* 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 的地方

DAY4 FreeRTOS TASK PART Code review

task.c task.h 負責所有關於建立、排程和維護任務的繁重工作

taskSELECT_HIGHEST_PRIORITY_TASK

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

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

#define taskSELECT_HIGHEST_PRIORITY_TASK()														
{
    /* 選出含有 ready task 的最高優先權 queue */								
    while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopReadyPriority ] ) ) )						
    {																								
        configASSERT( uxTopReadyPriority );	     //如果找不到則 assert exception														
        --uxTopReadyPriority;																		
    }																								
																									
    /* listGET_OWNER_OF_NEXT_ENTRY indexes through the list, so the tasks of						
       the same priority get an equal share of the processor time. */									
       listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopReadyPriority ] ) );		
} 

一個ReadyTaskList中的每個索引(pxIndex)各自指向了一串 task listt,所以listGET_OWNER_OF_NEXT_ENTRY 就是在某個ReadyTaskList索引中去取得其中 task list 裡某個 task 的 TCB

#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;
}

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
configASSERT()程式開階段所使用的DEBUG工具,當輸入為false會關閉中斷功能進入無限迴圈。

/* Define configASSERT() to disable interrupts and sit in a loop. */ 
#define configASSERT( x )         if( x == 0 ) { taskDISABLE_INTERRUPTS(); for(;;); }

xTaskCreate()

建立TASK的函數 xTaskCreate

BaseType_t xTaskCreate(TaskFunction_t pxTaskCode,
                        const char * const pcName,		
                        const configSTACK_DEPTH_TYPE usStackDepth,
                        void * const pvParameters,
                        UBaseType_t uxPriority,
                        TaskHandle_t * const pxCreatedTask )
  • pvTaskCode:就是我們定義好用來建立 task 的 C 函數
  • pcName:任意給定的 task name,這個名稱只被用來作識別,不會在 task 管理中被採用
  • usStackDepth:堆疊的大小
  • pvParameters:要傳給 task 的參數陣列,也就是我們在 C 函數宣告的參數
  • uxPriority:定義這個任務的優先權,在 FreeRTOS 中,0 最低,(configMAX_PRIORITIES – 1) 最高
  • pxCreatedTask:handle,是一個被建立出來的 task 可以用到的識別符號
  • 配置TCB及stack的函式 - prvAllocateTCBAndStack()
    xTaskCreate() 函數被呼叫的時候,一個任務會被建立。FreeRTOS 會為每一個任務分配一個新的 TCB target,用來記錄它的名稱、優先權和其他細節,接著配置用戶所請求的 Heap and Stack 空間(假設有足夠使用的記憶體),並在 TCB 的 pxStack 成員中記錄 Stack 的記憶體起始位址。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
WHY using dynamic alloca for TCB?

TCB_t *pxNewTCB;
BaseType_t xReturn;

    /* If the stack grows down then allocate the stack then the TCB so the stack
    does not grow into the TCB.  Likewise if the stack grows up then allocate
    the TCB then the stack. */
    #if( portSTACK_GROWTH > 0 )
    {
        /* Allocate space for the TCB.  Where the memory comes from depends on
        the implementation of the port malloc function and whether or not static
        allocation is being used. */
        pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );

        if( pxNewTCB != NULL )
        {
            /* Allocate space for the stack used by the task being created.
            The base of the stack memory stored in the TCB so the task can
            be deleted later if required. */
            pxNewTCB->pxStack = 
                ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); 

            if( pxNewTCB->pxStack == NULL )
            {
                /* Could not allocate the stack.  Delete the allocated TCB. */
                vPortFree( pxNewTCB );
                pxNewTCB = NULL;
            }
        }
    }
    #else /* portSTACK_GROWTH */
    {
    StackType_t *pxStack;

        /* Allocate space for the stack used by the task being created. */
        pxStack = pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); 

        if( pxStack != NULL )
        {
            /* Allocate space for the TCB. */
            pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); 

            if( pxNewTCB != NULL )
            {
                /* Store the stack location in the TCB. */
                pxNewTCB->pxStack = pxStack;
            }
            else
            {
                /* The stack cannot be used as the TCB was not created.  Free
                it again. */
                vPortFree( pxStack );
            }
        }
        else
        {
            pxNewTCB = NULL;
        }
    }
    #endif /* portSTACK_GROWTH */

    if( pxNewTCB != NULL )
    {
        #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) 
        {
            /* Tasks can be created statically or dynamically, so note this
            task was created dynamically in case it is later deleted. */
            pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
        }
        #endif /* tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE */

        prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, 
                             pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
        prvAddNewTaskToReadyList( pxNewTCB );
        xReturn = pdPASS;
    }
    else
    {
        xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
    }

    return xReturn;

在進行pvPortMalloc時候,會先進行vTaskSuspendAll(); ,藉由不發生context swiitch的swap out動作,配置記憶體空間,等到配置完成再呼叫xTaskResumeAll()。pvPortMalloc在heap.c裡面定義,基本上就是做記憶體配置,根據各不同port去實作pvPortMalloc。

void *pvPortMalloc( size_t xWantedSize )
{
BlockLink_t *pxBlock, *pxPreviousBlock, *pxNewBlockLink;
void *pvReturn = NULL;

	/* The heap must be initialised before the first call to
	prvPortMalloc(). */
	configASSERT( pxEnd );

	vTaskSuspendAll();
	{
        //...
	}
	( void ) xTaskResumeAll();
	return pvReturn;
}

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

/* 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 還沒設定好前就被中斷設定(例如 systick)。

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

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
利用組語進行實際記憶體的操作 但why用組語?

/* 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()藉此 mask 遮罩掉所有的 interrupt (所有優先權低於configMAX_SYSCALL_INTERRUPT_PRIORITY 的 task 將無法被執行)

使用FreeRTOS,main中的架構如下

int main(void)
{  
    // initialization
    xTaskCreate(); //TASK1
     xTaskCreate(); //TASK2
    // ...
    vTaskStartScheduler();  
    while(1);   
}

當使用 vTaskCreate() 將 task 被建立出來以後,需要使用 vTaskStartScheduler() 來啟動排程器決定讓哪個 task 開始執行,當 vTaskStartScheduler() 被呼叫時,會先建立一個 idle task,這個 task 是為了確保 CPU 在任一時間至少有一個 task 可以執行 (取代直接切換回 kernel task) 而在 vTaskStartScheduler() 被呼叫時自動建立的 user task,idle task 的 priority 為 0 (lowest),目的是為了確保當有其他 user task 進入 ready list 時可以馬上被執行。

最後利用xPortStartScheduler()來啟動timer,並開始第一個任務xPortStartScheduler()的呼叫與硬體特性有關,所以此function會定義在HAL,以提供不同硬體架構實現。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
xPortStartScheduler

void vTaskStartScheduler( void )
{
BaseType_t xReturn;

	/* Add the idle task at the lowest priority. */
	#if ( INCLUDE_xTaskGetIdleTaskHandle == 1 )
	{
		/* Create the idle task, storing its handle in xIdleTaskHandle so it can
		be returned by the xTaskGetIdleTaskHandle() function. */
		xReturn = xTaskCreate( prvIdleTask, "IDLE", tskIDLE_STACK_SIZE, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), &xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
	}
	#else
	{
		/* Create the idle task without storing its handle. */
		xReturn = xTaskCreate( prvIdleTask, "IDLE", tskIDLE_STACK_SIZE, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), NULL );  /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
	}
	#endif /* INCLUDE_xTaskGetIdleTaskHandle */

	#if ( configUSE_TIMERS == 1 )
	{
		if( xReturn == pdPASS )
		{
			xReturn = xTimerCreateTimerTask();
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}
	#endif /* configUSE_TIMERS */

	if( xReturn == pdPASS )
	{
		/* Interrupts are turned off here, to ensure a tick does not occur
		before or during the call to xPortStartScheduler().  The stacks of
		the created tasks contain a status word with interrupts switched on
		so interrupts will automatically get re-enabled when the first task
		starts to run. */
		portDISABLE_INTERRUPTS();

		#if ( configUSE_NEWLIB_REENTRANT == 1 )
		{
			/* Switch Newlib's _impure_ptr variable to point to the _reent
			structure specific to the task that will run first. */
			_impure_ptr = &( pxCurrentTCB->xNewLib_reent );
		}
		#endif /* configUSE_NEWLIB_REENTRANT */

		xSchedulerRunning = pdTRUE;
		xTickCount = ( TickType_t ) 0U;

		/* If configGENERATE_RUN_TIME_STATS is defined then the following
		macro must be defined to configure the timer/counter used to generate
		the run time counter time base. */
		portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

		/* Setting up the timer tick is hardware specific and thus in the
		portable interface. */
		if( xPortStartScheduler() != pdFALSE )
		{
			/* Should not reach here as if the scheduler is running the
			function will not return. */
		}
		else
		{
			/* Should only reach here if a task calls xTaskEndScheduler(). */
		}
	}
	else
	{
		/* This line will only be reached if the kernel could not be started,
		because there was not enough FreeRTOS heap to create the idle task
		or the timer task. */
		configASSERT( xReturn );
	}
}

刪除TASK的函數

void vTaskDelete( xTaskHandle pxTaskToDelete );

pxTaskToDelete: 利用handle去識別出哪一個task。 這種可能性存在於如果在 loop 中發生執行錯誤 (fail),則需要跳出迴圈並終止(自己)執行,此時就需要使用 vTaskDelete 來刪除自己,發生錯誤的例子:

  1. 假如今天一個 task 是要存取資料庫,但是資料庫或資料表不存在,則應該結束 task
  2. 假如今天一個 client task 是要跟 server 做連線( listening 就是 loop),卻發現 client 端沒有網路連線,則應結束 task

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
handle目前還不確定用途,TaskDelete實際用法待補充。

TASK排程

在scheduler啟動之後FreeRTOS主要靠三個中斷,去進行任務的切換,執行CONTEXT SWITCH,在FreeRTOS中會將原本系統的中斷Systick_Handler、SVC_Handler和PendSV_Handler重新指定給FreeRTOS用,至於這三個在系統中有甚麼作用可參考

/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
standard names. */
#define vPortSVCHandler    SVC_Handler
#define xPortPendSVHandler PendSV_Handler
#define xPortSysTickHandler SysTick_Handler

//待整理

DAY5 CMSIS RTOS API

在ST MCSDK中TASK在建立的時候會被稱作Thread,並且利用CMSIS-RTOS API進行封裝如下,其中instance就是pvParameters的數量,這邊我們設定為0,osThreadCreate第二參數為NULL

  /* Create the thread(s) */
  /* definition and creation of mediumFrequency */
  osThreadDef(mediumFrequency, startMediumFrequencyTask, osPriorityNormal, 0, 128);
  mediumFrequencyHandle = osThreadCreate(osThread(mediumFrequency), NULL);
/// Thread Definition structure contains startup information of a thread.
/// \note CAN BE CHANGED: \b os_thread_def is implementation specific in every CMSIS-RTOS.
typedef struct os_thread_def  {
  char                   *name;        ///< Thread name 
  os_pthread             pthread;      ///< start address of thread function
  osPriority             tpriority;    ///< initial thread priority
  uint32_t               instances;    ///< maximum number of instances of that thread function
  uint32_t               stacksize;    ///< stack size requirements in bytes; 0 is default stack size
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
  uint32_t               *buffer;      ///< stack buffer for static allocation; NULL for dynamic allocation
  osStaticThreadDef_t    *controlblock;     ///< control block to hold thread's data for static allocation; NULL for dynamic allocation
#endif
} osThreadDef_t;/// Thread Definition structure contains startup information of a thread.
/// \note CAN BE CHANGED: \b os_thread_def is implementation specific in every CMSIS-RTOS.
typedef struct os_thread_def  {
  char                   *name;        ///< Thread name 
  os_pthread             pthread;      ///< start address of thread function
  osPriority             tpriority;    ///< initial thread priority
  uint32_t               instances;    ///< maximum number of instances of that thread function
  uint32_t               stacksize;    ///< stack size requirements in bytes; 0 is default stack size
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
  uint32_t               *buffer;      ///< stack buffer for static allocation; NULL for dynamic allocation
  osStaticThreadDef_t    *controlblock;     ///< control block to hold thread's data for static allocation; NULL for dynamic allocation
#endif
} osThreadDef_t;

Generic RTOS Interface (CMSIS standards)

FreeRTOS 通訊 -> queue實作 待續~~

FreeRTOS API 整理 待續

azureRTOS/FreeRTOS Porting 待續

FreeRTOS VS AzureRTOS

Defer interrupt實作 待續

AzureRTOS with MCSDK實作