RTO Lab 2
RTOS‑Lab‑02
Pierwsze kroki z współbieżnością na FreeRTOS
Wprowadzenie
W klasycznym programie mikrokontrolera, dla uproszczenia we Frameworku Arduino, który mogliście poznać na innych zajęciach program wykonuje się krok po kroku:
void loop() {
blinkLed();
readSensor();
sendData();
}
Jednak gdy jedna z funkcji zajmuje zbyt dużo czasu, np. sendData(); staje cały system - nie mamy drugiej pętli, która mogłaby coś w międzyczasie wykonać.
Problemem jest tu brak równoległości
W klasycznym programie mikrokontrolera (np. Arduino UNO z ATmegą) kod wykonywany jest sekwencyjnie — procesor wykonuje jedną instrukcję naraz, linijka po linijce. Jedna funkcja musi się zakończyć, zanim zacznie się kolejna.
ESP32 to jednak bardziej zaawansowany mikrokontroler, wyposażony w dwa rdzenie CPU:
- Core 0 – często obsługuje zadania systemowe, komunikację Wi-Fi/Bluetooth,
- Core 1 – zwykle uruchamia kod użytkownika (ale można to zmienić).
System FreeRTOS zarządza oboma rdzeniami i potrafi wykonywać różne taski równolegle – jeden na każdym rdzeniu.
Przykład pierwszy: kod nierównoległy
void setup() {
pinMode(2, OUTPUT);
Serial.begin(9600);
}
void loop() {
// "Zadanie" 1 — LED (blokujące)
digitalWrite(2, !digitalRead(2));
delay(500); // blokuje całe loop na 500 ms
// "Zadanie" 2 — log (też blokujące)
Serial.println("Hello!");
delay(1000); // znów blokada na 1000 ms
}
Na pierwszy rzut oka może się wydawać, że wyjście na pinie 2 (Builtin LED w ESP32) będzie się przełączać co pół sekundy. Na drugi rzut oka okazuje się jednak, że zmiana stanu następować będzie co półtorej sekundy.
W tym podejściu jedna operacja blokuje drugą
Wejście w świat systemów czasu rzeczywistego
Program we FreeRTOS możemy podzielić na niezależne jednostki wykonywalne, zwane taskami (ang. tasks).
Każdy task to w praktyce funkcja w nieskończonej pętli, która wykonuje swoje zadanie, a następnie oddaje sterowanie schedulerowi, aby inne taski również mogły działać.
Czym jest ten task?
Task można porównać do „miniaturowego programu”, który działa własnym rytmem:
- ma własny stos (pamięć lokalną),
- posiada priorytet – czyli informację, jak ważny jest dla systemu,
- może być wstrzymany, zablokowany, uśpiony lub wykonywany.
W tradycyjnym programie Arduino mamy tylko jedną pętlę -> loop().
W FreeRTOS każda pętla (każdy task) jest traktowana osobno i to scheduler decyduje, kiedy i na jakim rdzeniu zostanie wykonana.
Scheduler to część FreeRTOS, która zarządza czasem procesora i decyduje, które zadanie ma być aktualnie wykonywane.
- W danym momencie każdy rdzeń ESP32 może wykonywać jeden task.
- Jeśli liczba zadań jest większa niż liczba rdzeni, scheduler przełącza je bardzo szybko (zwykle co 1 ms – tzw. tick).
- W efekcie wszystkie taski wyglądają na działające równocześnie.
Każde zadanie powinno co jakiś czas:
- wykonać operację
vTaskDelay(...)lub - oczekiwać na zdarzenie (np. kolejkę, semafor, powiadomienie), aby oddać CPU innym taskom.
Dzięki temu system zachowuje płynność i nie blokuje innych funkcji!
Stany tasków
| Stan | Opis |
|---|---|
| Running | Zadanie aktualnie działa na CPU. |
| Ready | Gotowe do działania, czeka na swoją kolej. |
| Blocked | Wstrzymane, np. przez vTaskDelay() lub oczekiwanie na zdarzenie. |
| Suspended | Uśpione — nie jest brane pod uwagę przez scheduler. |
Jak tworzony jest task?
xTaskCreate(
TaskBlink, // funkcja taska
"Blink", // nazwa (dla debugowania)
2048, // rozmiar stosu w bajtach
NULL, // argument przekazywany do taska
1, // priorytet (większy = ważniejszy)
NULL // uchwyt (opcjonalnie)
);
Kod z początku
Ale tym razem - zadania realizowane równolegle
void TaskBlink(void *pv) {
pinMode(2, OUTPUT);
for (;;) {
digitalWrite(2, !digitalRead(2));
vTaskDelay(pdMS_TO_TICKS(500)); // oddaje CPU na ~500 ms
}
}
void TaskLog(void *pv) {
// krótka pauza, żeby Serial z setup() zdążył się zestawić
vTaskDelay(pdMS_TO_TICKS(200));
for (;;) {
Serial.println("Hello!");
vTaskDelay(pdMS_TO_TICKS(1000)); // oddaje CPU na ~1000 ms
}
}
void setup() {
Serial.begin(9600);
// Dwa taski o tym samym priorytecie (1). Stosy po 2048 bajtów.
xTaskCreate(TaskBlink, "Blink", 2048, NULL, 1, NULL);
xTaskCreate(TaskLog, "Log", 2048, NULL, 1, NULL);
}
void loop() {
// puste — scheduler FreeRTOS zarządza zadaniami
}
Funkcja vTaskDelay() służy do wstrzymania wykonywania taska na określony czas (liczony w tickach systemowych). To podstawowy i najprostszy sposób, by oddać procesor (CPU) innym zadaniom w systemie FreeRTOS.
Aby wygodnie przeliczać milisekundy na ticki, używamy makra:
pdMS_TO_TICKS(ms)
- Gdy task wywoła
vTaskDelay(), FreeRTOS zmienia jego stan z „Running” na „Blocked”. - Scheduler usuwa go z listy zadań gotowych do działania.
- Upływ czasu (ticki systemowe) jest zliczany przez zegar RTOS.
- Po upływie zadanego czasu scheduler przywraca task do stanu „Ready” – i od tej chwili może znowu zostać uruchomiony.
Ważne: w tym czasie CPU może spokojnie wykonywać inne taski.