信號(hào)量和互斥(互斥)是用于同步、資源管理和保護(hù)資源免受損壞的內(nèi)核對(duì)象。在本教程的前半部分,我們將了解Semaphore背后的理念,以及如何以及在何處使用它。
什么是信號(hào)量?
信號(hào)量是一種信號(hào)機(jī)制,其中處于等待狀態(tài)的任務(wù)由另一個(gè)任務(wù)發(fā)出信號(hào)以執(zhí)行。換句話(huà)說(shuō),當(dāng)一個(gè)task1完成它的工作時(shí),它會(huì)顯示一個(gè)標(biāo)志或?qū)⒁粋€(gè)標(biāo)志加1,然后另一個(gè)任務(wù)(task2)收到這個(gè)標(biāo)志,表明它現(xiàn)在可以執(zhí)行它的工作了。當(dāng) task2 完成其工作時(shí),標(biāo)志將減 1。
因此,基本上,它是一種“給予”和“接受”機(jī)制,而信號(hào)量是一個(gè)整數(shù)變量,用于同步對(duì)資源的訪問(wèn)。
FreeRTOS 中的信號(hào)量類(lèi)型:
信號(hào)量有兩種類(lèi)型。
二進(jìn)制信號(hào)量
計(jì)數(shù)信號(hào)量
1、Binary Semaphore:它有兩個(gè)整數(shù)值0和1。有點(diǎn)類(lèi)似于長(zhǎng)度為1的Queue。比如我們有兩個(gè)task,task1和task2。task1向task2發(fā)送數(shù)據(jù),因此task2不斷檢查隊(duì)列項(xiàng),如果有1,那么它可以讀取數(shù)據(jù),否則它必須等到它變成1。取完數(shù)據(jù)后,task2將隊(duì)列遞減,使其為0,即task1再次可以將數(shù)據(jù)發(fā)送到task2。
從上面的例子可以說(shuō),二進(jìn)制信號(hào)量是用于任務(wù)之間或任務(wù)與中斷之間的同步。
2. Counting Semaphore:它的值大于0,可以認(rèn)為是長(zhǎng)度大于1的隊(duì)列。這個(gè)semaphore用于對(duì)事件進(jìn)行計(jì)數(shù)。在這種使用場(chǎng)景中,事件處理程序?qū)⒃诿看问录l(fā)生時(shí)“給予”一個(gè)信號(hào)量(增加信號(hào)量計(jì)數(shù)值),而處理程序任務(wù)將在每次處理事件時(shí)“獲取”一個(gè)信號(hào)量(減少信號(hào)量計(jì)數(shù)值) 。
因此,計(jì)數(shù)值是已發(fā)生的事件數(shù)與已處理的事件數(shù)之差。
現(xiàn)在,讓我們看看如何在我們的 FreeRTOS 代碼中使用 Semaphore。
如何在 FreeRTOS 中使用信號(hào)量?
FreeRTOS 支持用于創(chuàng)建信號(hào)量、獲取信號(hào)量和提供信號(hào)量的不同 API。
現(xiàn)在,同一個(gè)內(nèi)核對(duì)象可以有兩種類(lèi)型的 API。如果我們必須從 ISR 提供??信號(hào)量,則無(wú)法使用正常的信號(hào)量 API。您應(yīng)該使用受中斷保護(hù)的 API。
在本教程中,我們將使用二進(jìn)制信號(hào)量,因?yàn)樗子诶斫夂蛯?shí)現(xiàn)。由于此處使用了中斷功能,因此您需要在 ISR 功能中使用受中斷保護(hù)的 API。當(dāng)我們說(shuō)將任務(wù)與中斷同步時(shí),這意味著在 ISR 之后立即將任務(wù)置于運(yùn)行狀態(tài)。
創(chuàng)建信號(hào)量:
要使用任何內(nèi)核對(duì)象,我們必須首先創(chuàng)建它。要?jiǎng)?chuàng)建二進(jìn)制信號(hào)量,請(qǐng)使用vSemaphoreCreateBinary()。
此 API 不接受任何參數(shù),并返回 SemaphoreHandle_t 類(lèi)型的變量。創(chuàng)建一個(gè)全局變量名sema_v來(lái)存儲(chǔ)信號(hào)量。
SemaphoreHandle_t sema_v;
sema_v = xSemaphoreCreateBinary();
給出信號(hào)量:
對(duì)于提供信號(hào)量,有兩種版本——一種用于中斷,另一種用于正常任務(wù)。
xSemaphoreGive():這個(gè) API 只接受一個(gè)參數(shù),它是信號(hào)量的變量名,如上面在創(chuàng)建信號(hào)量時(shí)給出的 sema_v。它可以從您想要同步的任何正常任務(wù)中調(diào)用。
xSemaphoreGiveFromISR():這是 xSemaphoreGive() 的受中斷保護(hù)的 API 版本。當(dāng)我們需要同步 ISR 和普通任務(wù)時(shí),應(yīng)該從 ISR 函數(shù)中使用 xSemaphoreGiveFromISR()。
獲取信號(hào)量:
要獲取信號(hào)量,請(qǐng)使用 API 函數(shù)xSemaphoreTake()。這個(gè) API 有兩個(gè)參數(shù)。
xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
xSemaphore:在我們的案例 sema_v 中要采用的信號(hào)量的名稱(chēng)。
xTicksToWait:這是任務(wù)在阻塞狀態(tài)下等待信號(hào)量變?yōu)榭捎玫淖铋L(zhǎng)時(shí)間。在我們的項(xiàng)目中,我們將 xTicksToWait 設(shè)置為portMAX_DELAY以使 task_1 無(wú)限期地等待阻塞狀態(tài),直到 sema_v 可用。
現(xiàn)在,讓我們使用這些 API 并編寫(xiě)代碼來(lái)執(zhí)行一些任務(wù)。
這里連接了一個(gè)按鈕和兩個(gè) LED。按鈕將充當(dāng)連接到 Arduino Uno 引腳 2 的中斷按鈕。按下此按鈕時(shí)將產(chǎn)生中斷,連接到引腳 8 的 LED 將打開(kāi),再次按下時(shí)將關(guān)閉。
因此,當(dāng)按下按鈕時(shí),將從 ISR 函數(shù)調(diào)用xSemaphoreGiveFromISR (),從 TaskLED 函數(shù)調(diào)用 xSemaphoreTake() 函數(shù)。
為了使系統(tǒng)看起來(lái)多任務(wù),將其他 LED 連接到引腳 7,引腳 7 將始終處于閃爍狀態(tài)。
信號(hào)量代碼說(shuō)明
讓我們通過(guò)打開(kāi) Arduino IDE 開(kāi)始編寫(xiě)代碼
1. 首先,包含Arduino_FreeRTOS.h頭文件?,F(xiàn)在,如果使用任何內(nèi)核對(duì)象,如隊(duì)列信號(hào)量,則還必須包含一個(gè)頭文件。
#include#include
2.聲明一個(gè)SemaphoreHandle_t類(lèi)型的變量來(lái)存儲(chǔ)信號(hào)量的值。
SemaphoreHandle_t 中斷信號(hào)量;
3. 在 void setup() 中,使用 xTaskCreate() API 創(chuàng)建兩個(gè)任務(wù)(TaskLED 和 TaskBlink),然后使用 xSemaphoreCreateBinary() 創(chuàng)建一個(gè)信號(hào)量。創(chuàng)建一個(gè)具有相同優(yōu)先級(jí)的任務(wù),然后嘗試使用這個(gè)數(shù)字。此外,將引腳 2 配置為輸入并啟用內(nèi)部上拉電阻并連接中斷引腳。最后,啟動(dòng)調(diào)度程序,如下所示。
無(wú)效設(shè)置(){ pinMode(2,INPUT_PULLUP); xTaskCreate(TaskLed, "Led", 128, NULL, 0, NULL ); xTaskCreate(TaskBlink, "LedBlink", 128, NULL, 0, NULL ); 中斷信號(hào)量 = xSemaphoreCreateBinary(); if (interruptSemaphore != NULL) { attachInterrupt(digitalPinToInterrupt(2), debounceInterrupt, LOW); } }
4. 現(xiàn)在,實(shí)現(xiàn) ISR 功能。創(chuàng)建一個(gè)函數(shù)并將其命名為與attachInterrupt()函數(shù)的第二個(gè)參數(shù)相同。為了使中斷正常工作,您需要使用millis或micros功能并通過(guò)調(diào)整去抖時(shí)間來(lái)消除按鈕的去抖問(wèn)題。從此函數(shù)調(diào)用interruptHandler()函數(shù),如下所示。
長(zhǎng)去抖時(shí)間 = 150; volatile unsigned long last_micros; void debounceInterrupt() { if((long)(micros() - last_micros) >= debounce_time * 1000) { interruptHandler(); last_micros = micros(); } }
在interruptHandler()函數(shù)中,調(diào)用xSemaphoreGiveFromISR()API。
void interruptHandler() { xSemaphoreGiveFromISR(interruptSemaphore, NULL); }
這個(gè)函數(shù)會(huì)給TaskLed一個(gè)信號(hào)量來(lái)打開(kāi)LED。
5. 創(chuàng)建一個(gè)TaskLed函數(shù)并在while循環(huán)中調(diào)用xSemaphoreTake()API 并檢查信號(hào)量是否被成功獲取。如果它等于 pdPASS(即 1),則使 LED 切換如下所示。
void TaskLed(void *pvParameters) { (void) pvParameters; pinMode(8,輸出); while(1) { if (xSemaphoreTake(interruptSemaphore, portMAX_DELAY) == pdPASS) { digitalWrite(8, !digitalRead(8)); } } }
6. 另外,創(chuàng)建一個(gè)函數(shù)來(lái)閃爍連接到引腳 7 的其他 LED。
void TaskLed1(void *pvParameters) { (void) pvParameters; pinMode(7,輸出); 而(1){ 數(shù)字寫(xiě)入(7,高); vTaskDelay(200 / portTICK_PERIOD_MS); 數(shù)字寫(xiě)入(7,低); vTaskDelay(200 / portTICK_PERIOD_MS); } }
7. void 循環(huán)函數(shù)將保持為空。不要忘記它。
無(wú)效循環(huán)(){}
就是這樣,完整的代碼可以在本教程的末尾找到?,F(xiàn)在,上傳此代碼并根據(jù)電路圖將 LED 和按鈕與 Arduino UNO 連接起來(lái)。
電路原理圖
上傳代碼后,您會(huì)看到一個(gè) LED 在 200 毫秒后閃爍,當(dāng)按下按鈕時(shí),第二個(gè) LED 會(huì)立即發(fā)光,如最后給出的視頻所示。
通過(guò)這種方式,信號(hào)量可以在帶有 Arduino 的 FreeRTOS 中使用,它需要將數(shù)據(jù)從一個(gè)任務(wù)傳遞到另一個(gè)任務(wù)而不會(huì)造成任何損失。
現(xiàn)在,讓我們看看什么是 Mutex 以及如何使用 FreeRTOS。
什么是互斥鎖?
如上所述,信號(hào)量是一種信號(hào)機(jī)制,類(lèi)似地,Mutex 是一種鎖定機(jī)制,與信號(hào)量不同,信號(hào)量具有單獨(dú)的遞增和遞減函數(shù),但在 Mutex 中,函數(shù)本身接受和給出。這是一種避免共享資源損壞的技術(shù)。
為了保護(hù)共享資源,需要為資源分配一個(gè)令牌卡(互斥體)。擁有這張卡的人可以訪問(wèn)其他資源。其他人應(yīng)該等到卡歸還。這樣,只有一個(gè)資源可以訪問(wèn)任務(wù),其他資源等待機(jī)會(huì)。
讓我們通過(guò)一個(gè)例子來(lái)了解FreeRTOS 中的 Mutex 。
這里我們有三個(gè)任務(wù),一個(gè)用于在 LCD 上打印數(shù)據(jù),第二個(gè)用于將 LDR 數(shù)據(jù)發(fā)送到 LCD 任務(wù),最后一個(gè)任務(wù)用于在 LCD 上發(fā)送溫度數(shù)據(jù)。所以這里兩個(gè)任務(wù)共享相同的資源,即 LCD。如果 LDR 任務(wù)和溫度任務(wù)同時(shí)發(fā)送數(shù)據(jù),則其中一個(gè)數(shù)據(jù)可能損壞或丟失。
因此,為了防止數(shù)據(jù)丟失,我們需要鎖定 task1 的 LCD 資源,直到它完成顯示任務(wù)。然后 LCD 任務(wù)將解鎖,然后 task2 可以執(zhí)行其工作。
您可以在下圖中觀察互斥量和信號(hào)量的工作。
如何在 FreeRTOS 中使用互斥鎖?
互斥量的使用方式也與信號(hào)量相同。首先,創(chuàng)建它,然后使用各自的 API 提供和獲取。
創(chuàng)建互斥鎖:
要?jiǎng)?chuàng)建互斥體,請(qǐng)使用xSemaphoreCreateMutex() API。顧名思義,互斥量是一種二進(jìn)制信號(hào)量。它們用于不同的上下文和目的。二進(jìn)制信號(hào)量用于同步任務(wù),而 Mutex 用于保護(hù)共享資源。
此 API 不接受任何參數(shù)并返回SemaphoreHandle_t類(lèi)型的變量。如果無(wú)法創(chuàng)建互斥鎖,則xSemaphoreCreateMutex()返回 NULL。
SemaphoreHandle_t mutex_v;
mutex_v = xSemaphoreCreateMutex();
采取互斥鎖:
當(dāng)任務(wù)想要訪問(wèn)資源時(shí),它將使用xSemaphoreTake() API 獲取 Mutex。它與二進(jìn)制信號(hào)量相同。它還需要兩個(gè)參數(shù)。
xSemaphore:在我們的例子中使用的 Mutex 的名稱(chēng)mutex_v。
xTicksToWait:這是任務(wù)在阻塞狀態(tài)下等待 Mutex 可用的最長(zhǎng)時(shí)間。在我們的項(xiàng)目中,我們將 xTicksToWait 設(shè)置為portMAX_DELAY以使 task_1 在 Blocked 狀態(tài)下無(wú)限期等待,直到mutex_v可用。
提供互斥鎖:
訪問(wèn)共享資源后,任務(wù)應(yīng)該返回 Mutex,以便其他任務(wù)可以訪問(wèn)它。xSemaphoreGive() API 用于返回 Mutex。
xSemaphoreGive() 函數(shù)只接受一個(gè)參數(shù),即在我們的案例 mutex_v 中給出的 Mutex。
使用上述 API,讓我們使用 Arduino IDE 在 FreeRTOS 代碼中實(shí)現(xiàn) Mutex。
互斥代碼說(shuō)明
這部分的目標(biāo)是使用串行監(jiān)視器作為共享資源和兩個(gè)不同的任務(wù)來(lái)訪問(wèn)串行監(jiān)視器以打印一些消息。
1. 頭文件將保持與信號(hào)量相同。
#include#include
2. 聲明一個(gè)SemaphoreHandle_t類(lèi)型的變量來(lái)存儲(chǔ) Mutex 的值。
SemaphoreHandle_t mutex_v;
3. 在void setup() 中,以 9600 波特率初始化串行監(jiān)視器,并使用xTaskCreate()API 創(chuàng)建兩個(gè)任務(wù)(Task1 和 Task2)。然后使用xSemaphoreCreateMutex()創(chuàng)建一個(gè) Mutex 。創(chuàng)建一個(gè)具有相同優(yōu)先級(jí)的任務(wù),然后嘗試使用這個(gè)數(shù)字。
無(wú)效設(shè)置(){ 序列.開(kāi)始(9600); mutex_v = xSemaphoreCreateMutex(); if (mutex_v == NULL) { Serial.println("無(wú)法創(chuàng)建互斥鎖"); } xTaskCreate(Task1, "任務(wù) 1", 128, NULL, 1, NULL); xTaskCreate(Task2, "任務(wù) 2", 128, NULL, 1, NULL); }
4. 現(xiàn)在,為T(mén)ask1 和Task2 制作任務(wù)函數(shù)。在任務(wù)函數(shù)的while循環(huán)中,在串行監(jiān)視器上打印消息之前,我們必須使用xSemaphoreTake()獲取 Mutex ,然后打印消息,然后使用xSemaphoreGive() 返回 Mutex。然后再拖延一些時(shí)間。
void Task1(void *pvParameters) { while(1) { xSemaphoreTake(mutex_v, portMAX_DELAY); Serial.println("來(lái)自 Task1 的您好"); xSemaphoreGive(mutex_v); vTaskDelay(pdMS_TO_TICKS(1000)); } }
同理,實(shí)現(xiàn)延遲500ms的Task2函數(shù)。
5. void loop()將保持為空。
現(xiàn)在,將此代碼上傳到 Arduino UNO 并打開(kāi)串行監(jiān)視器。
您將看到正在從 task1 和 task2 打印消息。
要測(cè)試 Mutex 的工作,只需注釋xSemaphoreGive(mutex_v); 從任何任務(wù)。您可以看到程序掛在最后一條打印消息上。
這就是使用 Arduino 在 FreeRTOS 中實(shí)現(xiàn)信號(hào)量和互斥量的方式。
信號(hào)量代碼:
#include
#include
長(zhǎng)去抖時(shí)間 = 150;
volatile unsigned long last_micros;
SemaphoreHandle_t 中斷信號(hào)量;
無(wú)效設(shè)置(){
pinMode(2, INPUT_PULLUP);
xTaskCreate(TaskLed, "Led", 128, NULL, 0, NULL );
xTaskCreate(TaskBlink, "LedBlink", 128, NULL, 0, NULL );
中斷信號(hào)量 = xSemaphoreCreateBinary();
如果(中斷信號(hào)量!= NULL){
attachInterrupt(digitalPinToInterrupt(2), debounceInterrupt, LOW);
}
}
無(wú)效循環(huán)(){}
無(wú)效中斷處理程序(){
xSemaphoreGiveFromISR(interruptSemaphore, NULL);
}
void TaskLed(void *pvParameters)
{
(void) pvParameters;
pinMode(8,輸出);
為了 (;;) {
if (xSemaphoreTake(interruptSemaphore, portMAX_DELAY) == pdPASS) {
數(shù)字寫(xiě)入(8,!數(shù)字讀取(8));
}
}
}
void TaskBlink(void *pvParameters)
{
(void) pvParameters;
pinMode(7,輸出);
為了 (;;) {
數(shù)字寫(xiě)入(7,高);
vTaskDelay(200 / portTICK_PERIOD_MS);
數(shù)字寫(xiě)入(7,低);
vTaskDelay(200 / portTICK_PERIOD_MS);
}
}
無(wú)效去抖中斷(){
if((long)(micros() - last_micros) >= debouncing_time * 1000) {
中斷處理程序();
last_micros = micros();
}
}
互斥體代碼:
#include
#include
SemaphoreHandle_t mutex_v;
無(wú)效設(shè)置(){
序列號(hào).開(kāi)始(9600);
mutex_v = xSemaphoreCreateMutex();
if (mutex_v == NULL) {
Serial.println("無(wú)法創(chuàng)建互斥鎖");
}
xTaskCreate(Task1, "Task1", 128, NULL, 1, NULL);
xTaskCreate(Task2, "Task2", 128, NULL, 1, NULL);
}
無(wú)效任務(wù)1(無(wú)效* pvParameters){
而(1){
xSemaphoreTake(mutex_v, portMAX_DELAY);
Serial.println("來(lái)自 Task1 的您好");
xSemaphoreGive(mutex_v);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void Task2(void *pvParameters) {
而(1){
xSemaphoreTake(mutex_v, portMAX_DELAY);
Serial.println("來(lái)自 Task2 的您好");
xSemaphoreGive(mutex_v);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
無(wú)效循環(huán)(){
}
-
FreeRTOS
+關(guān)注
關(guān)注
12文章
484瀏覽量
62226 -
Arduino
+關(guān)注
關(guān)注
188文章
6472瀏覽量
187319 -
信號(hào)量
+關(guān)注
關(guān)注
0文章
53瀏覽量
8357
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論