- Що таке семафор?
- Як використовувати Semaphore у FreeRTOS?
- Пояснення коду семафору
- Кругова діаграма
- Що таке Mutex?
- Як використовувати Mutex у FreeRTOS?
- Пояснення коду Mutex
У попередніх підручниках ми розглянули основи FreeRTOS за допомогою Arduino та об'єкт ядра Queue у FreeRTOS Arduino. Тепер у цьому третьому навчальному посібнику FreeRTOS ми дізнаємось більше про FreeRTOS та його передові API, які допоможуть глибше зрозуміти багатозадачну платформу.
Semaphore та Mutex (взаємне виключення) - це об’єкти ядра, які використовуються для синхронізації, управління ресурсами та захисту ресурсів від пошкодження. У першій половині цього підручника ми побачимо ідею Semaphore, як і де її використовувати. У другому таймі ми продовжимо з Mutex.
Що таке семафор?
У попередніх підручниках ми обговорювали пріоритети завдань, а також дізналися, що завдання з вищим пріоритетом попереджає завдання з нижчим пріоритетом, тому під час виконання завдання з високим пріоритетом може виникнути можливість пошкодження даних у завданні з нижчим пріоритетом, оскільки ще не виконується, і дані постійно надходять до цього завдання від датчика, що спричиняє втрату даних та несправність всього додатка.
Отже, існує необхідність захистити ресурси від втрати даних, і тут Семафор відіграє важливу роль.
Семафор - це сигнальний механізм, при якому завдання у стані очікування сигналізується іншим завданням для виконання. Іншими словами, коли завдання1 закінчило свою роботу, тоді воно покаже прапор або збільшить прапор на 1, а потім цей прапор отримує інше завдання (завдання2), показуючи, що воно може виконувати свою роботу зараз. Коли завдання2 завершить свою роботу, прапор буде зменшено на 1.
Отже, це, по суті, механізм “Дай” і “Візьми”, а семафор - ціла змінна, яка використовується для синхронізації доступу до ресурсів.
Типи семафорів у FreeRTOS:
Семафор буває двох типів.
- Бінарний семафор
- Підрахунок семафору
1. Бінарний семафор: він має два цілих значення 0 і 1. Це чимось схоже на Чергу довжини 1. Наприклад, у нас є два завдання, task1 і task2. Завдання1 відправляє дані в task2, тому task2 постійно перевіряє елемент черги, якщо є 1, тоді він може читати дані, інакше йому потрібно почекати, поки вони не стануть 1. Після отримання даних task2 зменшує чергу і робить її 0 Це означає, що task1 знову може надіслати дані до task2.
З наведеного прикладу можна сказати, що двійковий семафор використовується для синхронізації між завданнями або між завданнями та переривання.
2. Підрахунок семафору: він має значення більше 0, і його можна вважати чергою довжиною більше 1. Цей семафор використовується для підрахунку подій. У цьому сценарії використання обробник подій буде "видавати" семафор кожного разу, коли відбувається подія (збільшуючи значення кількості семафора), а завдання обробника "прийматиме" семафор кожного разу, коли обробляє подію (зменшуючи значення кількості семафора).
Отже, значення підрахунку - це різниця між кількістю подій, що відбулися, та числом, яке було оброблено.
Тепер давайте подивимося, як використовувати Semaphore у нашому коді FreeRTOS.
Як використовувати Semaphore у FreeRTOS?
FreeRTOS підтримує різні API для створення семафору, взяття семафору та надання семафору.
Тепер для одного і того ж об’єкта ядра можуть існувати два типи API. Якщо нам доводиться давати семафор з ISR, тоді не можна використовувати звичайний API семафору. Ви повинні використовувати захищені від переривань API.
У цьому підручнику ми будемо використовувати двійковий семафор, оскільки його легко зрозуміти та реалізувати. Оскільки тут використовується функція переривання, вам потрібно використовувати захищені від переривань API у функції ISR. Коли ми говоримо про синхронізацію завдання з перериванням, це означає переведення завдання в стан запуску відразу після ISR.
Створення семафору:
Щоб використовувати будь-який об'єкт ядра, ми повинні спочатку створити його. Для створення бінарного семафора використовуйте vSemaphoreCreateBinary ().
Цей API не приймає жодного параметра і повертає змінну типу SemaphoreHandle_t. Для зберігання семафору створено глобальну назву змінної sema_v.
SemaphoreHandle_t sema_v; sema_v = xSemaphoreCreateBinary ();
Даємо семафор:
Для надання семафору існує дві версії - одна для переривання та інша для звичайного завдання.
- xSemaphoreGive (): Цей API бере лише один аргумент, який є назвою змінної семафору, наприклад sema_v, як зазначено вище, під час створення семафору. Його можна викликати з будь-якого звичайного завдання, яке потрібно синхронізувати.
- xSemaphoreGiveFromISR (): Це захищена версія переривання API xSemaphoreGive (). Коли нам потрібно синхронізувати ISR і звичайне завдання, тоді з функції ISR слід використовувати xSemaphoreGiveFromISR ().
Беручи семафор:
Щоб взяти семафор, використовуйте функцію API xSemaphoreTake (). Цей API приймає два параметри.
xSemaphoreTake (SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
xSemaphore: Назва семафора, який слід взяти в нашому випадку sema_v.
xTicksToWait: це максимальна кількість часу, протягом якого завдання буде чекати в заблокованому стані, поки семафор стане доступним. У нашому проекті ми встановимо xTicksToWait на portMAX_DELAY, щоб завдання_1 нескінченно чекало в заблокованому стані, поки sema_v не стане доступним.
Тепер давайте використаємо ці API та напишемо код для виконання деяких завдань.
Тут є одна кнопка та два світлодіоди. Кнопка буде діяти як кнопка переривання, яка прикріплена до висновку 2 Arduino Uno. При натисканні на цю кнопку генерується переривання, а світлодіод, підключений до виводу 8, увімкнеться, і при повторному натисканні він вимкнеться.
Отже, при натисканні кнопки xSemaphoreGiveFromISR () буде викликано з функції ISR, а функція xSemaphoreTake () буде викликана з функції TaskLED.
Щоб система виглядала багатозадачною, підключіть інші світлодіоди за допомогою штифта 7, який буде постійно блимати.
Пояснення коду семафору
Почнемо писати код для відкриття IDE Arduino
1. По-перше, включіть файл заголовка Arduino_FreeRTOS.h . Тепер, якщо який-небудь об’єкт ядра використовується як семафор черги, для нього також повинен бути включений файл заголовка.
# включити # включити
2. Оголосіть змінну типу SemaphoreHandle_t для зберігання значень семафору.
SemaphoreHandle_t interruptSemaphore;
3. У void setup () створіть два завдання (TaskLED і TaskBlink) за допомогою API xTaskCreate (), а потім створіть семафор за допомогою xSemaphoreCreateBinary (). Створіть завдання з однаковими пріоритетами, а потім спробуйте зіграти з цим номером. Крім того, налаштуйте штифт 2 як вхід і включіть внутрішній підтягуючий резистор і приєднайте штифт переривання. Нарешті, запустіть планувальник, як показано нижче.
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. Тепер реалізуйте функцію ISR. Створіть функцію та назвіть її так само, як другий аргумент функції attachInterrupt () . Щоб перерва працювала належним чином, потрібно усунути проблему розриву натискання кнопки, використовуючи функцію міліс або мікро і регулюючи час зняття звуку. За допомогою цієї функції викличте функцію interruptHandler (), як показано нижче.
довгий час розголошення_ 150 = 150; volatile unsigned long last_micros; void debounceInterrupt () { if ((long) (micros () - last_micros)> = debouncing_time * 1000) { interruptHandler (); last_micros = мікрос (); } }
У функції interruptHandler () викличте API xSemaphoreGiveFromISR () .
void interruptHandler () { xSemaphoreGiveFromISR (interruptSemaphore, NULL); }
Ця функція дасть семафору TaskLed увімкнути світлодіод.
5. Створіть TaskLed функції і всередині в той час циклу, викличте xSemaphoreTake () API і перевірити, якщо семафор успішно прийнятий чи ні. Якщо воно дорівнює pdPASS (тобто 1), тоді переведіть світлодіод, як показано нижче.
void TaskLed (void * pvParameters) { (void) pvParameters; pinMode (8, ВИХІД); while (1) { if (xSemaphoreTake (interruptSemaphore, portMAX_DELAY) == pdPASS) { digitalWrite (8,! digitalRead (8)); } } }
6. Також створіть функцію, яка блиматиме іншим світлодіодом, підключеним до виводу 7.
void TaskLed1 (void * pvParameters) { (void) pvParameters; pinMode (7, ВИХІД); while (1) { digitalWrite (7, HIGH); vTaskDelay (200 / портTICK_PERIOD_MS); digitalWrite (7, LOW); vTaskDelay (200 / портTICK_PERIOD_MS); } }
7. Функція void loop залишатиметься порожньою. Не забувайте про це.
недійсний цикл () {}
Ось і все, повний код можна знайти в кінці цього підручника. Тепер завантажте цей код і підключіть світлодіоди та кнопку до Arduino UNO відповідно до принципової схеми.
Кругова діаграма
Після завантаження коду ви побачите, що світлодіод блимає через 200 мс, і при натисканні кнопки негайно другий світлодіод буде світитися, як показано на відео, поданому в кінці.
Таким чином, семафори можна використовувати у FreeRTOS з Arduino, де йому потрібно передавати дані від одного завдання до іншого без втрат.
Тепер давайте подивимося, що таке Mutex і як ним користуватися FreeRTOS.
Що таке Mutex?
Як пояснювалося вище, семафор - це сигнальний механізм, аналогічним чином, Mutex є блокуючим механізмом, на відміну від семафора, який має окремі функції для збільшення та зменшення, але в Mutex функція приймає і віддає сама по собі. Це методика уникнення корупції спільних ресурсів.
Щоб захистити спільний ресурс, присвоюється ресурсу картка символів (mutex). Хто має цю картку, може отримати доступ до іншого ресурсу. Інші повинні почекати, поки картка повернеться. Таким чином, лише один ресурс може отримати доступ до завдання, а інші чекають свого шансу.
Давайте розберемося з Mutex у FreeRTOS на прикладі.
Тут ми маємо три завдання, одне для друку даних на РК-дисплеї, друге для надсилання даних LDR на РК-завдання та останнє завдання для надсилання даних про температуру на РК-дисплеї. Отже, тут два завдання мають спільний ресурс, тобто РК. Якщо завдання LDR і завдання температури надсилають дані одночасно, то один із даних може бути пошкоджений або втрачений.
Отже, щоб захистити втрату даних, нам потрібно заблокувати РК-ресурс для task1, поки він не закінчить завдання відображення. Потім завдання РК-дисплея розблокується, і тоді task2 зможе виконати свою роботу.
Ви можете спостерігати за роботою Mutex та семафорів на діаграмі нижче.
Як використовувати Mutex у FreeRTOS?
Мьютекси також використовуються так само, як і семафори. Спочатку створіть його, а потім дайте і візьміть, використовуючи відповідні API.
Створення Mutex:
Щоб створити Mutex, використовуйте API xSemaphoreCreateMutex () . Як випливає з назви, Mutex - це тип бінарного семафора. Вони використовуються в різних контекстах та цілях. Двійковий семафор призначений для синхронізації завдань, тоді як Mutex використовується для захисту спільного ресурсу.
Цей API не приймає жодного аргументу і повертає змінну типу SemaphoreHandle_t . Якщо мьютекс створити не вдається, xSemaphoreCreateMutex () повертає NULL.
SemaphoreHandle_t mutex_v; mutex_v = xSemaphoreCreateMutex ();
Беручи мутекс:
Коли завдання хоче отримати доступ до ресурсу, воно прийме Mutex за допомогою API xSemaphoreTake () . Це те саме, що і двійковий семафор. Він також приймає два параметри.
xSemaphore: Назва Mutex, яку слід взяти в нашому випадку mutex_v .
xTicksToWait: це максимальна кількість часу, протягом якого завдання буде чекати в заблокованому стані, поки Mutex стане доступним. У нашому проекті ми встановимо xTicksToWait на portMAX_DELAY, щоб завдання_1 нескінченно чекало в заблокованому стані, поки mutex_v не стане доступним.
Надання мьютексу:
Після доступу до спільного ресурсу завдання повинно повернути Mutex, щоб інші завдання могли отримати до нього доступ. API API xSemaphoreGive () використовується для повернення Mutex.
Функція xSemaphoreGive () приймає лише один аргумент, який є Mutex, який повинен бути заданий у нашому випадку mutex_v.
Використовуючи вищезазначені API, давайте реалізуємо Mutex у коді FreeRTOS за допомогою Arduino IDE.
Пояснення коду Mutex
Тут метою цієї частини є використання послідовного монітора як спільного ресурсу та двох різних завдань для доступу до послідовного монітора для друку якогось повідомлення.
1. Заголовкові файли залишаться такими ж, як і в семафорі.
# включити # включити
2. Оголосіть змінну типу SemaphoreHandle_t для зберігання значень Mutex.
SemaphoreHandle_t mutex_v;
3. У void setup () ініціалізуйте послідовний монітор зі швидкістю передачі даних 9600 і створіть два завдання (Task1 і Task2) за допомогою API xTaskCreate () . Потім створіть Mutex за допомогою xSemaphoreCreateMutex (). Створіть завдання з однаковими пріоритетами, а пізніше спробуйте зіграти з цим номером.
void setup () { 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. У той час як петля функції завдання, перед друком повідомлення на послідовному моніторі ми повинні прийняти м'ютекс з допомогою xSemaphoreTake () потім виводять повідомлення, а потім повернути м'ютекс з допомогою xSemaphoreGive (). Тоді дайте трохи затримки.
void Task1 (void * pvParameters) { while (1) { xSemaphoreTake (mutex_v, portMAX_DELAY); Serial.println ("Привіт із завдання 1"); xSemaphoreGive (mutex_v); vTaskDelay (pdMS_TO_TICKS (1000)); } }
Подібним чином реалізуйте функцію Task2 із затримкою в 500 мс.
5. Цикл Void () залишатиметься порожнім.
Тепер завантажте цей код на Arduino UNO і відкрийте послідовний монітор.
Ви побачите повідомлення, що друкуються із завдання1 та завдання2.
Щоб перевірити роботу Mutex, просто прокоментуйте xSemaphoreGive (mutex_v); з будь-якого завдання. Ви бачите, що програма зависає на останньому повідомленні для друку .
Ось як Semaphore і Mutex можуть бути реалізовані у FreeRTOS разом з Arduino. Щоб отримати додаткову інформацію про Semaphore та Mutex, ви можете відвідати офіційну документацію FreeRTOS.
Повні коди та відео для Semaphore та Mutes наведені нижче.