Информационный портал технической поддержки Центра проектирования интегральных микросхем |
Операционные системы реального времени удобны и активно используются во встраиваемых системах, которые производятся на базе микроконтроллеров и других микросхем. На рынке ОСРВ для микроконтроллеров лидирующие позиции занимает ОСРВ 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 | Поток заблокирован, ожидает события от ОС |
RTOS: уровни приоритета |
---|
osPriorityIdle |
osPriorityLow |
osPriorityBelowNormal |
osPriorityNormal |
osPriorityAboveNormal |
osPriorityHigh |
osPriorityRealTime |
osPriorityError |
Теперь подключим в проект саму ОСРВ RTOS2. Ниже будут рассмотрены два варианта подключения: автоматическое и ручное.
Рисунок 3 - Дерево проекта после подключения библиотеки RTOS2
При этом важно для корректной сборки не забыть указать компилятору пути до основных файлов RTOS2 во вкладке "C/C++" настроек проекта, как это показано на рисунке 4:
Рисунок 4 - Указание компилятору путей до файлов ОСРВ RTOS2 в IDE Keil
#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 тиков таймера системы на выполнение каждого потока, но это значение можно и увеличивать, тем самым, задавая верхнюю границу выполнения каждой структурной части программного обеспечения
В микроконтроллере К1986ВЕ1QI присутствует неустранимая ошибка системного таймера, который используется системой реального времени. Системный таймер работает правильно только при частоте ядра до 25 МГц. При более высоких частотах времена растягиваются. Подробнее в файле Errata, ошибка "0011"
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)
// Получить статус переполнения
Фрагмент кода 3int32_t OS_Tick_Setup (uint32_t freq, IRQHandler_t handler)
// Установка таймера для генерации периодических тиков ядра RTOS2
void OS_Tick_Enable (void)
// Выполнить включение таймера
void OS_Tick_AcknowledgeIRQ (void)
// Обработчик прерывания для таймера
Фрагмент кода 4// Подключение заголовочных файлов
#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Сайт: | https://support.milandr.ru |
E-mail: | support@milandr.ru |
Телефон: | +7 495 221-13-55 |