======Начальные cведения о Keil RTOS======
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 можно посмотреть на [[http://www.keil.com/pack/doc/CMSIS/RTOS2/html/functionOverview.html|официальном сайте]].
=====Создание проекта=====
Создаём новый проект, как описано в статье [[https://startmilandr.ru/doku.php/prog:start:new_project|Создание проекта]]. Выбираем микроконтроллер 1986ВЕ92 и подключаем разделы библиотеки: Startup_MDR1986BE9x, PORT, RST_CLK. Для настройки проекта можно воспользоваться [[https://startmilandr.ru/doku.php/prog:start:project_options_9x|Настройки проекта для 1986ВЕ9х»]].
Теперь подключаем в наш проект саму ОСРВ. Конечно, это можно было сделать и на этапе создания проекта, но так мы выделим отдельные шаги (или шаг) для подключения только ОСРВ.
Переходим в окно «Manage Run-Time Environment», кликнув на соответствующий значок
{{prog:spec:rtos_manage.jpg}}
В окне «Manage Run-Time Environment» выбираем:
**CMSIS :: CORE** и **CMSIS :: RTOS2 (API) :: Keil RTX5**. Можно добавить RTX в качестве библиотеки (Library), либо добавить полный исходный код (Source). Мы выберем Library.
{{prog:spec:rtos_library.png}}
Нажимаем «ОК». В окне «Project» можно увидеть файлы, которые были автоматически добавлены в проект: RTX_Config.h, RTX_Config.c, библиотека или файлы исходного кода.
{{prog:spec:rtos_project.jpg}}
=====Программа мигания светодиодами=====
#include
#include
// Библиотека для работы с RTOS
#include
// Прототипы функций мигания светодиодами, реализованные ниже
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
Рассмотрим процесс запуска 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.
{{prog:spec:rtos_config.png}}
Этот параметр задаётся в тиках таймера системы, что по умолчанию соответствует миллисекундам. Немного выше располагается параметр «Kernel Tick Frequency», который как раз и задаёт частоту тиков системного таймера, по умолчанию это 1000 Гц, т.е. один тик за одну миллисекунду.
Сохраним RTX_Config.h, скомпилируем наш проект ещё раз и зашьём в МК. Теперь светодиоды мигают по очереди, по 1 секунде на каждый светодиод.
Для отладки программы с ОСРВ в Keil предусмотрен ряд функций, позволяющих просматривать выполнение потоков во времени, кумулятивное время выполнения функций потоков и многое другое. Но только в версиях Keil 5.24 и позже. У меня же версия 5.23 и всех прелестей отладки RTOS я не увижу. Однако, разработчики всё же добавили несколько функций и в мою версию Keil, что ж, воспользуемся ими!
Запускаем режим отладки CTRL+F5 и в тулбаре выбираем RTX RTOS, как показано на рисунке ниже. Слева откроется встроенное окошечко с основной информацией о настройках RTOS и текущем состоянии потоков.
{{prog:spec:rtos_debug1.png}}
Нас интересует вкладка Threads. Как и следует из названия, здесь представлены потоки, используемые в данном проекте: два наших потока LED0 и LED1, а также поток простоя osRtxIdle. В поле «Value» напротив каждого из потоков написано их текущее состояние и приоритет. Кликнув на плюсик слева от названия потока, можно увидеть более подробную информацию о состоянии потока, например, выставленные флаги.