- Какво е семафор?
- Как да използвам Semaphore в FreeRTOS?
- Обяснение на кода на семафор
- Електрическа схема
- Какво е Mutex?
- Как да използвам Mutex в FreeRTOS?
- Обяснение на Mutex кода
В предишни уроци разгледахме основите на FreeRTOS с Arduino и обекта на ядрото Queue в FreeRTOS Arduino. Сега, в този трети урок за FreeRTOS, ще научим повече за FreeRTOS и неговите приложни програмни интерфейси (API), които могат да ви накарат да разберете платформата за многозадачност по-задълбочено.
Semaphore и Mutex (взаимно изключване) са обектите на ядрото, които се използват за синхронизация, управление на ресурси и защита на ресурси от повреда. В първата половина на този урок ще видим идеята зад Semaphore, как и къде да го използваме. През второто полувреме ще продължим с Mutex.
Какво е семафор?
В предишни уроци обсъждахме приоритетите на задачите и също така се запознахме, че задача с по-висок приоритет изпреварва задача с по-нисък приоритет, така че докато изпълнението на задача с висок приоритет може да има вероятност повреда на данните да се случи в задача с по-нисък приоритет, защото все още не се изпълнява и данните непрекъснато идват към тази задача от сензор, който причинява загуба на данни и неизправност на цялото приложение.
И така, има нужда от защита на ресурсите от загуба на данни и тук Semaphore играе важна роля.
Семафорът е сигнален механизъм, при който задача в състояние на изчакване се сигнализира от друга задача за изпълнение. С други думи, когато задача1 завърши работата си, тя ще покаже флаг или ще увеличи флаг с 1 и след това този флаг се получава от друга задача (задача2), показваща, че може да изпълнява работата си сега. Когато task2 приключи работата си, флагът ще бъде намален с 1.
И така, по принцип това е механизъм „Дай“ и „Вземи“, а семафорът е цяло число променлива, която се използва за синхронизиране на достъпа до ресурси.
Видове семафор в FreeRTOS:
Семафорът е два вида.
- Двоичен семафор
- Преброяване на семафор
1. Бинарен семафор: Той има две целочислени стойности 0 и 1. Той е донякъде подобен на опашката с дължина 1. Например, имаме две задачи, task1 и task2. Task1 изпраща данни към 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 и нормална задача, тогава xSemaphoreGiveFromISR () трябва да се използва от функцията ISR.
Вземане на семафор:
За да вземете семафор, използвайте API функцията xSemaphoreTake (). Този API взема два параметъра.
xSemaphoreTake (SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
xSemaphore: Име на семафора, който трябва да се вземе в нашия случай sema_v.
xTicksToWait: Това е максималният период от време, който задачата ще изчака в блокирано състояние, за да стане семафорът достъпен. В нашия проект ще зададем xTicksToWait на portMAX_DELAY, за да накараме task_1 да чака безкрайно в блокирано състояние, докато sema_v стане наличен.
Сега, нека използваме тези API и да напишем код за изпълнение на някои задачи.
Тук са свързани един бутон и два светодиода. Бутонът ще действа като прекъсващ бутон, който е прикрепен към щифт 2 на Arduino Uno. При натискане на този бутон ще се генерира прекъсване и светодиод, който е свързан към щифт 8, ще бъде включен и когато го натиснете отново, той ще бъде изключен.
И така, при натискане на бутона xSemaphoreGiveFromISR () ще бъде извикан от функцията ISR, а функцията xSemaphoreTake () ще бъде извикана от функцията TaskLED.
За да изглежда системата многозадачна, свържете други светодиоди с щифт 7, който винаги ще мига.
Обяснение на кода на семафор
Нека започнем да пишем код за отваряне на IDE на Arduino
1. Първо, включете заглавния файл Arduino_FreeRTOS.h . Сега, ако се използва какъвто и да е обект на ядрото като семафор на опашката, тогава трябва да се включи и заглавен файл за него.
#include #include
2. Декларирайте променлива от тип SemaphoreHandle_t, за да съхранявате стойностите на семафора.
SemaphoreHandle_t interruptSemaphore;
3. В void setup (), създайте две задачи (TaskLED и TaskBlink) с помощта на xTaskCreate () API и след това създайте семафор с помощта на 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 (), както е показано по-долу.
дълго debouncing_time = 150; летливи неподписани long last_micros; void debounceInterrupt () { if ((long) (micros () - last_micros)> = debouncing_time * 1000) { interruptHandler (); last_micros = micros (); } }
В interruptHandler () функция, обадете xSemaphoreGiveFromISR () API.
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, ИЗХОД); докато (1) { digitalWrite (7, HIGH); vTaskDelay (200 / портTICK_PERIOD_MS); digitalWrite (7, LOW); vTaskDelay (200 / портTICK_PERIOD_MS); } }
7. Функцията void loop ще остане празна. Не го забравяйте.
невалиден цикъл () {}
Това е всичко, пълен код можете да намерите в края на този урок. Сега качете този код и свържете светодиодите и бутона с Arduino UNO съгласно схемата.
Електрическа схема
След като качите кода, ще видите, че светодиод мига след 200 ms и когато бутонът е натиснат, веднага вторият светодиод ще свети, както е показано във видеото, дадено в края.
По този начин семафорите могат да се използват във FreeRTOS с Arduino, където трябва да предават данните от една задача на друга без загуба.
Сега да видим какво е Mutex и как да го използваме FreeRTOS.
Какво е Mutex?
Както беше обяснено по-горе, семафорът е сигнализиращ механизъм, подобно на това, Mutex е заключващ механизъм, за разлика от семафора, който има отделни функции за увеличаване и намаляване, но в Mutex функцията приема и дава в себе си. Това е техника за избягване на корупцията на споделените ресурси.
За да се защити споделеният ресурс, човек присвоява символна карта (mutex) на ресурса. Който има тази карта, може да получи достъп до другия ресурс. Други трябва да изчакат, докато картата се върне. По този начин само един ресурс може да има достъп до задачата, а други чакат своя шанс.
Нека разберем Mutex във FreeRTOS с помощта на пример.
Тук имаме три задачи, една за отпечатване на данни на LCD, втора за изпращане на LDR данни към LCD задача и последна задача за изпращане на данни за температурата на LCD. Така че тук две задачи споделят един и същ ресурс, т.е. LCD. Ако задачата LDR и задачата за температура изпращат данни едновременно, тогава една от данните може да бъде повредена или загубена.
Така че, за да защитим загубата на данни, трябва да заключим LCD ресурса за task1, докато завърши задачата за показване. Тогава задачата на LCD ще се отключи и тогава 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, за да накараме task_1 да чака безкрайно в блокирано състояние, докато mutex_v стане наличен.
Даване на мутекс:
След достъп до споделения ресурс, задачата трябва да върне Mutex, така че други задачи да имат достъп до него. xSemaphoreGive () API се използва за връщане на Mutex обратно.
Функцията xSemaphoreGive () приема само един аргумент, който е Mutex, който се дава в нашия случай mutex_v.
Използвайки горните API, нека внедрим Mutex в кода на FreeRTOS, използвайки Arduino IDE.
Обяснение на Mutex кода
Тук целта на тази част е да се използва сериен монитор като споделен ресурс и две различни задачи за достъп до серийния монитор за отпечатване на някакво съобщение.
1. Заглавните файлове ще останат същите като семафора.
#include #include
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 (Task1, "Task 1", 128, NULL, 1, NULL); xTaskCreate (Task2, "Task 2", 128, NULL, 1, NULL); }
4. Сега направете функции за задачи за Task1 и Task2. В докато контур на функция задача, преди отпечатване на съобщение на сериен монитора трябва да вземе Mutex използване 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 със закъснение от 500ms.
5. Void loop () ще остане празен.
Сега качете този код на Arduino UNO и отворете серийния монитор.
Ще видите, че съобщенията се отпечатват от task1 и task2.
За да тествате работата на Mutex, просто коментирайте xSemaphoreGive (mutex_v); от всяка задача. Можете да видите, че програмата виси на последното съобщение за печат .
Ето как Semaphore и Mutex могат да бъдат внедрени във FreeRTOS с Arduino. За повече информация относно Semaphore и Mutex можете да посетите официалната документация на FreeRTOS.
Пълните кодове и видео за Semaphore и Mutes са дадени по-долу.