Существует целый ряд классов задач, которые должны быть выполнены за строго определённое время и не более. Среди подобных задач можно назвать: срабатывание предохранительных клапанов, катапультирование пилота из терпящего бедствие воздушного судна, срабатывание подушек безопасности у автомобиля, промышленную робототехнику и т.д. В сфере программного обеспечения также существует целый ряд задач, которые должны быть выполнены точно срок — для этого и служат операционные системы реального времени, одной из которых является FreeRTOS. Именно о ней мы и поговорим в этой статье.

Говоря об операционных системах реального времени, можно условно разделить их на:

  • системы жёсткого реального времени;
  • системы мягкого реального времени.

Где ОС жёсткого реального времени — используются для решения задач, описанных выше, то есть требующих выполнения за строго определённое время и не более, так как превышение этого времени может вызвать катастрофические последствия.

В то время как ОС мягкого реальном времени — допускают выполнение задач с превышением указанного времени (только это приведёт к увеличению накладных расходов: потребности в повторной отправке пакетов, подтверждения их получения и т. д.)

Поговорим об использовании ОС реального времени в микроконтроллерах, тут следует начать с главного: а для чего вообще нужно использовать операционную систему для микроконтроллеров? Потому что именно этот вопрос зачастую возникает у каждого, кто задумывается о чём-то более сложном, чем просто код.

И на него можно ответить достаточно просто: подобная операционная система вам нужна, если вы разрабатываете достаточно сложную систему, которая обладает множеством функций. Причём ситуация может усугубляться ещё и тем, что эти функции являются достаточно разнородными: отображение информации на экране, межсетевой обмен, постоянный опрос ряда датчиков и пользовательских клавиатур и т. д. Во всём этом комплексе задач вам сможет помочь FreeRTOS. Благодаря своему достаточно богатому API и популярности, система портирована на множество архитектур и содержит подробные примеры своего использования.

Почему стоит выбрать именно эту операционную систему?


Дело в том, что она разрабатывается уже в течение 18 лет в сотрудничестве с ведущими производителями микросхем и имеет поддержку даже новейших микроконтроллеров RISC-V и ARMv8-M, есть большая экосистема разработчиков, масса настроенных примеров (нет нужды самому программировать «с нуля» — можно использовать «заготовки»).

Как заявляется на самом сайте FreeRTOS, операционная система занимает лидирующие места в каждом обзоре рынка, начиная с 2011 года. Я не поленился поднять один из таких обзоров, за 2019 год — свежее просто нет, и судя по ретроспективе подобных исследований, которые посвящены встраиваемым системам — embedded.com, они их выпускают раз в два года. Так что, по идее, должны были уже выпустить новый отчёт, но что-то там произошло, и нового отчёта пока нет.

Так вот, на 57-58 страницах этого отчёта имеется любопытная информация, касающаяся FreeRTOS. На 57 странице говорится о том, какие операционные системы разработчики по всему миру уже используют, а на 58 странице — какие операционные системы планируют использовать в последующие 12 месяцев. И действительно, FreeRTOS занимает лидирующие позиции в 37% рынка по азиатско-тихоокеанскому региону. В целом по миру система находится на втором месте, так как первое место занимает embedded linux.

Видимо, это связано с тем, что система пока не обладает таким внушительным списком поддерживаемых драйверов, а также инструментами, касающимися сети или работой с памятью, какие имеются у linux. Зато потребление ресурсов этой системы намного меньше — 0,5 КБ оперативной памяти и от 5 до 10 КБ ПЗУ. О чём, кстати говоря, упоминает и обзор встраиваемых систем от itweek.

И кроме того — она абсолютно бесплатна!

В рамках FreeRTOS возможно достижение многозадачности, когда каждая отдельная задача может функционировать без оглядки на все остальные, то есть, можно сказать, что в основе FreeRTOS лежит ключевое понятие «самостоятельной задачи».

Подобная многозадачность достигается за счёт того, что задачи выполняются поделёнными на маленькие частички, каждая из которых обрабатывается за один так называемый «тик таймера». То есть когда один из таймеров микроконтроллера один раз «тикнул», произошло прерывание, сработал диспетчер, который выполнил маленькую часть одной из параллельно работающих задач, после чего подобный цикл повторился ещё и ещё раз.

Говоря о задачах, следует сказать, что в этой операционной системе они представляют из себя бесконечные циклы без выхода из них.

Для создания задачи используется функция xTaskCreate (), характеристики которой выглядят следующим образом:

  BaseType_t xTaskCreate(    TaskFunction_t pvTaskCode,
                            const char * const pcName,
                            configSTACK_DEPTH_TYPE usStackDepth,
                            void *pvParameters,
                            UBaseType_t uxPriority,
                            TaskHandle_t *pxCreatedTask
                          );

Если параметры этой задачи представить на русском языке, то выглядеть она будет следующим образом:

 BaseType_t xTaskCreate( Указатель на функцию,
                            имя_задачи,
                            размер_стека_под_задачу,
                            параметры_задачи,
                            приоритет_задачи,
                            ссылка_на_задачу_для_передачи_наружу
                          );

Одним из довольно спорных и сложных вопросов для понимания является — а насколько большим нужно делать стек, выделяемый под задачу и указываемый в её параметрах? Поэтому на этот счёт был написан достаточно развёрнутый FAQ, который можно глянуть вот по этой ссылке.

Кроме создания задачи, возможно вызвать функцию для её удаления:

void vTaskDelete( TaskHandle_t xTask );

Пример использования:

 void vOtherFunction( void )
 {
 TaskHandle_t xHandle = NULL;

     // Create the task, storing the handle.
     xTaskCreate( vTaskCode, "NAME", STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );

     // Use the handle to delete the task.
     if( xHandle != NULL )
     {
         vTaskDelete( xHandle );
     }
 }

Или для приостановки задачи на некоторое количество системных тиков:

void vTaskDelay( const TickType_t xTicksToDelay );

Пример использования:

 void vTaskFunction( void * pvParameters )
 {
 /* Block for 500ms. */
 const TickType_t xDelay = 500 / portTICK_PERIOD_MS;

     for( ;; )
     {
         /* Simply toggle the LED every 500ms, blocking between each toggle. */
         vToggleLED();
         vTaskDelay( xDelay );
     }
}

В приведённом ниже коде вы можете видеть, как происходит работа с рассмотренными выше концепциями создания задачи, а также включения задержки. Этот код мигает светодиодом и считывает с аналогового пина значения в бесконечном цикле. Подробные комментарии, содержащиеся в самом коде, позволяют достаточно хорошо понять происходящее и не требуют дополнительного пояснения:
Код
#include <Arduino_FreeRTOS.h>

// define two tasks for Blink & AnalogRead
void TaskBlink( void *pvParameters );
void TaskAnalogRead( void *pvParameters );

// the setup function runs once when you press reset or power the board
void setup() {
 
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
 
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB, on LEONARDO, MICRO, YUN, and other 32u4 based boards.
  }

  // Now set up two tasks to run independently.
  xTaskCreate(
    TaskBlink
    ,  "Blink"   // A name just for humans
    ,  128  // This stack size can be checked & adjusted by reading the Stack Highwater
    ,  NULL
    ,  2  // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,  NULL );

  xTaskCreate(
    TaskAnalogRead
    ,  "AnalogRead"
    ,  128  // Stack size
    ,  NULL
    ,  1  // Priority
    ,  NULL );

  // Now the task scheduler, which takes over control of scheduling individual tasks, is automatically started.
}

void loop()
{
  // Empty. Things are done in Tasks.
}

/*--------------------------------------------------*/
/*---------------------- Tasks ---------------------*/
/*--------------------------------------------------*/

void TaskBlink(void *pvParameters)  // This is a task.
{
  (void) pvParameters;

/*
  Blink
  Turns on an LED on for one second, then off for one second, repeatedly.

  Most Arduinos have an on-board LED you can control. On the UNO, LEONARDO, MEGA, and ZERO
  it is attached to digital pin 13, on MKR1000 on pin 6. LED_BUILTIN takes care
  of use the correct LED pin whatever is the board used.
 
  The MICRO does not have a LED_BUILTIN available. For the MICRO board please substitute
  the LED_BUILTIN definition with either LED_BUILTIN_RX or LED_BUILTIN_TX.
  e.g. pinMode(LED_BUILTIN_RX, OUTPUT); etc.
 
  If you want to know what pin the on-board LED is connected to on your Arduino model, check
  the Technical Specs of your board  at https://www.arduino.cc/en/Main/Products
 
  This example code is in the public domain.

  modified 8 May 2014
  by Scott Fitzgerald
 
  modified 2 Sep 2016
  by Arturo Guadalupi
*/

  // initialize digital LED_BUILTIN on pin 13 as an output.
  pinMode(LED_BUILTIN, OUTPUT);

  for (;;) // A Task shall never return or exit.
  {
    digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
    vTaskDelay( 1000 / portTICK_PERIOD_MS ); // wait for one second
    digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
    vTaskDelay( 1000 / portTICK_PERIOD_MS ); // wait for one second
  }
}

void TaskAnalogRead(void *pvParameters)  // This is a task.
{
  (void) pvParameters;
 
/*
  AnalogReadSerial
  Reads an analog input on pin 0, prints the result to the serial monitor.
  Graphical representation is available using serial plotter (Tools > Serial Plotter menu)
  Attach the center pin of a potentiometer to pin A0, and the outside pins to +5V and ground.

  This example code is in the public domain.
*/

  for (;;)
  {
    // read the input on analog pin 0:
    int sensorValue = analogRead(A0);
    // print out the value you read:
    Serial.println(sensorValue);
    vTaskDelay(1);  // one tick delay (15ms) in between reads for stability
  }
}

Как вы, наверное, уже догадались, имеющийся в наличии API не ограничивается только этими функциями. Полный список можно посмотреть здесь.

Кстати сказать, так как мы упомянули возможность приостановки задачи на n-системных тиков, в этот момент задача переходит в статус Blocked (задача простаивает).

В принципе, таких статусов только четыре: Ready (в данный момент задача запущена), Running (в данный момент задача исполняется на процессоре), Blocked (простой), Suspended (в данный момент задача полностью выключена).

Среди параметров создания задачи одним из интересным является приоритет. Понятие приоритета означает, в каком порядке задачи будут получать процессорное время. То есть задача с низким приоритетом его не получит до тех пор, пока задачи с более высоким приоритетом не перейдут в статус ожидания (Blocked). Причём количество возможных приоритетов не ограничено, и вы можете создавать их сами как на этапе создания задачи, в её параметрах, так и «на лету», используя функцию vTaskPrioritySet():

void vTaskPrioritySet( TaskHandle_t xTask,
                       UBaseType_t uxNewPriority );

Пример использования:

void vAFunction( void )
 {
 TaskHandle_t xHandle;
     // Create a task, storing the handle.
     xTaskCreate( vTaskCode, "NAME", STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
     // ...
     // Use the handle to raise the priority of the created task.
     vTaskPrioritySet( xHandle, tskIDLE_PRIORITY + 1 )
     // ...
     // Use a NULL handle to raise our priority to the same value.
     vTaskPrioritySet( NULL, tskIDLE_PRIORITY + 1 );
 }

Если задачи имеют одинаковые приоритеты, то они будут выполняться в бесконечном цикле и получать процессорное время одна за другой. Количество задач с одинаковыми или разными приоритетами не ограничено.

Как мы уже говорили ранее, задачи в рамках этой операционной системы являются, по сути, изолированными и выполняются поочерёдно маленькими кусочками. Для того чтобы задачи могли обмениваться друг с другом информацией, была придумана система очередей.

Любая задача может поместить информацию в очередь: в её начало, используя функцию xQueueSendToFront():

image
Источник картинки: easyelectronics

 BaseType_t xQueueSendToFront( QueueHandle_t xQueue,
                               const void * pvItemToQueue,
                               TickType_t xTicksToWait );

Пример использования:
Код размещения в начало очереди
struct AMessage
{
    char ucMessageID;
    char ucData[ 20 ];
} xMessage;

unsigned long ulVar = 10UL;

void vATask( void *pvParameters )
{
QueueHandle_t xQueue1, xQueue2;
struct AMessage *pxMessage;

    /* Create a queue capable of containing 10 unsigned long values. */
    xQueue1 = xQueueCreate( 10, sizeof( unsigned long ) );

    /* Create a queue capable of containing 10 pointers to AMessage
    structures.  These should be passed by pointer as they contain a lot of
    data. */
    xQueue2 = xQueueCreate( 10, sizeof( struct AMessage * ) );

    /* ... */

    if( xQueue1 != 0 )
    {
        /* Send an unsigned long.  Wait for 10 ticks for space to become
        available if necessary. */
        if( xQueueSendToFront( xQueue1,
                              ( void * ) &ulVar,
                              ( TickType_t ) 10 ) != pdPASS )
        {
            /* Failed to post the message, even after 10 ticks. */
        }
    }

    if( xQueue2 != 0 )
    {
        /* Send a pointer to a struct AMessage object.  Don't block if the
        queue is already full. */
        pxMessage = & xMessage;
        xQueueSendToFront( xQueue2, ( void * ) &pxMessage, ( TickType_t ) 0 );
    }

    /* ... Rest of task code. */
}

или, соответственно, в её конец — xQueueSendtoBack():

 BaseType_t xQueueSendToBack(
                                   QueueHandle_t xQueue,
                                   const void * pvItemToQueue,
                                   TickType_t xTicksToWait
                               );

Пример использования:
Код размещения в конец очереди
struct AMessage
{
    char ucMessageID;
    char ucData[ 20 ];
} xMessage;

unsigned long ulVar = 10UL;

void vATask( void *pvParameters )
{
QueueHandle_t xQueue1, xQueue2;
struct AMessage *pxMessage;

    /* Create a queue capable of containing 10 unsigned long values. */
    xQueue1 = xQueueCreate( 10, sizeof( unsigned long ) );

    /* Create a queue capable of containing 10 pointers to AMessage
    structures.  These should be passed by pointer as they contain a lot of
    data. */
    xQueue2 = xQueueCreate( 10, sizeof( struct AMessage * ) );

    /* ... */

    if( xQueue1 != 0 )
    {
        /* Send an unsigned long.  Wait for 10 ticks for space to become
        available if necessary. */
        if( xQueueSendToBack( xQueue1,
                             ( void * ) &ulVar,
                             ( TickType_t ) 10 ) != pdPASS )
        {
            /* Failed to post the message, even after 10 ticks. */
        }
    }

    if( xQueue2 != 0 )
    {
        /* Send a pointer to a struct AMessage object.  Don't block if the
        queue is already full. */
        pxMessage = & xMessage;
        xQueueSendToBack( xQueue2, ( void * ) &pxMessage, ( TickType_t ) 0 );
    }

    /* ... Rest of task code. */
}

Также есть простая команда xQueueSend() — которая по своей сути эквивалентна функции, добавляющей в конец очереди.

image
Источник картинки: easyelectronics

Разумеется, работа с очередями не исчерпывается указанными командами. Полный список доступных функций находится вот здесь. До начала работы с очередью она должна быть создана с использованием команды xQueueCreate().

Код, приведённый ниже, иллюстрирует один из самых простых вариантов создания очереди:
Пример кода очереди
/*
 * Example of a basic FreeRTOS queue
 * https://www.freertos.org/Embedded-RTOS-Queues.html
 */

// Include Arduino FreeRTOS library
#include <Arduino_FreeRTOS.h>

// Include queue support
#include <queue.h>

/*
 * Declaring a global variable of type QueueHandle_t
 *
 */
QueueHandle_t integerQueue;

void setup() {

  /**
   * Create a queue.
   * https://www.freertos.org/a00116.html
   */
  integerQueue = xQueueCreate(10, // Queue length
                              sizeof(int) // Queue item size
                              );
 
  if (integerQueue != NULL) {
    
    // Create task that consumes the queue if it was created.
    xTaskCreate(TaskSerial, // Task function
                "Serial", // A name just for humans
                128,  // This stack size can be checked & adjusted by reading the Stack Highwater
                NULL,
                2, // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
                NULL);


    // Create task that publish data in the queue if it was created.
    xTaskCreate(TaskAnalogRead, // Task function
                "AnalogRead", // Task name
                128,  // Stack size
                NULL,
                1, // Priority
                NULL);
    
  }


  xTaskCreate(TaskBlink, // Task function
              "Blink", // Task name
              128, // Stack size
              NULL,
              0, // Priority
              NULL );

}

void loop() {}


/**
 * Analog read task
 * Reads an analog input on pin 0 and send the readed value through the queue.
 * See Blink_AnalogRead example.
 */
void TaskAnalogRead(void *pvParameters)
{
  (void) pvParameters;
 
  for (;;)
  {
    // Read the input on analog pin 0:
    int sensorValue = analogRead(A0);

    /**
     * Post an item on a queue.
     * https://www.freertos.org/a00117.html
     */
    xQueueSend(integerQueue, &sensorValue, portMAX_DELAY);

    // One tick delay (15ms) in between reads for stability
    vTaskDelay(1);
  }
}

/**
 * Serial task.
 * Prints the received items from the queue to the serial monitor.
 */
void TaskSerial(void * pvParameters) {
  (void) pvParameters;

  // Init Arduino serial
  Serial.begin(9600);

  // Wait for serial port to connect. Needed for native USB, on LEONARDO, MICRO, YUN, and other 32u4 based boards.
  while (!Serial) {
    vTaskDelay(1);
  }

  int valueFromQueue = 0;

  for (;;)
  {

    /**
     * Read an item from a queue.
     * https://www.freertos.org/a00118.html
     */
    if (xQueueReceive(integerQueue, &valueFromQueue, portMAX_DELAY) == pdPASS) {
      Serial.println(valueFromQueue);
    }
  }
}

/*
 * Blink task.
 * See Blink_AnalogRead example.
 */
void TaskBlink(void *pvParameters)
{
  (void) pvParameters;

  pinMode(LED_BUILTIN, OUTPUT);

  for (;;)
  {
    digitalWrite(LED_BUILTIN, HIGH);
    vTaskDelay( 250 / portTICK_PERIOD_MS );
    digitalWrite(LED_BUILTIN, LOW);
    vTaskDelay( 250 / portTICK_PERIOD_MS );
  }
}

В программировании есть такая весьма полезная вещь, как система флагов, в FreeRTOS подобной сущностью выступают семафоры. Полный список их разновидностей, можно найти вот здесь.

Также для доступа множественных задач к единому ресурсу, существуют мьютексы. Для тех, кто не знает, это такая сущность, которая гарантирует, что уникальный ресурс в один момент времени будет использован только одним потребителем, который заблокирует доступ к нему. В качестве примера такого мьютекса можно привести пример зарплатной карты, которая находятся только у вас (и пока она находится у вас, жена не будет иметь доступ к уникальному ресурсу, которым является ваш банковский счёт. Но как только вы оставите свою карту на тумбочке...) Впрочем, не будем о грустном.

Один из примеров работы с концепцией мьютекса показан в коде ниже:
Использование мьютекса
/*
   Example of a FreeRTOS mutex
   https://www.freertos.org/Real-time-embedded-RTOS-mutexes.html
*/

// Include Arduino FreeRTOS library
#include <Arduino_FreeRTOS.h>


// Include mutex support
#include <semphr.h>

/*
   Declaring a global variable of type SemaphoreHandle_t

*/
SemaphoreHandle_t mutex;

int globalCount = 0;

void setup() {

  Serial.begin(9600);

  /**
       Create a mutex.
       https://www.freertos.org/CreateMutex.html
  */
  mutex = xSemaphoreCreateMutex();
  if (mutex != NULL) {
    Serial.println("Mutex created");
  }

  /**
     Create tasks
  */
  xTaskCreate(TaskMutex, // Task function
              "Task1", // Task name for humans
              128,
              1000, // Task parameter
              1, // Task priority
              NULL);

  xTaskCreate(TaskMutex, "Task2", 128, 1000, 1, NULL);

}

void loop() {}

void TaskMutex(void *pvParameters)
{
  int delay = *((int*)pvParameters); // Use task parameters to define delay

  for (;;)
  {
    /**
       Take mutex
       https://www.freertos.org/a00122.html
    */
    if (xSemaphoreTake(mutex, 10) == pdTRUE)
    {
      Serial.print(pcTaskGetName(NULL)); // Get task name
      Serial.print(", Count read value: ");
      Serial.print(globalCount);

      globalCount++;

      Serial.print(", Updated value: ");
      Serial.print(globalCount);

      Serial.println();
      /**
         Give mutex
         https://www.freertos.org/a00123.html
      */
      xSemaphoreGive(mutex);
    }

    vTaskDelay(delay / portTICK_PERIOD_MS);
  }
}

Кстати сказать, эта операционная система доступна и через стандартную Arduino IDE. Для этого нужно зайти в менеджер библиотек и установить себе, например, следующую библиотеку:



Библиотека использует в качестве генератора «тиков» Watchdog Timer, что даёт разрешение «тиков» в 15 миллисекунд между ними.

Или же вы можете скачать её с официального github-а системы. Или просто с сайта freertos.org. Вместе с этой библиотекой вы получите ряд примеров, на которых можете научиться работать с системой, что очень удобно.

Пример использования FreeRTOS для незамысловатой задачи мигания массивом светодиодов можно посмотреть в видео ниже. Код, показанный в видео, находится по ссылке.

А вот здесь чуть более сложный пример использования FreeRTOS:

И листинги программы с объяснениями.

Многие пользователи операционной системы FreeRTOS подчёркивают, что не нужно её выбирать, если вы хотите только «использовать, чтобы просто использовать». Однако если вам нужна надёжная, проработанная, многозадачная операционная система реального времени, которая обладает весьма скромными потребностями в плане ПЗУ и ОЗУ — то она для вас. Например, размер ядра системы в двоичном коде составляет всего лишь от 6 до 12 КБ.

Кроме того, интересным моментом является то, что система бесплатна, даже когда она используется для коммерческих применений. Здесь также существует отдельный вариант Safe RTOS, который имеет соответствующие сертификаты для применения его даже в рамках медицинских, автомобильных и промышленных секторов.

Посмотреть список устройств, на которые портирована система, можно вот по этой ссылке.

Тема эта в целом достаточно обширна, и мы прошлись только «по самому входу кроличьей норы», а она достаточно глубока, чтобы получить общее представление, так что если кто-то действительно заинтересуется этой темой — следует более подробно поизучать систему на головном сайте freertos.org либо просто в интернете.


НЛО прилетело и оставило здесь промокод для читателей нашего блога:

15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.

Комментарии (27)


  1. ladle
    06.06.2022 15:06
    +10

    В то время как ОС мягкого реальном времени — допускают выполнение задач с превышением указанного времени (только это приведёт к увеличению накладных расходов: потребности в повторной отправке пакетов, подтверждения их получения и т. д.)

    Когда слышу про "мягкое реальное время", сразу вспоминается М.А.Булгаков:

    «— Осетрину прислали второй свежести, — сообщил буфетчик.
    Голубчик, это вздор!
    Чего вздор?
    Вторая свежесть — вот что вздор! Свежесть бывает только одна — первая, она же и последняя. А если осетрина второй свежести, то это означает, что она тухлая!»


    1. semennikov
      07.06.2022 15:08

      это не совсем так, есть разные задачи с разными требованиями, например время вам вообще не важно - тогда Windows, если вам время важно очень тогда Vxworks, QNX а есть где важно, но если просрочите то катастрофы не будет, но будут убытки, тогда как раз и нужно "мягкое время".

      И, кстати, реальное время это точно вовремя, не раньше и не позже, и одно из важнейших если не самое важное - это отсутствие зависания, ни мягкое ни жесткое реальное время их не допускают


      1. predator86
        07.06.2022 15:16

        но будут убытки, тогда как раз и нужно «мягкое время».
        Что за убытки?


        1. Sergeant101
          08.06.2022 12:51

          Клапан не вовремя открылся и вся партия продукции ушла в каныгу.


          1. predator86
            08.06.2022 13:10

            Так это допустимый убыток, при котором можно использовать «мягкое время»?


      1. ladle
        07.06.2022 16:33
        +1

        Вообще-то я работаю с системами реального времени с начала 70-х годов прошлого века (компьютеры HP2100, ОС - RTE(Real Time Executive), языки программирования - FORTRAN и ассемблер). В те дремучие времена предложенных Вами систем и близко не было, а вот реальное время - да, было и без всякой мягкости и жёсткости. Выражение "мягкое реальное время" (не могу назвать это термином, так как не понимаю, что оно означает, вернее понимаю, что не означает ничего) появилось намного-намного позднее как маркетинговый ход, когда реального времени нет, а для продвижения продукта нужно, чтобы как бы было. Меня от этого выражения слегка тошнит (как от осетрины второй свежести :).

        Что касается "реальное время это точно вовремя, не раньше и не позже" - не согласен, обычно имеют в виду гарантированное максимальное время реакции.

        Насчёт "самое важное - это отсутствие зависания" - согласен, но к рельному времени это отношения не имеет.


        1. semennikov
          07.06.2022 23:49

          Извините, но реальное время это именно точно вовремя, управляя механизмами сработать раньше ничуть не лучше чем сработать позже. По поводу отсутствия зависания - это тоже основополагающая характеристика именно систем реального времени, следующая просто из ее требований гарантии срабатывания не позже чем установлено


          1. ladle
            08.06.2022 01:06
            +1

            Извините, но реальное время это именно точно вовремя, управляя механизмами сработать раньше ничуть не лучше чем сработать позже. 

            Как раз часто, управляя механизмами, сработать раньше - намного лучше, чем сработать позже.  Во многих случаях реакция системы происходит немедленно по наступлению события. Например было бы странно привязывать срабатывание пожарной сигнализации в школе ко времени окончания урока или при обнаружении пусков со стороны партнёров медлить с ударом возмездия. Здесь как раз важно максимальное время реакции, чтобы успеть провести эвакуацию или уложиться в подлётное время.

            Что касается зависаний, то не должна зависать любая система (я думаю со мной согласятся многие пользователи). Для этого совсем не обязательно объявлять её системой реального времени, хотя бы и "мягкого".


            1. sim2q
              08.06.2022 01:42

              под зависаниями наверное имелось в виду "freeze", типа как у винды - Отстань!, я в swap :)


              1. semennikov
                08.06.2022 09:35

                Да, я имел в виду именно это


            1. semennikov
              08.06.2022 09:53

              Да, конечно есть системы где нужно сработать как можно раньше, но обычно системы управления объектами в реальном времени должны работать точно в указанное время. Классическим примером задачи, где требуется ОС РВ, является управление роботом, берущим деталь с ленты конвейера. Деталь движется и робот имеет лишь маленький промежуток времени, когда он может её взять. Если он опоздает, то деталь уже не будет на нужном участке конвейера, и следовательно, работа не будет сделана, несмотря на то, что робот находится в правильном месте. Если он спозиционируется раньше, то деталь ещё не успеет подъехать, и он заблокирует ей путь. Система управления автомобилем и самолетом тоже требует сработать точно в указанное время, а не как можно раньше.


              1. DungeonLords
                08.06.2022 10:08

                Очень интересно. Как же операционная система реального времени сработает точно в указанное время, если существуют прерывания, особенно вложенные прерывания? Или ваше "точное время" не очень точное и запаздывание в десяток наносекунд никому не повредит?


                1. semennikov
                  08.06.2022 10:58

                  В ОС РВ есть понятие "квант времени", все реакции системы гарантируются только с точностью до кванта времени. Размер кванта зависит и от конкретной системы и, естественно, от машины на которой это реализовано. Лучше всего это описано "Введение в QNX Neutrino" (Роберт Кртен) это хоть и старая и про QNX но все основы любой ОС РВ там описаны очень хорошо и понятно, мне и близко так не сказать


                  1. ladle
                    08.06.2022 14:49

                    В ОС РВ есть понятие "квант времени", все реакции системы гарантируются только с точностью до кванта времени. 

                    Не совсем так. Это верно для прерываний от системного таймера. Для других прерываний (в частности от других таймеров) в нормальной ОС РВ управление передаётся в диспетчер немедленно, ну если оно не заблокировано по той или иной причине. Можно, конечно, просыпаться раз в системный тик и смотреть вокруг себя - а вдруг что-то происходило пока мы спали, но это прямая дорожка к "мягкому" реальному времени.


                    1. semennikov
                      08.06.2022 16:07

                      Извините, но Вы постоянно путаете минимально возможное время и системы жесткого реального времени. ОС РВ дают не минимально возможное время реакции, а дают гарантированное время реакции, а это далеко не одно и тоже. Более того, ОС РВ жесткого времени часто работают в среднем медленнее чем обычные системы и главное не то, что они быстрее а то, что они гарантируют время реакции. И вот именно это гарантированное время (оно в разных системах называется по разному) и есть главная их особенность. Кстати, так не любимые Вами системы "мягкого" времени как раз оптимизированы на минимальное в среднем время отклика, но не дают на него гарантии.

                      Еще один важный момент - этот самый квант времени жесткой ОС РВ обычно довольно велик, для базовой версии скажем QNX это 10 мксек. Можно задать (и задают) меньшие интервалы, но их соблюдение не гарантируется


                      1. ladle
                        08.06.2022 17:08

                        Ну и Вы меня извините, про минимально возможное время я нигде не писал. Речь шла о гарантированном максимальном времени реакции.

                        Пример:

                        системный тик - 1 сек (специально утрирую),

                        по внешнему прерыванию запускается высокоприоритетный таск, который гарантированно отрабатывает за 0.35сек

                        Гарантированное максимальное время реакции - 0.35сек.

                        Другой пример (из жизни, под FreeRTOS на Cortex M4): системный тик - 1мсек,

                        по внешнему прерыванию запускается высокоприоритетный таск, который гарантированно отрабатывает за 0.35сек (делает преобразование Фурье).

                        Гарантированное максимальное время реакции - 0.35сек.

                        Вывод: Гарантированное максимальное время реакции никак (ну никак!) не зависит от периода системного таймера ("кванта времени ").


        1. semennikov
          08.06.2022 10:08

          Интересно, а как FORTRAN мог использоваться для реального времени? Там же нет никаких привязок ни к количеству циклов ни к прерываниям.

          В упомянутые времена не было многозадачности, и реальное время можно было получить только используя либо С либо ассемблер, да и то необходимо было привязываться к таймеру, учитывать прерывания и даже при использовании одного и того же вычислителя можно было получить разные результаты из-за разных кварцев


          1. ladle
            08.06.2022 14:27

            В упомянутые времена не было многозадачности, и реальное время можно было получить только используя либо С либо ассемблер

            Ну Вы даёте! Это утверждение из серии "'электрическую лампочку придумал Ильич" или "партия изобрела самолёты". Посмотрите на PDP8 (1965г.), IBM1800 (1964г.), PDP11(1970г.). Многозадачность имел даже флагман советской вычислительной техники БЭСМ6 (1967г.). Язык С в упомянутые времена только разрабатывался (1969-1973) и применения в народном хозяйстве ещё не получил.

            Компьютер HP2100 (1971г.) имел вполне функциональный диспетчер реального времени с приоритетами и вытесняющей многозадачностью, некоторыми средствами коммуникации между задачами, системными вызовами, доступными из FORTRANа. Задачи могли быть как резидентными в оперативной памяти, так и загружаемые с диска (и свопируемыми на диск). Размер и количество задач ограничивались в основном оперативной памятью (32К 16-битных слов). Так как точности системного таймера (10мсек) для решаемых задач хватало, циклы никто не считал. По временной привязке наиболее критичной задачей была та, которая посылала в самодельный синтезатор очередную ноту, Мелодии проигрывались без искажений. Кроме музыки система использовалась для непрерывного приёма данных физического эксперимента, их записи на магнитую ленту, on-line мониторинга аппаратуры и выборочной обработки физической информации, для простого UI c графическим дисплеем и кнопочной клавиатурой, а также дя решения шахматных задач. Всё это крутилось одновременно на одном 16-битном процессоре с циклом 980нсек.

            Производитель (Hewlett-Packard) поставлял диспетчер, компилятор с FORTRANа и загрузчик (линкер). Остальное (файловая система на диске, командный интерпретатор, текстовый редактор и др. утилиты) бало самописным (вестимо на FORTRANе) по мотивам многозадачной многопользовательской системы GEORGE4 для компютеров ICL1906A (1970г.).

            Извините за много букв, приятно вспомнить молодость.


  1. jaha33
    06.06.2022 21:23

    Зато потребление ресурсов этой системы намного меньше — 0,5 КБ оперативной памяти

    Почему то про все rtos пишут что ОЗУ практически не требуется. По факту, если надо крутить например 2 задачи и как то между ними взаимодействовать, то это сразу 5-10 кб ОЗУ. При этом, в случае с freertos при нехватке кучи будет вываливаться исключение без пояснений в чем проблема.

    Уже как несколько лет появились стильные модные молодежные rtos: Azure RTOS и Zephyr RTOS, первая от майкрософт, имеет кучу всяких сертификатов безопасности, но платная. Беспатно она доступна только для STM, как я понял они ее решили использовать на замену фриртоса. Вторая поддерживается сообществом linux, бесплатная, но из крупных вендоров ее поддержали Nordic и NXP. Вот про них куда интереснее что то почитать


    1. Mih-mih
      06.06.2022 22:22

      Почему то про все rtos пишут что ОЗУ практически не требуется. По факту, если надо крутить например 2 задачи и как то между ними взаимодействовать, то это сразу 5-10 кб ОЗУ

      Есть csmRTOS, с примерно такими же возможностями, как и фриртос. Которую запускали на 256 байтах ОЗУ. На atmega128 (4 кБ) вполне успешно крутится с десяток задач.


      1. predator86
        06.06.2022 22:55
        +1

        А нужна ли там вообще ОС, тем более вытесняющая?


        1. Mih-mih
          06.06.2022 23:05
          +1

          Ну а почему бы и нет? Конечно, можно обойтись и без неё, точно так же, как и без freertos на stm-ах, и всё написать на одном глобальном цикле. Но с осью всё равно немножко поприятнее организовывать межзадачную передачу данных, да и жестко заданная периодичность вызова процессов часто весьма удобна. Так что если небольшая ось без особых проблем влазит в кристалл, то почему бы и не да.


          1. predator86
            06.06.2022 23:16

            Согласен.


    1. Electrologic
      07.06.2022 14:57
      +1

      в случае с freertos при нехватке кучи будет вываливаться исключение без пояснений в чем проблема.

      Работать с freertos можно не пользуясь кучей, а создавать все статически. Например, для создания таски для этого есть ф-ия xTaskCreateStatic(). Для остальных сущностей - тоже см. ф-ии с суффиксом 'Static'.

      Уже как несколько лет появились стильные модные молодежные rtos: Azure RTOS и Zephyr RTOS

      Как это часто бывает, "стильные, модные молодежные" содержат больше шероховатостей и недоработок в силу того, что у сообщества было меньше времени набить шишки и отрепортить разработчикам.

      Насчет Zephyr - так вообще в плане потребления памяти уступает. Если у вас меньше 150кб ROM, то собрать получится только крайне ограниченную конфигурацию, без сетевых стеков и консолей и прочего.


      1. jaha33
        07.06.2022 16:05

        Да про static понятно, но насколько я знаю в релизах за последние пару лет желательно использовать динамическую память. Например uC OS умеет вменяемые ошибки выдавать в случае проблем.

        У  Azure RTOS вполне есть шансы занять заметную долю RTOS, ее вместо Freertos начали применять STM. Microchip и Renesas тоже начали применять Azure для новых контроллеров. Плюс у этой RTOS куча всяких сертификатов безопасности и надежности, т.е. эту RTOS вполне можно будет применять для ответственных областей


        1. DungeonLords
          08.06.2022 10:10

          "в релизах за последние пару лет желательно использовать динамическую память"

          Требуются пояснения.


  1. JackKatch
    07.06.2022 10:30

    Я бы предложил не использовать слово микроконтроллер для крупных мощных кристаллов. Как то это вводит в заблуждение. Автору спасибо за статью!