- Τι είναι το Semaphore;
- Πώς να χρησιμοποιήσετε το Semaphore στο FreeRTOS;
- Επεξήγηση κώδικα Semaphore
- Διάγραμμα κυκλώματος
- Τι είναι το Mutex;
- Πώς να χρησιμοποιήσετε το Mutex στο FreeRTOS;
- Επεξήγηση κώδικα Mutex
Σε προηγούμενα μαθήματα, έχουμε καλύψει τα βασικά του FreeRTOS με το Arduino και το αντικείμενο Queue πυρήνα στο FreeRTOS Arduino. Τώρα, σε αυτό το τρίτο σεμινάριο του FreeRTOS, θα μάθουμε περισσότερα για το FreeRTOS και τα προηγμένα API του, τα οποία θα σας κάνουν να κατανοήσετε την πλατφόρμα πολλαπλών εργασιών πιο βαθιά.
Το Semaphore και το Mutex (Mutual Exclusion) είναι τα αντικείμενα του πυρήνα που χρησιμοποιούνται για συγχρονισμό, διαχείριση πόρων και προστασία πόρων από καταστροφή. Στο πρώτο μισό αυτού του σεμιναρίου, θα δούμε την ιδέα πίσω από το Semaphore, πώς και πού να το χρησιμοποιήσουμε. Στο δεύτερο ημίχρονο, θα συνεχίσουμε με το Mutex.
Τι είναι το Semaphore;
Σε προηγούμενα μαθήματα, έχουμε συζητήσει για τις προτεραιότητες εργασιών και επίσης γνωρίζουμε ότι μια εργασία υψηλότερης προτεραιότητας προκαλεί μια εργασία χαμηλότερης προτεραιότητας, οπότε ενώ η εκτέλεση εργασιών υψηλής προτεραιότητας ενδέχεται να υπάρχει πιθανότητα να προκληθεί διαφθορά δεδομένων σε εργασίες χαμηλότερης προτεραιότητας, επειδή δεν έχει ακόμη εκτελεστεί και τα δεδομένα έρχονται συνεχώς σε αυτήν την εργασία από έναν αισθητήρα που προκαλεί απώλεια δεδομένων και δυσλειτουργία ολόκληρης της εφαρμογής.
Επομένως, υπάρχει ανάγκη προστασίας των πόρων από την απώλεια δεδομένων και εδώ η Semaphore παίζει σημαντικό ρόλο.
Το Semaphore είναι ένας μηχανισμός σηματοδότησης στον οποίο μια εργασία σε κατάσταση αναμονής σηματοδοτείται από άλλη εργασία για εκτέλεση. Με άλλα λόγια, όταν μια εργασία1 τελείωσε το έργο της, τότε θα εμφανίσει μια σημαία ή θα αυξήσει μια σημαία κατά 1 και, στη συνέχεια, αυτή η σημαία θα ληφθεί από μια άλλη εργασία (εργασία2) που δείχνει ότι μπορεί να εκτελέσει την εργασία της τώρα. Όταν η εργασία2 τελειώσει τη δουλειά της τότε η σημαία θα μειωθεί κατά 1.
Έτσι, βασικά, είναι ένας μηχανισμός "Δώστε" και "Λήψη" και το semaphore είναι μια ακέραια μεταβλητή που χρησιμοποιείται για τον συγχρονισμό της πρόσβασης σε πόρους.
Τύποι Semaphore στο FreeRTOS:
Το Semaphore είναι δύο τύπων.
- Δυαδικό Semaphore
- Μετρώντας το Semaphore
1. Binary Semaphore: Έχει δύο ακέραιες τιμές 0 και 1. Είναι κάπως παρόμοια με την ουρά μήκους 1. Για παράδειγμα, έχουμε δύο εργασίες, task1 και task2. Το Task1 στέλνει δεδομένα στο task2, οπότε το task2 ελέγχει συνεχώς το στοιχείο ουράς αν υπάρχει 1, τότε μπορεί να διαβάσει τα δεδομένα που πρέπει να περιμένει μέχρι να γίνει 1. Μετά τη λήψη των δεδομένων, το task2 μειώνει την ουρά και το κάνει 0 Αυτό σημαίνει task1 ξανά μπορεί να στείλει τα δεδομένα στην εργασία2.
Από το παραπάνω παράδειγμα, μπορεί να ειπωθεί ότι το δυαδικό σηματοφόρο χρησιμοποιείται για συγχρονισμό μεταξύ εργασιών ή μεταξύ εργασιών και διακοπής.
2. Καταμέτρηση Semaphore: Έχει τιμές μεγαλύτερες από 0 και μπορεί να θεωρηθεί ως ουρά μήκους μεγαλύτερη από 1. Αυτός ο σηματοφόρος χρησιμοποιείται για την καταμέτρηση συμβάντων. Σε αυτό το σενάριο χρήσης, ένας χειριστής συμβάντων «δίνει» ένα σηματοφόρο κάθε φορά που συμβαίνει ένα συμβάν (αυξάνοντας την τιμή μέτρησης του σηματοφόρου) και μια εργασία χειριστή «παίρνει» ένα σηματοφόρο κάθε φορά που επεξεργάζεται ένα συμβάν (μείωση της τιμής μέτρησης του σηματοφόρου).
Η τιμή μέτρησης είναι, επομένως, η διαφορά μεταξύ του αριθμού των συμβάντων που έχουν συμβεί και του αριθμού που έχει υποστεί επεξεργασία.
Τώρα, ας δούμε πώς να χρησιμοποιήσετε το Semaphore στον κώδικα FreeRTOS.
Πώς να χρησιμοποιήσετε το Semaphore στο FreeRTOS;
Το FreeRTOS υποστηρίζει διαφορετικά API για τη δημιουργία ενός σηματοφόρου, τη λήψη ενός σηματοφόρου και τη χορήγηση ενός σηματοφόρου.
Τώρα, μπορεί να υπάρχουν δύο τύποι API για το ίδιο αντικείμενο πυρήνα. Εάν πρέπει να δώσουμε το σηματοφόρο από ένα ISR, τότε δεν μπορεί να χρησιμοποιηθεί το κανονικό semaphore API. Θα πρέπει να χρησιμοποιείτε προστατευμένα API που διακόπτονται από διακοπή.
Σε αυτό το σεμινάριο, θα χρησιμοποιήσουμε δυαδικό σηματοφόρο επειδή είναι εύκολο να κατανοηθεί και να εφαρμοστεί. Καθώς χρησιμοποιείται η λειτουργία διακοπής εδώ, πρέπει να χρησιμοποιήσετε API που προστατεύονται από διακοπή στη λειτουργία ISR. Όταν λέμε συγχρονισμό μιας εργασίας με διακοπή, αυτό σημαίνει ότι θέτετε την εργασία σε κατάσταση λειτουργίας αμέσως μετά την ISR.
Δημιουργία ενός Semaphore:
Για να χρησιμοποιήσουμε οποιοδήποτε αντικείμενο πυρήνα, πρέπει πρώτα να το δημιουργήσουμε. Για τη δημιουργία ενός δυαδικού semaphore, χρησιμοποιήστε το vSemaphoreCreateBinary ().
Αυτό το API δεν λαμβάνει καμία παράμετρο και επιστρέφει μια μεταβλητή τύπου SemaphoreHandle_t. Δημιουργείται ένα καθολικό όνομα μεταβλητής sema_v για την αποθήκευση του semaphore.
SemaphoreHandle_t sema_v; sema_v = xSemaphoreCreateBinary ();
Δίνοντας ένα σηματοφόρο:
Για να δώσετε ένα σηματοφόρο, υπάρχουν δύο εκδόσεις - μία για διακοπή και άλλη για την κανονική εργασία.
- xSemaphoreGive (): Αυτό το API παίρνει μόνο ένα όρισμα που είναι το μεταβλητό όνομα του semaphore όπως το sema_v όπως δίνεται παραπάνω κατά τη δημιουργία ενός semaphore. Μπορεί να κληθεί από οποιαδήποτε κανονική εργασία που θέλετε να συγχρονίσετε.
- xSemaphoreGiveFromISR (): Αυτή είναι η προστατευμένη από το IP έκδοση έκδοση του xSemaphoreGive (). Όταν πρέπει να συγχρονίσουμε μια ISR και μια κανονική εργασία, τότε xSemaphoreGiveFromISR () θα πρέπει να χρησιμοποιείται από τη συνάρτηση ISR.
Λήψη ενός σηματοφόρου:
Για να πάρετε ένα σηματοφόρο, χρησιμοποιήστε τη συνάρτηση API xSemaphoreTake (). Αυτό το API λαμβάνει δύο παραμέτρους.
xSemaphoreTake (SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
xSemaphore: Όνομα του σηματοφόρου που θα ληφθεί στην περίπτωσή μας sema_v.
xTicksToWait: Αυτός είναι ο μέγιστος χρόνος που θα περιμένει η εργασία σε κατάσταση αποκλεισμού για να γίνει διαθέσιμο το σηματοφόρο. Στο έργο μας, θα ορίσουμε το xTicksToWait στη θύραMAX_DELAY για να κάνει το task_1 να περιμένει επ 'αόριστον σε κατάσταση αποκλεισμού έως ότου το sema_v είναι διαθέσιμο.
Τώρα, ας χρησιμοποιήσουμε αυτά τα API και γράψτε έναν κώδικα για την εκτέλεση ορισμένων εργασιών.
Εδώ συνδέεται ένα μπουτόν και δύο LED. Το μπουτόν θα λειτουργήσει ως κουμπί διακοπής που είναι προσαρτημένο στον ακροδέκτη 2 του Arduino Uno. Όταν πατηθεί αυτό το κουμπί, θα δημιουργηθεί διακοπή και ένα LED που είναι συνδεδεμένο στον ακροδέκτη 8 θα ανάβει και όταν το πατήσετε ξανά θα είναι απενεργοποιημένο.
Έτσι, όταν πατηθεί το κουμπί xSemaphoreGiveFromISR () θα κληθεί από τη λειτουργία ISR και η συνάρτηση xSemaphoreTake () θα κληθεί από τη λειτουργία TaskLED.
Για να κάνετε το σύστημα να μοιάζει με πολλαπλές εργασίες, συνδέστε άλλες λυχνίες LED με τον ακροδέκτη 7 που θα είναι πάντα σε αναλαμπή.
Επεξήγηση κώδικα Semaphore
Ας αρχίσουμε να γράφουμε κώδικα για το άνοιγμα του Arduino IDE
1. Πρώτα, συμπεριλάβετε το αρχείο κεφαλίδας Arduino_FreeRTOS.h . Τώρα, εάν οποιοδήποτε αντικείμενο πυρήνα χρησιμοποιείται όπως η ουρά semaphore, τότε πρέπει επίσης να συμπεριληφθεί ένα αρχείο κεφαλίδας.
# συμπερίληψη # συμπερίληψη
2. Δηλώστε μια μεταβλητή τύπου SemaphoreHandle_t για να αποθηκεύσετε τις τιμές του semaphore.
SemaphoreHandle_t interruptSemaphore;
3. Στην κενή ρύθμιση (), δημιουργήστε δύο εργασίες (TaskLED και TaskBlink) χρησιμοποιώντας το xTaskCreate () API και, στη συνέχεια, δημιουργήστε ένα semaphore χρησιμοποιώντας το xSemaphoreCreateBinary (). Δημιουργήστε μια εργασία με ίσες προτεραιότητες και αργότερα προσπαθήστε να παίξετε με αυτόν τον αριθμό. Επίσης, διαμορφώστε τον πείρο 2 ως είσοδο και ενεργοποιήστε την εσωτερική αντίσταση έλξης και συνδέστε τον πείρο διακοπής. Τέλος, ξεκινήστε τον προγραμματιστή όπως φαίνεται παρακάτω.
άκυρη ρύθμιση () { 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. Τώρα, εφαρμόστε τη λειτουργία ISR. Κάντε μια συνάρτηση και ονομάστε την ίδια με το δεύτερο όρισμα της συνάρτησης attachInterrupt () . Για να λειτουργήσει σωστά η διακοπή, θα πρέπει να αφαιρέσετε το πρόβλημα απόρριψης του κουμπιού χρησιμοποιώντας τη λειτουργία millis ή micros και προσαρμόζοντας το χρόνο αποβίβασης. Από αυτήν τη λειτουργία, καλέστε τη συνάρτηση interruptHandler () όπως φαίνεται παρακάτω.
μεγάλο debouncing_time = 150; πτητικά χωρίς υπογραφή μεγάλο Last_micros; void debounceInterrupt () { if ((long) (micros () - last_micros)> = debouncing_time * 1000) { interruptHandler (); last_micros = micros (); } }
Στη συνάρτηση interruptHandler () , καλέστε το API xSemaphoreGiveFromISR () .
void interruptHandler () { xSemaphoreGiveFromISR (interruptSemaphore, NULL); }
Αυτή η λειτουργία θα δώσει ένα σηματοφόρο στο TaskLed για να ανάψει το LED.
5. Δημιουργήστε ένα TaskLed λειτουργία και στο εσωτερικό του , ενώ βρόχο, καλέστε xSemaphoreTake () API και ελέγξτε αν η σηματοφορέων επιτυχία ληφθεί ή όχι. Εάν είναι ίσο με το pdPASS (δηλ. 1), κάντε την εναλλαγή LED όπως φαίνεται παρακάτω.
void TaskLed (void * pvParameters) { (void) pvParameters; pinMode (8, ΕΞΟΔΟΣ); ενώ (1) { if (xSemaphoreTake (interruptSemaphore, portMAX_DELAY) == pdPASS) { digitalWrite (8,! digitalRead (8)); } } }
6. Επίσης, δημιουργήστε μια λειτουργία για να αναβοσβήσετε άλλα LED που είναι συνδεδεμένα στον ακροδέκτη 7.
void TaskLed1 (void * pvParameters) { (void) pvParameters; pinMode (7, ΕΞΟΔΟΣ); ενώ (1) { digitalWrite (7, HIGH); vTaskDelay (200 / portTICK_PERIOD_MS); digitalWrite (7, LOW); vTaskDelay (200 / portTICK_PERIOD_MS); } }
7. Η λειτουργία κενού βρόχου θα παραμείνει κενή. Μην το ξεχνάς.
κενός βρόχος () {}
Αυτό είναι, πλήρης κωδικός μπορεί να βρεθεί στο τέλος αυτού του σεμιναρίου. Τώρα, ανεβάστε αυτόν τον κωδικό και συνδέστε τις λυχνίες LED και το κουμπί με το Arduino UNO σύμφωνα με το διάγραμμα κυκλώματος.
Διάγραμμα κυκλώματος
Αφού ανεβάσετε τον κωδικό, θα δείτε ότι ένα LED αναβοσβήνει μετά από 200 ms και όταν πατηθεί το κουμπί, αμέσως το δεύτερο LED θα ανάψει όπως φαίνεται στο βίντεο που δίνεται στο τέλος.
Με αυτόν τον τρόπο, οι σηματοφόροι μπορούν να χρησιμοποιηθούν στο FreeRTOS με το Arduino όπου πρέπει να μεταφέρει τα δεδομένα από τη μία εργασία στην άλλη χωρίς καμία απώλεια.
Τώρα, ας δούμε τι είναι το Mutex και πώς να το χρησιμοποιήσουμε FreeRTOS.
Τι είναι το Mutex;
Όπως εξηγήθηκε παραπάνω, το semaphore είναι ένας μηχανισμός σηματοδότησης, ομοίως, το Mutex είναι ένας μηχανισμός κλειδώματος σε αντίθεση με τον semaphore που έχει ξεχωριστές λειτουργίες για αύξηση και μείωση, αλλά στο Mutex, η συνάρτηση παίρνει και δίνει από μόνη της. Είναι μια τεχνική για την αποφυγή της διαφθοράς των κοινών πόρων.
Για την προστασία του κοινόχρηστου πόρου, κάποιος εκχωρεί μια κάρτα διακριτικού (mutex) στον πόρο. Όποιος έχει αυτήν την κάρτα μπορεί να έχει πρόσβαση στον άλλο πόρο. Άλλοι θα πρέπει να περιμένουν μέχρι να επιστραφεί η κάρτα. Με αυτόν τον τρόπο, μόνο ένας πόρος μπορεί να έχει πρόσβαση στην εργασία και άλλοι περιμένουν την ευκαιρία τους.
Ας κατανοήσουμε το Mutex στο FreeRTOS με τη βοήθεια ενός παραδείγματος.
Εδώ έχουμε τρεις εργασίες, μία για την εκτύπωση δεδομένων σε LCD, δεύτερη για την αποστολή δεδομένων LDR σε εργασία LCD και την τελευταία εργασία για την αποστολή δεδομένων θερμοκρασίας σε LCD. Έτσι εδώ δύο εργασίες μοιράζονται τον ίδιο πόρο, δηλαδή LCD. Εάν η εργασία LDR και η εργασία θερμοκρασίας στέλνουν δεδομένα ταυτόχρονα, τότε ένα από τα δεδομένα μπορεί να είναι κατεστραμμένο ή να χαθεί.
Επομένως, για να προστατεύσουμε την απώλεια δεδομένων, πρέπει να κλειδώσουμε τον πόρο LCD για την εργασία1 έως ότου τελειώσει η εργασία εμφάνισης. Στη συνέχεια, η εργασία LCD θα ξεκλειδωθεί και, στη συνέχεια, το task2 μπορεί να εκτελέσει τη δουλειά του.
Μπορείτε να παρατηρήσετε τη λειτουργία του Mutex και των σηματοφόρων στο παρακάτω διάγραμμα.
Πώς να χρησιμοποιήσετε το Mutex στο FreeRTOS;
Τα Mutexs χρησιμοποιούνται επίσης με τον ίδιο τρόπο όπως και οι σηματοφόροι. Αρχικά, δημιουργήστε το και μετά δώστε και πάρτε χρησιμοποιώντας αντίστοιχα API.
Δημιουργία Mutex:
Για να δημιουργήσετε ένα Mutex, χρησιμοποιήστε το API xSemaphoreCreateMutex () . Όπως το όνομά του υποδηλώνει ότι το Mutex είναι ένας τύπος Binary semaphore. Χρησιμοποιούνται σε διαφορετικά περιβάλλοντα και σκοπούς. Ένα δυαδικό semaphore προορίζεται για συγχρονισμό εργασιών ενώ το Mutex χρησιμοποιείται για την προστασία ενός κοινόχρηστου πόρου.
Αυτό το API δεν λαμβάνει κανένα όρισμα και επιστρέφει μια μεταβλητή τύπου SemaphoreHandle_t . Εάν δεν είναι δυνατή η δημιουργία του mutex, xSemaphoreCreateMutex () επιστρέφει NULL.
SemaphoreHandle_t mutex_v; mutex_v = xSemaphoreCreateMutex ();
Λήψη ενός Mutex:
Όταν μια εργασία θέλει να αποκτήσει πρόσβαση σε έναν πόρο, θα χρειαστεί ένα Mutex χρησιμοποιώντας το API xSemaphoreTake () . Είναι το ίδιο με ένα δυαδικό σηματοφόρο. Παίρνει επίσης δύο παραμέτρους.
xSemaphore: Το όνομα του Mutex που θα ληφθεί στην περίπτωσή μας mutex_v .
xTicksToWait: Αυτός είναι ο μέγιστος χρόνος που θα περιμένει η εργασία σε κατάσταση αποκλεισμού για να είναι διαθέσιμο το Mutex. Στο έργο μας, θα ρυθμίσουμε το xTicksToWait στη θύραMAX_DELAY για να κάνουμε την εργασία_1 να περιμένει επ 'αόριστον σε κατάσταση αποκλεισμού έως ότου είναι διαθέσιμη το mutex_v .
Δίνοντας ένα Mutex:
Μετά την πρόσβαση στον κοινόχρηστο πόρο, η εργασία θα πρέπει να επιστρέψει το Mutex, ώστε να μπορούν να έχουν πρόσβαση σε άλλες εργασίες. Το API xSemaphoreGive () χρησιμοποιείται για την επιστροφή του Mutex.
Η συνάρτηση xSemaphoreGive () παίρνει μόνο ένα όρισμα που είναι το Mutex που θα δοθεί στην περίπτωσή μας mutex_v.
Χρησιμοποιώντας τα παραπάνω API, ας εφαρμόσουμε το Mutex στον κώδικα FreeRTOS χρησιμοποιώντας το Arduino IDE.
Επεξήγηση κώδικα Mutex
Εδώ ο στόχος για αυτό το μέρος είναι να χρησιμοποιήσετε μια σειριακή οθόνη ως κοινόχρηστο πόρο και δύο διαφορετικές εργασίες για να αποκτήσετε πρόσβαση στη σειριακή οθόνη για να εκτυπώσετε κάποιο μήνυμα.
1. Τα αρχεία κεφαλίδας θα παραμείνουν ίδια με ένα σηματοφόρο.
# συμπερίληψη # συμπερίληψη
2. Δηλώστε μια μεταβλητή τύπου SemaphoreHandle_t για να αποθηκεύσετε τις τιμές του Mutex.
SemaphoreHandle_t mutex_v;
3. Στην κενή ρύθμιση (), αρχικοποιήστε τη σειριακή οθόνη με ρυθμό baud 9600 και δημιουργήστε δύο εργασίες (Task1 και Task2) χρησιμοποιώντας το API xTaskCreate () . Στη συνέχεια, δημιουργήστε ένα Mutex χρησιμοποιώντας το xSemaphoreCreateMutex (). Δημιουργήστε μια εργασία με ίσες προτεραιότητες και αργότερα προσπαθήστε να παίξετε με αυτόν τον αριθμό.
άκυρη ρύθμιση () { Serial.begin (9600); mutex_v = xSemaphoreCreateMutex (); if (mutex_v == NULL) { Serial.println ("Δεν είναι δυνατή η δημιουργία του Mutex"); } xTaskCreate (Εργασία 1, "Εργασία 1", 128, NULL, 1, NULL); xTaskCreate (Εργασία 2, "Εργασία 2", 128, NULL, 1, NULL); }
4. Τώρα, κάντε λειτουργίες εργασίας για τις Εργασίες 1 και Εργασία 2. Σε λίγο το loop of task function, πριν εκτυπώσουμε ένα μήνυμα στη σειριακή οθόνη, πρέπει να κάνουμε ένα Mutex χρησιμοποιώντας το xSemaphoreTake () και μετά να εκτυπώσουμε το μήνυμα και μετά να επιστρέψουμε το Mutex χρησιμοποιώντας το xSemaphoreGive (). Στη συνέχεια, δώστε κάποια καθυστέρηση.
void Task1 (void * pvParameters) { ενώ (1) { xSemaphoreTake (mutex_v, portMAX_DELAY); Serial.println ("Γεια από την εργασία 1"); xSemaphoreGive (mutex_v); vTaskDelay (pdMS_TO_TICKS (1000)); } }
Ομοίως, υλοποιήστε τη συνάρτηση Task2 με καθυστέρηση 500ms.
5. Ο βρόχος κενού () θα παραμείνει άδειος.
Τώρα, ανεβάστε αυτόν τον κωδικό στο Arduino UNO και ανοίξτε τη σειριακή οθόνη.
Θα δείτε ότι τα μηνύματα εκτυπώνονται από τα task1 και task2.
Για να ελέγξετε τη λειτουργία του Mutex, απλώς σχολιάστε το xSemaphoreGive (mutex_v). από οποιαδήποτε εργασία. Μπορείτε να δείτε ότι το πρόγραμμα κρέμεται από το τελευταίο μήνυμα εκτύπωσης .
Έτσι μπορούν να εφαρμοστούν τα Semaphore και Mutex στο FreeRTOS με το Arduino. Για περισσότερες πληροφορίες σχετικά με το Semaphore και το Mutex, μπορείτε να επισκεφθείτε την επίσημη τεκμηρίωση του FreeRTOS.
Παρακάτω δίνονται πλήρεις κωδικοί και βίντεο για το Semaphore και το Mute.