Операционные системы (ОС), которые работают внутри встраиваемой электроники (embedded devices), в большинстве случаев являются операционными системами реального времени (RTOS – Real-Time Operating System). В этих системах критически важно, чтобы задачи исполнялись в строго отведенное для них время. Задачи реального времени в этих системах подразумевают под собой то, что время ответа на определенное событие всегда занимает строго фиксированное время. Операционные системы реального времени (RTOS) спроектированы таким образом, чтобы обеспечивать запуск приложений с максимально точным таймингом и гарантировать им высокую надежность работы. Также ОС реального времени помогают реализовать многозадачность в микроконтроллерах (микропроцессорах) с одним ядром. Ранее на нашем сайте мы уже рассматривали многозадачность в Arduino с помощью функции millis, но это был достаточно простой пример многозадачности, в более сложных случаях без ОС реального времени здесь не обойтись.
В данной статье мы рассмотрим основы работы с ОС реального времени FreeRTOS в плате Arduino на примере простой задачи мигания светодиодом. FreeRTOS представляет собой ОС реального времени для встраиваемой электроники, которая достаточно мала для того, чтобы ее можно было запускать на 8/16-битных микроконтроллерах, хотя ее применение не ограничивается подобными (сравнительно малопроизводительными) микроконтроллерами. Данная ОС с открытым исходным кодом и полностью бесплатна, ее код можно скачать на github. FreeRTOS представляет собой хорошо задокументированное API, которое можно непосредственным образом применять в коде своей программы. Полную документацию по FreeRTOS можно найти по следующей ссылке.
Поскольку FreeRTOS может выполняться на 8-битных микроконтроллерах, следовательно, она может выполняться и на плате Arduino Uno. Поэтому мы можем просто скачать библиотеку FreeRTOS для Arduino и непосредственно использовать ее возможности в коде программы.
Эта статья предназначена для начинающих в этой тематике, тех, кто раньше никогда не сталкивался с FreeRTOS, поэтому в ней мы рассмотрим следующие вопросы:
- Как работает RTOS.
- Некоторые наиболее часто используемые термины в RTOS.
- Установка FreeRTOS в Arduino IDE.
- Как создать задачу FreeRTOS.
Как работает RTOS
Прежде чем переходить к изучению принципов работы RTOS (операционных систем реального времени) давайте сначала рассмотрим что такое задача (Task). Задача – это кусок кода программы, которая выполняется микроконтроллером в строго назначенное время (по расписанию). То есть если вы хотите выполнить какую-нибудь задачу, то ей должно быть назначено расписание с помощью задержки на уровне ядра (kernel delay) или с помощью прерываний. Это можно сделать с помощью планировщика, присутствующего в ядре. В одноядерном процессоре планировщик помогает задачам выполняться в заданное время, при этом часто может создаваться впечатление, что различные задачи выполняются одновременно. Каждая задача выполняется в соответствии с приоритетом, который ей назначен.
Теперь давайте посмотрим что происходит в ядре RTOS если мы захотим создать задачу для мигания светодиодом с интервалом в 1 секунду и назначим этой задаче наивысший приоритет.
Но независимо от нашей задачи мигания светодиодом, ядром RTOS всегда создается еще одна дополнительная задача, которая называется "холостой" задачей (idle task). Холостая задача создается когда нет никакой другой задачи для исполнения. Эта задача всегда выполняется с приоритетом 0 (самый низкий приоритет). Если мы проанализируем временной граф, показанный на рисунке выше, мы можем увидеть что выполнение всегда начинается с задачи мигания светодиодом (LED task) и она выполняется определенное время, а в оставшееся время выполняется холостая задача (idle task) до тех пор пока не наступит момент прерывания (tick interrupt). При наступлении этого момента прерывания ядро решает какая задача должна выполняться исходя из приоритета задачи и общего затраченного времени на выполнение задачи мигания светодиодом. Когда интервал времени 1 секунда заканчивается, ядро снова выбирает для выполнения задачу мигания светодиодом (LED task) поскольку она имеет более высокий приоритет чем холостая задача (idle task). Если в конкретный момент времени имеется для выполнения две или более задачи с одинаковым приоритетом, то они выполняются циклически, друг за другом.
На следующем рисунке показана диаграмма состояний, которая показывает переключение с невыполняемой задачи на выполняемую.
Каждая новая созданная задача сначала пребывает в состоянии готовности (Ready state), которое является частью состояния, во время которого не происходит выполнения задачи (not running state). Если созданная задача (Task1) имеет более высокий приоритет, чем все остальные задачи, она переходит в состояние выполнения (running). Если во время ее выполнения поступает задача с более высоким приоритетом чем у нее, то она снова возвращается в состояние Ready state. Если происходит блокировка выполнения этой задачи со стороны API (программный интерфейс приложения), то процессор больше не занимается этой задачей до тех пор, пока не закончится лимит времени, определенный пользователем.
Если выполнение задачи Task1 приостанавливается со стороны API, то задача Task1 переходит в состояние приостановки (Suspended state) и она в этом состоянии будет не видна планировщику (scheduler). Если вы возобновляете (resume) задачу 1 из состояния приостановки, то она снова переходит в состояние Ready (готова к исполнению) как показано на приведенном рисунке.
Итак, мы рассмотрели общие принципы выполнения задач и изменения их состояния в операционной системе реального времени. В этой статье мы рассмотрим выполнение трех задач в плате Arduino Uno используя FreeRTOS API.
Часто используемые термины в RTOS
Задача (Task) – кусок кода, который выполняется в процессоре по расписанию (в определенное время).
Планировщик (Scheduler) – отвечает за выбор и перевод задач из состояния готовности (ready state) в состояние выполнения (running state).
Вытеснение (Preemption) – действие, направленное на временное прерывание выполняющейся задачи с целью удаления ее из состояния выполнения.
Переключение контекста (Context Switching) - процесс переключения процессора на другую задачу (процесс, поток) (например, при обработке прерывания), обычно сопровождающийся операциями сохранения в стеке состояния текущего потока (регистров и другой информации), восстановлением ранее сохранённого состояния другого потока и передачей ему управления. Во время работы в моменты прерывания планировщик сравнивает приоритет задач, которые находятся в состоянии готовности, с приоритетом выполняемой задачи. Если приоритет какой-нибудь задачи, находящейся в состоянии готовности, выше, чем приоритет выполняемой задачи, происходит событие переключения контекста. Состояние задачи, выполнение которой прерывается, обычно сохраняется в стеке.
Типы политики (плана действий) планировщика:
- приоритетное планирование (Preemptive Scheduling) – в этом случае все задачи выполняются примерно одинаковую часть времени независимо от их приоритетов;
- планирование на основе приоритетов (Priority-based Preemptive) – задачи с высоким приоритетом выполняются в первую очередь;
- совместное планирование (Co-operative Scheduling) – операция переключения контекста случается только при взаимодействии (сотрудничестве) выполняемых задач.
Объекты ядра (Kernel Objects) – для информирования задач о том, что они должны выполнить определенную работу, используется процесс синхронизации. Для осуществления этого процесса и используются объекты ядра, такие, к примеру, как события (Events), семафоры (Semaphores), очереди (Queues), флаги/мьютексы (Mutex), почтовые ящики (Mailboxes) и т.д.
Установка библиотеки Arduino FreeRTOS
Откройте Arduino IDE и в ней выберите пункт меню Sketch -> Include Library -> Manage Libraries. Введите там в строке поиска FreeRTOS и установите найденную таким образом библиотеку как показано на следующем рисунке.
Также вы можете скачать эту библиотеку с github и добавить zip файл библиотеки с помощью пункта меню Sketch-> Include Library -> Add .zip file.
После этого перезагрузите (сделайте рестарт) Arduino IDE. Вы можете найти примеры для работы с этой библиотекой в пункте меню File -> Examples -> FreeRTOS как показано на следующем рисунке.
Схема проекта
Схема проекта создания задачи мигания светодиодом с помощью FreeRTOS в плате Arduino представлена на следующем рисунке.
Создание задач FreeRTOS в Arduino IDE
Рассмотрим базовую структуру написания проекта FreeRTOS.
1. Первым делом в программе подключим заголовочный файл библиотеки FreeRTOS.
1 |
#include <Arduino_FreeRTOS.h> |
2. Далее запишем прототипы функций (имя функции и список ее формальных параметров с указанием их типов) которые мы будем использовать в программе.
1 2 3 4 |
void Task1( void *pvParameters ); void Task2( void *pvParameters ); .. …. |
3. Затем в функции void setup() создадим задачи и запустим планировщик задач (task scheduler).
Для создания задачи нужно вызвать API-функцию xTaskCreate() с необходимыми параметрами/аргументами.
1 |
xTaskCreate( TaskFunction_t pvTaskCode, const char * const pcName, uint16_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask ); |
Всего необходимо указать 6 аргументов при создании любой задачи:
- pvTaskCode – указатель на функцию, которая будет выполнять эту задачу (то есть просто имя этой функции).
- pcName – описательное имя для задачи. Не используется в FreeRTOS. Записывается, в основном, для целей отладки программы.
- usStackDepth – каждая задача имеет свой собственный уникальный стек, который выделяется ей ядром (kernel) когда задача создается. Значение этого аргумента определяет число слов в этом стеке. К примеру, если стек имеет ширину 32 бита и мы в качестве параметра usStackDepth указали значение 100, то тогда под эту задачу будет выделено 400 байт (100 * 4 байта) стека в оперативной памяти. Используйте этот параметр осторожно поскольку плата Arduino Uno имеет всего 2 Кбайта оперативной памяти.
- pvParameters – входной параметр задачи, может быть NULL.
- uxPriority – приоритет задачи (0 – самый меньший приоритет).
- pxCreatedTask – может быть использован для того чтобы пропустить обработчик созданной задачи. Можно использовать в виде ссылки в API для изменения приоритета задачи или ее удаления, к примеру. Может быть NULL.
Пример создания задачи
1 2 |
xTaskCreate(task1,"task1",128,NULL,1,NULL); xTaskCreate(task2,"task2",128,NULL,2,NULL); |
Из представленного примера видно, что задача Task2 имеет более высокий приоритет, поэтому будет выполняться первой.
4. После создания задачи необходимо запустить планировщик в функции void setup используя API-функцию vTaskStartScheduler();.
5. Функция void loop() будет оставаться пустой поскольку мы не планируем выполнять (запускать) какую либо задачу вручную и в бесконечном режиме. Выполнение задач в нашем случае будет обрабатываться планировщиком (Scheduler).
6. Теперь нам осталось запрограммировать соответствующие наши задачам функции и записать внутри них команды, которые мы хотим выполнять. Имена функций должны быть в точности такими, какими мы их указали первым аргументом в API-функции xTaskCreate().
1 2 3 4 5 6 7 |
void task1(void *pvParameters) { while(1) { .. ..//your logic } } |
7. В большинстве обычных программ часто используется функция delay чтобы остановить выполнение программы на некоторое время, но в ОС реального времени (RTOS) не рекомендуется использовать функцию Delay() поскольку она останавливает работу процессора, и, следовательно, функционирование RTOS также останавливается. Поэтому в операционной системе FreeRTOS имеется ядро API чтобы блокировать выполнение какой-либо задачи на определенное время.
1 |
vTaskDelay( const TickType_t xTicksToDelay ); |
Это API может быть использовано для целей задержки. Приведенная команда задерживает выполнение задачи на указанное число тиков (ticks). Реальное время, на которое задача будет блокирована, зависит от скорости тиков. Константа portTICK_PERIOD_MS может быть использована для расчета реального времени блокировки на основе скорости тиков.
К примеру, если вы хотите организовать задержку на 200 мс, то вам необходимо выполнить следующую команду:
1 |
vTaskDelay( 200 / portTICK_PERIOD_MS ); |
В этой статье мы будем использовать следующие FreeRTOS API для выполнения трех задач:
- xTaskCreate();
- vTaskStartScheduler();
- vTaskDelay();
Задачи, которые мы создадим в этой статье:
- Мигание светодиодом на цифровом контакте 8 с частотой 200 мс.
- Мигание светодиодом на цифровом контакте 7 с частотой 300 мс.
- Вывод чисел в окно монитора последовательной связи с частотой 500 мс.
Выполнение задач FreeRTOS в Arduino IDE
Исходя из основных принципов использования FreeRTOS в Arduino, рассмотренных в предыдущем разделе статьи, первым делом в программе мы подключим заголовочный файл библиотеки FreeRTOS. Затем создадим прототипы функций. Поскольку у нас будет три задачи то, соответственно, создадим для них три прототипа.
1 2 3 4 |
#include <Arduino_FreeRTOS.h> void TaskBlink1( void *pvParameters ); void TaskBlink2( void *pvParameters ); void Taskprint( void *pvParameters ); |
Далее в функции void setup() мы инициализируем последовательную связь со скоростью 9600 бит/с и создадим три задачи используя API-функцию xTaskCreate(). Первоначально, установим приоритет для всех задач равный ‘1’, и запустим планировщик (scheduler).
1 2 3 4 5 6 7 |
void setup() { Serial.begin(9600); xTaskCreate(TaskBlink1,"Task1",128,NULL,1,NULL); xTaskCreate(TaskBlink2,"Task2 ",128,NULL,1,NULL); xTaskCreate(Taskprint,"Task3",128,NULL,1,NULL); vTaskStartScheduler(); } |
Запрограммируем функцию для первой задачи task1 мигания светодиодом.
1 2 3 4 5 6 7 8 9 10 11 |
void TaskBlink1(void *pvParameters) { pinMode(8, OUTPUT); while(1) { digitalWrite(8, HIGH); vTaskDelay( 200 / portTICK_PERIOD_MS ); digitalWrite(8, LOW); vTaskDelay( 200 / portTICK_PERIOD_MS ); } } |
Аналогично запрограммируем функцию для выполнения задачи Task2. Функция для выполнения задачи Task3 будет выглядеть следующим образом:
1 2 3 4 5 6 7 8 9 10 |
void Taskprint(void *pvParameters) { int counter = 0; while(1) { counter++; Serial.println(counter); vTaskDelay( 500 / portTICK_PERIOD_MS ); } } |
На этом все – мы успешно запрограммировали проект FreeRTOS для Arduino. Более подробно его работу вы можете посмотреть на видео, приведенном в конце статьи.
Чтобы проверить работу проекта, подключите два светодиода к цифровым контактам 7 и 8, загрузите код программы в плату Arduino и откройте окно монитора последовательной связи (Serial monitor) – в нем вы увидите как счетчик обновляется каждые 500 мс.
Также вы сможете наблюдать как мигают два светодиода с различными интервалами. Попробуйте изменять введенные приоритеты задач в функции xTaskCreate и посмотрите как меняется работа проекта.
В данной статье мы рассмотрели простые задачи FreeRTOS, выполняемые в Arduino Uno, но вы на их основе можете реализовать уже более сложные задачи, пригодные для практического применения.
Исходный код программы (скетча)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
#include <Arduino_FreeRTOS.h> void TaskBlink1( void *pvParameters ); void TaskBlink2( void *pvParameters ); void Taskprint( void *pvParameters ); void setup() { // initialize serial communication at 9600 bits per second: Serial.begin(9600); xTaskCreate( TaskBlink1 , "task1" , 128 , NULL , 1 , NULL ); xTaskCreate( TaskBlink2 , "task2" , 128 , NULL , 1 , NULL ); xTaskCreate( Taskprint , "task3" , 128 , NULL , 1 , NULL ); vTaskStartScheduler(); } void loop() { } void TaskBlink1(void *pvParameters) { pinMode(8, OUTPUT); while(1) { Serial.println("Task1"); digitalWrite(8, HIGH); vTaskDelay( 200 / portTICK_PERIOD_MS ); digitalWrite(8, LOW); vTaskDelay( 200 / portTICK_PERIOD_MS ); } } void TaskBlink2(void *pvParameters) { pinMode(7, OUTPUT); while(1) { Serial.println("Task2"); digitalWrite(7, HIGH); vTaskDelay( 300 / portTICK_PERIOD_MS ); digitalWrite(7, LOW); vTaskDelay( 300 / portTICK_PERIOD_MS ); } } void Taskprint(void *pvParameters) { int counter = 0; while(1) { counter++; Serial.println(counter); vTaskDelay(500 / portTICK_PERIOD_MS); } } |
Русские надписи на рисунках сделать религия не позволяет?
Нет, Андрей. К сожалению, я очень плохо рисую, поэтому приходится использовать готовые рисунки из сети
Может ещё C++ тебе на русский перевести? Хочешь программировать учи английский.
На 1С вроде можно программировать на русском
При чем здесь С++? И как бы хочешь программировать - программируй/решай практические задачи, изучение английского тут ничем не поможет. А человек выше, видимо, говорит о том, что если взялся делать руководство, так делай его до конца, а не кусками. Типа возьмем перевод с англоязычной статьи, картинки оставим как есть.
По сабжу -- вообще не вижу никакого смысла в этой rtos, если оно работает именно так. А именно: называем наши процедуры задачами и вызываем их не сами, а поручаем это планировщику. Ожидал чего-то большего...
Спасибо за ваше мнение, но все таки эта операционная система используется множеством энтузиастов и профессионалов. Значит, все таки польза от ее применения есть
Возьмите и сделайте. помогите автору
Весьма занимательно. Считаю что не только мне, но и другим читателям было бы интересно увидеть новые статьи по данной тематике.
Вот еще две статьи в продолжение этой тематики: использование очередей в Arduino FreeRTOS и использование семафоров и мьютексов в FreeRTOS на Arduino Uno. Больше пока нет.