======Начальные 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» напротив каждого из потоков написано их текущее состояние и приоритет. Кликнув на плюсик слева от названия потока, можно увидеть более подробную информацию о состоянии потока, например, выставленные флаги.