V tomto článku objasníme, co to je FreeRTOS, k čemu je dobrý, kdy se vyplatí ho používat a kdy to není vhodné. Zkusíme si naprogramovat jednoduchý program se dvěma vlákny, které spolu budou komunikovat.
Co je operační systém reálného času (RTOS)?
Nejsnadnější odpověď je ta, že ho porovnáme s programy, které jste psali doposud.
Tyto programy jsou psané tak, že provádějí jednotlivé úkoly jeden za druhým v nekonečné hlavní smyčce. Tento styl programování má některé výhody. Jedním z nich je přímočarost. Tato přímočarost je velkou výhodou, například když chceme debuggovat náš program.
Další výhoda tohoto stylu "bare metall" programování je v tom, že programy jsou malé a rychlé, méně těžkotonážní v termínech zatížení CPU a paměti než jejich ekvivalent psaný v operačním systému reálného času.
Největší nevýhoda tohoto stylu programování se ukáže tehdy, budeme-li potřebovat stihnout naše úlohy v přesně definovaném čase, t.j. máme časově kritické úseky programu (úlohy).
Problém je v tom, že pokud se podíváme na obrázek 1, tak zjistíme, že "úloha 2" musí počkat na to, až se dokončí "úloha 1" a podobně "úloha 3" musí čekat na to, až se dokončí úlohy 1 a 2. Jestliže například "úloha 1" bude trvat z jakéhokoliv důvodu poměrně dlouho a "úloha 2" bude třeba zkoušet přečíst nějaké data ze sensoru nebo očekávat vstup od uživatele (stisknutí tlačítka například), tak je velmi pravděpodobné, že některá data budou prostě ztracena kvůli tomu, že tyto události nastanou v čase, kdy stále běží "úloha 1".
Operační systém reálného času nám může vyřešit tento typ problému. Viz obrázek 2.
Teď nám všechny tři úlohy nám běží zdánlivě současně. Zdůrazněme slovo zdánlivě. Je to dané tím, že plánovač procesů (scheduler) přiděluje jednotlivé časové porce na procesoru úlohám 1, 2 a 3 a rychle je mezi sebou přepíná.
Nevýhoda je v tom, že při přepnutí z jedné úlohy na druhou si musíme někam uložit stav první úlohy a tuto úlohu pozastavit (suspendovat) a potom vyzvednout odněkud z paměti uložený stav druhé úlohy a tuto úlohu spustit. To samé pro úlohu 2 a úlohu 3, úlohu 3 a úlohu 1, neustále dokolečka. To trvá nějaký čas a zabírá to nějakou paměť navíc oproti programu "bare metall".
Poznámka: Procesor RP2040 má dvě jádra, takže by mohly dvě úlohy běžet skutečně současně, ale v tomto jednoduchém výkladu této vlastnosti procesoru nevyužijeme. Později ukážeme, jak zapojit do činnosti obě jádra procesoru RP2040, čili jak využívat symetrický multiprocesing.
Termíny úloha (task) nebo vlákno (thread) jsou v tomto výkladu vzájemně zaměnitelné. Task (úloha) je obvykle množina instrukcí načtená do paměti a thread (vlákno) je jednotka použití CPU se svým vlastním programovým čítačem a pamětí. FreeRTOS odkazuje na úlohy jako na něco velmi blízko k vláknům, v dokumentaci k jiným operačním systémům jsou to vlákna.
Task je:
-
Mini program
-
funkce vrací void a přijímá parametr void *
-
běží stále dokolečka v nekonečné smyčce a nemá návrat
-
nemá příkaz return
-
není-li již potřeba, musí být smazán
Ke komunikaci mezi úlohami (tasks) se používají fronty (queue).
Fronta je:
-
FIFO buffer
-
je thread safe (data nemohou být přepsána jiným vláknem)

Pro zápis do fronty a čtení z fronty používáme dvě funkce xQueueSend() a xQueueReceive()
// zápis do fronty
xQueueSend(Queue_Name, &Variable_to_Send, Delay); (1)
// čtení z fronty
xQueueReceive(Queue_Name, &Variable_to_Receive, Delay); (2)
| 1 | Parametr Delay určuje, jak dlouho bude funkce čekat, pokud je fronta plná. Po uplynutí této doby funkce xQueueSend vrátí chybu. |
| 2 | Parametr Delay určuje, jak dlouho bude funkce čekat, pokud je fronta prázdná. Po uplynutí této doby funkce xQueueReceive vrátí chybu. |
Podrobný popis jak to funguje najdete zde (in English of course).
Zdrojové kódy, nastavení a sestavení
$ cd ~/pico
$ git clone -b master https://github.com/raspberrypi/pico-examples.git mojepokusy
$ cd mojepokusy
$ mkdir build
$ editace src/CMakeLists.txt
$ editace src/main.c
cd build
cmake ..
cd src
make
add_executable(blink
main.c
)
target_link_libraries(blink pico_stdlib freertos)
pico_enable_stdio_usb(blink 1)
pico_enable_stdio_uart(blink 0)
pico_add_extra_outputs(blink)
#include <FreeRTOS.h>
#include <task.h>
#include <stdio.h>
#include <queue.h>
#include "pico/stdlib.h"
static QueueHandle_t xQueue = NULL;
void led_task(void *pvParameters)
{
const uint LED_PIN = PICO_DEFAULT_LED_PIN;
uint uIValueToSend = 0;
gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);
while (true) {
gpio_put(LED_PIN, 1);
uIValueToSend = 1;
xQueueSend(xQueue, &uIValueToSend, 0U);
vTaskDelay(100);
gpio_put(LED_PIN, 0);
uIValueToSend = 0;
xQueueSend(xQueue, &uIValueToSend, 0U);
vTaskDelay(100);
}
}
void usb_task(void *pvParameters){
uint uIReceivedValue;
while(1){
xQueueReceive(xQueue, &uIReceivedValue, portMAX_DELAY);
if(uIReceivedValue == 1){
printf("LED is ON! \n");
}
if(uIReceivedValue == 0){
printf("LED is OFF! \n");
}
}
}
int main()
{
stdio_init_all();
xQueue = xQueueCreate(1, sizeof(uint));
xTaskCreate(led_task, "LED_Task", 256, NULL, 1, NULL);
xTaskCreate(usb_task, "USB_Task", 256, NULL, 1, NULL);
vTaskStartScheduler();
while(1){};
}
Po spuštění blikátka můžeme pozorovat na sériovém výstupu, co se na Picu děje. Používám k tomu program zcom, který poběží na libovolném 64bitovém Linuxu. (Je to statická binárka, naprogramovaná v ZIGu.)
jirka@jirka-Precision-T3610:~/pico$ zcom
#1 /dev/ttyACM0 (driver=cdc_acm)
#2 /dev/ttyS0 (driver=serial)
#3 /dev/ttyS1 (driver=serial8250)
#4 /dev/ttyS10 (driver=serial8250)
#5 /dev/ttyS11 (driver=serial8250)
#6 /dev/ttyS12 (driver=serial8250)
#7 /dev/ttyS13 (driver=serial8250)
#8 /dev/ttyS14 (driver=serial8250)
#9 /dev/ttyS15 (driver=serial8250)
#10 /dev/ttyS16 (driver=serial8250)
#11 /dev/ttyS17 (driver=serial8250)
#12 /dev/ttyS18 (driver=serial8250)
#13 /dev/ttyS19 (driver=serial8250)
#14 /dev/ttyS2 (driver=serial8250)
#15 /dev/ttyS20 (driver=serial8250)
#16 /dev/ttyS21 (driver=serial8250)
#17 /dev/ttyS22 (driver=serial8250)
#18 /dev/ttyS23 (driver=serial8250)
#19 /dev/ttyS24 (driver=serial8250)
#20 /dev/ttyS25 (driver=serial8250)
#21 /dev/ttyS26 (driver=serial8250)
#22 /dev/ttyS27 (driver=serial8250)
#23 /dev/ttyS28 (driver=serial8250)
#24 /dev/ttyS29 (driver=serial8250)
#25 /dev/ttyS3 (driver=serial8250)
#26 /dev/ttyS30 (driver=serial8250)
#27 /dev/ttyS31 (driver=serial8250)
#28 /dev/ttyS4 (driver=serial8250)
#29 /dev/ttyS5 (driver=serial8250)
#30 /dev/ttyS6 (driver=serial8250)
#31 /dev/ttyS7 (driver=serial8250)
#32 /dev/ttyS8 (driver=serial8250)
#33 /dev/ttyS9 (driver=serial8250)
Select port [2]: 1 (1)
Connected. Use C-a C-q to exit. (2)
LED is OFF! (3)
LED is ON!
LED is OFF!
LED is ON!
LED is OFF!
LED is ON!
LED is OFF!
LED is ON!
LED is OFF!
LED is ON!
LED is OFF!
LED is ON!
LED is OFF!
LED is ON!
LED is OFF!
LED is ON!
LED is OFF!
LED is ON!
LED is OFF!
LED is ON!
LED is OFF!
LED is ON!
LED is OFF!
LED is ON!
LED is OFF!
LED is ON!
LED is OFF!
LED is ON!
LED is OFF!
LED is ON!
LED is OFF!
LED is ON!
LED is OFF!
LED is ON!
... a tak dále, až dokud to nevypneme.
| 1 | Vyberu si v zcomu zařízení /dev/ttyACM0, což je USB port kam je připojené Pico, softwarově předělaný na sériovou linku. |
| 2 | zcom se ukončuje stiskem kláves Ctrl+a a potom Ctrl+q. |
| 3 | A už to jede. |
Nebo se dá použít minicom.
$ sudo apt install minicom (1)
$ minicom -b 115200 -o -D /dev/ttyACM0 (2)
| 1 | Instalace minicomu na Debianu a klonech. |
| 2 | Spuštění minicomu. minicom se ukončuje stisknutím kláves Ctrl+a a potom z.
Poznámka: Monitoring pomocí minicomu se bohužel nedá zachytit v terminálu a zobrazit zde na webu, protože po ukončení po sobě smaže obrazovku, na rozdíl od zcomu. Budu proto nadále používat zcom. |