24210

[i] Начальные сведения и применение RTX RTOS2 от Keil в МК Миландр. Запуск ОС на К1986ВЕ1QI (К1986ВЕ1FI, К1986ВЕ1GI) - OS Tick API

Автор статьи: Лампадов Илья Александрович (Инженер)
Дата последнего изменения: 30.11.2023 08:59:07
Материал из настоящей статьи, относящийся к микросхеме К1986ВЕ1QI, распространяется в том числе на микроконтроллеры К1986ВЕ1FI и К1986ВЕ1GI

Операционные системы реального времени удобны и активно используются во встраиваемых системах, которые производятся на базе микроконтроллеров и других микросхем. На рынке ОСРВ для микроконтроллеров лидирующие позиции занимает ОСРВ FreeRTOS - процент её использования достигает 70%. ОСРВ от Keil RTOS занимает второе место и применяется в 20% случаев. Остальные 10% рынка ОСРВ для микроконтроллеров разделяют небольшие системы от частных компаний. В этой статье хотелось бы подробнее рассказать именно об ОСРВ от Keil.

Keil RTOS (Real-Time Operating System) - многозадачная операционная система реального времени (ОСРВ) RTX, интегрированная в среду Keil. ОСРВ выполняет важное дело – реализует вытесняющую многозадачность. Применение ОСРВ позволяет улучшить управление проектами и облегчает повторное использование кода. Обратной стороной медали является использование повышенного объёма памяти и увеличение времени реакции на прерывания. Однако сейчас, когда объём ОЗУ в микроконтроллере (МК) составляет 32 Кб и более, а размер ОСРВ составляет до 5 Кб, возможностей для внедрения ОСРВ более чем достаточно. 

На сегодняшний день Keil предоставляет две версии своей ОСРВ: RTOS v1 (Keil RTX 4) и RTOS v2 (Keil RTX 5). Будем использовать RTOS v2 (далее просто RTOS2). Основным «строительным материалом» в обычной Си-программе являются функции, которые вызываются для выполнения определённых операций и которые затем передают управление в вызывающую их функцию. В ОСРВ такими базовыми исполнительными элементами являются потоки (процессы). Поток похож на Си-функцию, но в то же время имеет несколько принципиальных отличий - подробно во фрагменте кода 1.

unsigned int function (void)
{
    ……
    return();
}

void thread (void)
{
      while (1)
    {
       ...
    }
Фрагмент кода 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ВЕ1QI (MDR32F1QI) и подключим разделы библиотеки:
  • Startup_MDR32F1QI;
  • PORT;
  • RST_CLK.
  • TIMER
Для настройки проекта можно воспользоваться статьей "Настройки проекта IDE Keil на примере К1986ВЕ1QI".

Теперь подключим в проект саму ОСРВ RTOS2. Ниже будут рассмотрены два варианта подключения: автоматическое и ручное.

Автоматическое подключение ОСРВ RTOS2 к программному проекту

Подключение ОСРВ RTOS2 к проекту осуществляется через сервис "Manage Run-Time Environment". Для работы системы необходимо подключить три элемента (рисунок 1):
  1. CMSIS > CORE;
  2. CMSIS > RTOS2 (API) > Keil RTX5 (вариант Library);
  3. Device > Startup.
Рисунок 1 - Подключение необходимых составляющих в окне "Manage Run-Time Environment" для работы ОСРВ RTOS2

После этого структура проекта дополнится разделом CMSIS с требуемыми файлами: RTX_CMx.lib (x - серия ядра Cortex), rtx_lib.c, RTX_Config.c и RTX_Config.h.
Стоит отметить, что RTX_CMx.lib для ядра Cortex-M1 такой же, как и для ядра Cortex-M0, поэтому, например, для микроконтроллера К1986ВЕ1QI (RISC-ядро, которое схоже с Cortex-M1) подключится файл RTX_CM0.lib. Связано это с тем, что ядра Cortex-M0 и Cortex-M1 имеют одну архитектуру ARMv6-M, а также одинаковый набор инструкций. Убедиться в этом можно здесь ARM Cortex-M instruction variations. Кроме этого, применимость RTX_CM0.lib для ядра Cortex-М1 обозначена в документации к RTOS CMSIS-RTOS RTX Library Files.
Далее, для использования сервисов ОС в модулях проекта требуется лишь подключить к ним файл rtx_os.h, используя директиву #include.

Файл RTX_Config.h содержит основные настройки ОС. Файл может иметь вид не только стандартного текстового редактора, но и графического интерфейса, именуемого "Configuration Wizard", который представлен на рисунке 2. Здесь можно задать такие настройки, как объем глобальной динамической памяти, временные единицы измерения для задержек и тайм-аутов, базовые атрибуты всех объектов операционной системы и так далее. На начальном этапе рекомендуется оставить значения всех настроек по умолчанию.

Рисунок 2 - Графическое окно настроек RTOS2 в окне "Configuration Wizard"

Ручное подключение ОСРВ RTOS2 к программному проекту

При использовании поставляемых паков (Программное обеспечение Миландр) для IDE Keil старых версий можно заметить, что подключение RTOS2 невозможно выполнить в автоматическом режиме через сервис "Manage Run-Time Environment". Но можно добавить ОСРВ к программному проекту вручную, выполнив внедрение основных файлов операционной системы, вот их список и расположение в папке со средой IDE Keil:

  • C:\Keil_v5\ARM\PACK\ARM\CMSIS\5.0.1\CMSIS\RTOS2\RTX\Source\rtx_lib.c
  • C:\Keil_v5\ARM\PACK\ARM\CMSIS\5.0.1\CMSIS\RTOS2\RTX\Library\ARM\RTX_CM0.lib
  • C:\Keil_v5\ARM\PACK\ARM\CMSIS\5.0.1\CMSIS\RTOS2\RTX\Config
  1. RTX_Config.c
  2. RTX_Config.h
Файлы RTX_Config.c и RTX_Config.h лучше скопировать к себе в проект, как это делает в среде автоматическое добавление. При этом дерево проекта должно выглядеть так, как на рисунке 3 (также оно выглядит, когда выполняется автоматическое подключение).

  Рисунок 3 - Дерево проекта после подключения библиотеки RTOS2

При этом важно для корректной сборки не забыть указать компилятору пути до основных файлов RTOS2 во вкладке "C/C++" настроек проекта, как это показано на рисунке 4:

Рисунок 4 - Указание компилятору путей до файлов ОСРВ RTOS2 в IDE Keil

Универсальный код HelloWorld 

Код представлен во фрагменте кода 2 и может быть адаптирован под любой микроконтроллер, который поддерживает работу с RTOS2.

#include <rtx_os.h>
#include <os_tick.h>
#include <MDR32FxQI_port.h>
#include <MDR32FxQI_rst_clk.h>


#define USEPORT MDR_PORTD
#define USEPORT_PIN PORT_Pin_7
#define CLK_USEPORT RST_CLK_PCLK_PORTD

// Структура настроек для потока
 typedef struct {
   uint32_t Led_PortPin;
   uint32_t Led_Delay;
} LED_ThreadCfg;


// Задержка мигания светодиодов в миллисекундах
#define Delay 500


// Функция потока - исполняется в отдельном потоке
void Thread_LED (void *argument)
{
// Структура для настройки портов
PORT_InitTypeDef GPIOInitStruct;
// Параметры для функции, задаются извне
LED_ThreadCfg cfgThread = *(LED_ThreadCfg *)(argument);


// Включение тактирования
RST_CLK_PCLKcmd (CLK_USEPORT, ENABLE);


// Настройка вывода порта
PORT_StructInit(&GPIOInitStruct);
GPIOInitStruct.PORT_Pin = cfgThread.Led_PortPin;
GPIOInitStruct.PORT_OE = PORT_OE_OUT;
GPIOInitStruct.PORT_SPEED = PORT_SPEED_MAXFAST;
GPIOInitStruct.PORT_MODE = PORT_MODE_DIGITAL;


PORT_Init(USEPORT, &GPIOInitStruct);


// Цикл исполнения - мигание светодиодом
while (1)
 {
  PORT_SetBits(USEPORT, cfgThread.Led_PortPin);
  osDelay(cfgThread.Led_Delay);


  PORT_ResetBits(USEPORT, cfgThread.Led_PortPin);
  osDelay(cfgThread.Led_Delay);
 }
}


int main(void)
{

// Настройки для потоков
LED_ThreadCfg Cfg_Thread1 = {USEPORT_PIN, Delay};


// Инициализация RTOS
osKernelInitialize();


// Создание потоков мигания светодиодом
osThreadNew (Thread_LED, &Cfg_Thread1, NULL);
// Запуск RTOS
osKernelStart();
}
Фрагмент кода 2

Рассмотрим процесс запуска RTOS2. После конфигурации структуры входных параметров для будущего потока в функции main() необходимо произвести инициализацию RTOS и создать поток, который описывается, как обычная функция в Си. После же запустить планировщик ядра ОСРВ функцией osKernelStart (), который и начнёт запускать созданный поток.

При вызове  функции osDelay() поток, вызывающий функцию, переводится в режим ожидания Wait на указанное время, и запускается поток ожидания osRtxIdleThread, в составе которого находится бесконечный цикл. При желании в него можно прописать свою функцию. Приоритет у этого потока наименьший - osPriorityIdle, поэтому после его запуска при следующем тике таймера системы (его частота по умолчанию 1000 Гц) запускается поток с наибольшим приоритетом, находящийся в состоянии готовности «Ready». Это очень удобно, так как пока запустивший функцию задержки поток находится в режиме ожидания, будут выполняться другие потоки. При этом важно понимать, что задержки, которые формируются функцией osDelay(), а также другие времена для отсчётов в ОСРВ, высчитываются на основе тиков системного таймера SysTick.

В текущем универсальном примере вывод микроконтроллера будет переключать своё состояние каждые 500 тиков таймера операционной системы (один тик связан с параметром "Kernel Tick Frequency [Hz]" (freq) при конфигурировании файла RTX_Config.h - см. рисунок 2)На основе этих данных можно очень точно разграничивать время выполнения каждого потока, если, например, их используется много. Умение системы разделять процессорное время и является преимуществом использования ОСРВ на практике. В свою очередь, можно регулировать время выполнения каждого потока. Для этого в файле "RTX_Config.h" необходимо задать параметр "Round-Robin Thread switching", который также измеряется в тиках таймера системы. По умолчанию, если опция включена, выставляется 5 тиков таймера системы на выполнение каждого потока, но это значение можно и увеличивать, тем самым, задавая верхнюю границу выполнения каждой структурной части программного обеспечения

Работа с ОСРВ RTOS 2 на микроконтроллере К1986ВЕ1QI

В микроконтроллере К1986ВЕ1QI присутствует неустранимая ошибка системного таймера, который используется системой реального времени. Системный таймер работает правильно только при частоте ядра до 25 МГц. При более высоких частотах времена растягиваются. Подробнее в файле Errata, ошибка "0011"

Учитывая эту ошибку, можно опрометчиво предположить, что работать с ОСРВ RTOS2 не выйдет, поскольку не будет необходимой точности в работе. Однако, компанией Keil в структуру RTOS2 внедрен инструментарий OS Tick API. Данный инструментарий представляет собой набор функций, которые позволяют произвести отвязку счёта от системного таймера и привязать его к счёту периферийного таймера. Данное решение подходит, и оно также активно применяется на микроконтроллерах с ядрами Cortex серии A (высокопроизводительные ядра), где системного таймера попросту нет (и аналогичных).

Подробнее о всех функциях можно прочитать на странице API официального сайта Keil. Общий перечень функций инструментария представлен фрагментом кода 3:

int32_t OS_Tick_Setup (uint32_t freq, IRQHandler_t handler)
   // Установка таймера для генерации периодических тиков ядра RTOS2
 
void OS_Tick_Enable (void)
  // Выполнить включение таймера
 
void OS_Tick_Disable (void)
  // Выполнить отключение таймера
 
void OS_Tick_AcknowledgeIRQ (void)
   // Обработчик прерывания для таймера
 
int32_t OS_Tick_GetIRQn (void)
  // Получить номер прерывания таймера
 
uint32_t OS_Tick_GetClock (void)
  // Получить тактовую частоту работы таймера
 
uint32_t OS_Tick_GetInterval (void)
   // Получить значение перезагрузки интервала таймера
 
uint32_t OS_Tick_GetCount (void)
  // Получить значение счётчика таймера
 
uint32_t OS_Tick_GetOverflow (void)
  // Получить статус переполнения 
Фрагмент кода 3

Принцип работы всей системы, связанной с отсчётами, описывается следующим образом:

Когда функции OS Tick API не определены, то после старта операционной системы функцией osKernelStart () внутри всё равно вызываются стандартные функции инструментария для таймера SysTick, ключевые функции представлены
фрагментом кода 4:

int32_t OS_Tick_Setup (uint32_t freq, IRQHandler_t handler)
   // Установка таймера для генерации периодических тиков ядра RTOS2
void OS_Tick_Enable (void)
  // Выполнить включение таймера
void OS_Tick_AcknowledgeIRQ (void)
   // Обработчик прерывания для таймера 
Фрагмент кода 4

После этого работа операционной системы ведётся по умолчанию на базе отсчётов системного таймера SysTick. Но как только в программе определяется хотя бы одна функция OS Tick API, то после старта, который инициируется функцией osKernelStart (), вызовется эта функция, что автоматически на уровне системы отключит счёт системного таймера, и ОСРВ попытается подвязать отсчёты к периферийному таймеру, который необходимо в этих функциях грамотно настроить. При этом важно понять, что фиксация одного тика внутри операционной системы всё равно производится обработчиком SysTick Handler, но этот обработчик при помощи общего таймера микроконтроллера можно вызывать с необходимой периодичностью, задавая очень точные задержки и на высоких частотах работы микроконтроллера, что определенно подходит для корректного использования RTOS2 на микроконтроллере К1986ВЕ1QI.

В процессе изучения был написан универсальный библиотечный файл (он также будет прикреплен в разделе "Файлы для скачивания" после текста статьи) api_os_tick.c, который содержит полную структуру всех функций инструментария OS Tick API и который можно подключить к своему проекту (необязательно ограничиваться применением только микроконтроллера К1986ВЕ1QI). Код библиотечного файла представлен во фрагменте кода 5:

// Подключение заголовочных файлов
#include <rtx_os.h>
#include <os_tick.h>
#include <MDR32FxQI_port.h>
#include <MDR32FxQI_rst_clk.h>

// Задание параметров под конкретный таймер
#define TIMER MDR_TIMER1
#define INT_TIMER TIMER1_IRQn
#define CLK_TIMER RST_CLK_PCLK_TIMER1

int32_t  OS_Tick_Setup (uint32_t freq, IRQHandler_t handler) {

uint32_t load;
uint32_t clktim;
uint32_t divtim = TIMER->PSG;

RST_CLK_PCLKcmd (CLK_TIMER, ENABLE); // Включение тактирования блока таймера 1

// ВАЖНО! Дополнительная настройка тактирования для таймеров. Может выполняться для некоторых таймеров в регистре UART_CLK (см. спецификацию)
MDR_RST_CLK->TIM_CLOCK = (1<<24); //Включение тактовой частоты таймера 1, TIM1_CLK == HCLK

  // Настраиваем работу основного счетчика
  TIMER->CNTRL = 0x00000000;//Режим инициализации таймера
  TIMER->CNT = 0x00000000;//Начальное значение счетчика
  TIMER->PSG = 0x00000000;//Предделитель частоты
  TIMER->IE  = 0x00000002;//Разрешение генерировать прерывание при CNT = ARR

SystemCoreClockUpdate(); // Обновление системной частоты
clktim = (SystemCoreClock/(divtim + 1)); // Подсчёт системной частоты таймера с учётом бита деления PSG

load = (clktim/ freq) - 1U; // Расчёт значения для основания счёта

while (load > 0xFFFF) // Пока полученное значение ARR больше разрядности таймера, инкрементируем значение переменной divtim (PSG), чтобы попасть в диапазон
{
   divtim = divtim++;
   clktim = (SystemCoreClock/(divtim + 1));
   load = (clktim / freq) - 1U;
}

NVIC_EnableIRQ(INT_TIMER);
NVIC_SetPriority(INT_TIMER, 192);
TIMER->PSG = divtim;
TIMER->ARR = load;
return (0);
}

void  OS_Tick_Enable (void) {
  TIMER->CNTRL = 0x00000001; //Счет вверх по TIM_CLK
}

int32_t  OS_Tick_GetIRQn (void) {
  return (INT_TIMER); // Какое прерывание получать в ОСРВ
}

void  OS_Tick_AcknowledgeIRQ (void) {
   TIMER->STATUS = 0x00; // Фактически обработчик прерываний. Сброс статуса вызова прерывания
}

uint32_t OS_Tick_GetClock (void) {
  return (SystemCoreClock); // Мониторинг тактовой частоты процессора
}

uint32_t OS_Tick_GetInterval (void) {
}

uint32_t OS_Tick_GetCount (void) {
}


uint32_t OS_Tick_GetOverflow (void) {
}
Фрагмента кода 5
Функции OS Tick API, предназначенные для мониторинга: OS_Tick_GetInterval (void)OS_Tick_GetCount (void) и OS_Tick_GetOverflow (void) - не описаны в библиотечном файле, их описанием при необходимости занимается разработчик.
В коде имеются комментарии, которые подробно описывают работу этого блока. Из особенностей можно отметить обработчик для таймера, который задействует функцию OS Tick API - void OS_Tick_AcknowledgeIRQ (void). В этой функции сбрасывается флаг формирования прерывания, чтобы прерывание генерировалось каждый раз при достижении таймером значения основания счёта ARR, которое рассчитывается отдельно под каждую системную частоту и переменную freq (задаётся в "Configuration Wizard" - см. Рисунок 2) в функции OS Tick API - int32_t  OS_Tick_Setup (uint32_t freq, IRQHandler_t handler).

Теперь для корректной работы важно вспомнить, что фиксация тика RTOS2 всё равно происходит в обработчике SysTick Handler, но нами настроены прерывания для обработчика таймера. Необходимо изменить в файле startup_*.s вектор обработчика таймера (например, TIMER1_IRQHandler) на обработчик системного таймера SysTick_Handler. Выглядеть настройка векторов прерываний в файле startup_*.s будет примерно так, как на рисунке 5:

Рисунок 5 - Вид файла startup_*.s для корректной работы OS Tick API

Готовый проект "HelloWorld", учитывающий все особенности работы с OS Tick API, для отладочной платы производства компании Миландр на базе микроконтроллера К1986ВЕ1QI в разделе "Файлы для скачивания" после статьи.
Сохранить статью в PDF

Файлы для скачивания

Документация

Теги

Была ли статья полезной?