原文地址:FreeRTOS學(xué)習(xí)記錄-ESP32 - 能跑就行_NPJX - 博客園 (cnblogs.com)
筆記的目的是胖秒,借此學(xué)習(xí)一下ESP32麸折,且快速回顧一下FreeRTOS,在需要時蒂教,可以快速找到對應(yīng)的概念和API接口肾扰。
ESP32使用FreeRTOS與原生FreeRTOS應(yīng)用程序入口有所不同畴嘶,
在 ESP-IDF 中使用 FreeRTOS 的用戶 永遠(yuǎn)不應(yīng)調(diào)用
vTaskStartScheduler()
和vTaskEndScheduler()
。相反集晚,ESP-IDF 會自動啟動 FreeRTOS窗悯。用戶必須定義一個void app_main(void)
函數(shù)作為用戶應(yīng)用程序的入口點,并在 ESP-IDF 啟動時被自動調(diào)用偷拔。
- 通常蒋院,用戶會從
app_main
中啟動應(yīng)用程序的其他任務(wù)。app_main
函數(shù)可以在任何時候返回(應(yīng)用終止前)莲绰。app_main
函數(shù)由main
任務(wù)調(diào)用欺旧。
以下FreeRTOS筆記無特殊說明外,默認(rèn)為單核
任務(wù)
創(chuàng)建任務(wù)
- 使用
xTaskCreate()
創(chuàng)建任務(wù)時,任務(wù)內(nèi)存動態(tài)分配。 - 使用
xTaskCreateStatic()
創(chuàng)建任務(wù)時智听,任務(wù)內(nèi)存靜態(tài)分配筝蚕,即由用戶提供。
執(zhí)行任務(wù)
- 只能處于以下任一狀態(tài):運行中、就緒、阻塞或掛起。
- 任務(wù)函數(shù)通常為無限循環(huán)鲫尊。
- 任務(wù)函數(shù)不應(yīng)返回。
刪除任務(wù)
- 使用
vTaskDelete()
刪除任務(wù)躬厌,若任務(wù)句柄為NULL
马昨,則會刪除當(dāng)前正在運行的任務(wù) - 注意事項:
- 請保證刪除任務(wù)時,任務(wù)是處于已知的狀態(tài)
- 比如任務(wù)內(nèi)部扛施,運行完成鸿捧,且釋放了任務(wù)內(nèi)分配的資源,再進(jìn)行刪除疙渣,不然會造成內(nèi)存泄露
- 刪除持有互斥鎖的任務(wù)匙奴,會導(dǎo)致別的任務(wù)永久鎖死
- 請保證刪除任務(wù)時,任務(wù)是處于已知的狀態(tài)
打印任務(wù)信息
- 使用
vTaskList()
羅列出所有任務(wù)的當(dāng)前狀態(tài),以及堆棧信息 - 使用
uxTaskGetStackHighWaterMark()
獲取任務(wù)棧剩余空間妄荔,越接近0越代表接近溢出泼菌,可以通過這個值谍肤,監(jiān)測任務(wù)函數(shù)棧空間是否充足
空閑任務(wù)
-
ESP-IDF會隱式創(chuàng)建一個優(yōu)先級為 0 的空閑任務(wù)哗伯。當(dāng)沒有其他任務(wù)準(zhǔn)備運行時荒揣,空閑任務(wù)運行并有以下作用:
- 釋放已刪除任務(wù)的內(nèi)存
- 執(zhí)行應(yīng)用程序的空閑函數(shù)
任務(wù)看門狗定時器 (TWDT)
任務(wù)看門狗定時器 (TWDT) 用于監(jiān)視特定任務(wù),確保任務(wù)在配置的超時時間內(nèi)執(zhí)行焊刹。
TWDT 主要監(jiān)視每個 CPU 的空閑任務(wù)
TWDT 是基于定時器組 0 中的硬件看門狗定時器構(gòu)建的系任。超時發(fā)生時會觸發(fā)中斷。
可以在用戶代碼中定義函數(shù)
esp_task_wdt_isr_user_handler
來接收超時事件虐块,并擴展默認(rèn)行為俩滥。-
調(diào)用以下函數(shù),用 TWDT 監(jiān)視任務(wù):
-
esp_task_wdt_init()
初始化 TWDT 并訂閱空閑任務(wù)贺奠。 -
esp_task_wdt_add()
為其他任務(wù)訂閱 TWDT霜旧。 - 訂閱后,應(yīng)從任務(wù)中調(diào)用
esp_task_wdt_reset()
來喂 TWDT儡率。 -
esp_task_wdt_delete()
可以取消之前訂閱的任務(wù)挂据。 -
esp_task_wdt_deinit
取消訂閱空閑任務(wù)并反初始化 TWDT。
-
-
注意事項:
-
擦除較大的 flash 區(qū)域可能會非常耗時喉悴,并可能導(dǎo)致任務(wù)連續(xù)運行棱貌,觸發(fā) TWDT 超時。以下兩種方法可以避免這種情況:
- 延長看門狗超時時間箕肃。
- 在擦除 flash 區(qū)域前,調(diào)用
esp_task_wdt_init()
今魔,增加看門狗超時時間勺像。
-
消息隊列
消息隊列就是通過 RTOS 內(nèi)核提供的服務(wù),任務(wù)或中斷服務(wù)子程序可以將一個消息(注意错森,F(xiàn)reeRTOS 消息隊列傳遞的是實際數(shù)據(jù)吟宦,并不是數(shù)據(jù)地址,RTX涩维,uCOS-II 和 uCOS-III 是傳遞的地址)放入到隊列殃姓。
同樣,一個或者多個任務(wù)可以通過 RTOS 內(nèi)核服務(wù)從隊列中得到消息瓦阐。通常蜗侈,先進(jìn)入消息隊列的消息先傳 給任務(wù),也就是說睡蟋,任務(wù)先得到的是最先進(jìn)入到消息隊列的消息踏幻,即先進(jìn)先出的原則(FIFO),F(xiàn)reeRTOS 的消息隊列支持 FIFO 和 LIFO 兩種數(shù)據(jù)存取方式戳杀。
- 消息隊列和全局變量相比该面,在FreeRTOS里更具以下優(yōu)勢:
* 使用消息隊列可以讓 RTOS 更有效管理任務(wù)夭苗,而全局變量是無法做到 1. 比如,任務(wù)的超時等待機制隔缀,用全局變量則需要用戶自己去實現(xiàn)题造。 * 消息隊列支持FIFO,更有利于數(shù)據(jù)處理 * 使用全局?jǐn)?shù)組猾瘸,還需要處理多任務(wù)的訪問沖突界赔,而消息隊列就處理好了這個問題 * 消息隊列可以有效解決中斷與任務(wù)之間通信問題
- 使用消息隊列傳輸數(shù)據(jù)時有兩種方法:
1. 拷貝:把數(shù)據(jù)、把變量的值復(fù)制進(jìn)隊列里 2. 引用:把數(shù)據(jù)须妻、把變量的地址復(fù)制進(jìn)隊列里
消息隊列--任務(wù)之間通信
消息隊列--中斷與任務(wù)之間通信
-
注意
- 在中斷發(fā)送消息需要使用
xQueueSendFromISR
仔蝌,且不支持超時設(shè)置,所以發(fā)送前要通過函數(shù)xQueueIsQueueFullFromISR
檢測 消息隊列是否滿 - 在中斷中處理越快越好荒吏,防止低于該優(yōu)先級的異常無法正常響應(yīng)
- 最好不要在中斷中處理消息隊列敛惊,只發(fā)送
- 中斷服務(wù)程序中一定要調(diào)用專用于中斷的消息隊列函數(shù),即以 FromISR 結(jié)尾的函數(shù)绰更。
- 在中斷發(fā)送消息需要使用
創(chuàng)建消息隊列
- 動態(tài)分配
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, /* 消息個數(shù) */
UBaseType_t uxItemSize ); /* 每個消息大小瞧挤,單位字節(jié) */
- 靜態(tài)分配(一般不用這個)
QueueHandle_t xQueueCreateStatic( UBaseType_t uxQueueLength, /* 消息個數(shù) */
UBaseType_t uxItemSize, /* 每個消息大小,單位字節(jié) */
uint8_t *pucQueueStorageBuffer, /* 如果uxItemSize非0儡湾,pucQueueStorageBuffer必須指向一個uint8_t數(shù)組特恬,此數(shù)組大小至少為"uxQueueLength * uxItemSize" */
StaticQueue_t *pxQueueBuffer ); /* 必須執(zhí)行一個StaticQueue_t結(jié)構(gòu)體,用來保存隊列的數(shù)據(jù)結(jié)構(gòu) */
寫消息隊列
/*
* 等同于xQueueSendToBack徐钠,往隊列尾部寫入數(shù)據(jù)癌刽,如果沒有空間,阻塞時間為xTicksToWait
*/
BaseType_t xQueueSend( QueueHandle_t xQueue, /* 消息隊列句柄 */
const void * pvItemToQueue, /* 要傳遞數(shù)據(jù)地址 */
TickType_t xTicksToWait ); /* 等待消息隊列有空間的最大等待時間 */
/*
* 往隊列尾部寫入數(shù)據(jù)尝丐,如果沒有空間显拜,阻塞時間為xTicksToWait
*/
BaseType_t xQueueSendToBack(
QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait
);
/*
* 往隊列尾部寫入數(shù)據(jù),此函數(shù)可以在中斷函數(shù)中使用爹袁,不可阻塞
*/
BaseType_t xQueueSendToBackFromISR(
QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken
);
/*
* 往隊列頭部寫入數(shù)據(jù)远荠,如果沒有空間,阻塞時間為xTicksToWait
*/
BaseType_t xQueueSendToFront(
QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait
);
/*
* 往隊列頭部寫入數(shù)據(jù)失息,此函數(shù)可以在中斷函數(shù)中使用譬淳,不可阻塞
*/
BaseType_t xQueueSendToFrontFromISR(
QueueHandle_t xQueue,
const void *pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken
);
讀消息隊列
/* 讀到一個數(shù)據(jù)后,隊列中該數(shù)據(jù)會被移除 */
BaseType_t xQueueReceive( QueueHandle_t xQueue, /* 消息隊列句柄 */
void * const pvBuffer, /* bufer指針盹兢,隊列的數(shù)據(jù)會被復(fù)制到這個buffer -*+*/
TickType_t xTicksToWait );/* 等待消息隊列有空間的最大等待時間 */
BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue,
void *pvBuffer,
BaseType_t *pxTaskWoken );
刪除消息隊列
/* vQueueDelete()只能刪除使用動態(tài)方法創(chuàng)建的隊列邻梆,它會釋放內(nèi)存 */
void vQueueDelete( QueueHandle_t xQueue );
查詢消息隊列
/*
* 返回隊列中可用數(shù)據(jù)的個數(shù)
*/
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );
/*
* 返回隊列中可用空間的個數(shù)
*/
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );
覆蓋/窺視
-
覆蓋
當(dāng)隊列長度為1時(隊列長度必須為1才可以使用),可以使用
xQueueOverwrite()
或xQueueOverwriteFromISR()
來覆蓋數(shù)據(jù)在FreeRTOS中蛤迎,隊列的發(fā)送和接收操作是原子的确虱,也就是說,在執(zhí)行這些操作期間替裆,不會被中斷或其他任務(wù)打斷校辩。這確保了數(shù)據(jù)的完整性和可靠性窘问。
而覆蓋操作是一種特殊情況,它允許在隊列滿時替換隊列中最早的消息宜咒,并添加新的消息惠赫。
為了保持原子性,只有在隊列長度為1時故黑,才能保證覆蓋操作的一致性儿咱。
如果隊列長度大于1,那么在進(jìn)行覆蓋操作時场晶,可能會涉及多個元素的移動和替換混埠。 由于隊列操作必須是原子的,這將涉及更復(fù)雜的同步和保護(hù)機制诗轻,以確保數(shù)據(jù)的一致性钳宪。這不僅增加了實現(xiàn)的復(fù)雜度,還可能引入競爭條件和死鎖等問題扳炬。
/* 覆蓋隊列
* xQueue: 寫哪個隊列
* pvItemToQueue: 數(shù)據(jù)地址
* 返回值: pdTRUE表示成功, pdFALSE表示失敗
*/
BaseType_t xQueueOverwrite(
QueueHandle_t xQueue,
const void * pvItemToQueue
);
BaseType_t xQueueOverwriteFromISR(
QueueHandle_t xQueue,
const void * pvItemToQueue,
BaseType_t *pxHigherPriorityTaskWoken
);
-
窺視
想讓隊列中的數(shù)據(jù)供多方讀取吏颖,也就是說讀取時不要移除數(shù)據(jù),要留給后來人恨樟。那么可以使用"窺視"半醉,也就是
xQueuePeek()
或xQueuePeekFromISR()
。這些函數(shù)會從隊列中復(fù)制出數(shù)據(jù)劝术,但是不移除數(shù)據(jù)缩多。這也意味著,如果隊列中沒有數(shù)據(jù)养晋,那么"偷看"時會導(dǎo)致阻塞瞧壮;一旦隊列中有數(shù)據(jù),以后每次"偷看"都會成功匙握。
/* 偷看隊列
* xQueue: 偷看哪個隊列
* pvItemToQueue: 數(shù)據(jù)地址, 用來保存復(fù)制出來的數(shù)據(jù)
* xTicksToWait: 沒有數(shù)據(jù)的話阻塞一會
* 返回值: pdTRUE表示成功, pdFALSE表示失敗
*/
BaseType_t xQueuePeek(
QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait
);
BaseType_t xQueuePeekFromISR(
QueueHandle_t xQueue,
void *pvBuffer,
);
隊列集合
知道有這個即可,實際很少用到陈轿!
隊列郵箱
郵箱的概念圈纺,其實就是,長度為1的消息隊列麦射,使用覆蓋函數(shù)蛾娶,之所以是覆蓋函數(shù),就是不管隊列有沒有值潜秋,都能及時更新蛔琅,不會陷入阻塞的情況,發(fā)送任務(wù)發(fā)送數(shù)據(jù)后峻呛,隊列就成了郵箱罗售,其他任務(wù)都在”訂閱“郵箱辜窑,使用窺視函數(shù)去,只獲取值寨躁,不刪除數(shù)據(jù)穆碎,達(dá)到一個發(fā)送,多個接收职恳。
這個也是知道概念即可所禀,具體用到的函數(shù)也是上述幾個。
具體流程如下
使用注意
- 分辨數(shù)據(jù)源
- 當(dāng)有多個發(fā)送任務(wù)放钦,通過同一個隊列發(fā)出數(shù)據(jù)色徘,接收任務(wù)如何分辨數(shù)據(jù)來源?數(shù)據(jù)本身帶有"來源"信息操禀,比如寫入隊列的數(shù)據(jù)是一個結(jié)構(gòu)體褂策,結(jié)構(gòu)體中的lDataSouceID用來表示數(shù)據(jù)來源:
typedef struct {
ID_t eDataID;
int32_t lDataValue;
}Data_t;
/* 不同的發(fā)送任務(wù),先構(gòu)造好結(jié)構(gòu)體床蜘,填入自己的eDataID辙培,再寫隊列;接收任務(wù)讀出數(shù)據(jù)后邢锯,根據(jù)eDataID就可以知道數(shù)據(jù)來源了 */
- 傳輸大塊數(shù)據(jù)
- 因為FreeRTOS的隊列使用拷貝傳輸扬蕊,如果是用uint32_t類型,那就拷貝4字節(jié)丹擎,如果用uint8_t類型尾抑,那就拷貝1字節(jié),但是如果要拷貝很大的數(shù)據(jù)蒂培,比如uint8_t data[1000]再愈,寫隊列的時候直接拷貝1000個字節(jié)嗎?讀隊列連續(xù)讀1000個字節(jié)护戳?翎冲?那樣效率未免也太低了吧。更為合適的方法是 使用
地址來間接傳數(shù)據(jù)
/* 比如 send_task 里面 malloc(1000)字節(jié)媳荒,把地址通過隊列寫進(jìn)去
在 recive_task 里面 free() 釋放空間抗悍,使用時要注意,成對出現(xiàn)
不要未使用就釋放內(nèi)存钳枕,導(dǎo)致野指針
也不要忘記釋放內(nèi)存缴渊,導(dǎo)致內(nèi)存泄漏
*/
void send_task(void *arg)
{
QueueHandle_t QueueHandle1 = (QueueHandle_t)arg;
BaseType_t status;
while(1) {
char *pStrSend = malloc(1000); //分配內(nèi)存鱼炒,并拷貝數(shù)據(jù)(我忽略了這步)
status = xQueueSend(QueueHandle1, &pStrSend , 5000);
if (status == pdPASS) {
printf("send success\n");
} else {
printf("send fail\n");
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void recive_task(void *arg)
{
QueueHandle_t QueueHandle1 = (QueueHandle_t)arg;
BaseType_t status;
char *pStrRecive;
while(1) {
status = xQueueReceive(QueueHandle1, &pStrRecive, 0);
if (status == pdPASS) {
// 在這里處理數(shù)據(jù),隨后并釋放空間
free(pStrRecive);
printf("pStrRecive:%s\n\n", pStrRecive);
} else {
printf("recive fail\n");
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
信號量
- 信號:起通知作用
- 量:還可以用來表示資源的數(shù)量
- 當(dāng)"量"沒有限制時指蚁,它就是"計數(shù)型信號量"(Counting Semaphores)
- 當(dāng)"量"只有0菩佑、1兩個取值時擎鸠,它就是"二進(jìn)制信號量"(Binary Semaphores)
- 支持的動作都一樣,give--給出資源缘圈,take--拿走資源,計數(shù)值減1
二進(jìn)制信號量跟計數(shù)型的唯一差別绢涡,就是計數(shù)值的最大值被限定為1雄可。
信號量的"give"数苫、"take"雙方并不需要相同虐急,可以用于生產(chǎn)者-消費者場合
:即多個任務(wù)產(chǎn)生信號量止吁,多個任務(wù)消費信號量
計數(shù)型信號量
/* 創(chuàng)建一個計數(shù)型信號量敬惦,返回它的句柄俄删。
* 此函數(shù)內(nèi)部會分配信號量結(jié)構(gòu)體
* uxMaxCount: 最大計數(shù)值
* uxInitialCount: 初始計數(shù)值
* 返回值: 返回句柄奏路,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);
/* 創(chuàng)建一個計數(shù)型信號量,返回它的句柄。
* 此函數(shù)無需動態(tài)分配內(nèi)存潜叛,所以需要先有一個StaticSemaphore_t結(jié)構(gòu)體威兜,并傳入它的指針
* uxMaxCount: 最大計數(shù)值
* uxInitialCount: 初始計數(shù)值
* pxSemaphoreBuffer: StaticSemaphore_t結(jié)構(gòu)體指針
* 返回值: 返回句柄椒舵,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount,
StaticSemaphore_t *pxSemaphoreBuffer );
二進(jìn)制信號量
信號量在創(chuàng)建后是空的狀態(tài)笔宿,在調(diào)用take獲取之前泼橘,需要先give釋放一個資源出來
/* 創(chuàng)建一個二進(jìn)制信號量炬灭,返回它的句柄重归。
* 此函數(shù)內(nèi)部會分配信號量結(jié)構(gòu)體
* 返回值: 返回句柄育苟,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinary( void );
/* 創(chuàng)建一個二進(jìn)制信號量宙搬,返回它的句柄勇垛。
* 此函數(shù)無需動態(tài)分配內(nèi)存闲孤,所以需要先有一個StaticSemaphore_t結(jié)構(gòu)體讼积,并傳入它的指針
* 返回值: 返回句柄勤众,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );
give/take 信號量
- 關(guān)于give的函數(shù)
/* 在任務(wù)中使用吕朵,釋放信號量
* xSemaphore:信號量句柄
* 返回值: pdTRUE表示成功,
* 如果二進(jìn)制信號量的計數(shù)值已經(jīng)是1努溃,再次調(diào)用此函數(shù)則返回失斘嗨啊第队;
* 如果計數(shù)型信號量的計數(shù)值已經(jīng)是最大值,再次調(diào)用此函數(shù)則返回失敗
*/
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
/* 在ISR中使用晾蜘,釋放信號量
* xSemaphore:信號量句柄
* pxHigherPriorityTaskWoken:如果釋放信號量導(dǎo)致更高優(yōu)先級的任務(wù)變?yōu)榱司途w態(tài)剔交,
* 則*pxHigherPriorityTaskWoken = pdTRUE
* 返回值: pdTRUE表示成功,
* 如果二進(jìn)制信號量的計數(shù)值已經(jīng)是1岖常,再次調(diào)用此函數(shù)則返回失斀甙啊;
* 如果計數(shù)型信號量的計數(shù)值已經(jīng)是最大值晒夹,再次調(diào)用此函數(shù)則返回失敗
*/
BaseType_t xSemaphoreGiveFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
);
- 關(guān)于take的函數(shù)
/* 在任務(wù)中使用丐怯,獲取信號量
* xSemaphore:信號量句柄
* xTicksToWait:阻塞時間读跷,0:不阻塞些膨,馬上返回, portMAX_DELAY: 一直阻塞直到成功矛洞,
* 其他值: 阻塞的Tick個數(shù)沼本,可以使用pdMS_TO_TICKS()來指定阻塞時間為若干ms
* 返回值: pdTRUE表示成功
*/
BaseType_t xSemaphoreTake(
SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait
);
/* 在ISR中使用,獲取信號量
* xSemaphore:信號量句柄
* pxHigherPriorityTaskWoken:如果獲取信號量導(dǎo)致更高優(yōu)先級的任務(wù)變?yōu)榱司途w態(tài)辫红,
* 則*pxHigherPriorityTaskWoken = pdTRUE
* 返回值: pdTRUE表示成功,
*/
BaseType_t xSemaphoreTakeFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
);
獲取信號量數(shù)量
/* * xSemaphore: 信號量句柄 * 返回值: 信號量個數(shù) */uxSemaphoreGetCount( xSemaphore );
刪除信號量
對于動態(tài)創(chuàng)建的信號量,不再需要它們時名惩,可以刪除它們以回收內(nèi)存娩鹉。
vSemaphoreDelete
可以用來刪除二進(jìn)制信號量弯予、計數(shù)型信號量。
/*
* xSemaphore: 信號量句柄祠挫,你要刪除哪個信號量
*/
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
互斥量
互斥量的用途:用來實現(xiàn)互斥訪問
值只有 0 和 1
資源互斥的核心在于:
誰上鎖等舔,就只能由誰開鎖
(代碼上又并沒有實現(xiàn)這點甚牲,只是約定成俗丈钙,誰上鎖誰開鎖)提及兩個概念:
對變量的非原子化訪問
修改變量雏赦、設(shè)置結(jié)構(gòu)體、在16位的機器上寫32位的變量俏橘,這些操作都是非原子的寥掐。也就是它們的操作過程都可能被打斷,如果被打斷的過程有其他任務(wù)來操作這些變量怎茫,就可能導(dǎo)致沖突轨蛤。
函數(shù)重入
“可重入的函數(shù)"是指:多個任務(wù)同時調(diào)用它、任務(wù)和中斷同時調(diào)用它缝呕,函數(shù)的運行也是安全的供常。可重入的函數(shù)也被稱為"線程安全”(thread safe)源祈。 每個任務(wù)都維持自己的棧香缺、自己的CPU寄存器原献,如果一個函數(shù)只使用局部變量,那么它就是線程安全的倔撞。 函數(shù)中一旦使用了全局變量、靜態(tài)變量躏啰、其他外設(shè)给僵,它就不是"可重入的",如果改函數(shù)正在被調(diào)用蹲诀,就必須阻止其他任務(wù)、中斷再次調(diào)用它痕慢。
任務(wù)A訪問這些全局變量守屉、函數(shù)代碼時滨巴,獨占它恭取,就是上個鎖。
這些全局變量攒发、函數(shù)代碼必須被獨占地使用惠猿,它們被稱為臨界資源。
互斥量有以下特點需要注意:
- 剛創(chuàng)建的互斥量可以被成功"take"
- “take"互斥量成功的任務(wù)趾访,被稱為"holder”,只能由它"give"互斥量藏鹊;別的任務(wù)"give"不成功
- 在ISR中不能使用互斥量
- 互斥量會去“繼承”,企圖獲取互斥量的任務(wù)的優(yōu)先級
創(chuàng)建互斥量
互斥量初始值為1
/* 創(chuàng)建一個互斥量竿痰,返回它的句柄。
* 此函數(shù)內(nèi)部會分配互斥量結(jié)構(gòu)體
* 返回值: 返回句柄蟹倾,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutex( void );
/* 創(chuàng)建一個互斥量肌厨,返回它的句柄柑爸。
* 此函數(shù)無需動態(tài)分配內(nèi)存,所以需要先有一個StaticSemaphore_t結(jié)構(gòu)體譬圣,并傳入它的指針
* 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );
刪除互斥量
/*
* xSemaphore: 信號量句柄,你要刪除哪個信號量, 互斥量也是一種信號量
*/
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );
獲取、釋放互斥量
/* 釋放 */
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );
/* 釋放(ISR版本) */
BaseType_t xSemaphoreGiveFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
);
/* 獲得 */
BaseType_t xSemaphoreTake(
SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait
);
/* 獲得(ISR版本) */
xSemaphoreGiveFromISR(
SemaphoreHandle_t xSemaphore,
BaseType_t *pxHigherPriorityTaskWoken
);
死鎖
死鎖又分為兩種情況:
互斥死鎖
假設(shè)有2個互斥量M1、M2份招,2個任務(wù)A、B:
- A獲得了互斥量M1
- B獲得了互斥量M2
- A還要獲得互斥量M2才能運行谐腰,結(jié)果A阻塞
- B還要獲得互斥量M1才能運行,結(jié)果B阻塞
- A砸西、B都阻塞衅疙,再無法釋放它們持有的互斥量
- 死鎖發(fā)生炼蛤!
自我死鎖
- 任務(wù)A獲得了互斥鎖M
- 它調(diào)用一個庫函數(shù)
- 庫函數(shù)要去獲取同一個互斥鎖M,于是它阻塞:任務(wù)A休眠,等待任務(wù)A來釋放互斥鎖兽愤!
- 死鎖發(fā)生浅萧!
為了解決上訴死鎖問題,又衍生出一種 遞歸互斥量
遞歸互斥量
遞歸互斥量實現(xiàn)了:誰上鎖就由誰解鎖
帝簇。
遞歸鎖 | 一般互斥量x | |
---|---|---|
創(chuàng)建 | xSemaphoreCreateRecursiveMutex | xSemaphoreCreateMutex |
獲得 | xSemaphoreTakeRecursive | xSemaphoreTake |
釋放 | xSemaphoreGiveRecursive | xSemaphoreGive |
假設(shè)任務(wù)1 需要去對資源A訪問,并且資源A需要對資源B進(jìn)行訪問,如果每次訪問都加一個普通互斥量途样,那這對代碼維護(hù)也十分麻煩,就有了遞歸互斥量
image.png
事件標(biāo)志組
事件標(biāo)志組是實現(xiàn)多任務(wù)同步的有效機制之一裆站。
事件標(biāo)志組和全局變量相比,在FreeRTOS里更具以下優(yōu)勢:
- 使用事件標(biāo)志組可以讓 RTOS 內(nèi)核有效地管理任務(wù),而全局變量是無法做到
- 比如宏胯,任務(wù)的超時等機制羽嫡, 用全局變量則需要用戶自己去實現(xiàn)。
- 使用了全局變量就要防止多任務(wù)的訪問沖突肩袍,而使用事件標(biāo)志組則處理好了這個問題。
- 使用事件標(biāo)志組可以有效地解決中斷服務(wù)程序和任務(wù)之間的同步問題氛赐。
TickType_t 數(shù)據(jù)類型可以是 16 位數(shù)或者 32 位數(shù)
創(chuàng)建事件標(biāo)志組
/* 創(chuàng)建一個事件組魂爪,返回它的句柄。
* 此函數(shù)內(nèi)部會分配事件組結(jié)構(gòu)體
* 返回值: 返回句柄艰管,非NULL表示成功
*/
EventGroupHandle_t xEventGroupCreate( void );
/* 創(chuàng)建一個事件組滓侍,返回它的句柄。
* 此函數(shù)無需動態(tài)分配內(nèi)存牲芋,所以需要先有一個StaticEventGroup_t結(jié)構(gòu)體撩笆,并傳入它的指針
* 返回值: 返回句柄,非NULL表示成功
*/
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t * pxEventGroupBuffer );
刪除事件組
/*
* xEventGroup: 事件組句柄缸浦,你要刪除哪個事件組
*/
void vEventGroupDelete( EventGroupHandle_t xEventGroup )
等待事件標(biāo)志位
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToWaitFor,
const BaseType_t xClearOnExit,
const BaseType_t xWaitForAllBits,
TickType_t xTicksToWait );
設(shè)置事件標(biāo)志位
-
使用
xEventGroupSetBits()
夕冲,不可以在中斷服務(wù)程序中調(diào)用此函數(shù)返回當(dāng)前的事件標(biāo)志組數(shù)值
-
用戶通過函數(shù)設(shè)置的標(biāo)志位,并不一定會保留到此函數(shù)的返回值中裂逐,下面舉兩種情況:
- 調(diào)用此函數(shù)的過程中耘擂,其它高優(yōu)先級的任務(wù)就緒了,并且也修改了事件標(biāo)志絮姆,此函數(shù)返回的事件 標(biāo)志位會發(fā)生變化。
- 調(diào)用此函數(shù)的任務(wù)是一個低優(yōu)先級任務(wù)秩霍,通過此函數(shù)設(shè)置了事件標(biāo)志后篙悯,讓一個等待此事件標(biāo)志的高優(yōu)先級任務(wù)就緒了,會立即切換到高優(yōu)先級任務(wù)去執(zhí)行铃绒,相應(yīng)的事件標(biāo)志位會被函數(shù)
xEventGroupWaitBits
清除掉鸽照,等從高優(yōu)先級任務(wù)返回到低優(yōu)先級任務(wù)后,函數(shù)xEventGroupSetBits
的返回值已經(jīng)被修改颠悬。
/* 設(shè)置事件組中的位
* xEventGroup: 哪個事件組
* uxBitsToSet: 設(shè)置哪些位?
* 如果uxBitsToSet的bitX, bitY為1, 那么事件組中的bitX, bitY被設(shè)置為1
* 可以用來設(shè)置多個位矮燎,比如 0x15 就表示設(shè)置bit4, bit2, bit0
* 返回值: 返回原來的事件值(沒什么意義, 因為很可能已經(jīng)被其他任務(wù)修改了)
*/
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet );
/* 設(shè)置事件組中的位
* xEventGroup: 哪個事件組
* uxBitsToSet: 設(shè)置哪些位?
* 如果uxBitsToSet的bitX, bitY為1, 那么事件組中的bitX, bitY被設(shè)置為1
* 可以用來設(shè)置多個位,比如 0x15 就表示設(shè)置bit4, bit2, bit0
* pxHigherPriorityTaskWoken: 有沒有導(dǎo)致更高優(yōu)先級的任務(wù)進(jìn)入就緒態(tài)? pdTRUE-有, pdFALSE-沒有
* 返回值: pdPASS-成功, pdFALSE-失敗
*/
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t * pxHigherPriorityTaskWoken );
中斷中設(shè)置事件標(biāo)志位
- 使用
xEventGroupSetBitsFromISR()
赔癌,在中斷中使用的是這個 - 設(shè)置事件組時诞外,有可能導(dǎo)致多個任務(wù)被喚醒,這會帶來很大的不確定性灾票。所以
xEventGroupSetBitsFromISR
函數(shù)不是直接去設(shè)置事件組峡谊,而是給一個FreeRTOS后臺任務(wù)(daemon task)發(fā)送隊列數(shù)據(jù),由這個任務(wù)來設(shè)置事件組。
事件組同步
有一個事情需要多個任務(wù)協(xié)同既们,使用
xEventGroupSync()
函數(shù)可以同步多個任務(wù):
- 可以設(shè)置某位濒析、某些位,表示自己做了什么事
- 可以等待某位啥纸、某些位忧设,表示要等等其他任務(wù)
- 期望的時間發(fā)生后,
xEventGroupSync()
才會成功返回纵苛。xEventGroupSync
成功返回后劳坑,會清除事件
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
const EventBits_t uxBitsToWaitFor,
TickType_t xTicksToWait );
任務(wù)通知
任務(wù)通知,簡單概括名船,就是具體通知到哪個任務(wù)去運行
我們使用隊列绰上、信號量、事件組等等方法時渠驼,并不知道對方是誰蜈块。使用任務(wù)通知時,可以明確指定:通知哪個任務(wù)迷扇。
使用隊列百揭、信號量、事件組時蜓席,我們都要事先創(chuàng)建對應(yīng)的結(jié)構(gòu)體器一,雙方通過中間的結(jié)構(gòu)體通信:
image.png
使用任務(wù)通知時,任務(wù)結(jié)構(gòu)體TCB中就包含了內(nèi)部對象厨内,可以直接接收別人發(fā)過來的"通知":
image.png
任務(wù)通知的特性
- 優(yōu)勢
- 效率更高:使用任務(wù)通知來發(fā)送事件祈秕、數(shù)據(jù)給某個任務(wù)時,效率更高雏胃。比隊列请毛、信號量、事件組都有大的優(yōu)勢瞭亮。
- 更節(jié)省內(nèi)存:使用其他方法時都要先創(chuàng)建對應(yīng)的結(jié)構(gòu)體方仿,使用任務(wù)通知時無需額外創(chuàng)建結(jié)構(gòu)體。
- 限制
- 不能發(fā)送數(shù)據(jù)給ISR: ISR并沒有任務(wù)結(jié)構(gòu)體统翩,所以無法使用任務(wù)通知的功能給ISR發(fā)送數(shù)據(jù)仙蚜。但是ISR可以使用任務(wù)通知的功能,發(fā)數(shù)據(jù)給任務(wù)厂汗。
- 數(shù)據(jù)只能給該任務(wù)獨享
使用隊列委粉、信號量、事件組時娶桦,數(shù)據(jù)保存在這些結(jié)構(gòu)體中艳丛,其他任務(wù)匣掸、ISR都可以訪問這些數(shù)據(jù)。使用任務(wù)通知時氮双,數(shù)據(jù)存放入目標(biāo)任務(wù)中碰酝,只有它可以訪問這些數(shù)據(jù)。 在日常工作中戴差,這個限制影響不大送爸。因為很多場合是從多個數(shù)據(jù)源把數(shù)據(jù)發(fā)給某個任務(wù),而不是把一個數(shù)據(jù)源的數(shù)據(jù)發(fā)給多個任務(wù)暖释。- 無法緩沖數(shù)據(jù)
使用隊列時袭厂,假設(shè)隊列深度為N,那么它可以保持N個數(shù)據(jù)球匕。 使用任務(wù)通知時纹磺,任務(wù)結(jié)構(gòu)體中只有一個任務(wù)通知值,只能保持一個數(shù)據(jù)亮曹。 無法廣播給多個任務(wù) 使用事件組可以同時給多個任務(wù)發(fā)送事件橄杨。 使用任務(wù)通知,只能發(fā)個一個任務(wù)照卦。 如果發(fā)送受阻式矫,發(fā)送方無法進(jìn)入阻塞狀態(tài)等待 假設(shè)隊列已經(jīng)滿了,使用xQueueSendToBack()給隊列發(fā)送數(shù)據(jù)時役耕,任務(wù)可以進(jìn)入阻塞狀態(tài)等待發(fā)送完成采转。 使用任務(wù)通知時,即使對方無法接收數(shù)據(jù)瞬痘,發(fā)送方也無法阻塞等待故慈,只能即刻返回錯誤。
通知狀態(tài)和通知值
每個任務(wù)都有一個結(jié)構(gòu)體:TCB(Task Control Block)框全,里面有2個成員:
- 一個是uint8_t類型察绷,用來表示通知狀態(tài)
- 一個是uint32_t類型,用來表示通知值
typedef struct tskTaskControlBlock
{
......
/* configTASK_NOTIFICATION_ARRAY_ENTRIES = 1 */
volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
......
} tskTCB;
通知狀態(tài)有3種取值
/* 任務(wù)沒有在等待通知竣况,也是初始狀態(tài) */
#define taskNOT_WAITING_NOTIFICATION ( ( uint8_t ) 0 )
/* 任務(wù)在等待通知 */
#define taskWAITING_NOTIFICATION ( ( uint8_t ) 1 )
/* 任務(wù)接收到了通知,也被稱為pending(有數(shù)據(jù)了筒严,待處理) */
#define taskNOTIFICATION_RECEIVED ( ( uint8_t ) 2 )
任務(wù)通知的使用
使用任務(wù)通知丹泉,可以實現(xiàn)輕量級的隊列(長度為1)、郵箱(覆蓋的隊列)鸭蛙、計數(shù)型信號量摹恨、二進(jìn)制信號量、事件組娶视。
任務(wù)通知有2套函數(shù)晒哄,簡化版睁宰、專業(yè)版,列表如下:
簡化版函數(shù)的使用比較簡單寝凌,它實際上也是使用專業(yè)版函數(shù)實現(xiàn)的
專業(yè)版函數(shù)支持很多參數(shù)柒傻,可以實現(xiàn)很多功能
簡化版 專業(yè)版 發(fā)出通知 xTaskNotifyGive
vTaskNotifyGiveFromISR
xTaskNotify
xTaskNotifyFromISR
取出通知 ulTaskNotifyTake
xTaskNotifyWait
具體這塊內(nèi)容,使用時再找demo看即可较木,大概了解使用就好红符,一般較少使用
軟件定時器
- 軟件定時器分為兩種狀態(tài):
- 運行(Running、Active):運行態(tài)的定時器伐债,當(dāng)指定時間到達(dá)之后预侯,它的回調(diào)函數(shù)會被調(diào)用
- 冬眠(Dormant):冬眠態(tài)的定時器還可以通過句柄來訪問它,但是它不再運行峰锁,它的回調(diào)函數(shù)不會被調(diào)用
- 軟件定時器工作原理:
首先萎馅,F(xiàn)reeRTOS有個Tick中斷,軟件定時器是基于Tick運行虹蒋,按照非操作系統(tǒng)的理解糜芳,我們是在Tick中斷里計數(shù),達(dá)到值就調(diào)用定時器回調(diào)千诬,但是在RTOS里耍目,它不允許在內(nèi)核、在中斷中執(zhí)行不確定的代碼:如果定時器函數(shù)很耗時徐绑,會影響整個系統(tǒng)邪驮。
所以,F(xiàn)reeRTOS中傲茄,不在Tick中斷中執(zhí)行定時器函數(shù)毅访。
在FreeRTOS中,有個RTOS守護(hù)任務(wù)(RTOS Daemon Task)盘榨,該任務(wù)跟普通任務(wù)基本一樣喻粹,不過守護(hù)任務(wù)的流程只有
- 處理命令:從命令隊列里取出命令、處理
- 執(zhí)行定時器的回調(diào)函數(shù)
<mark style="margin: 0px; padding: 0px;">定時器的回調(diào)函數(shù)是在守護(hù)任務(wù)中被調(diào)用的草巡,守護(hù)任務(wù)不是專為某個定時器服務(wù)的守呜,它還要處理其他定時器</mark>。注意如下:- 回調(diào)函數(shù)要盡快實行山憨,不能進(jìn)入阻塞狀態(tài)
- 不要調(diào)用會導(dǎo)致阻塞的API函數(shù)查乒,比如
vTaskDelay()
- 可以調(diào)用
xQueueReceive()
之類的函數(shù),但是超時時間要設(shè)為0:即刻返回郁竟,不可阻塞
創(chuàng)建軟件定時器
/* 使用動態(tài)分配內(nèi)存的方法創(chuàng)建定時器
* pcTimerName:定時器名字, 用處不大, 盡在調(diào)試時用到
* xTimerPeriodInTicks: 周期, 以Tick為單位
* uxAutoReload: 類型, pdTRUE表示自動加載, pdFALSE表示一次性
* pvTimerID: 回調(diào)函數(shù)可以使用此參數(shù), 比如分辨是哪個定時器
* pxCallbackFunction: 回調(diào)函數(shù)
* 返回值: 成功則返回TimerHandle_t, 否則返回NULL
*/
TimerHandle_t xTimerCreate( const char * const pcTimerName,
const TickType_t xTimerPeriodInTicks,
const UBaseType_t uxAutoReload,
void * const pvTimerID,
TimerCallbackFunction_t pxCallbackFunction );
/* 使用靜態(tài)分配內(nèi)存的方法創(chuàng)建定時器
* pcTimerName:定時器名字, 用處不大, 盡在調(diào)試時用到
* xTimerPeriodInTicks: 周期, 以Tick為單位
* uxAutoReload: 類型, pdTRUE表示自動加載, pdFALSE表示一次性
* pvTimerID: 回調(diào)函數(shù)可以使用此參數(shù), 比如分辨是哪個定時器
* pxCallbackFunction: 回調(diào)函數(shù)
* pxTimerBuffer: 傳入一個StaticTimer_t結(jié)構(gòu)體, 將在上面構(gòu)造定時器
* 返回值: 成功則返回TimerHandle_t, 否則返回NULL
*/
TimerHandle_t xTimerCreateStatic(const char * const pcTimerName,
TickType_t xTimerPeriodInTicks,
UBaseType_t uxAutoReload,
void * pvTimerID,
TimerCallbackFunction_t pxCallbackFunction,
StaticTimer_t *pxTimerBuffer );
回調(diào)函數(shù)類型
void ATimerCallback( TimerHandle_t xTimer );
typedef void (* TimerCallbackFunction_t)( TimerHandle_t xTimer );
刪除軟件定時器
/* 刪除定時器
* xTimer: 要刪除哪個定時器
* xTicksToWait: 超時時間
* 返回值: pdFAIL表示"刪除命令"在xTicksToWait個Tick內(nèi)無法寫入隊列
* pdPASS表示成功
*/
BaseType_t xTimerDelete( TimerHandle_t xTimer, TickType_t xTicksToWait );
啟動玛迄、暫停、復(fù)位
-
啟動
啟動定時器就是設(shè)置它的狀態(tài)為運行態(tài)(Running棚亩、Active)
這些函數(shù)的
xTicksToWait
表示的是蓖议,把命令寫入命令隊列的超時時間虏杰。命令隊列可能已經(jīng)滿了,無法馬上把命令寫入隊列里勒虾,可以等待一會纺阔。xTicksToWait
不是定時器本身的超時時間,不是定時器本身的"周期"从撼。如果定時器已經(jīng)被啟動州弟,但是它的函數(shù)尚未被執(zhí)行,再次執(zhí)行
xTimerStart()
函數(shù)相當(dāng)于執(zhí)行xTimerReset()
低零,重新設(shè)定它的啟動時間婆翔。
/* 啟動定時器
* xTimer: 哪個定時器
* xTicksToWait: 超時時間
* 返回值: pdFAIL表示"啟動命令"在xTicksToWait個Tick內(nèi)無法寫入隊列
* pdPASS表示成功
*/
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );
/* 啟動定時器(ISR版本)
* xTimer: 哪個定時器
* pxHigherPriorityTaskWoken: 向隊列發(fā)出命令使得守護(hù)任務(wù)被喚醒,
* 如果守護(hù)任務(wù)的優(yōu)先級比當(dāng)前任務(wù)的高,
* 則"*pxHigherPriorityTaskWoken = pdTRUE",
* 表示需要進(jìn)行任務(wù)調(diào)度
* 返回值: pdFAIL表示"啟動命令"無法寫入隊列
* pdPASS表示成功
*/
BaseType_t xTimerStartFromISR( TimerHandle_t xTimer,
BaseType_t *pxHigherPriorityTaskWoken );
-
暫停
停止定時器就是設(shè)置它的狀態(tài)為冬眠(Dormant),讓它不能運行
/* 停止定時器
* xTimer: 哪個定時器
* xTicksToWait: 超時時間
* 返回值: pdFAIL表示"停止命令"在xTicksToWait個Tick內(nèi)無法寫入隊列
* pdPASS表示成功
*/
BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait );
/* 停止定時器(ISR版本)
* xTimer: 哪個定時器
* pxHigherPriorityTaskWoken: 向隊列發(fā)出命令使得守護(hù)任務(wù)被喚醒,
* 如果守護(hù)任務(wù)的優(yōu)先級比當(dāng)前任務(wù)的高,
* 則"*pxHigherPriorityTaskWoken = pdTRUE",
* 表示需要進(jìn)行任務(wù)調(diào)度
* 返回值: pdFAIL表示"停止命令"無法寫入隊列
* pdPASS表示成功
*/
BaseType_t xTimerStopFromISR( TimerHandle_t xTimer,
BaseType_t *pxHigherPriorityTaskWoken );
-
復(fù)位
xTimerReset()
函數(shù)- 讓定時器的狀態(tài)從冬眠態(tài)轉(zhuǎn)換為運行態(tài)掏婶,相當(dāng)于使用
xTimerStart()
函數(shù) - 如果定時器已經(jīng)處于運行態(tài)啃奴,使用
xTimerReset()
函數(shù)就相當(dāng)于重新確定超時時間。
- 讓定時器的狀態(tài)從冬眠態(tài)轉(zhuǎn)換為運行態(tài)掏婶,相當(dāng)于使用
/* 復(fù)位定時器
* xTimer: 哪個定時器
* xTicksToWait: 超時時間
* 返回值: pdFAIL表示"復(fù)位命令"在xTicksToWait個Tick內(nèi)無法寫入隊列
* pdPASS表示成功
*/
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );
/* 復(fù)位定時器(ISR版本)
* xTimer: 哪個定時器
* pxHigherPriorityTaskWoken: 向隊列發(fā)出命令使得守護(hù)任務(wù)被喚醒,
* 如果守護(hù)任務(wù)的優(yōu)先級比當(dāng)前任務(wù)的高,
* 則"*pxHigherPriorityTaskWoken = pdTRUE",
* 表示需要進(jìn)行任務(wù)調(diào)度
* 返回值: pdFAIL表示"停止命令"無法寫入隊列
* pdPASS表示成功
*/
BaseType_t xTimerResetFromISR( TimerHandle_t xTimer,
BaseType_t *pxHigherPriorityTaskWoken );
修改周期
/* 修改定時器的周期
* xTimer: 哪個定時器
* xNewPeriod: 新周期
* xTicksToWait: 超時時間, 命令寫入隊列的超時時間
* 返回值: pdFAIL表示"修改周期命令"在xTicksToWait個Tick內(nèi)無法寫入隊列
* pdPASS表示成功
*/
BaseType_t xTimerChangePeriod( TimerHandle_t xTimer,
TickType_t xNewPeriod,
TickType_t xTicksToWait );
/* 修改定時器的周期
* xTimer: 哪個定時器
* xNewPeriod: 新周期
* pxHigherPriorityTaskWoken: 向隊列發(fā)出命令使得守護(hù)任務(wù)被喚醒,
* 如果守護(hù)任務(wù)的優(yōu)先級比當(dāng)前任務(wù)的高,
* 則"*pxHigherPriorityTaskWoken = pdTRUE",
* 表示需要進(jìn)行任務(wù)調(diào)度
* 返回值: pdFAIL表示"修改周期命令"在xTicksToWait個Tick內(nèi)無法寫入隊列
* pdPASS表示成功
*/
BaseType_t xTimerChangePeriodFromISR( TimerHandle_t xTimer,
TickType_t xNewPeriod,
BaseType_t *pxHigherPriorityTaskWoken );
定時器ID
- 用途:
- 可以用來標(biāo)記定時器雄妥,表示自己是什么定時器
- 可以用來保存參數(shù)最蕾,給回調(diào)函數(shù)使用
- 接口類型:
- 更新ID:使用
vTimerSetTimerID()
函數(shù)- 查詢ID:查詢
pvTimerGetTimerID()
函數(shù)
這兩個函數(shù)不涉及命令隊列,它們是直接操作定時器結(jié)構(gòu)體
/* 獲得定時器的ID
* xTimer: 哪個定時器
* 返回值: 定時器的ID
*/
void *pvTimerGetTimerID( TimerHandle_t xTimer );
/* 設(shè)置定時器的ID
* xTimer: 哪個定時器
* pvNewID: 新ID
* 返回值: 無
*/
void vTimerSetTimerID( TimerHandle_t xTimer, void *pvNewID );
中斷處理
FreeRTOS中很多API函數(shù)都有兩套:一套在任務(wù)中使用老厌,另一套在ISR中使用瘟则。后者的函數(shù)名含有"FromISR"后綴。
為什么要引入兩套API函數(shù)枝秤?(在任務(wù)中醋拧、在ISR中,這些函數(shù)的功能是有差別的)
- 很多API函數(shù)會導(dǎo)致任務(wù)計入阻塞狀態(tài):
* 運行這個函數(shù)的**任務(wù)**進(jìn)入阻塞狀態(tài) * 比如寫隊列時淀弹,如果隊列已滿丹壕,可以進(jìn)入阻塞狀態(tài)等待一會
- ISR調(diào)用API函數(shù)時,ISR不是"任務(wù)"薇溃,ISR不能進(jìn)入阻塞狀態(tài)
如果使用一套函數(shù)的話菌赖,則需要在函數(shù)內(nèi)部進(jìn)行判斷,這樣大量增加復(fù)雜代碼沐序,會更難以測試琉用,并且不同平臺內(nèi)部框架也不一樣,這也大大加大了代碼的復(fù)雜度策幼。
中斷的延遲處理
在中斷中處理內(nèi)容邑时,盡量要快,這是因為:
- 其他低優(yōu)先級的中斷無法被處理:實時性無法保證垄惧。
- 用戶任務(wù)無法被執(zhí)行:系統(tǒng)顯得很卡頓刁愿。
- 如果運行中斷嵌套绰寞,這會更復(fù)雜到逊,ISR越快執(zhí)行約有助于中斷嵌套铣口。
如果這個硬件中斷的處理,就是非常耗費時間呢觉壶?對于這類中斷的處理就要分為2部分:
- ISR:盡快做些清理脑题、記錄工作,然后觸發(fā)某個任務(wù)
- 任務(wù):更復(fù)雜的事情放在任務(wù)中處理
資源管理
臨界資源
要獨占式地訪問臨界資源铜靶,有3種方法:
- 公平競爭:比如使用互斥量叔遂,誰先獲得互斥量誰就訪問臨界資源,這部分內(nèi)容前面講過争剿。
- 誰要跟我搶已艰,我就滅掉誰:
* 中斷要跟我搶?我屏蔽中斷 * 其他任務(wù)要跟我搶蚕苇?我禁止調(diào)度器哩掺,不運行任務(wù)切換
在任務(wù)中屏蔽中斷
/* 在任務(wù)中,當(dāng)前時刻中斷是使能的
* 執(zhí)行這句代碼后涩笤,屏蔽中斷
*/
taskENTER_CRITICAL();
/* 訪問臨界資源 */
/* 重新使能中斷 */
taskEXIT_CRITICAL();
在ISR中屏蔽中斷
void vAnInterruptServiceRoutine( void )
{
/* 用來記錄當(dāng)前中斷是否使能 */
UBaseType_t uxSavedInterruptStatus;
/* 在ISR中嚼吞,當(dāng)前時刻中斷可能是使能的,也可能是禁止的
* 所以要記錄當(dāng)前狀態(tài), 后面要恢復(fù)為原先的狀態(tài)
* 執(zhí)行這句代碼后蹬碧,屏蔽中斷
*/
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
/* 訪問臨界資源 */
/* 恢復(fù)中斷狀態(tài) */
taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
/* 現(xiàn)在舱禽,當(dāng)前ISR可以被更高優(yōu)先級的中斷打斷了 */
}
暫停調(diào)度器
/* 暫停調(diào)度器 */
void vTaskSuspendAll( void );
/* 恢復(fù)調(diào)度器
* 返回值: pdTRUE表示在暫定期間有更高優(yōu)先級的任務(wù)就緒了
* 可以不理會這個返回值
*/
BaseType_t xTaskResumeAll( void );