FreeRTOS 正点原子教程学习笔记
正點原子視頻教程
FreeRTOS(教程非常詳細)
小知識
如果創建了任務卻完全空著,沒有while(1){延時}的話,整個程序會卡住,其他正常的任務無法運行。如果任務里單單有賦值之類的操作也會卡死在這個任務,一定要記得加延時vTaskDelay(10);。
任務調度周期是1ms,能改,但是別改。
FreeRTOS中的變量,函數命名規則(u.v.x.p什么意思)
前面的字母是返回值的縮寫
u :代表unsigned。
s :代表short。
c :char。 所以類似uc,us類的變量就是unsigned char,unsigned
short,分別對應uint8_t,uint16_t。
x :為用戶自定義的數據類型,比如結構體,隊列等。
常看到ux開頭的函數,就是unsigned且用戶自定義的類型。需要注意的是size_t變量前綴也是ux。
e :枚舉變量
p :指針變量 類似(uint16_t *)變量前綴為pus。
prv :static函數
v: void函數
1、c語言中%x的意思是16進制輸出。
2、c語言中符合%#的意思是帶格式輸出。比如,%#x的意思是在輸出前面加上0x,%#b的意思是在輸出前面加上0b。
用來輸出地址
各種函數
- vTaskList()能一次性看所有任務的狀態、優先級、堆棧剩余大小和編號
- vTaskGetRunTimeStates()用來統計各任務運行時間及其占比【很有用的】如果哪個太耗時就可以拆分。不過要用的話得專門弄個定時器。正點原子官網視頻第13.2講會詳細講,我用到再學吧。
- 調用taskYIELD(),主動讓出cpu,讓同優先級的其他task獲得cpu
- vTaskDelay(1000);延時一秒,可被調度
FreeRTOSConfig.h
使用“INCLUDE_”開頭的宏用來表示使能或除能 FreeRTOS 中相應的 API 函數,作用就 是用來配置 FreeRTOS 中的可選 API 函數的
“config”開始的宏和“INCLUDE_”開始的宏一樣,都是用來完成 FreeRTOS 的配置和裁剪的
有些宏定義有默認值,是用#ifndef實現的
中斷
關閉中斷函數只能關掉大于等于5【5這個值是宏定義的,下圖第一行】的中斷優先級的事件【優先級數值比較小的比較高】
臨界段代碼也叫做臨界區,是指那些必須完整運行,不能被打斷的代碼段。
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
沒有_FROM_ISR的是任務級的臨界段代碼保護,另外倆是中斷級的臨界段代碼保護。
中斷里的臨界區出入函數有返回值和傳入參數
任務堆棧
用來存被中斷時的保存現場的指針以及函數內部定義的內部變量、數組等。 如果堆棧開得太小運行時會卡。當堆內存不夠的時候 這個函數的返回值會返回錯誤信息
堆棧開的大小是都要定的,兩種函數都要定,用宏定義自己定,上面的代碼里是LED0_STK_SIZE 開辟的大小是LED0_STK_SIZE*4 字節
截圖里說的是堆棧所在的首地址,它下面的那個傳入參數是任務控制塊的首地址。如果不是靜態創建而是動態創建的話,就不用這倆參數,而是要一個句柄。
欸,任務刪除的傳入參數是句柄啊,那靜態創建的任務的句柄在哪啊?? 啊知道了,靜態任務創建函數的返回值是任務句柄,句柄要先自己定義,類型是TaskHandle_t
刪除任務
假設現在正在編寫的子函數是一個任務,并且要在這個函數執行完成之后就刪除任務,那就在最后一句寫 vTaskDelete(StartTask_Handler); 刪除掉任務。傳入參數可以是這個函數創建時的句柄,也可以是NULL。
不過刪除任務的時候要先判斷那個句柄是否已經是NULL了,免得把已經刪除的任務再刪幾次?!救耸遣荒芩纼纱蔚?#xff0c;任務也不能】
但是刪除自身的時候.要是這個任務有返回值,又只運行這一次就刪掉,return是最后一句,vTaskDelete(NULL); 是倒數第二句。這樣沒問題嘛??
任務優先級
0級最低。
這里說的是線程里面的函數調用的優先級,范圍是 0~(configMAX_PRIORITIES-1) ,空閑任務的優先級為0,一般都是自己創建任務的時候自己寫個宏定義然后傳入對應的參數,比如上面的LED0_TASK_PRIO。
上面說的是中斷優先級,反正不一樣,哼。
亂來的,UCOS的任務優先級里0就是最高級,而且前面幾個都是系統用的,我們自己創的任務直接10起步。
視頻第20講,文章第14.6章提到【優先級翻轉】
優先級翻轉是指同樣要插隊運行,較低優先級的插隊到高優先級前面去了。
原因是
高優先級的任務需要等一個信號量,這個信號量交給優先級最低的任務處理了,中優先級的任務就跑來插最低優先級的任務 它做完再讓最低優先級任務繼續處理信號量,然后等最低優先級處理完、釋放信號量之后 最高優先級才能繼續任務。
創建靜態任務
首先說明一下,靜態任務并不比動態好什么東西,又麻煩。以后的教程也只會用動態不會用靜態。
官網講解優缺點
FreeRTOS的軟件結構 //大佬
FreeRTOS動態內存管理
靜態任務創建的函數的定義在tasks.c文件里,剛移植好的時候是不支持創建靜態任務的
上面的宏定義要跳轉過去修改【這個系統原本給它的默認值是0】
當我們把這個開關打開的時候,麻煩來了
我們之前關著的時候調用 vTaskStartScheduler(); 開啟任務調度。它在不被支持靜態創建的時候會動態創建空閑函數,但是支持靜態的時候就會創建靜態的空閑函數了,然而它要創建卻少條件,還得自己寫函數寫堆棧,把空閑任務和定時器任務所需的內存定義上。正點原子有教,靜態和動態所創建的堆棧大小是一樣的。我嘛,直接把宏定義再加個條件,仍然讓它創建動態的。
記錄一下自己作死的地方,免得忘了
我在main函數上面定義了 #define STATIC_MAFAN 0 然后在下面兩個地方 &&了這么個條件
話說為什么開啟任務調度函數里創建函數的時候 那些傳入參數都不用強制類型轉換的??
靜態和動態不一樣的參數就最后兩個,一個是堆棧,應該創建一個數組,另一個是控制塊,創建一個結構體變量
StackType_t StackBuffer[START_STK_SIZE];
StaticTask_t TaskBuffer_TCB;
任務掛起和恢復
掛起就是暫停
- 可以重復掛起任務很多次,恢復只要調用一次就能恢復 【任務調度器也能掛起和恢復,但是如果掛起它兩次就得調用恢復兩次】
- 掛起自身任務的時候傳入參數可以是NULL
- 掛起時,函數內的局部變量的內存不會被釋放。
- 中斷的恢復函數有返回值
(1)優先級設置成5也正常,但是4就不行了。定時器中斷、串口中斷的優先級也要小于等于5
(3)這條函數就是任務重新調度的
按正點原子的教程做,KEY0按鍵中斷的時候程序會卡在xEventGroupSetBitsFromISR()這個函數。解決方法是HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);//這個中斷優先級分組要改成NVIC_PRIORITYGROUP_4 總共的優先級分為16個等級 //如果分組用第二組的話就只有4個優先級,沒辦法把按鍵中斷優先級設為6.
第0組:所有4位用于指定響應優先級
第1組:最高1位用于指定搶占式優先級,最低3位用于指定響應優先級
第2組:最高2位用于指定搶占式優先級,最低2位用于指定響應優先級
第3組:最高3位用于指定搶占式優先級,最低1位用于指定響應優先級
第4組:所有4位用于指定搶占式優先級
程序是正常運行了,但是我怎么證明這中斷里的恢復有重新安排優先級更高的程序呢??
我先復述一下在這個任務掛起和恢復里的程序效果:
兩個燈,4個按鍵,兩個按鍵負責掛起,兩個按鍵負責恢復,其中按鍵KEY0是外部中斷 負責LED1的恢復。兩個燈在正常情況下會不斷閃爍。
我在任務函數里用delay_xms(500);給燈做延時,這個函數不能被任務調度打斷,所以運行時只有一個任務優先級比較高的那個燈會閃【任務創建時給定的宏,數字大的優先級高】,中斷時也只能幫優先級高的任務搶回閃燈權,否則就只能等高優先級的任務掛起了才能讓低優先級的燈閃。
列表與列表項
1.先定義一個列表 List_t 和三個列表項 ListItem_t
2.在一個任務里進行初始化。調用vListInitialise()和vListInitialiseItem()函數,傳入第一步定義的變量的地址就好
3.然后給列表項的.xItemValue值賦值,用來排序
現在就是一個空的TestList和三個獨立的列表項
4.vListInsert(&列表,&列表項);往列表里自動按從小到大的順序插入那個輸入的列表項
5.刪除列表項 uxListRemove(&列表項);
6.在末尾添加列表項 vListInsertEnd(&列表,&列表項);
隊列
先進先出,值傳遞(數據量大的時候可以把指針當作值來傳遞啊!)【UCOS是引用傳遞,源數據在消息傳到之前不能刪改】
動態創建隊列
返回句柄 xQueueGenericCreate(隊列可容納的消息數量,單個消息的長度,隊列類型)
if(Key_Queue!=NULL) //在用隊列之前都先問問這個隊列是不是創建成功
//二值信號量和互斥信號量的隊列長度都是1 ,單個消息的長度都是0.只用看是有信號還是沒信號就行,不用存具體消息。
在哪創建啊??刪除任務之后隊列會被刪掉嗎??
句柄是全局變量 要跨任務傳消息
創建隊列是和創建一大批任務的時候一起創的,返回值已經賦值給全局的句柄所以刪掉任務沒事。
向隊列發送消息
xQueueSend( 目標隊列句柄, 消息內容的變量的地址, 阻塞時間 )
中斷級入隊函數的參數里有一個pxHigherPriorityTaskWoken 它要我們在這個子函數里新建一個變量,然后把地址給它當傳入參數。整好之后要用portYIELD_FROM_ISR(變量名);重新按任務優先級選擇要運行的任務。
xQueueReceive(key_queue,&key,0);
//從key_queue隊列中取出消息存到新建u8變量里去,等0ms,要是沒拿到消息就返回0 拿到就返回1
信號量
要#include “semphr.h”
- 情景要求是電腦發數據,串口接收,任務獲取數據并處理
如果在串口中斷把消息內容直接傳給任務,就用隊列傳消息
如果在串口中斷做個標記,任務里查詢標記然后從串口緩沖區拿消息,就用信號量做標記
信號量其實就是隊列,創建信號量的函數調用的就是創建隊列的函數。
多個任務同時要讀取某一個信號量時,最高優先級的任務能讀到,其他的進入阻塞態
這個文章適合復習操作
二值信號量
只有一個隊列項 而且只用判斷是滿的還是空的 不用看內容
give函數是任務發出信號量給隊列,take函數是任務從隊列里取出信號量。初始化、發消息和收消息的流程和隊列一樣,函數也分任務級和中斷級,就是函數名不一樣,在不同的頭文件里。
//創建二值信號量
BinarySemaphore=xSemaphorecreateBinary ();
//二值信號量創建成功以后要先釋放一下才能給別的任務使用
if(BinarySemaphore!=NULL)xSemaphoreGive(BinarySemaphore) ;
在什么情況下用?
【串口收到消息時,要避免在串口中斷里進行文本處理以及顯示反饋】串口助手里發送消息之后會標記二值信號量,定時器中斷里查到二值信號量被標記時會在LCD和串口助手里顯示收到的字符串?!敬诘膬炏燃壥?,定時器的優先級是8】
計數型信號量/數值信號量
反正就是幫忙計數的,可以設最大值和初值。一般用來看資源使用量或者剩余量
xSemaphoreCreateCounting()創建
后面任務里每調用一次xSemaphoreGive(句柄); 就會有個變量加1.
semavalue=uxSemaphoreGetCount(句柄);讀那個變量
xSemaphoreTake(句柄,portMAX_DELAY); 變量減1.
互斥信號量
互斥信號量擁有優先級繼承機制,二值信號量沒有優先級繼承。
因此二值信號量更適合用于同步(任務與任務或任務與中斷的同步),而互斥信號量適合用于簡單的互斥訪問
定時器中斷和按鍵中斷這些中斷能迅速插入一段新任務運行,但是如果要在程序運行過程中某個變量置1時插入新任務的話,在裸機寫代碼的時候只能等程序運行到那去檢查那個變量,現在有操作系統就能實時檢查信號量并在拿到信號時運行新任務。
創建 xSemaphoreCreateMutex(),//創建之后不用特地釋放
發信號和讀信號的函數跟二值信號量一樣
不同的地方是
1 當高優先級的任務向低優先級的任務請求互斥信號量的時候,如果高優先級任務要等著,就會把那個低優先級任務的優先級拉高,讓它快點處理好所需要的信號量【這就叫優先級繼承】
2 互斥信號量不能用在中斷函數里。中斷里也不能用長時間的阻塞態等信號量來著,所以沒必要
//文檔里講互斥信號量和遞歸互斥信號量比視頻里講的要多很多
釋放互斥信號量的時候和二值信號量、計數型信號量一樣,都是用的函數 xSemaphoreGive()(實際上完成信號量釋放的是函數
xQueueGenericSend())。不過由于互斥信號量 涉及到優先級繼承的問題,所以具體處理過程會有點區別。使用函數
xSemaphoreGive()釋放信 號 量 最 重 要 的 一 步 就 是 將 uxMessagesWaiting 加 一 , 而 這
一 步 就 是 通 過 函 數 prvCopyDataToQueue() 來 完 成 的 , 釋 放 信 號 量 的 函 數
xQueueGenericSend() 會調用 prvCopyDataToQueue()?;コ庑盘柫康膬炏燃壚^承也是在函數
prvCopyDataToQueue()中完成的
已經獲取了互斥信號量的任務就不能 再次獲取這個互斥信號量,但是遞歸互斥信號量不同,已經獲取了遞歸互斥信號量的任務可以 再次獲取這個遞歸互斥信號量,而且次數不限!一個任務使用函數 xSemaphoreTakeRecursive()成功的獲取了多少次遞歸互斥信號量就得使用函數 xSemaphoreGiveRecursive()釋放多少次
同互斥信號量一樣,遞歸互斥信號量的獲取和釋放要在同一個任務中完成!如果當前正在運行的任務不是遞歸互斥信號量的擁有者就不能釋放!
創建 xSemaphoreCreateRecursiveMutex()
軟件定時器
硬件定時器時間準 但是資源不多,拿軟件定時器湊合用?;卣{函數里不能有延時或者阻塞
可以設置成 周期定時器 或者 單次定時器
有創建、開啟、復位、停止 函數
可以用于LCD背光每按下一次按鍵就亮5秒的應用
事件標志組
要#include “event_groups.h”
使用信號量來同步的話任務只能與單個的事件或任務進行同步。要是某個任務可能會需要與多個事件或任務進行同步就要用事件標志組?!纠汤锞褪?#xff1a;兩個按鍵各自管一個標志位,另一個任務等待指定的事件位,達成的條件可以自己設置為 兩個標志位都要為1/其中有一個置1就行/達成條件后要不要清除標志位/等多久】
實際使用時可以
● 事件標志組的 bit0 表示隊列中的消息是否處理掉。
● 事件標志組的 bit1 表示是否有消息需要從網絡中發送出去。
● 事件標志組的 bit2 表示現在是否需要向網絡發送心跳信息。
//跟手冊里的寄存器說明差不多。
在代碼里用的時候是
#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
創建 xEventGroupCreate()
清零 xEventGroupClearBits(句柄,要清零的事件位)
xEventGroupClearBitsFromISR(同上)
置1 xEventGroupSetBits(同上) //說是有檢查任務狀況,清零沒有。沒聽懂,文檔里沒寫
xEventGroupSetBitsFromISR(同上)
獲取標志 xEventGroupGetBits()
xEventGroupGetBitsFromISR()
要清零的事件位:比如要清除 bit3 的話就設置為 0X08??梢酝瑫r清除多個bit,如設置為 0X09 的話就是同時清除 bit3 和 bit0。//一個字節8個位,8421 8421
通過宏定義可以選擇事件標志組是有8位還是24位
等待指定的事件位 //這個常用
xEventGroup: 指定要等待的事件標志組。
uxBitsToWaitFord: 指定要等待的事件位,比如要等待bit0和(或)bit2的時候此參數就是0X05
xClearOnExit: 此參數要是為pdTRUE的話,那么在退出此函數之前由參數uxBitsToWaitFor 所設置的這些事件位就會清零。如果設置位 pdFALSE 的話這些事件位就不會改變。
xWaitForAllBits: 此參數如果設置為 pdTRUE 的話,當 uxBitsToWaitFor 所設置的這些事件位都置 1,或者指定的阻塞時間到的時候函數 xEventGroupWaitBits()才會返回。當此函數為 pdFALSE 的話,只要 uxBitsToWaitFor 所設置的這些事 件位其中的任意一個置 1,或者指定的阻塞時間到的話函數 xEventGroupWaitBits()就會返回。
xTicksToWait: 設置阻塞時間,單位為節拍數。
返回值 返回當所等待的事件位置 1 以后的事件標志組的值,或者阻塞時間到。根據這個值我們就知道哪些事件位置 1 了。如果函數因為阻塞時間到而返回的話那么這個返回值就不代表任何的含義。
任務通知
可以使用任務通知來代替信號量、消息隊列、事件標志組等這些東西。使用任務通知的話效率會更高。以前是創建對應的結構體存信號量信息,現在是直接往任務的句柄里存那些信息。
但是任務通知只能單個任務對單個任務進行通信,要一對多的時候還是要用那些功能。
而且任務通知只有出隊阻塞沒有入隊阻塞【接收任務時可能阻塞,但是如果發送失敗就不會阻塞】
代替二值信號量
刪掉二值信號量的創建和信號量的NULL檢測。要檢測任務句柄是不是NULL。
釋放/發送信號量
原先 xSemaphoreGiveFromISR(二值信號量句柄,任務切換變量)
現在 vTaskNotifyGiveFromISR(目標任務句柄,任務切換變量)//我要把這個信號量給多個任務的話,我再寫一句不就好了??
等待/獲取信號量
原先 xSemaphoreTake(二值信號量句柄,阻塞時間)
現在 ulTaskNotifyTake(要清零還是減一,阻塞時間) //參數為 pdFALSE 模擬計數型信號量;參數為 pdTRUE 模擬二值信號量。
代替數值信號量
數值信號量原本有計數和資源管理兩個功能。但是資源管理涉及多個任務,所以這個功能代替不了,只能代替它計數。
獲取信號量的函數的返回值是執行那個函數之前的信號量的數值。簡單講就是 把原本的值另外存起來,函數運行的時候會減一,返回值返回的是原本的值。
函數的替換操作和二值一樣
代替隊列 實現消息內容的傳遞//內容也可以是指針
1、只能發送 32 位的數據值。//強制類型轉換
2、消息被保存為任務的任務通知值,而且一次只能保存一個任務通知值,相當于隊列長度為 1。
釋放/發送信號量
原先 xQueueSend(隊列句柄,消息內容變量的地址,阻塞時間)//任務通知就沒這個入隊阻塞
現在 xTaskNotify(目標任務句柄,消息內容變量,要不要覆寫)//第三個參數為 eSetValueWithOverwrite 的話不管接收任務的通知值是否已經被處理,這個通知值都會被更新;為 eSetValueWithoutOverwrite 的話如果上一個任務通知值話還沒有被處理,那么新的任務通知值就不會更新。
等待/獲取信號量
原先 xQueueReceive(隊列句柄,保存消息內容變量的地址,阻塞時間)
現在 xTaskNotifyWait(沒有接收到任務通知時 是否清空消息,接收到了任務通知并處理好內容后 是否清空消息,保存消息內容變量的地址,阻塞時間) /前面兩個參數 如果填0就不清空,填ULONG_MAX就清空。//這個宏要加一個頭文件limits.h才能用
代替事件標志組
釋放/發送信號量
原先 xEventGroupSetBits(事件句柄,要設置的事件位)
現在 xTaskNotify(目標任務句柄,要設置的事件位,eSetBits)//第三個參數選擇要修改指定的位//對,上面寫的是要不要覆寫,其實第三個參數總共有5個選項可以選。
等待/獲取信號量
原先 xEventGroupWaitBits(事件組,事件位,退出是否清零,與/或,阻塞時間)
現在 xTaskNotifyWait(沒有接收到任務通知時 是否清空消息,接收到了任務通知并處理好內容后 是否清空消息,保存消息內容變量的地址,阻塞時間) //填ULONG_MAX就清空,要清除指定的位就對應位置1換成十六進制傳入。
在這個任務通知里,獲取信號量的函數里沒有與/或的功能選項,所以接到一個位的信號后如果選擇清零,那就是跟二值信號量一樣的用法;如果不清零,那這個與的事件真正達成之后怎么觸發第二次??要不自己另外清零?
低功耗模式 //Tickless模式
任務處理函數的運行時間很少,大部分時間都是在運行空閑任務。所以空閑任務的耗電量一定要想辦法降低。所以,要在空閑任務階段進入低功耗模式?!具M入低功耗模式前還可以關掉時鐘,降低系統主頻等等 出來的時候記得恢復】
如果關定時器是要自己操作,那低功耗模式究竟做了什么事能讓功耗下降啊??
進入低功耗模式的要求
- 空閑任務是唯一可運行的任務,其他所有的任務都處于阻塞態或者掛起態。
- 系統處于低功耗模式的時間至少大于2
進入低功耗模式時
- 要維持滴答定時器的運行【心跳時鐘】
- 要在下次任務需要運行時退出低功耗模式【中斷任務本身就能打斷低功耗模式】//居然有一個變量直接就存著’距離下一個任務需要開始運行的時間還有多久‘
- 低功耗模式有最大運行時長,在這里是93ms。要維持更久的話就出去重新進一次低功耗模式
空閑任務的工作內容
1、釋放內存
2、檢查是否使用搶占內核,如果沒使用,調用taskYIELD
3、如果使用搶占式內核,而且configIDLE_SHOULD_YIELD等于1,那么空閑任務就把CPU使用權讓給同優先級的其他任務
4、是否使能鉤子函數,使能的話就調用
5、是否使能Tickless模式,使能的話就做相應的處理工
鉤子函數
Freertos的鉤子函數
在任務里給指針賦內存不可以用malloc() 和free ()要用pvPortMalloc()和vPortFree()
而且使用的時候要判斷NULL,釋放之后要賦值NULL
任務調度器源碼講解
艾瑪,一直在講匯編。遭不住了。溜了溜了
總結
以上是生活随笔為你收集整理的FreeRTOS 正点原子教程学习笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vi替换字符串用法
- 下一篇: 软件项目实施进度计划表