IT Академия Samsung - RTOS # RTOS - Real-Time Operating System Операционная система реального времени (ОСРВ или Real-Time Operating System, RTOS) — это специализированная операционная система, предназначенная для создания систем реального времени на конкретном оборудовании. Собственно, *система реального времени* — система, которая должна реагировать на события во внешней, по отношению к системе, среде или воздействовать на среду в рамках требуемых временных ограничений. По такому принципу строятся все управляющие устройства в промышленности или в быту, будь то стиральная машинка или автоматический прокатный стан. Системы IoT — не исключение! ## RTOS на примере Mbed OS В рамках этого курса мы используем встраиваемую операционную систему [Mbed OS](https://mbed.com). В современной, 6-й версии, Mbed OS включает в себя многопоточность и переключение задач как часть основного ядра, и чтобы задействовать эти функции нужно при компиляции установить флаг `MBED_CONF_RTOS_API_PRESENT`. В PlatformIO с этой целью нужно в файле `platformio.ini` добавить этот флаг с ключом `-D` к параметру `build_flags`. То есть для наших плат, где этим же параметром мы указываем дополнительный путь для таргета, соответствующий фрагмент файла `platformio.ini` будет выглядеть так: ``` build_flags = -I$PROJECT_SRC_DIR/TARGET_UNWIRED_RANGE -DMBED_CONF_RTOS_API_PRESENT ``` ### Таймер (тикер) В Mbed есть простой таймер для вызова функции с определенной периодичностью. Он называется `Ticker`. Посмотрите следующий несложный код: ```cpp #include "mbed.h" Ticker toggle_led_ticker; DigitalOut led1(LED1); void toggle_led() { led1 = !led1; } int main() { // Init the ticker with the address of the function (toggle_led) to be attached and the interval (100 ms) toggle_led_ticker.attach(&toggle_led, 100ms); while (true) { // Do other things... } } ``` Мы заводим объект класса `Ticker`, и привязываем (`attach`) срабатывание функции `toggle_led` каждые 100 миллисекунд. Синтаксис очень похож на JavaScript и иные языки, в которых есть абстракция таймера. Скомпилируйте и посмотрите на работу этого примера. Увидите быстро (10 раз в секунду) мигающий светодиод. ![unwr-blink](/assets/images/iot-academy/unwr-blink.gif) Произведем небольшие изменеия в коде программы: ```cpp #include "mbed.h" Ticker toggle_led_ticker; DigitalOut led1(LED1); void toggle_led() { led1 = !led1; } int main() { int i = 0; // Init the ticker with the address of the function (toggle_led) to be attached and the interval (100 ms) toggle_led_ticker.attach(&toggle_led, 100ms); while (true) { // Do other things... printf("This program runs since %d seconds.\n", i++); ThisThread::sleep_for(1s); } } ``` Функция `printf()` осуществляет вывод сообщения каждую секунду в последовательный порт, а таймер (тикер) переключает состояние ветодиода каждые 100 мс. ### Потоки Давайте модифицируем нашу программу так, чтобы она мигала светодиодом в отдельном потоке. Тогда вызов будет неблокирующий, и программа сможет делать что-то ещё помимо мигания светодиодом. Посмотрим на самый простой пример программы с двумя потоками. Здесь создается объект класса "Поток" (`Thread`), и получается, что в программе участвуют два потока: один (основной поток программы) мигает светодиодом, а другой (новый, который мы завели) печатает символы. ```cpp #include "mbed.h" void print_char(char c = '*') { printf("%c", c); fflush(stdout); } Thread thread; DigitalOut led1(LED1); void print_thread() { while (true) { ThisThread::sleep_for(1s); print_char(); } } int main() { printf("\n\n*** RTOS basic example ***\n"); thread.start(print_thread); while (true) { led1 = !led1; ThisThread::sleep_for(500ms); } } ``` #### Задание Переделайте чуть-чуть эту программу, чтобы в ней не было в main никакого цикла, и вся логика программы выполнялась в двух отдельных потоках. ### Прерывания (+ Кнопка) C выходами разобрались. Разберемся теперь со входами. Как повесить прерывание на кнопку? Загрузите следующий пример кода. При удерживании кнопки User Button (это кнопка **SAFE** на плате-адаптере UMDK-RF) мигание будет происходить быстрее. ```cpp #include "mbed.h" InterruptIn button(USER_BUTTON, PullUp); DigitalOut led(LED1); uint32_t delay = 500; void pressed() { delay = 100; } void released() { delay = 500; } int main() { // Assign functions to button button.fall(&pressed); button.rise(&released); while (1) { led = !led; ThisThread::sleep_for(delay); } } ``` Обратите внимание, что работа со временем как с безразмерной величиной может приводить к недоразумениям и ошибкам конвертации временных единиц измерения. Поэтому в стандарте C++11 была добавлена библиотека `chrono` (`namespace std::chrono`). Таким образом, более правильным было бы изменить наш пример: ```cpp #include "mbed.h" InterruptIn button(USER_BUTTON, PullUp); DigitalOut led(LED1); Kernel::Clock::duration_u32 delay = 500ms; void pressed() { delay = 100ms; } void released() { delay = 500ms; } int main() { // Assign functions to button button.fall(&pressed); button.rise(&released); while (1) { led = !led; ThisThread::sleep_for(delay); } } ``` Также обратите внимание, что в отличие от примера на сайте [IT Академии](https://myitschool.ru/edu/mod/book/view.php?id=1835&chapterid=1668) вместо `InterruptIn button(USER_BUTTON);` мы пишим `InterruptIn button(USER_BUTTON, PullUp);`, то есть указываем еще параметр `PullUp` так как в отличие от платы STM323Nucleo на нашей плате кнопка **SAFE** не имеет аппаратной подтяжки, и мы ее делаем средствами самого микроконтроллера STM32. ### Устраняем дребезг контактов Наверное, вы уже заметили, что когда мы нажимаем кнопку - иногда происходят ложные срабатывания. Например, вы нажали кнопку однократно, а программа выдает, что было несколько нажатий кнопки. Это известная проблема, называемая “дребезг контактов”. Ее причина в том, что механические контакты кнопки за счет упругости материала колеблются еще некоторое время после того, как вы уже отпустили кнопку, и поэтому регистрируются ложные нажатия. Бороться с этой проблемой можно на физическом уровне (разные кнопки выдают разный дребезг, зависит от их качества исполнения), но чаще всего ее решают программным образом. Обязательно рассмотрите этот пример, здесь объясняется, как избавиться от дребезга контактов при помощи таймера и двух переменных-флагов. Изучите этот код самостоятельно и добейтесь того, чтобы у вас было абсолютное понимание каждой строчки кода. ```cpp #include "mbed.h" DigitalOut led1(LED1); InterruptIn button1(USER_BUTTON); volatile bool button1_pressed = false; // Used in the main loop volatile bool button1_enabled = true; // Used for debouncing Timeout button1_timeout; // Used for debouncing // Enables button when bouncing is over void button1_enabled_cb(void) { button1_enabled = true; } // ISR handling button pressed event void button1_onpressed_cb(void) { if (button1_enabled) { // Disabled while the button is bouncing button1_enabled = false; button1_pressed = true; // To be read by the main loop button1_timeout.attach(callback(button1_enabled_cb), 30ms); // Debounce time 30 ms } } int main() { button1.mode(PullUp); // Activate pull-up button1.fall(callback(button1_onpressed_cb)); // Attach ISR to handle button press event int idx = 0; // Just for printf below while(1) { if (button1_pressed) { // Set when button is pressed button1_pressed = false; printf("Button pressed %d\n", idx++); led1 = !led1; } } } ``` По сравнению с материалом на сайте [IT Академии](https://myitschool.ru/edu/mod/book/view.php?id=1835&chapterid=1669) в этом коде есть уже известное нам отличие - включение подтяжки `PullUp` для входа, к которому подключена кнопка, только сделано это несколько иначе. Ну и, конечно, это использование `std::chrono` вместо безразмерных данных для временных величи. Ах, да! 300 мс для программного подавления дребезга контактов - это целая вечность! Считается, что время переходного процесса, в течение которого наблюдается дребезг, составляет порядка 20 мс. Так что в нашем примере мы выдерживаем паузу в 30 мс.