- Vad är Semaphore?
- Hur använder jag Semaphore i FreeRTOS?
- Förklaring till Semaphore Code
- Kretsschema
- Vad är Mutex?
- Hur man använder Mutex i FreeRTOS?
- Mutex-kodförklaring
I tidigare handledning har vi täckt grunderna i FreeRTOS med Arduino och kö-kärnobjektet i FreeRTOS Arduino. Nu, i denna tredje FreeRTOS-handledning, kommer vi att lära oss mer om FreeRTOS och dess avancerade API: er, vilket kan få dig att förstå plattformen för flera uppgifter djupare.
Semaphore och Mutex (ömsesidig uteslutning) är kärnobjekten som används för synkronisering, resurshantering och skydd av resurser från korruption. Under den första halvan av denna handledning kommer vi att se idén bakom Semaphore, hur och var den ska användas. Under andra halvåret fortsätter vi med Mutex.
Vad är Semaphore?
I tidigare handledning har vi diskuterat om uppgiftsprioriteringar och lär oss också att en uppgift med högre prioritet förhindrar en uppgift med lägre prioritet, så medan exekvering av uppgift med hög prioritet kan det finnas en möjlighet att datakorruption kan inträffa i uppgift med lägre prioritet eftersom det körs inte ännu och data kommer kontinuerligt till denna uppgift från en sensor som orsakar dataförlust och funktionsfel i hela applikationen.
Så det finns ett behov av att skydda resurser från dataförlust och här spelar Semaphore en viktig roll.
Semaphore är en signalmekanism där en uppgift i vänteläge signaleras av en annan uppgift för exekvering. Med andra ord, när en uppgift1 avslutade sitt arbete, kommer den att visa en flagga eller öka en flagga med 1 och sedan mottas denna flagga av en annan uppgift (uppgift2) som visar att den kan utföra sitt arbete nu. När uppgift2 avslutat sitt arbete kommer flaggan att minskas med 1.
Så i grund och botten är det en "Give" och "Take" -mekanism och semafor är en heltalsvariabel som används för att synkronisera tillgången till resurser.
Typer av semafor i FreeRTOS:
Semafor är av två typer.
- Binär semafor
- Räknar Semaphore
1. Binär semafor: Den har två heltalsvärden 0 och 1. Den liknar något av längdkön 1. Till exempel har vi två uppgifter, uppgift1 och uppgift2. Uppgift1 skickar data till uppgift2 så uppgift2 kontrollerar kontinuerligt köobjektet om det finns 1, då kan den läsa data annars måste den vänta tills den blir 1. Efter att ha tagit data, minskar uppgift2 kön och gör den till 0 Det betyder uppgift1 igen kan skicka data till uppgift2.
Från exemplet ovan kan man säga att binär semafor används för synkronisering mellan uppgifter eller mellan uppgifter och avbrott.
2. Räkna semafor: Den har värden större än 0 och kan tänkas som en kö med en längd på mer än 1. Denna semafor används för att räkna händelser. I det här användningsscenariot kommer en händelsehanterare att "ge" en semafor varje gång en händelse inträffar (öka värdet av semaforantalet), och en hanteraruppgift kommer att "ta" en semafor varje gång den bearbetar en händelse (minskar semaforantalet).
Räknarvärdet är därför skillnaden mellan antalet händelser som har inträffat och det antal som har bearbetats.
Låt oss nu se hur du använder Semaphore i vår FreeRTOS-kod.
Hur använder jag Semaphore i FreeRTOS?
FreeRTOS stöder olika API: er för att skapa en semafor, ta en semafor och ge en semafor.
Nu kan det finnas två typer av API: er för samma kärnobjekt. Om vi måste ge semafor från en ISR kan normalt semafor-API inte användas. Du bör använda avbrutna skyddade API: er.
I denna handledning använder vi binär semafor eftersom det är lätt att förstå och implementera. Eftersom avbrottsfunktionalitet används här måste du använda avbrottsskyddade API: er i ISR-funktionen. När vi säger att synkronisera en uppgift med ett avbrott betyder det att sätta uppgiften i körläge direkt efter ISR.
Skapa en semafor:
För att kunna använda något kärnobjekt måste vi först skapa det. För att skapa en binär semafor, använd vSemaphoreCreateBinary ().
Detta API tar ingen parameter och returnerar en variabel av typen SemaphoreHandle_t. Ett globalt variabelnamn sema_v skapas för att lagra semaforen.
SemaphoreHandle_t sema_v; sema_v = xSemaphoreCreateBinary ();
Ge en semafor:
För att ge en semafor finns det två versioner - en för avbrott och en annan för den normala uppgiften.
- xSemaphoreGive (): Det här API : et tar bara ett argument som är variabelnamnet på semafor som sema_v enligt ovan medan du skapar en semafor. Det kan anropas från vilken normal uppgift du vill synkronisera.
- xSemaphoreGiveFromISR (): Detta är den avbrutna skyddade API-versionen av xSemaphoreGive (). När vi behöver synkronisera en ISR och en normal uppgift ska xSemaphoreGiveFromISR () användas från ISR-funktionen.
Ta en semafor:
För att ta en semafor, använd API-funktionen xSemaphoreTake (). Detta API tar två parametrar.
xSemaphoreTake (SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
xSemaphore: Namnet på semaforen som ska tas i vårt fall sema_v.
xTicksToWait: Det här är den maximala tid som uppgiften väntar i blockerat tillstånd för att semaforen ska bli tillgänglig. I vårt projekt kommer vi att ställa in xTicksToWait till portMAX_DELAY så att task_1 väntar på obestämd tid i blockerat tillstånd tills sema_v är tillgänglig.
Låt oss nu använda dessa API: er och skriva en kod för att utföra vissa uppgifter.
Här är en tryckknapp och två lysdioder gränssnitt. Tryckknappen fungerar som en avbrytarknapp som är fäst vid stift 2 i Arduino Uno. När du trycker på denna knapp genereras ett avbrott och en lysdiod som är ansluten till stift 8 tänds och när du trycker på den igen kommer den att vara AV.
Så när knappen trycks in kommer xSemaphoreGiveFromISR () att anropas från ISR-funktionen och xSemaphoreTake () -funktionen kommer att anropas från TaskLED-funktionen.
För att systemet ska se ut multitasking, anslut andra lysdioder med stift 7 som alltid kommer att blinka.
Förklaring till Semaphore Code
Låt oss börja skriva kod för genom att öppna Arduino IDE
1. Inkludera först rubrikfilen Arduino_FreeRTOS.h . Nu, om något kärnobjekt används som kösemafor, måste en rubrikfil också inkluderas för det.
# inkludera # inkludera
2. Förklara en variabel av typen SemaphoreHandle_t för att lagra värdena för semafor.
SemaphoreHandle_t interruptSemaphore;
3. I ogiltig installation (), skapa två uppgifter (TaskLED och TaskBlink) med xTaskCreate () API och skapa sedan en semafor med xSemaphoreCreateBinary (). Skapa en uppgift med lika prioriteringar och försök senare spela med detta nummer. Konfigurera också stift 2 som ingång och aktivera det interna uppdragningsmotståndet och fäst avbrottsstiftet. Slutligen starta schemaläggaren enligt nedan.
ogiltig installation () { pinMode (2, INPUT_PULLUP); xTaskCreate (TaskLed, "Led", 128, NULL, 0, NULL); xTaskCreate (TaskBlink, "LedBlink", 128, NULL, 0, NULL); interruptSemaphore = xSemaphoreCreateBinary (); if (interruptSemaphore! = NULL) { attachInterrupt (digitalPinToInterrupt (2), debounceInterrupt, LOW); } }
4. Implementera nu ISR-funktionen. Gör en funktion och namnge den samma som det andra argumentet för attachInterrupt () -funktionen. För att få avbrottet att fungera korrekt måste du ta bort avstängningsproblemet för tryckknappen med millis- eller mikrofunktionen och genom att justera avvisningstiden. Från denna funktion, ring interruptHandler () -funktionen enligt nedan.
lång debouncing_time = 150; flyktiga osignerade long last_micros; ogiltigt debounceInterrupt () { if ((long) (micros () - last_micros)> = debouncing_time * 1000) { interruptHandler (); last_micros = micros (); } }
I interruptHandler () -funktionen, ring xSemaphoreGiveFromISR () API.
void interruptHandler () { xSemaphoreGiveFromISR (interruptSemaphore, NULL); }
Denna funktion ger TaskLed en semafor för att sätta PÅ lysdioden.
5. Skapa en TaskLed- funktion och inuti while- slingan, ring xSemaphoreTake () API och kontrollera om semaforen har tagits eller inte. Om det är lika med pdPASS (dvs. 1) gör du att lysdioden växlar enligt bilden nedan.
void TaskLed (void * pvParameters) { (void) pvParameters; pinMode (8, OUTPUT); medan (1) { if (xSemaphoreTake (interruptSemaphore, portMAX_DELAY) == pdPASS) { digitalWrite (8,! digitalRead (8)); } } }
6. Skapa också en funktion för att blinka annan LED ansluten till stift 7.
ogiltig TaskLed1 (ogiltig * pvParameters) { (ogiltig) pvParameters; pinMode (7, OUTPUT); medan (1) { digitalWrite (7, HIGH); vTaskDelay (200 / portTICK_PERIOD_MS); digitalWrite (7, LOW); vTaskDelay (200 / portTICK_PERIOD_MS); } }
7. Tömningsfunktionen förblir tom. Glöm det inte.
ogiltig slinga () {}
Det är det, fullständig kod kan hittas i slutet av denna handledning. Ladda nu upp den här koden och anslut LED-lamporna och tryckknappen till Arduino UNO enligt kretsschemat.
Kretsschema
Efter att ha laddat upp koden kommer du att se en LED blinkar efter 200 ms och när du trycker på knappen lyser omedelbart den andra lysdioden som visas i videon i slutet.
På detta sätt kan semaforer användas i FreeRTOS med Arduino där den behöver överföra data från en uppgift till en annan utan förlust.
Låt oss nu se vad som är Mutex och hur man använder det FreeRTOS.
Vad är Mutex?
Som förklarats ovan är semafor en signalmekanism, på samma sätt är Mutex en låsmekanism till skillnad från semaforen som har separata funktioner för steg och minskning men i Mutex tar funktionen och ger i sig själv. Det är en teknik för att undvika korruption av delade resurser.
För att skydda den delade resursen tilldelar man resursen ett token-kort (mutex). Den som har det här kortet kan komma åt den andra resursen. Andra bör vänta tills kortet returneras. På detta sätt kan bara en resurs komma åt uppgiften och andra väntar på sin chans.
Låt oss förstå Mutex i FreeRTOS med hjälp av ett exempel.
Här har vi tre uppgifter, en för att skriva ut data på LCD, en andra för att skicka LDR-data till LCD-uppgift och sista uppgift för att skicka temperaturdata på LCD. Så här delar två uppgifter samma resurs, dvs LCD. Om LDR-uppgiften och temperaturuppgiften skickar data samtidigt kan en av data skadas eller gå förlorad.
Så för att skydda dataförlusten måste vi låsa LCD-resursen för uppgift 1 tills den slutför visningsuppgiften. Då låses LCD-uppgiften upp och sedan kan task2 utföra sitt arbete.
Du kan se hur Mutex och semaforer fungerar i nedanstående diagram.
Hur man använder Mutex i FreeRTOS?
Mutexs används också på samma sätt som semaforer. Skapa först det, ge sedan och ta med respektive API: er.
Skapa en Mutex:
För att skapa en Mutex, använd xSemaphoreCreateMutex () API . Som namnet antyder är Mutex en typ av binär semafor. De används i olika sammanhang och syften. En binär semafor är för synkronisering av uppgifter medan Mutex används för att skydda en delad resurs.
Detta API tar inget argument och returnerar en variabel av typen SemaphoreHandle_t . Om mutex inte kan skapas returnerar xSemaphoreCreateMutex () NULL.
SemaphoreHandle_t mutex_v; mutex_v = xSemaphoreCreateMutex ();
Ta en Mutex:
När en uppgift vill komma åt en resurs tar det en Mutex med hjälp av xSemaphoreTake () API. Det är detsamma som en binär semafor. Det tar också två parametrar.
xSemaphore: Namnet på Mutex som ska tas i vårt fall mutex_v .
xTicksToWait: Detta är den maximala tid som uppgiften väntar i blockerat tillstånd för att Mutex ska bli tillgänglig. I vårt projekt kommer vi att ställa in xTicksToWait till portMAX_DELAY så att task_1 väntar på obestämd tid i blockerat tillstånd tills mutex_v är tillgänglig.
Ge en Mutex:
Efter åtkomst till den delade resursen ska uppgiften returnera Mutex så att andra uppgifter kan komma åt den. API för xSemaphoreGive () används för att ge tillbaka Mutex.
Funktionen xSemaphoreGive () tar bara ett argument som är Mutex som ska ges i vårt fall mutex_v.
Med hjälp av ovanstående API: er, låt oss implementera Mutex i FreeRTOS-koden med Arduino IDE.
Mutex-kodförklaring
Här är målet för denna del att använda en seriell bildskärm som en delad resurs och två olika uppgifter för att komma åt den seriella bildskärmen för att skriva ut ett meddelande.
1. Rubrikfilerna förblir desamma som en semafor.
# inkludera # inkludera
2. Förklara en variabel av typen SemaphoreHandle_t för att lagra värdena på Mutex.
SemaphoreHandle_t mutex_v;
3. I ogiltig installation (), initiera seriell bildskärm med 9600 baudhastighet och skapa två uppgifter (Task1 och Task2) med hjälp av xTaskCreate () API. Skapa sedan en Mutex med xSemaphoreCreateMutex (). Skapa en uppgift med lika prioriteringar och försök senare spela med detta nummer.
ogiltig installation () { Serial.begin (9600); mutex_v = xSemaphoreCreateMutex (); if (mutex_v == NULL) { Serial.println ("Mutex kan inte skapas"); } xTaskCreate (Task1, "Task 1", 128, NULL, 1, NULL); xTaskCreate (Task2, "Task 2", 128, NULL, 1, NULL); }
4. Gör nu uppgiftsfunktioner för Task1 och Task2. Inom en stund slinga av uppgiftsfunktionen, innan vi skriver ut ett meddelande på den seriella bildskärmen, måste vi ta en Mutex med xSemaphoreTake () och sedan skriva ut meddelandet och sedan returnera Mutex med xSemaphoreGive (). Ge sedan lite försening.
void Task1 (void * pvParameters) { while (1) { xSemaphoreTake (mutex_v, portMAX_DELAY); Serial.println ("Hej från uppgift1"); xSemaphoreGive (mutex_v); vTaskDelay (pdMS_TO_TICKS (1000)); } }
På samma sätt implementera Task2-funktionen med en fördröjning på 500 ms.
5. Void loop () förblir tom.
Ladda nu upp den här koden på Arduino UNO och öppna den seriella bildskärmen.
Du ser meddelanden skrivs ut från uppgift 1 och uppgift 2.
För att testa funktionen hos Mutex, kommentera bara xSemaphoreGive (mutex_v); från alla uppgifter. Du kan se att programmet hänger på det senaste utskriftsmeddelandet .
Så här kan Semaphore och Mutex implementeras i FreeRTOS med Arduino. För mer information om Semaphore och Mutex kan du besöka den officiella dokumentationen för FreeRTOS.
Kompletta koder och video för Semaphore och Mutes ges nedan.