筆記:FreeRTOS 要點總結


一、基本配置

1.1 數據類型

FreeRTOS 使用的數據類型主要分為 stdint.h 文件中定義的和自己定義的兩種
FreeRTOS 主要自定義了以下四種數據類型:
TickType_t----32 位無符號數( 32位MCU,配置configUSE_16_BIT_TICKS = 0)
BaseType_t----32 位有符號數(32位MCU)
UBaseType_t---32 位無符號數(BaseType_t類型無符號版本)
StackType_t----32 位變量(棧變量數據類型定義,32位MCU)

1.2 代碼風格

變量

  • uint32_t 定義的變量都加上前綴 ul。u 代表 unsigned 無符號,l 代表 long 長整型。
  • uint16_t 定義的變量都加上前綴 us。u 代表 unsigned 無符號,s 代表 short 短整型。
  • uint8_t 定義的變量都加上前綴 uc。u 代表 unsigned 無符號,c 代表 char 字符型。
  • stdint.h 文件中未定義的變量類型,在定義變量時需要加上前綴 x,比如 BaseType_t 和
    TickType_t 定義的變量。
  • stdint.h 文件中未定義的無符號變量類型,在定義變量時要加上前綴 u,比如 UBaseType_t 定義
    的變量要加上前綴 ux。
  • size_t 定義的變量也要加上前綴 ux。
  • 枚舉變量會加上前綴 e。
  • 指針變量會加上前綴 p,比如 uint16_t 定義的指針變量會加上前綴 pus。
  • 根據 MISRA 代碼規則,char 定義的變量只能用於 ASCII 字符,前綴使用 c。
  • 根據 MISRA 代碼規則,char *定義的指針變量只能用於 ASCII 字符串,前綴使用 pc。

函數

  • 加上了 static 聲明的函數,定義時要加上前綴 prv,這個是單詞 private 的縮寫。
  • 帶有返回值的函數,根據返回值的數據類型,加上相應的前綴,如果沒有返回值,即 void 類型
    ,函數的前綴加上字母 v。
  • 根據文件名,文件中相應的函數定義時也將文件名加到函數命名中,比如 tasks.c 文件中函數
    vTaskDelete,函數中的 task 就是文件名中的 task。

宏定義

  • 根據宏定義所在的文件,文件中的宏定義聲明時也將文件名加到宏定義中,比如宏定義
    configUSE_PREEMPTION 是定義在文件 FreeRTOSConfig.h 里面。宏定義中的 config 就是文
    件名中的 config。另外注意,前綴要小寫。
  • 除了前綴,其余部分全部大寫,同時用下划線分開。

排版和注釋

  • 縮進
    Tab 制表符用於縮進,Tab 一次縮進 4 個字符空間。
  • 注釋
    FreeRTOS 中注釋不會超過 80 個字符寬度,除非對函數的參數進行注釋時。源碼中主要是采用/* */
    的形式進行注釋,不采用 C++中的雙斜杠風格來注釋。

1.3 關鍵配置

configCPU_CLOCK_HZ---配置MCU主頻,單位Hz
configTICK_RATE_HZ---配置系統時鍾節拍數,單位 Hz
configMAX_PRIORITIES---配置用戶可用最大優先級數,0~configMAX_PRIORITIES-1
configTOTAL_HEAP_SIZE---配置堆大小
configUSE_TASK_NOTIFICATIONS---配置任務通知
configUSE_TIME_SLICING---配置時間片輪轉調度
configLIBRARY_LOWEST_INTERRUPT_PRIORITY---受RTOS管理的最小中斷優先級
configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY---受RTOS管理的最大中斷優先級

二、任務管理

2.1 任務棧

#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 17 * 1024 ) )

1,局部變量,函數調用時的現場保護和返回地址,函數的形參,進入中斷函數前和中斷嵌套等都需要棧空間,棧空間定義小了會造成系統崩潰
2,實際分配的棧大小可以在最小棧需求的基礎上乘以一個安全系數,一般取 1.5-2
3,棧生長方向從高地址向低地址生長(M4 和 M3 是這種方式)

2.2 系統棧

任務棧不使用系統棧控件,中斷函數和中斷嵌套使用
Cortex-M3 和 M4 內核具有雙堆棧指針,MSP 主堆棧指針和 PSP 進程堆棧指針
在 FreeRTOS 操作系統中,主堆棧指針 MSP 是給系統棧空間使用的,進
程堆棧指針 PSP 是給任務棧使用的
對於 Cortex-M3 內核和未使用 FPU(浮點運算單元)功能的 Cortex-M4 內核,需要64字節
對於具有 FPU(浮點運算單元)功能的 Cortex-M4 內核,需要200字節

2.3 任務優先級

  1. FreeRTOS 中任務的最高優先級是通過 FreeRTOSConfig.h 文件中的 configMAX_PRIORITIES 進行配置的,用戶實際可以使用的優先級范圍是 0 到 configMAX_PRIORITIES – 1
  2. 用戶配置任務的優先級數值越小,那么此任務的優先級越低,空閑任務的優先級是 0
  3. 建議用戶配置宏定義 configMAX_PRIORITIES 的最大值不要超過 32
  4. FreeRTOS 中處於運行狀態的任務永遠是當前能夠運行的最高優先級任務

2.3.1 任務優先級分配

  1. IRQ 任務:IRQ 任務是指通過中斷服務程序進行觸發的任務,此類任務應該設置為所有任務里面優先級最高的
  2. 高優先級后台任務:比如按鍵檢測,觸摸檢測,USB 消息處理,串口消息處理等,都可以歸為這一類任務 低優先級的時間片調度任務:比如 emWin 的界面顯示,LED 數碼管的顯示等不需要實時執行的都可以歸為這一類任務
    實際應用中用戶不必拘泥於將這些任務都設置為優先級 1 的同優先級任務,可以設置多個優先級,只需注意這類任務不需要高實時性
  3. 空閑任務:空閑任務是系統任務
  4. 特別注意:IRQ 任務和高優先級任務必須設置為阻塞式(調用消息等待或者延遲等函數即可), 只有這樣,高優先級任務才會釋放 CPU 的使用權,,從而低優先級任務才有機會得到執行

2.3.2 中斷優先級與任務優先級

  • 中斷的優先級永遠高於任何任務的優先級,即任務在執行的過程中,中斷來了就開始執行中斷服務程序
  • 對於 STM32F103,F407 和 F429 來說,中斷優先級的數值越小,優先級越高。而 FreeRTOS的任務優先級是,任務優先級數值越小,任務優先級越低

2.3.2 任務調度

2.2.3 相關API

vTaskStartScheduler-任務啟動

  • 空閑任務和可選的定時器任務是在調用這個函數后自動創建
  • 正常情況下這個函數是不會返回的,運行到這里極有可能是用於定時器任務或者空閑任務的 heap 空 間不足造成創建失敗,此時需要加大 FreeRTOSConfig.h 文件中定義的 heap 大小
  • 如果在此類任務函數里面填的任務 ID 是 NULL,即數值 0 的話,那么執行生效的就是當前正在執行的任務

系統框架寫法

int main(void)
{
    /* 初始化外設 */

    xTaskCreate();          /* 創建開始任務 */  

    vTaskStartScheduler();  /* 開啟任務調度 */
}

void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();           /*  */進入臨界區

    xTaskCreate();  /* 創建任務  1 */
    xTaskCreate();  /* 創建任務  2 */
    xTaskCreate();  /* 創建任務  3 */

    vTaskDelete(StartTask_Handler); /* 刪除開始任務 */
    taskEXIT_CRITICAL();            /* 退出臨界區 */
}

任務創建、掛起、刪除

/* 相關配置 */
#define configSUPPORT_DYNAMIC_ALLOCATION        1                       /* 支持動態內存申請  */
//#define configSUPPORT_STATIC_ALLOCATION           1                       /* 支持靜態內存申請 */
#define configTOTAL_HEAP_SIZE                   ((size_t)(20*1024))     /* 系統所有總的堆大小,heap_x.h需要,動態申請 */

/* 任務創建 */
/* 任務創建宏定義,便於修改 */

#define START_TASK_PRIO         1       /* 任務優先級 */
#define START_STK_SIZE          256     /* 任務堆棧大小 */

TaskHandle_t StartTask_Handler;         /* 任務句柄 */
void start_task(void *pvParameters);    /* 任務函數 */

/* 任務創建函數 */

xTaskCreate((TaskFunction_t )start_task,            /* 任務函數 */
            (const char*    )"start_task",          /* 任務名稱 */
            (uint16_t       )START_STK_SIZE,        /* 任務堆棧大小 */
            (void*          )NULL,                  /* 傳遞給任務函數的參數 */
            (UBaseType_t    )START_TASK_PRIO,       /* 任務優先級 */
            (TaskHandle_t*  )&StartTask_Handler);   /* 任務句柄 */

/* 任務掛起 */
vTaskSuspend(Task1Task_Handler);

/* 任務內解掛 */
vTaskResume(Task1Task_Handler);

/* 中斷內解掛 */
BaseType_t YieldRequired;
YieldRequired=xTaskResumeFromISR(Task1Task_Handler);
portYIELD_FROM_ISR(YieldRequired);    /* 判斷是否需要調度到恢復的任務 */

/* 任務刪除 */
vTaskDelete(Task1Task_Handler);

任務調度

  • 嵌入式實時操作系統的核心就是調度器和任務切換,調度器的核心就是調度算法
搶占式務調度
  • 每個任務都有不同的優先級,任務會一直運行直到被高優先級任務搶占或者遇到阻塞式的 API 函數,搶占式調度器會獲得就緒列表中優先級最高的任務,並運行這個任
  • 根據搶占式調度器,當前的任務要么被高優先級任務搶占,
    要么通過調用阻塞式 API 來釋放 CPU 使用權讓低優先級任務執行,沒有用戶任務執行時就執行空閑任務
時間片調度
  • 實現 Round-robin 調度算法需要給同優先級的任務分配一個專門的列表,用於記錄當前就緒的任務,
    並為每個任務分配一個時間片
  • 每個任務都有相同的優先級,任務會運行固定的時間片個數或者遇到阻塞式的 API 函數
  • 在 FreeRTOS 操作系統中只有同優先級任務才會使用時間片調度
  • 默認情況下,此宏定義已經在 FreeRTOS.h 文件里面使能
  • FreeRTOS的時間片僅支持一個時鍾周期,比如你的系統時鍾節拍是1000Hz,那么時間片的大小就是1ms,時間片調度僅存在於同優先級任務
  • 在 RTOS 中,最小的時間單位為一個 tick,即 SysTick 的中斷周期,RT-Thread 和 μC/OS可以指定時間片的大小為多個 tick,但是 FreeRTOS不一樣,時間片只能是一個 tick。與其說 FreeRTOS 支持時間片,倒不如說它的時間片就是正常的任務調度
/* 相關配置,時間片長度即1/Tick中斷頻率 */
#define configUSE_PREEMPTION                    1                       /* 1使用搶占式內核,0使用協程 */
#define configUSE_TIME_SLICING                  1                       /* 1使能時間片調度(默認式使能的) */
#define configTICK_RATE_HZ                      (20)                    /* 時鍾節拍頻率,20HZ = 50ms */

/* 兩個任務優先級相等 */
#define TASK1_TASK_PRIO     2
#define TASK1_STK_SIZE      128 
TaskHandle_t Task1Task_Handler;
void task1_task(void *pvParameters);

#define TASK2_TASK_PRIO     2
#define TASK2_STK_SIZE      128 
TaskHandle_t Task2Task_Handler;
void task2_task(void *pvParameters);

三、系統延時

const TickType_t xDelayTime = pdMS_TO_TICKS(300);/* 將延時ms轉換為系統節拍 */
const TickType_t xDelayTime = 300 / portTICK_RATE_MS;/* 將延時ms轉換為系統節拍 */
/* TickType_t 等價於 portTickType */
/* 系統延時函數,單位:系統節拍,阻塞延時   portMAX_DELAY-最大延時等待 */

/* 相對延時 */
const TickType_t xDelayTime = pdMS_TO_TICKS(300);
vTaskDelay(xDelayTime);

/* 絕對延時 */
static portTickType xLastWakeTime;
const portTickType xFrequency = pdMS_TO_TICKS(500); 
vTaskDelayUntil(&xLastWakeTime,xFrequency);

如果想精確周期性執行某個任務,可以調用系統節拍鈎子函數vApplicationTickHook(),它在系統節拍中斷服務函數中被調用,因此這個函數中的代碼必須簡潔

四、軟件定時器

/* 定時器結構體、函數聲明 */
TimerHandle_t   MyTimer_Handle;    /* 定時器句柄 */
void ReloadCallback(TimerHandle_t xTimer);      /* 定時器回調函數 */

/* 創建軟件定時器 */
MyTimer_Handle=xTimerCreate((const char*        )"ReloadTimer",         /* 定時器名稱 */
                            (TickType_t         )1000,                  /* 周期1s(1000個時鍾節拍) */
                            (UBaseType_t        )pdTRUE,                /* 周期模式 */
                            (void*              )1,                     /* 定時器ID */
                            (TimerCallbackFunction_t)ReloadCallback);   /* 定時器回調函數 */
/* 定時器回調函數 */
void ReloadCallback(TimerHandle_t xTimer)
{
    //do something
}

xTimerStart(MyTimer_Handle,0);  /* 開啟定時器 */
xTimerStop(MyTimer_Handle,0);   /* 定時器停止 */
xTimerReset(MyTimer_Handle, 0); /* 定時器復位 */

五、消息隊列

/* 消息隊列宏定義 */
#define MESSAGE_Q_NUM   4           /* 發送數據的消息隊列的數量 */
#define MESSAGE_Q_ITEM_NUM  200     /* 每個消息的空間大小 */
QueueHandle_t Message_Queue;        /* 信息隊列句柄 */

/* 創建消息隊列 */
Message_Queue=xQueueCreate(MESSAGE_Q_NUM,MESSAGE_Q_ITEM_NUM);

/* 任務內消息發送 */
u8 sendData[MESSAGE_Q_ITEM_NUM];
BaseType_t err;
err=xQueueSend(Message_Queue,&senddata,10);    /* 10為發送等待時間,有可能隊列已滿,err = errQUEUE_FULL 或 err = pdPASS */

/* 中斷內消息發送 */
8 sendData[MESSAGE_Q_ITEM_NUM];
BaseType_t xHigherPriorityTaskWoken;
xQueueSendFromISR(Message_Queue,sendData,&xHigherPriorityTaskWoken);    /* 向隊列中發送數據,返回值,依然是 滿了或Pass,第三個參數是判斷高優先級 */接受到隊列后,退出中斷,是否需要調度
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);/*如果需要的話進行一次任務切換  */

/* 任務內消息接收 */
u8 *receiveData;
xQueueReceive(Message_Queue,receiveData,portMAX_DELAY)    /* 返回值為pdPASS 或 errQUEUE_EMPTY,這里等待時間用portMAX_DELAY阻塞,所以不用再判斷 */

/* 中斷內消息接收 */
u8 *receiveData;
err=xQueueReceiveFromISR(Message_Queue,receiveData,&xTaskWokenByReceive); /* 向隊列中接受數據,返回值, FAIL或Pass,第三個參數是判斷高優先級接受到隊列后,退出中斷,是否需要調度 */
portYIELD_FROM_ISR(xTaskWokenByReceive);/* 如果需要的話進行一次任務切換 */

u8 remain_size;     /* 消息隊列剩余大小 */
remain_size=uxQueueSpacesAvailable(Message_Queue);  /* 得到隊列剩余大小 */
u8 used_size;       /* 消息隊列使用大小 */
used_size=uxQueueMessagesWaiting(Message_Queue);    /* 得到隊列使用大小 */

六、信號量

  • FreeRTOS的信號量包括二進制信號量、計數信號量、互斥信號量(以后簡稱互斥量)和遞歸互斥信號量(以后簡稱遞歸互斥量
    信號量API函數實際上都是宏,它使用現有的隊列機制。這些宏定義在semphr.h文件中。如果使用信號量或者互斥量,需要包含semphr.h頭文件。
    • 二進制信號量、計數信號量和互斥量信號量的創建API函數是獨立的,但是獲取和釋放API函數都是相同的;遞歸互斥信號量的創建、獲取和釋放API函數都是獨立的

6.1 二值信號量

/* 二值信號量創建 */
SemaphoreHandle_t BinarySemaphore;  /* 二值信號量句柄 */
BinarySemaphore=xSemaphoreCreateBinary();   /* 創建二值信號量 */

/* 二值信號量等待 */
BaseType_t err;
err = xSemaphoreTake(BinarySemaphore,portMAX_DELAY);    /* 獲取信號量 */

/* 二值信號量發送 */
BaseType_t err;
err = xSemaphoreGive(BinarySemaphore);  /* 釋放二值信號量 */

6.2 計數信號量

/* 計數信號量創建 */
SemaphoreHandle_t CountSemaphore;/* 計數型信號量句柄 */
CountSemaphore=xSemaphoreCreateCounting(255,0);     /* 創建計數型信號量,最大計數和初始化計數,參數沒改動的話,為long,所以最大值可以設計為不止255 */

/* 計數信號量等待 */
UBaseType_t semavalue;
xSemaphoreTake(CountSemaphore,portMAX_DELAY);   /* 等待數值信號量,阻塞 */
semavalue=uxSemaphoreGetCount(CountSemaphore);  /* 獲取數值信號量值 */

/* 計數信號量發送 */
BaseType_t err;
err=xSemaphoreGive(CountSemaphore);/* 釋放計數型信號量 */

6.3 互斥信號量

/* 創建互斥信號量 */
SemaphoreHandle_t MutexSemaphore; /* 互斥信號量句柄 */
MutexSemaphore=xSemaphoreCreateMutex();     /* 創建互斥信號量 */

/* 互斥信號量等待 */
xSemaphoreTake(MutexSemaphore,portMAX_DELAY);   /* 獲取互斥信號量,因為是阻塞,也就不需要查看什么返回值 */

/* 互斥信號量發送 */    
xSemaphoreGive(MutexSemaphore);                 /* 釋放互斥信號量 */

6.4 遞歸信號量

SemaphoreHandle_t RecursiveMutex;/* 遞歸互斥信號量句柄 */
RecursiveMutex = xSemaphoreCreateRecursiveMutex();        /* 創建遞歸互斥信號量 */

/* 遞歸互斥信號量等待 */
xSemaphoreTakeRecursive(RecursiveMutex,10);        /* 10為等待節拍 */

/* 遞歸互斥信號量發送 */
xSemaphoreGiveRecursive(RecursiveMutex);        /* 發送遞歸互斥信號量 */

七、事件標志組

/* 例子,3個事件 */
#define EVENTBIT_0  (1<<0)               
#define EVENTBIT_1  (1<<1)
#define EVENTBIT_2  (1<<2)
#define EVENTBIT_ALL    (EVENTBIT_0|EVENTBIT_1|EVENTBIT_2)

EventGroupHandle_t EventGroupHandler;    /* 事件標志組句柄 */
EventGroupHandler=xEventGroupCreate();   /* 創建事件標志組 */

/* 事件標志組置位 */
xEventGroupSetBits(EventGroupHandler,EVENTBIT_1);        /* 事件1置位 */

/* 事件標志組清除 */
xEventGroupClearBits(EventGroupHandler,EVENTBIT_1);       /* 事件1清除 */

/* 事件標志組獲取 */
EventBits_t NewValue;
NewValue = xEventGroupGetBits(EventGroupHandler);   

/* 事件標志組等待 */
EventValue=xEventGroupWaitBits((EventGroupHandle_t  )EventGroupHandler,     /* 句柄 */   
                               (EventBits_t         )EVENTBIT_ALL,          /* 標志位 */
                               (BaseType_t          )pdTRUE,                /* 獲取成功后 清除  */
                               (BaseType_t          )pdTRUE,                /* 等待所有標志位 置位 */
                               (TickType_t          )portMAX_DELAY);        /* 阻塞 */

八、任務通知

任務通知可完成消息隊列、信號量、事件標志組的功能,不過任務通知是只能實現一對一的,也就是一個任務對一個任務

/* 通知值發送,設置通知值,可發送一個數據 */
u8 data;
BaseType_t err;
err=xTaskNotify((TaskHandle_t   )Task_Handler,      /* 接收任務通知的任務句柄 */
                (uint32_t       )data,                      /* 任務通知值 */
                (eNotifyAction  )eSetValueWithOverwrite);   /* 覆寫的方式發送任務通知 */


/* 通知值發送,設置通知值,可做標記位組 */
#define EVENTBIT_1  (1<<1)
xTaskNotify((TaskHandle_t   )Task_Handler,      /* 接收任務通知的任務句柄 */
            (uint32_t       )EVENTBIT_1,        /* 要更新的bit */
            (eNotifyAction  )eSetBits);         /* 更新指定的bit */

/* 通知值獲取,獲取通知值,並判斷是否需要清零 */
BaseType_t err;
uint32_t NotifyValue;
err=xTaskNotifyWait((uint32_t   )0x00,              /* 進入函數,沒有接受到通知,不清除任何bit */
                    (uint32_t   )ULONG_MAX,         /* 退出函數,接受到通知,清除所有(0xffffffffUL)位的bit, */
                    (uint32_t*  )&NotifyValue,      /* 保存任務通知值 */
                    (TickType_t )portMAX_DELAY);    /* 阻塞時間 */

九、臨界段

/* 任務內臨界段處理 */
taskEXIT_CRITICAL();
/* 任務處理 */
taskEXIT_CRITICAL();


/* 中斷內臨界段處理 */
taskENTER_CRITICAL_FROM_ISR();
/* 中斷內處理 */
taskEXIT_CRITICAL_FROM_ISR();

十、內存管理

/* 內存申請 */
u8 *buffer;
buffer=pvPortMalloc(30);            /* 申請內存,30個字節 */

/* 釋放內存 */
vPortFree(buffer); 

/* 獲取內存剩余空間 */
u32 freeSize;
freeSize = xPortGetFreeHeapSize();  


免責聲明!

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



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