RTOS (real-time operating system) - многозадачная операционная система реального времени (ОСРВ) RTX, интегрированная в среду Keil. ОСРВ выполняет важное дело – реализует вытесняющую многозадачность. Применение ОСРВ позволяет улучшить управление проектами и облегчает повторное использование кода. Обратной стороной медали является использование повышенного объёма памяти и увеличение времени реакции на прерывания. Однако сейчас, когда объём ОЗУ в МК составляет 32 Кб и более, а размер ОСРВ составляет до 5 Кб, возможностей для внедрения ОСРВ более чем достаточно. Поэтому на вопрос: «Зачем использовать ОСРВ?» можно ответить просто: «Потому что мы можем!».
На сегодняшний день, Keil предоставляет две версии своей ОСРВ: RTOS v1 (Keil RTX 4) и RTOS v2 (Keil RTX 5). Мы будем использовать RTOS v2. Основным «строительным материалом» в обычной Си-программе являются функции, которые мы вызываем для выполнения определённых операций и которые затем передают управление в вызывающую их функцию. В ОСРВ такими базовыми исполнительными элементами являются потоки (процессы). Поток очень похож на Си-функцию, но в то же время имеет несколько принципиальных отличий.
unsigned int function (void) { …… return(); }
void thread (void) { while(1) { …… } }
Если из функции мы рано или поздно возвращаемся, то поток, запущенный однажды, не завершится никогда, так как в его теле имеется бесконечный цикл while(1). ОСРВ состоит из набора таких потоков, выполнением которых управляет специальный модуль – планировщик. Этот планировщик представляет собой обработчик прерываний от таймера, предоставляющий каждому процессу некий интервал времени для управления. Таким образом, например, «процесс 1» будет выполняться в течение 100 мс, затем управление передаётся на такое же время «процессу 2», после чего происходит переход к «процессу 3», и в конце концов возвращается обратно к «процессу 1». Циклически предоставляя каждому процессу «кусочки» времени, мы получаем иллюзию их одновременного выполнения. Время, предоставляемое на выполнение функции потока, является настраиваемым параметром. Его мы рассмотрим позже.
А пока, рассмотрим некоторые свойства потока, позволяющие планировщику выстраивать логику управления. Поток может находиться в одном из трёх состояний:
Running | Поток выполняется | |
---|---|---|
Ready | Поток готов к запуску | |
Wait | Поток заблокирован, ожидает события от ОС |
Запуск потоков осуществляется исходя из определения их приоритетов. Если несколько потоков находятся в состоянии готовности к запуску «Ready», то в сначала выполняются процессы с наибольшим приоритетом, либо при равенстве приоритетов в порядке очереди, или в так называемом «карусельном» режиме. Таблица приоритетов в порядке возрастания приведена ниже:
RTOS: уровни приоритета |
---|
osPriorityIdle |
osPriorityLow |
osPriorityBelowNormal |
osPriorityNormal |
osPriorityAboveNormal |
osPriorityHigh |
osPriorityRealTime |
osPriorityError |
При создании потока ему присваивается приоритет osPriorityNormal и уникальный идентификатор, который используется при установке определённых параметров потоку. Управление ОСРВ осуществляется с помощью специальных функций, каждая из которых начинается с приставки os и, далее, название самой функции, например, osKernelStart () – запуск планировщика ядра ОСРВ, по сути, запуск самой ОС. Полный перечень функций RTOS2 можно посмотреть на официальном сайте.
Создаём новый проект, как описано в статье Создание проекта. Выбираем микроконтроллер 1986ВЕ92 и подключаем разделы библиотеки: Startup_MDR1986BE9x, PORT, RST_CLK. Для настройки проекта можно воспользоваться Настройки проекта для 1986ВЕ9х».
Теперь подключаем в наш проект саму ОСРВ. Конечно, это можно было сделать и на этапе создания проекта, но так мы выделим отдельные шаги (или шаг) для подключения только ОСРВ.
Переходим в окно «Manage Run-Time Environment», кликнув на соответствующий значок
В окне «Manage Run-Time Environment» выбираем: CMSIS :: CORE и CMSIS :: RTOS2 (API) :: Keil RTX5. Можно добавить RTX в качестве библиотеки (Library), либо добавить полный исходный код (Source). Мы выберем Library.
Нажимаем «ОК». В окне «Project» можно увидеть файлы, которые были автоматически добавлены в проект: RTX_Config.h, RTX_Config.c, библиотека или файлы исходного кода.
#include <MDR32F9Qx_port.h> #include <MDR32F9Qx_rst_clk.h> // Библиотека для работы с RTOS #include <rtx_os.h> // Прототипы функций мигания светодиодами, реализованные ниже void THREAD_LED0 (void *argument); void THREAD_LED1 (void *argument); void Delay(int waitTicks); // Точка входа int main (void) { // Заводим структуру конфигурации вывода(-ов) порта GPIO PORT_InitTypeDef GPIOInitStruct; // Тактирование для PORTC RST_CLK_PCLKcmd (RST_CLK_PCLK_PORTC, ENABLE); // Конфигурация на вывод PORT_StructInit(&GPIOInitStruct); GPIOInitStruct.PORT_Pin = PORT_Pin_0 | PORT_Pin_1; GPIOInitStruct.PORT_OE = PORT_OE_OUT; GPIOInitStruct.PORT_SPEED = PORT_SPEED_SLOW; GPIOInitStruct.PORT_MODE = PORT_MODE_DIGITAL; PORT_Init(MDR_PORTC, &GPIOInitStruct); // Инициализация RTOS osKernelInitialize(); // Создание потоков LED0 и LED1 osThreadNew(THREAD_LED0, NULL, NULL); osThreadNew(THREAD_LED1, NULL, NULL); // Запуск RTOS osKernelStart(); } void THREAD_LED0 (void *argument) { // Основной цикл while(1) { PORT_ResetBits (MDR_PORTC, PORT_Pin_0); // Выключаем светодиод 0 Delay (195000); // Функция задержки PORT_SetBits (MDR_PORTC, PORT_Pin_0); // Включаем светодиод 0 Delay (195000); } } void THREAD_LED1 (void *argument) { // Основной цикл while(1) { PORT_ResetBits (MDR_PORTC, PORT_Pin_1); // Выключаем светодиод 1 Delay (195000); PORT_SetBits (MDR_PORTC, PORT_Pin_1); // Включаем светодиод 1 Delay (195000); } } void Delay(int waitTicks) { int i; for (i = 0; i <waitTicks; i++) { __NOP(); } }
Рассмотрим процесс запуска RTOS. После конфигурации выводов и задания тактирования, в main.c необходимо произвести инициализацию RTOS и создать потоки, которые описываются как обычные функции в СИ. После же, запустить планировщик ядра ОСРВ функцией osKernelStart (), который и начнёт запускать наши потоки.
В функциях потоков LED0 и LED1 используется функция простоя Delay (N_тактов). Намного удобнее для задания задержки использовать стандартную функцию ОСРВ osDelay (N_ms), однако в нашем проекте она не подходит. При вызове функции osDelay () поток, вызывающий функцию переводится в режим ожидания Wait на указанное время и запускается поток ожидания osRtxIdleThread, в составе которого просто бесконечный цикл. При желании в него можно прописать свою функцию. Приоритет у этого потока наименьший osPriorityIdle, поэтому после его запуска при следующем тике таймера системы (его частота по умолчанию 1000 Гц) запускается поток с наибольшим приоритетом, находящийся в состоянии готовности «Ready». Это очень удобно, т.к пока запустивший функцию задержки поток находится в режиме ожидания, будут выполняться другие потоки. В нашем проекте она не подходит лишь потому, что с её помощью нельзя наглядно показать увеличение времени выполнения потока. Так, выделяя на поток определённое время, например 100 мс, оно в полном объёме использоваться не будет, так как всегда при выполнении мы будем натыкаться на функцию osDelay (), переводящую поток в режим ожидания и передающее управление другому потоку. Поэтому будем использовать обычную функцию задержки.
Ну что ж, пора переходить к практике. Компилируем наш проект и зашиваем в микроконтроллер. Сейчас светодиоды мигают «одновременно», т.к. ОСРВ делит процессорное время между двумя потоками LED0 и LED1, выделяя на каждый из них по 5 мс (по умолчанию). Чтобы своими глазами увидеть, как ОСРВ разделяет процессорное время, увеличим время выполнения каждого потока. Для этого откроем файл «RTX_Config.h», определяющий параметры конфигурации RTOS2. Слева, под окном с кодом нашей программы, выбираем вкладку «Configuration Wizard», представляющую более удобное представление настроек. Далее выбираем «System Configuration» → «Round-Robin Thread switching» и в «Round-Robin Timeout» ставим значение 1000.
Этот параметр задаётся в тиках таймера системы, что по умолчанию соответствует миллисекундам. Немного выше располагается параметр «Kernel Tick Frequency», который как раз и задаёт частоту тиков системного таймера, по умолчанию это 1000 Гц, т.е. один тик за одну миллисекунду.
Сохраним RTX_Config.h, скомпилируем наш проект ещё раз и зашьём в МК. Теперь светодиоды мигают по очереди, по 1 секунде на каждый светодиод.
Для отладки программы с ОСРВ в Keil предусмотрен ряд функций, позволяющих просматривать выполнение потоков во времени, кумулятивное время выполнения функций потоков и многое другое. Но только в версиях Keil 5.24 и позже. У меня же версия 5.23 и всех прелестей отладки RTOS я не увижу. Однако, разработчики всё же добавили несколько функций и в мою версию Keil, что ж, воспользуемся ими!
Запускаем режим отладки CTRL+F5 и в тулбаре выбираем RTX RTOS, как показано на рисунке ниже. Слева откроется встроенное окошечко с основной информацией о настройках RTOS и текущем состоянии потоков.
Нас интересует вкладка Threads. Как и следует из названия, здесь представлены потоки, используемые в данном проекте: два наших потока LED0 и LED1, а также поток простоя osRtxIdle. В поле «Value» напротив каждого из потоков написано их текущее состояние и приоритет. Кликнув на плюсик слева от названия потока, можно увидеть более подробную информацию о состоянии потока, например, выставленные флаги.