- Cos'è il semaforo?
- Come usare Semaphore in FreeRTOS?
- Spiegazione del codice semaforo
- Schema elettrico
- Cos'è Mutex?
- Come usare Mutex in FreeRTOS?
- Spiegazione del codice Mutex
Nei tutorial precedenti, abbiamo coperto le basi di FreeRTOS con Arduino e l'oggetto del kernel Queue in FreeRTOS Arduino. Ora, in questo terzo tutorial di FreeRTOS, impareremo di più su FreeRTOS e le sue API avanzate, che possono farti capire più a fondo la piattaforma multi-tasking.
Semaphore e Mutex (Mutual Exclusion) sono gli oggetti del kernel utilizzati per la sincronizzazione, la gestione delle risorse e la protezione delle risorse dalla corruzione. Nella prima metà di questo tutorial, vedremo l'idea alla base di Semaphore, come e dove usarlo. Nella ripresa continueremo con Mutex.
Cos'è il semaforo?
Nelle esercitazioni precedenti, abbiamo discusso delle priorità delle attività e abbiamo anche imparato che un'attività con priorità più alta anticipa un'attività con priorità più bassa, quindi durante l'esecuzione di un'attività ad alta priorità potrebbe esserci la possibilità che il danneggiamento dei dati possa verificarsi in un'attività con priorità inferiore perché non viene ancora eseguito ei dati arrivano continuamente a questa attività da un sensore che causa la perdita di dati e il malfunzionamento dell'intera applicazione.
Quindi, è necessario proteggere le risorse dalla perdita di dati e qui Semaphore gioca un ruolo importante.
Il semaforo è un meccanismo di segnalazione in cui un'attività in uno stato di attesa viene segnalata da un'altra attività per l'esecuzione. In altre parole, quando un'attività1 ha terminato il suo lavoro, mostrerà un flag o incrementerà un flag di 1 e quindi questo flag viene ricevuto da un'altra attività (task2) che mostra che può eseguire il suo lavoro ora. Quando task2 ha terminato il suo lavoro, il flag verrà diminuito di 1.
Quindi, fondamentalmente, è un meccanismo "Give" e "Take" e il semaforo è una variabile intera che viene utilizzata per sincronizzare l'accesso alle risorse.
Tipi di semaforo in FreeRTOS:
Il semaforo è di due tipi.
- Semaforo binario
- Conteggio del semaforo
1. Semaforo binario: ha due valori interi 0 e 1. È in qualche modo simile alla coda di lunghezza 1. Ad esempio, abbiamo due attività, attività1 e attività2. Task1 invia i dati a task2 in modo che task2 controlli continuamente l'elemento della coda se c'è 1, quindi può leggere i dati altrimenti deve aspettare fino a quando non diventa 1. Dopo aver preso i dati, task2 decrementa la coda e lo rende 0 Ciò significa di nuovo task1 può inviare i dati a task2.
Dall'esempio sopra, si può dire che il semaforo binario viene utilizzato per la sincronizzazione tra le attività o tra le attività e l'interruzione.
2. Semaforo di conteggio: ha valori maggiori di 0 e può essere considerato una coda di lunghezza maggiore di 1. Questo semaforo viene utilizzato per il conteggio degli eventi. In questo scenario di utilizzo, un gestore di eventi "fornirà" un semaforo ogni volta che si verifica un evento (incrementando il valore del conteggio del semaforo) e un'attività del gestore "prenderà" un semaforo ogni volta che elabora un evento (diminuendo il valore del conteggio del semaforo).
Il valore di conteggio è, quindi, la differenza tra il numero di eventi che si sono verificati e il numero che è stato elaborato.
Vediamo ora come usare Semaphore nel nostro codice FreeRTOS.
Come usare Semaphore in FreeRTOS?
FreeRTOS supporta diverse API per creare un semaforo, prendere un semaforo e dare un semaforo.
Ora, possono esserci due tipi di API per lo stesso oggetto kernel. Se dobbiamo fornire un semaforo da un ISR, non è possibile utilizzare la normale API del semaforo. È necessario utilizzare API protette da interrupt.
In questo tutorial, useremo il semaforo binario perché è facile da capire e implementare. Poiché qui viene utilizzata la funzionalità di interrupt, è necessario utilizzare API protette da interrupt nella funzione ISR. Quando diciamo sincronizzare un'attività con un interrupt, significa mettere l'attività nello stato In esecuzione subito dopo l'ISR.
Creazione di un semaforo:
Per utilizzare qualsiasi oggetto del kernel, dobbiamo prima crearlo. Per creare un semaforo binario, usa vSemaphoreCreateBinary ().
Questa API non accetta alcun parametro e restituisce una variabile di tipo SemaphoreHandle_t. Viene creato un nome di variabile globale sema_v per memorizzare il semaforo.
SemaphoreHandle_t sema_v; sema_v = xSemaphoreCreateBinary ();
Dare un semaforo:
Per dare un semaforo, ci sono due versioni: una per l'interruzione e un'altra per l'attività normale.
- xSemaphoreGive (): questa API accetta solo un argomento che è il nome della variabile del semaforo come sema_v come indicato sopra durante la creazione di un semaforo. Può essere richiamato da qualsiasi attività normale che si desidera sincronizzare.
- xSemaphoreGiveFromISR (): questa è la versione API protetta da interruzioni di xSemaphoreGive (). Quando è necessario sincronizzare un ISR e un'attività normale, è necessario utilizzare xSemaphoreGiveFromISR () dalla funzione ISR.
Prendendo un semaforo:
Per prendere un semaforo, usa la funzione API xSemaphoreTake (). Questa API accetta due parametri.
xSemaphoreTake (SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
xSemaphore: nome del semaforo da prendere nel nostro caso sema_v.
xTicksToWait: è il tempo massimo che l'attività attenderà nello stato Bloccato prima che il semaforo diventi disponibile. Nel nostro progetto, imposteremo xTicksToWait su portMAX_DELAY per fare in modo che task_1 attenda indefinitamente nello stato Bloccato fino a quando sema_v non sarà disponibile.
Ora usiamo queste API e scriviamo un codice per eseguire alcune attività.
Qui si interfacciano un pulsante e due LED. Il pulsante agirà come un pulsante di interruzione che è collegato al pin 2 di Arduino Uno. Quando questo pulsante viene premuto verrà generato un interrupt e un LED che è collegato al pin 8 si accenderà e quando lo si premerà di nuovo sarà OFF.
Quindi, quando si preme il pulsante xSemaphoreGiveFromISR () verrà chiamato dalla funzione ISR e la funzione xSemaphoreTake () verrà chiamata dalla funzione TaskLED.
Per far sembrare il sistema multitasking, collega altri LED con il pin 7 che sarà sempre lampeggiante.
Spiegazione del codice semaforo
Iniziamo a scrivere il codice aprendo l'IDE di Arduino
1. Innanzitutto, includi il file di intestazione Arduino_FreeRTOS.h . Ora, se un oggetto del kernel viene utilizzato come semaforo della coda, deve essere incluso anche un file di intestazione.
#include #include
2. Dichiarare una variabile di tipo SemaphoreHandle_t per memorizzare i valori di semaphore.
SemaphoreHandle_t interruptSemaphore;
3. In void setup (), creare due attività (TaskLED e TaskBlink) utilizzando l'API xTaskCreate () e quindi creare un semaforo utilizzando xSemaphoreCreateBinary (). Creare un'attività con priorità uguali e successivamente provare a giocare con questo numero. Inoltre, configurare il pin 2 come ingresso e abilitare il resistore di pull-up interno e collegare il pin di interrupt. Infine, avvia lo scheduler come mostrato di seguito.
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. Ora implementate la funzione ISR. Crea una funzione e chiamala come il secondo argomento della funzione attachInterrupt () . Per far funzionare correttamente l'interrupt, è necessario rimuovere il problema del debounce del pulsante utilizzando la funzione millis o micros e regolando il tempo di debouncing. Da questa funzione, chiama la funzione interruptHandler () come mostrato di seguito.
long debouncing_time = 150; volatile unsigned long last_micros; void debounceInterrupt () { if ((long) (micros () - last_micros)> = debouncing_time * 1000) { interruptHandler (); last_micros = micros (); } }
Nella funzione interruptHandler () , chiama l' API xSemaphoreGiveFromISR () .
void interruptHandler () { xSemaphoreGiveFromISR (interruptSemaphore, NULL); }
Questa funzione darà un semaforo a TaskLed per accendere il LED.
5. Creare una funzione TaskLed e all'interno del ciclo while , chiamare l' API xSemaphoreTake () e verificare se il semaforo è stato acquisito o meno. Se è uguale a pdPASS (cioè 1), fai alternare il LED come mostrato sotto.
void TaskLed (void * pvParameters) { (void) pvParameters; pinMode (8, OUTPUT); while (1) { if (xSemaphoreTake (interruptSemaphore, portMAX_DELAY) == pdPASS) {digitalWrite (8 ,! digitalRead (8)); } } }
6. Inoltre, creare una funzione per far lampeggiare l'altro LED collegato al pin 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. La funzione void loop rimarrà vuota. Non dimenticarlo.
void loop () {}
Ecco fatto, il codice completo può essere trovato alla fine di questo tutorial. Ora, carica questo codice e collega i LED e il pulsante con Arduino UNO secondo lo schema del circuito.
Schema elettrico
Dopo aver caricato il codice, vedrai un LED lampeggiare dopo 200ms e quando il pulsante viene premuto, immediatamente il secondo LED si accenderà come mostrato nel video riportato alla fine.
In questo modo, i semafori possono essere utilizzati in FreeRTOS con Arduino dove deve passare i dati da un'attività all'altra senza alcuna perdita.
Ora vediamo cos'è Mutex e come usarlo FreeRTOS.
Cos'è Mutex?
Come spiegato sopra, il semaforo è un meccanismo di segnalazione, allo stesso modo, Mutex è un meccanismo di blocco a differenza del semaforo che ha funzioni separate per l'incremento e il decremento ma in Mutex, la funzione prende e dà in sé. È una tecnica per evitare il danneggiamento delle risorse condivise.
Per proteggere la risorsa condivisa, si assegna una carta gettone (mutex) alla risorsa. Chi ha questa carta può accedere all'altra risorsa. Altri dovrebbero aspettare fino al ritorno della carta. In questo modo, solo una risorsa può accedere all'attività e altre aspettano la loro possibilità.
Comprendiamo Mutex in FreeRTOS con l'aiuto di un esempio.
Qui abbiamo tre attività, una per la stampa dei dati sull'LCD, la seconda per l'invio dei dati LDR all'attività LCD e l'ultima operazione per l'invio dei dati di temperatura sull'LCD. Quindi qui due attività condividono la stessa risorsa, ad esempio LCD. Se l'attività LDR e l'attività di temperatura inviano dati contemporaneamente, uno dei dati potrebbe essere danneggiato o perso.
Quindi, per proteggere la perdita di dati, dobbiamo bloccare la risorsa LCD per task1 fino al termine dell'attività di visualizzazione. Quindi l'attività LCD si sbloccherà e quindi task2 potrà eseguire il suo lavoro.
Puoi osservare il funzionamento del Mutex e dei semafori nel diagramma sottostante.
Come usare Mutex in FreeRTOS?
Anche i mutex sono usati allo stesso modo dei semafori. Innanzitutto, crealo, quindi dai e ricevi utilizzando le rispettive API.
Creazione di un Mutex:
Per creare un Mutex, utilizza l' API xSemaphoreCreateMutex () . Come suggerisce il nome, Mutex è un tipo di semaforo binario. Sono utilizzati in diversi contesti e scopi. Un semaforo binario serve per sincronizzare le attività mentre Mutex viene utilizzato per proteggere una risorsa condivisa.
Questa API non accetta alcun argomento e restituisce una variabile di tipo SemaphoreHandle_t . Se non è possibile creare il mutex, xSemaphoreCreateMutex () restituisce NULL.
SemaphoreHandle_t mutex_v; mutex_v = xSemaphoreCreateMutex ();
Prendere un Mutex:
Quando un'attività vuole accedere a una risorsa, richiederà un Mutex utilizzando l' API xSemaphoreTake () . È lo stesso di un semaforo binario. Accetta anche due parametri.
xSemaphore: nome del Mutex da prendere nel nostro caso mutex_v .
xTicksToWait: questo è il tempo massimo che l'attività attenderà nello stato Bloccato prima che il Mutex diventi disponibile. Nel nostro progetto, imposteremo xTicksToWait su portMAX_DELAY per fare in modo che task_1 attenda indefinitamente nello stato bloccato fino a quando mutex_v non sarà disponibile.
Dare un mutex:
Dopo l'accesso alla risorsa condivisa, l'attività dovrebbe restituire il Mutex in modo che altre attività possano accedervi. L' API xSemaphoreGive () viene utilizzata per restituire il Mutex.
La funzione xSemaphoreGive () accetta un solo argomento che è il Mutex da fornire nel nostro caso mutex_v.
Utilizzando le API precedenti, implementiamo Mutex nel codice FreeRTOS utilizzando l'IDE di Arduino.
Spiegazione del codice Mutex
Qui l'obiettivo di questa parte è utilizzare un monitor seriale come risorsa condivisa e due diverse attività per accedere al monitor seriale per stampare un messaggio.
1. I file di intestazione rimarranno gli stessi di un semaforo.
#include #include
2. Dichiarare una variabile di tipo SemaphoreHandle_t per memorizzare i valori di Mutex.
SemaphoreHandle_t mutex_v;
3. In void setup (), inizializzare il monitor seriale con velocità di 9600 baud e creare due attività (Task1 e Task2) utilizzando l' API xTaskCreate () . Quindi crea un Mutex usando xSemaphoreCreateMutex (). Crea un'attività con le stesse priorità e in seguito prova a giocare con questo numero.
void setup () { Serial.begin (9600); mutex_v = xSemaphoreCreateMutex (); if (mutex_v == NULL) { Serial.println ("Mutex non può essere creato"); } xTaskCreate (Task1, "Task 1", 128, NULL, 1, NULL); xTaskCreate (Task2, "Task 2", 128, NULL, 1, NULL); }
4. Ora, crea funzioni di attività per Task1 e Task2. In un ciclo while della funzione task, prima di stampare un messaggio sul monitor seriale dobbiamo prendere un Mutex usando xSemaphoreTake () quindi stampare il messaggio e poi restituire il Mutex usando xSemaphoreGive (). Quindi dai un po 'di ritardo.
void Task1 (void * pvParameters) { while (1) { xSemaphoreTake (mutex_v, portMAX_DELAY); Serial.println ("Ciao da Task1"); xSemaphoreGive (mutex_v); vTaskDelay (pdMS_TO_TICKS (1000)); } }
Allo stesso modo, implementa la funzione Task2 con un ritardo di 500 ms.
5. Void loop () rimarrà vuoto.
Ora carica questo codice su Arduino UNO e apri il monitor seriale.
Vedrai che i messaggi vengono stampati da task1 e task2.
Per testare il funzionamento di Mutex, basta commentare xSemaphoreGive (mutex_v); da qualsiasi attività. Puoi vedere che il programma si blocca sull'ultimo messaggio di stampa .
È così che Semaphore e Mutex possono essere implementati in FreeRTOS con Arduino. Per ulteriori informazioni su Semaphore e Mutex, puoi visitare la documentazione ufficiale di FreeRTOS.
Di seguito sono riportati i codici completi e il video per Semaphore e Mute.