- Ce este Semafor?
- Cum se utilizează Semaphore în FreeRTOS?
- Explicația codului semaforului
- Diagrama circuitului
- Ce este Mutex?
- Cum se utilizează Mutex în FreeRTOS?
- Explicația codului Mutex
În tutorialele anterioare, am acoperit elementele de bază ale FreeRTOS cu Arduino și obiectul kernel Queue din FreeRTOS Arduino. Acum, în acest al treilea tutorial FreeRTOS, vom afla mai multe despre FreeRTOS și API-urile sale avansate, care vă pot face să înțelegeți mai profund platforma multi-tasking.
Semaphore și Mutex (excluderea reciprocă) sunt obiectele kernel care sunt utilizate pentru sincronizare, gestionarea resurselor și protejarea resurselor împotriva corupției. În prima jumătate a acestui tutorial, vom vedea ideea din spatele Semaphore, cum și unde să-l folosim. În a doua jumătate, vom continua cu Mutex.
Ce este Semafor?
În tutoriale anterioare, am discutat despre prioritățile sarcinilor și, de asemenea, am aflat că o sarcină cu prioritate mai mare previne o sarcină cu prioritate mai mică, astfel încât în timp ce executarea sarcinii cu prioritate ridicată poate exista posibilitatea ca corupția datelor să se întâmple în sarcina cu prioritate mai mică, deoarece nu este încă executat și datele vin în mod continuu la această sarcină de la un senzor care cauzează pierderea datelor și funcționarea defectuoasă a întregii aplicații.
Deci, este nevoie să protejăm resursele împotriva pierderii de date și aici Semaphore joacă un rol important.
Semaforul este un mecanism de semnalizare în care o sarcină într-o stare de așteptare este semnalată de o altă sarcină pentru executare. Cu alte cuvinte, atunci când o sarcină 1 și-a terminat activitatea, atunci va afișa un steag sau va crește un steag cu 1 și apoi acest steag este primit de o altă sarcină (sarcina 2) care arată că își poate îndeplini activitatea acum. Când sarcina 2 și-a terminat activitatea, steagul va fi redus cu 1.
Deci, practic, este un mecanism „Give” și „Take”, iar semaforul este o variabilă întreagă care este utilizată pentru a sincroniza accesul la resurse.
Tipuri de semafor în FreeRTOS:
Semaforul este de două tipuri.
- Semafor binar
- Numărând semafor
1. Semafor binar: Are două valori întregi 0 și 1. Este oarecum similar cu Coada de lungime 1. De exemplu, avem două sarcini, task1 și task2. Task1 trimite date la task2, astfel încât task2 verifică continuu elementul de coadă dacă există 1, apoi poate citi datele altceva trebuie să aștepte până devine 1. După preluarea datelor, task2 decrementează coada și o face 0 Asta înseamnă task1 din nou poate trimite datele la task2.
Din exemplul de mai sus, se poate spune că semaforul binar este utilizat pentru sincronizarea între sarcini sau între sarcini și întrerupere.
2. Semafor de numărare: are valori mai mari de 0 și se poate gândi la o coadă de lungime mai mare de 1. Acest semafor este utilizat pentru numărarea evenimentelor. În acest scenariu de utilizare, un gestionar de evenimente va „da” un semafor de fiecare dată când apare un eveniment (incrementând valoarea numărării semaforului), iar o sarcină de gestionare va „lua” un semafor de fiecare dată când procesează un eveniment (decrementând valoarea numărării semaforului).
Valoarea de numărare este, prin urmare, diferența dintre numărul de evenimente care au avut loc și numărul care a fost procesat.
Acum, să vedem cum să folosim Semaphore în codul nostru FreeRTOS.
Cum se utilizează Semaphore în FreeRTOS?
FreeRTOS acceptă diferite API-uri pentru crearea unui semafor, preluarea unui semafor și oferirea unui semafor.
Acum, pot exista două tipuri de API-uri pentru același obiect kernel. Dacă trebuie să oferim semafor dintr-un ISR, atunci API-ul semaforului normal nu poate fi utilizat. Ar trebui să utilizați API-uri protejate împotriva întreruperii.
În acest tutorial, vom folosi semafor binar, deoarece este ușor de înțeles și implementat. Deoarece funcționalitatea de întrerupere este utilizată aici, trebuie să utilizați API-uri protejate împotriva întreruperii în funcția ISR. Când spunem sincronizarea unei sarcini cu o întrerupere, înseamnă punerea sarcinii în starea de rulare imediat după ISR.
Crearea unui semafor:
Pentru a utiliza orice obiect din nucleu, trebuie mai întâi să-l creăm. Pentru crearea unui semafor binar, utilizați vSemaphoreCreateBinary ().
Acest API nu ia niciun parametru și returnează o variabilă de tip SemaphoreHandle_t. Se creează un nume global de variabilă sema_v pentru a stoca semaforul.
SemaphoreHandle_t sema_v; sema_v = xSemaphoreCreateBinary ();
Oferirea unui semafor:
Pentru a da un semafor, există două versiuni - una pentru întrerupere și alta pentru sarcina normală.
- xSemaphoreGive (): Acest API acceptă un singur argument, care este numele variabil al semaforului, cum ar fi sema_v, așa cum este dat mai sus, în timp ce creează un semafor. Poate fi apelat din orice sarcină normală pe care doriți să o sincronizați.
- xSemaphoreGiveFromISR (): Aceasta este versiunea API protejată împotriva întreruperii a xSemaphoreGive (). Când trebuie să sincronizăm un ISR și o activitate normală, atunci xSemaphoreGiveFromISR () trebuie utilizat din funcția ISR.
Luând un semafor:
Pentru a lua un semafor, utilizați funcția API xSemaphoreTake (). Acest API ia doi parametri.
xSemaphoreTake (SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
xSemaphore: Numele semaforului care trebuie luat în cazul nostru sema_v.
xTicksToWait: Aceasta este cantitatea maximă de timp pe care sarcina o va aștepta în stare blocată pentru ca semaforul să devină disponibil. În proiectul nostru, vom seta xTicksToWait la portMAX_DELAY pentru ca sarcina_1 să aștepte nedefinit în starea Blocat până când sema_v este disponibil.
Acum, să folosim aceste API-uri și să scriem un cod pentru a efectua unele sarcini.
Aici sunt interfațate un buton și două LED-uri. Butonul va acționa ca un buton de întrerupere care este atașat pinului 2 al Arduino Uno. Când este apăsat acest buton, va fi generată o întrerupere și un LED conectat la pinul 8 va fi aprins, iar când îl apăsați din nou, acesta va fi OFF.
Deci, când butonul este apăsat, xSemaphoreGiveFromISR () va fi apelat din funcția ISR și funcția xSemaphoreTake () va fi apelată din funcția TaskLED.
Pentru ca sistemul să arate multitasking, conectați alte LED-uri cu pinul 7, care vor fi mereu intermitente.
Explicația codului semaforului
Să începem să scriem codul prin deschiderea IDE Arduino
1. Mai întâi, includeți fișierul de antet Arduino_FreeRTOS.h . Acum, dacă vreun obiect de nucleu este folosit ca semafor de coadă, trebuie să fie inclus și un fișier de antet.
#include #include
2. Declarați o variabilă de tip SemaphoreHandle_t pentru a stoca valorile semaforului.
SemaphoreHandle_t interruptSemaphore;
3. În setarea nulă (), creați două sarcini (TaskLED și TaskBlink) utilizând API xTaskCreate () și apoi creați un semafor folosind xSemaphoreCreateBinary (). Creați o sarcină cu priorități egale și mai târziu încercați să jucați cu acest număr. De asemenea, configurați pinul 2 ca intrare și activați rezistorul de tragere intern și atașați pinul de întrerupere. În cele din urmă, porniți planificatorul așa cum se arată mai jos.
void setup () { 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. Acum, implementați funcția ISR. Faceți o funcție și denumiți-o la fel ca al doilea argument al funcției attachInterrupt () . Pentru a face întreruperea să funcționeze corect, trebuie să eliminați problema de retragere a butonului utilizând funcția de milis sau micros și ajustând timpul de retragere. Din această funcție, apelați funcția interruptHandler () așa cum se arată mai jos.
timp de dezbatere lung = 150; volatile nesemnate lung last_micros; void debounceInterrupt () { if ((long) (micros () - last_micros)> = debouncing_time * 1000) { interruptHandler (); last_micros = micros (); } }
În funcția interruptHandler () , apelați xSemaphoreGiveFromISR () API.
void interruptHandler () { xSemaphoreGiveFromISR (interruptSemaphore, NULL); }
Această funcție va da un semafor TaskLed pentru a porni LED-ul.
5. Creați o funcție TaskLed și în interiorul buclei while , apelați xSemaphoreTake () API și verificați dacă semaforul este luat cu succes sau nu. Dacă este egal cu pdPASS (adică 1), atunci comutați LED-ul așa cum se arată mai jos.
void TaskLed (void * pvParameters) { (void) pvParameters; pinMode (8, OUTPUT); while (1) { if (xSemaphoreTake (interruptSemaphore, portMAX_DELAY) == pdPASS) { digitalWrite (8,! digitalRead (8)); } } }
6. De asemenea, creați o funcție pentru a clipi alte LED-uri conectate la pinul 7.
void TaskLed1 (void * pvParameters) { (void) pvParameters; pinMode (7, OUTPUT); while (1) { digitalWrite (7, HIGH); vTaskDelay (200 / portTICK_PERIOD_MS); digitalWrite (7, LOW); vTaskDelay (200 / portTICK_PERIOD_MS); } }
7. Funcția de buclă de gol va rămâne goală. Nu uita.
bucla nulă () {}
Gata, codul complet poate fi găsit la sfârșitul acestui tutorial. Acum, încărcați acest cod și conectați LED-urile și butonul cu Arduino UNO conform schemei de circuit.
Diagrama circuitului
După încărcarea codului, veți vedea că un LED clipește după 200ms și când butonul este apăsat, imediat al doilea LED va lumina așa cum se arată în videoclipul dat la final.
În acest fel, semaforele pot fi utilizate în FreeRTOS cu Arduino, unde trebuie să treacă datele de la o sarcină la alta fără nici o pierdere.
Acum, să vedem ce este Mutex și cum să-l folosim FreeRTOS.
Ce este Mutex?
Așa cum s-a explicat mai sus, semaforul este un mecanism de semnalizare, în mod similar, Mutex este un mecanism de blocare spre deosebire de semaforul care are funcții separate pentru creșterea și descreșterea, dar în Mutex, funcția ia și dă în sine. Este o tehnică de evitare a corupției resurselor comune.
Pentru a proteja resursa partajată, se atribuie resursă un card token (mutex). Oricine are acest card poate accesa cealaltă resursă. Alții ar trebui să aștepte până la returnarea cardului. În acest fel, o singură resursă poate accesa sarcina și alții își așteaptă șansa.
Să înțelegem Mutex în FreeRTOS cu ajutorul unui exemplu.
Aici avem trei sarcini, una pentru imprimarea datelor pe LCD, a doua pentru trimiterea datelor LDR la sarcina LCD și ultima sarcină pentru trimiterea datelor de temperatură pe LCD. Deci, aici două sarcini împart aceeași resursă, adică LCD. Dacă sarcina LDR și sarcina de temperatură trimit date simultan, atunci una dintre date poate fi deteriorată sau pierdută.
Deci, pentru a proteja pierderea de date, trebuie să blocăm resursa LCD pentru task1 până când aceasta termină sarcina de afișare. Apoi sarcina LCD se va debloca și apoi task2 își poate îndeplini activitatea.
Puteți observa funcționarea Mutex și a semaforelor în diagrama de mai jos.
Cum se utilizează Mutex în FreeRTOS?
Mutexurile sunt, de asemenea, utilizate în același mod ca semaforele. Mai întâi, creați-l, apoi dați și luați folosind API-urile respective.
Crearea unui Mutex:
Pentru a crea un Mutex, utilizați xSemaphoreCreateMutex () API . După cum sugerează și numele său, Mutex este un tip de semafor binar. Acestea sunt utilizate în contexte și scopuri diferite. Un semafor binar este pentru sincronizarea sarcinilor, în timp ce Mutex este utilizat pentru protejarea unei resurse partajate.
Acest API nu ia niciun argument și returnează o variabilă de tip SemaphoreHandle_t . Dacă mutex-ul nu poate fi creat, xSemaphoreCreateMutex () returnează NULL.
SemaphoreHandle_t mutex_v; mutex_v = xSemaphoreCreateMutex ();
Luarea unui Mutex:
Când o sarcină dorește să acceseze o resursă, va lua un Mutex utilizând xSemaphoreTake () API. Este la fel ca un semafor binar. De asemenea, este nevoie de doi parametri.
xSemaphore: Numele Mutex-ului care trebuie luat în cazul nostru mutex_v .
xTicksToWait: Aceasta este durata maximă pe care sarcina o va aștepta în stare blocată pentru ca Mutex să devină disponibil. În proiectul nostru, vom seta xTicksToWait la portMAX_DELAY pentru ca sarcina_1 să aștepte la nesfârșit în stare blocată până când mutex_v este disponibil.
Oferirea unui Mutex:
După accesarea resursei partajate, sarcina ar trebui să returneze Mutex, astfel încât alte sarcini să o poată accesa. xSemaphoreGive () API este folosit pentru a da Mutex înapoi.
Funcția xSemaphoreGive () ia un singur argument, care este Mutex care trebuie dat în cazul nostru mutex_v.
Folosind API-urile de mai sus, Să implementăm Mutex în codul FreeRTOS folosind Arduino IDE.
Explicația codului Mutex
Aici scopul acestei părți este de a utiliza un monitor serial ca resursă partajată și două sarcini diferite pentru a accesa monitorul serial pentru a imprima un mesaj.
1. Fișierele antet vor rămâne aceleași ca un semafor.
#include #include
2. Declarați o variabilă de tip SemaphoreHandle_t pentru a stoca valorile Mutex.
SemaphoreHandle_t mutex_v;
3. În setarea nulă (), inițializați monitorul serial cu 9600 baud rate și creați două sarcini (Task1 și Task2) folosind API-ul xTaskCreate () . Apoi creați un Mutex folosind xSemaphoreCreateMutex (). Creați o sarcină cu priorități egale și mai târziu încercați să jucați cu acest număr.
void setup () { Serial.begin (9600); mutex_v = xSemaphoreCreateMutex (); if (mutex_v == NULL) { Serial.println ("Mutex nu poate fi creat"); } xTaskCreate (Task1, "Task 1", 128, NULL, 1, NULL); xTaskCreate (Task2, "Task 2", 128, NULL, 1, NULL); }
4. Acum, creați funcții de sarcină pentru Task1 și Task2. Într - o în timp ce buclă a funcției de sarcini, înainte de a imprima un mesaj pe monitorul de serie trebuie să ia un mutex folosind xSemaphoreTake () apoi imprimați mesajul și apoi a reveni mutex folosind xSemaphoreGive (). Apoi dați o oarecare întârziere.
void Task1 (void * pvParameters) { while (1) { xSemaphoreTake (mutex_v, portMAX_DELAY); Serial.println ("Bună din Task1"); xSemaphoreGive (mutex_v); vTaskDelay (pdMS_TO_TICKS (1000)); } }
În mod similar, implementați funcția Task2 cu o întârziere de 500 ms.
5. Bucla de vid () va rămâne goală.
Acum, încărcați acest cod pe Arduino UNO și deschideți monitorul serial.
Veți vedea că mesajele sunt tipărite din task1 și task2.
Pentru a testa funcționarea Mutex, trebuie doar să comentați xSemaphoreGive (mutex_v); din orice sarcină. Puteți vedea că programul se blochează pe ultimul mesaj de tipărire .
Acesta este modul în care Semaphore și Mutex pot fi implementate în FreeRTOS cu Arduino. Pentru mai multe informații despre Semaphore și Mutex, puteți vizita documentația oficială a FreeRTOS.
Codurile și videoclipurile complete pentru Semaphore și Mutes sunt prezentate mai jos.