Альтернативный подход к проектированию ПО для Embedded

Данный топик я решил написать после ознакомления со статьей «Два подхода к проектированию ПО для embedded». При прочтении которой я наткнулся на фразу: «Если же система собирается стать большой, соединяющей в себе много разных действий и реакций, которые к тому же критичны ко времени – то альтернативы использования ОС реального времени нет». «Как это нет?», — подумал я. Конечно, если речь идет о больших высоконагруженных системах реального времени, где используются большие процессоры, то без ОС может не обойтись, а вот для более скромных микроконтроллерных решений вполне существует альтернативный вариант. Ведь задачки можно выполнять при помощи обычного switch-case и при этом обеспечивать необходимое время реакции.


Почему лично я не использую RTOS



Товарищ olekl рассказал про RTOS, не буду заострять на этом внимание. Отмечу пару пунктов, которые я лично для себя выделил — почему я не использую RTOS:
  • Операционка для своей работы требует ресурсы микроконтроллера: память и системное время их и так немного. Пустить бы их на задачки, но придется отдавать диспетчеру. Пожалуй, это самый основной для меня пункт.
  • Не простой для меня способ организации задач. Мьютексы, семафоры, приоритеты и т.п. — заблудиться можно.
  • Некоторые RTOS стоят денег. Причем не маленьких.
  • Есть некоторые сомнения по поводу поддержки RTOS контроллерами. Вдруг захочу перенести проект на новейший контроллер, а для него еще нет поддержки этой операционки.
  • Сомнение: а вдруг в ядре ошибка? Наверняка предлагаемые RTOS оттестированы на миллион раз, но кто его знает: вдруг что-нибудь вылетит в миллион первый раз.


Подход со switch-case



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

В устройстве присутствуют два датчика температуры. Время опроса первого датчика не критично: «опросили, да и ладно», пускай будет периодичность в 0.2 мс. По превышению заданного порога температуры будем зажигать светодиод. Показания второго датчика для нас напротив очень важны. Его нужно опрашивать как можно чаще и по превышению заданного порога выдавать «1» на пин, для того чтобы дискретным сигналом включить вентилятор охлаждения. При понижении температуры до другого порога выключаем вентилятор. Где-то каждые 100 мс значение со второго датчика необходимо записывать в ПЗУ.
Для реализации потребуется прерывание аппаратного таймера. Ведь только так мы сможем гарантировать выполнение задач в отведенное им время. В таком случае возможности для использования других прерываний резко сокращаются.
Работу с большей частью периферии можно сделать без прерываний, а очень важные коммуникационные прерывания (например: UART/SCI) обычно имеют более высокий приоритет, чем таймер и обычно служат для фиксирования принятых/отправленный байт, т.е. много времени не отнимут.
Подход, когда в таймере только отсчитывается время для задач, а сами задачи выполняются в фоне (или суперцикле while) без запрета прерываний не гарантирует нужной реакции выполнения.

Для начала сделаем драйвер датчика температуры. Основная его функция – это считывание значения температуры по SPI.

Структура:
typedef struct 
   {
	unsigned char SpiCh;   // Используемый модуль SPI (A, B, C)
	unsigned int SpiBrr;     // Частота SPI-модуля
	unsigned int Value;              // Значение с датчика
	void (*ChipSelect) (unsigned char level_);    // Callback Функция выбора микросхемы
		…				   	          // Что-нибудь еще
   }TSensor;

Функция опроса датчика температуры:

void SensorDriver(TSensor *p)
{
   p->ChipSelect(0);			// Выбрали микросхему
   p->Value = SpiRead(p->SpiCh, p->SpiBrr);	// Считали значение по SPI
   p->ChipSelect(1);				// Сняли выбор микросхемы
}

Наш драйвер готов. Чтобы его использовать нужна инициализация. Структуру можно проинициализировать целиком с помощью #define, а можно каждое поле в отдельности. Датчиков температуры у нас два. Создаем две структуры.
TSensor Sensor1;
TSensor Sensor2;

void Init(void)
{
   Sensor1.ChipSelect = &ChipSelectFunc1;		// Ссылка на функцию выбора микросхемы
   Sensor1.SpiCh = 0;					// Линия SPI
   Sensor1.SpiBrr = 1000;				// Частота SPI
   Sensor2.ChipSelect = &ChipSelectFunc2;
   Sensor2.SpiCh = 0;
   Sensor2.SpiBrr = 1000;
}


Основная функция драйвера – это чтение температуры. Что с этими данными делать будем решать вне драйвера.

Зажигаем светодиод:

void SensorLed(void)
{
   if (Sensor1.Value >= SENSOR_LED_LIMIT)
      LedPin = 1;
   else If (Sensor1.Value < SENSOR_LED_LIMIT)
      LedPin = 0;
}

Включаем/выключаем вентилятор дискретной ножкой:

void SensorCooler(void)
{
   if (Sensor2.Value >= SENSOR_LED_LIMIT)
      CoolerPin = 1;
   else if (Sensor1.Value < SENSOR_LED_LIMIT)
      CoolerPin = 0;
}

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

void SensorValueRecord()
{
   unsigned int Data = Sensor2.Value;			// Значение температуры с датчика
   unsigned int Address = 0;				// Адрес в памяти

   if (EepromReady())				        // Проверяем готовность ПЗУ
   {
	// Отправляем данные, адрес и указание, что данные нужно записать
      EepromFunction(Address, Data, WRITE);
   }
}

Данные мы отправили и когда драйвер памяти вступит в работу (а делает он это в 100 раз быстрее, чем функция SensorValueRecord), то он уже будет знать, что ему делать.
Наши функции готовы. Теперь их нужно правильно организовать.
Для этого заведем прерывание таймера с частотой 10кГц (100 мкс). Это будет наша максимальная гарантированная частота вызова задач. Пусть этого будет достаточно. Создаем функции планировщика задач, в которых будем определять, когда какую задачу запускать.

#define MAIN_HZ         10000
#define TASK0_FREQ   1000
#define TASK1_FREQ   50
#define TASK2_FREQ   10

// Основная функция диспетпчера
void AlternativeTaskManager(void)
{
   SensorDriver(&Sensor2);	// Важная задачка опроса второго датчика
   SensorCooler();			// Важная задачка включения вентилятора
   Task0_Execute();		// Запускаем задачи нулевого цикла
}

// Задачи 1кГц
void Task0_Execute(void)
{
   switch (TaskIndex0)
   {
      case 0:  EepromDriver(&Eeprom);	break;
      case 1:  Task1_Execute(); 	break;
      case 2:  Task2_Execute();		break;
   }

   // Зацикливаем задачки
   if (++TaskIndex0 >= MAIN_HZ / TASK0_FREQ)
   TaskIndex0 = 0;
}

// Задачи с частотой 50 Гц
void Task1_Execute(void)
{
   switch (TaskIndex1)
   {
      case 0: SensorDriver(&Sensor1);	break;
      case 1: SensorLed(); 		break;
   }

   if (++TaskIndex1 >= TASK0_FREQ / TASK1_FREQ)
      TaskIndex1 = 0;
}

// Задачи с частотой 10 Гц
void Task2_Execute(void)
{
   switch (TaskIndex2)
   {
   case 0: SensorValueRecord();		break;
   case 1:	 			break;
   }

   if (++TaskIndex2 >= TASK0_FREQ / TASK2_FREQ)
   TaskIndex2 = 0;
}


Теперь осталось запустить планировщик в прерывании таймера и готово.

interrupt void Timer1_Handler(void)
{
   AlternativeTaskManager();
}

Данная система выглядит как эдакий механизм с шестеренками: самая главная шестеренка непосредственно на валу двигателя и она крутит остальные шестеренкии.
Задачки выполняются «по кольцу». Частота их выполнения зависит от места вызова. Функция Task0_Execute будет выполнятся с частотой 10кГц, поскольку вызываем ее непосредственно в прерывании таймера (наша главная шестеренка). В ней происходит деление частоты и с помощью switch-case с TaskIndex0 определяется для какой задачи пришло время. Частота вызова задачек должна быть меньше, чем 10кГц.
Мы установили частоту задач для цикла Task0_Execute равную 1кГц, значит в нем может быть выполнено 10 задач с частотой в 1кГц:

#define MAIN_HZ	   10000
#define TASK0_FREQ  1000

if (++TaskIndex0 >= MAIN_HZ / TASK0_FREQ)


Структура switch-case системы


Аналогично для Task1_Execute и Task2_Execute. Вызываем их с частотой в 1кГц. В первом цикле задачи должны выполняться с частотой в 50Гц, а во втором — 10Гц. Получаем, что всего будет 20 и 100 задач соответственно.
После выполнения задач диспетчера программа возвращается в фон (суперцикл background).
Какие-нибудь не критичные по времени реакции, то их вполне можно поместить туда.

void main(void)
{
   Init();

   while (1)
   {
      DoSomething();
   }
}

К устройству добавляется ЦАП и вместе с зажиганием светодиода нужно генерировать сигнал 4-20? Не вопрос. Создаем драйвер ЦАП и запускаем его. В функцию SensorLed добавляем две строчки, которые будут указывать драйверу какое ему значение выдавать на выход и диспетчере вызываем функцию драйвера.

void SensorLed(void)
{
   if (Sensor1.Value >= SENSOR_LED_LIMIT)
   {
      LedPin = 1;
      Dac.Value = 20;					// Значение на выходе ЦАП
   }
   else If (Sensor1.Value < SENSOR_LED_LIMIT)
   {
      LedPin = 0;
      Dac.Value = 4;                                     // Значение на выходе ЦАП
   }
}

// Задачи с частотой 50 Гц
void Task1_Execute(void)
{
   switch (TaskIndex1)
   {
      case 0: SensorDriver(&Sensor1);	break;
      case 1: SensorLed(); 		break;
      case 2: DacDriver(&Dac) 		break;             // Функция драйвера ЦАП
   }

   if (++TaskIndex1 >= TASK0_FREQ / TASK1_FREQ)
      TaskIndex1 = 0;
}



Добавили двухстрочный индикатор? Тоже не проблема. Запускаем его драйвер на частоте 1кГц, т.к. символы нужно передавать быстро, а в других более медленных функциях указываем драйверу какие именно символы и строки нужно будет отображать.

Оценка загрузки



Для того, чтобы оценить загрузку необходимо включить второй аппаратный таймер, который работает с такой же частотой как и первый таймер. По хорошему бы сделать так, чтобы период таймеров был не впритык.
Перед запуском менеджера задач сбросили счетчик таймера, а после работы считали его значение. Оценка загрузки проводится по периоду таймера. Например, период первого таймера равен 100. Т.е., счетчик досчитает до 100 и возникнет прерывание. Если счетчик второго таймера (CpuTime) насчитал меньше 100 значит — хорошо. Если впритык или больше – плохо: время реакции задач поплывет.
unsigned int CpuTime = 0;
unsigned int CpuTimeMax = 0;
interrupt void Timer1_Handler(void)
{
	Timer2.TimerValue = 0;			// Сбросили таймер

	AlternativeTaskManager();              // Наш switch-case диспетчер задач

	CpuTime = Timer2.Value;		// Считали значение таймера = загрузка
	if (CpuTime > CpuTimeMax )		// Определяем пиковую загрузку
		CpuTime = CpuTimeMax;
	
}


Что в результате



Какие лично я получил преимущества по сравнению с RTOS:
— Расход ресурсов при работе диспетчера мизерный.
— Организация задач хоть и не простая, но она сводится к определению: где какую функцию запустить. Нет никаких семафоров, мьютексов и т.п. Не нужно читать многостраничные мануалы к RTOS. Не сказать, чтобы преимущество, но я так привык.
— Код можно легко перенести с одного контроллера на другой. Главное не забыть про типы, которые используются.

Недостаток:
— Усложнение ПО. Если в случае RTOS можно написать функцию и тут же ее запустить, если хватит ресурсов, то в случае со switch-case придется более плотно подходить к оптимизации. Придется думать как повлияет то или иное действие на производительность всей системы. Лишний набор действий может привести к нарушению «движения шестеренок». Чем больше система, тем сложнее ПО. Если для операционки функция может быть выполнена в один заход, то здесь возможно придется разбивать по шагам более детально (конечный автомат). Например, драйвер индикатора не сразу пересылает все символы, а по строчкам:
1) выставил строб, переслал верхнюю строку, вышел;
2) переслал нижнюю строку, убрал строб, чтобы символы отобразились, вышел.
Если наработок мало, то такой подход повлияет на скорость разработки.
Я пользуюсь таким подходом не первый год. Есть много наработок, библиотек. А вот новичкам будет сложновато.

В данном материале я попробовал раскрыть альтернативный подход к проектированию ПО для встраиваемых систем без использования RTOS. Надеюсь кто-нибудь почерпнул что-нибудь полезное.

Upd. Я не отказываюсь от идеи использования RTOS. Я не говорю, что RTOS — это плохо и сразу надо всем ее бросать. Я описал лично свое мироощущение по этому поводу и его никому не навязываю. Данная статья возникла благодаря топику про проектирование ПО для Embedded, где автор указал, что альтернативы нет. Я наоборот показал, что альтернатива все же существует, причем существует в коммерческих проектах.
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 47

    0
    А зачем тут усложнение задачи с псевдо-процессами? Пускай выполняются себе последовательно и все.

    Кстати, если писать каждую секунду в EEPROM, то она очень быстро помрет :)
      +1
      Через 100 000 секунд. Не так уж и быстро.
        +2
        Учитывая, что многие embedded железки рассчитаны на многолетнюю работу без выключения, дохлая память за 1 сутки не катит.
          +2
          100 тыс записей на ячейку. А значит, от того, сколько у нас емкость EEPROM и какой длины данные нужно писать каждую секунду, будет зависеть, на длину суток какой именно планеты ее хватит.
          Иными словами, если нам нужно ежесекундно сохранять туда несколько байт какого-либо состояния, а памяти этой у нас — десятки килобайт, то применив любой простенький алгоритм циклической записи, мы поимеем время жизни EEPROM те самые годы. Да еще и лог в несколько сотен последних записей с девайса можно снять всегда.
            0
            Скажем, у EEPROM серии 25LC1024 от Microchip страничная организация, при записи даже одного байта (ячейки) происходит перезапись целой страницы (256 байт). В её даташите ресурс тоже указан для страницы, а не для ячейки. В такой ситуации leveling-алгоритм мало чем поможет.
              0
              Ну… на все болезни лекарств может и не хватить.
              Значит ставим кондер на запас питалова и страницу кешируем, когда в оперативке накопятся эти 256 байт, пишем страницу в EEPROM. Либо когда внешнее питалово пропало, тоже сбрасываем кеш «на диск».
              Конечно, можно возразить, что нет у нас нигде лишних 256 байт для кеша, и девайс мы не с нуля разрабатываем, а исключительно старье перешиваем, в схему сильно не вмешаешься и т.п.
              Но в частных случаях вешаются частные костыли.
              В большинстве же случаев, как правило, девайс мы делаем сами. Ну не берите эту микрочиповскую память, возьмите другую.
              В случае допиливания готового девайса повесьте сверху свою платку со своей EEPROM. Вплоть до дополнительного контроллера на этой платке, эмулирующего наружу(для остальной схемы) работу старой EEPROM.
              Или между процом и EEPROM(не меняя ее) повесить платку с Тинькой и вышеописанным кондером-бесперебойником. При невысоких частотах Тиньке должно хватить мозгов работать как EEPROM-Proxy и держать в себе этот гребаный кеш в 256 байт.
        –1
        Если задачек 5, то пускай выполняются последовательно. А если 50, то попробуй уследи за ними потом.
        Драйвер EEPROM не пишет каждую секунду. Он ожидает. Пришел запрос — пишем. Не пришел — ждем.
          +1
          Не правильно выразился :) В конкретном примере на псевдокоде он может и пишет, да и ладно. Целью статьи не служил рассказ про особенности периферии (можно и FRAM воткнуть) или про особенности конкретного контроллера. Я рассказывал про подход разработки ПО.
        +5
        Это не альтернативный подход, а классический.

        Тут проблема в более сложном добавлении задач которые работают фоном. Скажем пнул посылку на i2c через DMA и надо будет каким то образом ловить событие окончания процесса и втыкаться в нужное место. На RTOS можно просто запрограммировать событие и ОС, по завершении задачи, сама даст управление в нужное место.
          +1
          Альтернативным я его назвал именно после прочтения хорошего материала «Два подхода к проектированию ПО для embedded».
          Само собой сложность ПО возрастает. Тут либо экономия ресурсов без операционки, но с затратами времени на разработку, либо экономия времени разработки ценой ресурсов контроллера.
          +6
          Операционка для своей работы требует ресурсы микроконтроллера: память и системное время их и так немного. Пустить бы их на задачки, но придется отдавать диспетчеру. Пожалуй, это самый основной для меня пункт.

          Для современных микроконтроллеров, как например stm, такого нет.

          Не простой для меня способ организации задач. Мьютексы, семафоры, приоритеты и т.п. — заблудиться можно.

          Наоборот), одна из фишек RTOS в том, что её использование придаёт программе логичную структуру, и упрощает написание), просто нужно изучить все эти «Мьютексы, семафоры, приоритеты», это не сложно.

          Некоторые RTOS стоят денег. Причем не маленьких.

          Для того FreeRTOS и придумали, она получше многих платных будет.

          Есть некоторые сомнения по поводу поддержки RTOS контроллерами. Вдруг захочу перенести проект на новейший контроллер, а для него еще нет поддержки этой операционки.

          У той же FreeRTOS например всегда свежий порт, только выпустили Cortex M4, а порт уже через две недели сделали)

          Сомнение: а вдруг в ядре ошибка? Наверняка предлагаемые RTOS оттестированы на миллион раз, но кто его знает: вдруг что-нибудь вылетит в миллион первый раз.

          Ну, а вдруг на вас завтра метеорит упадёт), нужно доверять коду, если что-то не работает то скорее это вы виноваты, heap мало выделили или ещё чего.

          В общем пока никто ещё не написал толком почему не стоит применять RTOS. Необоснованные страхи не в счёт, преимущества ОС затмевают любые мелкие недостатки.
            0
            >Для современных микроконтроллеров, как например stm
            Почему? Там не программный диспетчер?
              0
              ) у stm32 столько памяти относительно 8 битников, что хоть ж… ой жуй)), кроме того embedded ОС довольно легки и не требовательны, некоторые даже ucLinux ставят и ещё дофига остаётся
                0
                Т.е. ресурсы все же под него уходят… А сколько вообще такой микроконтроллер с целой ОС на борту потребляет? Я вот чисто программер и микроконтроллеры для меня тема далекая, но возможность писать софт в такую железку как под линухом выглядит очень заманчиво в контексте домашней автоматизации. Но для мелких задач такое использование выглядит как расточительство )
                  0
                  Ресурсы всегда на что-то уходят :)
                  Потребление ресурсов зависит от функционала который был активирован при компиляции FreeRTOS.
                  В типичном случае она отъедает совсем немного — несколько килобайт кода. Потребление оперативки зависит от количества задач, размера стека и тп. Сама FreeRTOS оперативки ест совсем мало, сравнимо с написанием собственного цикла.
                    0
                    А сколько в плане электричества? Их можно одновременно питать от тех же аккумуляторов формата АА?
                      0
                      ) что-то вопросы у вас странные, у микроконтроллеров есть режимы потребления (сна, простоя, работы) и кстати использование ОС позволяет разительно снизить энергопотребление, т.к. есть задача простоя, когда нет обычных задач, если вы это имели в виду
                        0
                        Отчего же странные, просто хочу знать, возможно ли делать автономные устройства. У моего научного руководителя было одно устройство регистрации. Опытный образец. Его в тепловоз устанавливали. Его некоторые машинисты отключали от питалова. А где-то и подглючивал, качество электроэнергии то еще там. Стало интересно, реально ли делать автономные черные коробочки и сколько в автономном режиме они могут протянуть.

                        P.S. Регистрировались там простейшие параметры: координаты по GPS модулю, время, скорость и положения группы сухих контактов по которым рассчитывалось на какой позиции контроллера идет тепловоз. Возможно еще какой обвес был, но я не про не в курсе.
                          +1
                          А ну понятно. Ну тут многое зависит от того, что в приборе у вас включено, чаще всего потребление в районе 20-40 мА в мобильном устройстве, нужно смотреть будет ли оно постоянно работать и что-нибудь обрабатывать или время от времени.
                          Вот для примера: stm32 в активном режиме потребляет ~20мА, какой-нибудь блютуз 40мА, вайфай 80-100мА, gps 10mA

                          ёмкость 1 батарейки АА = 1800-2800мА*ч в зависимости от типа
                          далее считайте: так
                          T = S / I, где
                          S — емкость батареи
                          I — потребляемый ток устройством
                          T — время работы устройства
                            0
                            Достачно было бы сделать автономное питание (безотносительно того, сколько оно проработает) и детекцию отключения питания внешнего с занесением этого в лог, а потом за это бить машинистов по рукам.
                0
                Для современных микроконтроллеров, как например stm, такого нет.

                Почему нет? Там RTOS вдруг вшита в контроллер и работает на аппаратном уровне? Или все проблемы можно решить поставив более мощное железо?
                Наоборот), одна из фишек RTOS в том, что её использование придаёт программе логичную структуру, и упрощает написание), просто нужно изучить все эти «Мьютексы, семафоры, приоритеты», это не сложно.

                Я написал, что для меня это не просто, а ты вдруг счел, что наоборот? :)
                В неумелых руках такие фишки могут превратиться в неуправляемого нелогичного монстра, особенно если будет over9000 семафоров и т.п.
                Для того FreeRTOS и придумали, она получше многих платных будет.

                Поэтому я написал не «все», а «некоторые :)
                В общем пока никто ещё не написал толком почему не стоит применять RTOS.

                Статья была не про это.
                  0
                  Почему нет? Там RTOS вдруг вшита в контроллер и работает на аппаратном уровне? Или все проблемы можно решить поставив более мощное железо?
                  RTOS на аппаратном уровне Оо? Вы где такое видели?
                  У современных МК столько ресурсов, что потерю нескольких килобайт флэша они не заметят.
                    0
                    У современных МК столько ресурсов, что потерю нескольких килобайт флэша они не заметят.
                    Я не берусь вот так судить. Все зависит от конкретного контроллера и от конкретных задач. Там не только флеш расходуется, но у процессорное время.
                +3
                Полностью поддерживаю.
                Не использовать RTOS имеет смысл лишь в случае написания такой мелочи как зажигание светодиодиков да чтение датчиков.
                Этот велосипед быстро превращается в монстра как только задачка становится посерьёзнее.
                Было бы интересно услышать тут для какого размера кода автор использует свой подход.
                  +1
                  Было бы интересно услышать тут для какого размера кода автор использует свой подход.

                  Один из случаев.
                  Периферия: RTC, 2xEEPROM, двухстрочных дисплей, ЦАП, датчик температуры, Bluetooth-модуль.
                  Датчики напряжения и тока на три фазы (6 датчиков) на АЦП, фильтруем сигнал фильтром третьего порядка, считаем RMS у каждого, расчет угла нагрузки на каждую фазу. Контроль этих значений (порядка 20 защит). Запись значений параметров при их изменении (пишем где-то 250 параметров), а так же отдельно запись списка параметров при срабатывании одной из защит (на кажадую сработавшую защиту около 160 байт в память).
                  Прием сигнала с ИК ПДУ: есть древовидное меню, где можно глянуть примерно 250 параметров.
                  Коммуникация: Modbus RTU и Bluetooth.
                  Там еще много чего есть, лень расписывать :)
                    +3
                    Все это крутится на MCU 100МГц.
                  0
                  Приходилось писать напрямую без rtos прошивки, работающие с кучей периферии, ethernet, usb, всякими dac/adc, видеоконтроллерами, радиотрансиверами и т.п. И ничего, все просто и понятно. На слабеньких контроллерах крутились такие вещи, которые и не каждый комп осилит (по причине отсутствия жесткого реального времени конечно).
                  Куда важнее наличие подробной документации с примерами, как работать с каждой периферийной железкой. Если ее нет, или она кривая — все, беда. А концепция «инициализация — обработчики прерываний + цикл обработки событий» сама по себе очень простая, со временем вырабатывается некая стандартная кодовая база, все что остается — писать логику и модули работы с периферией.
                    +1
                    1. Есть очень много RTOS. Есть огромные монстры с кучей функций, а есть ассемблерные легковесы, которые потребляют минимум ресурсов, при этом контекст переключают идеально и упрощают жизнь программисту.
                    2. Могу предложить ещё один «альтернативный» вариант — протопотоки (protothreads).
                    3. Если уже отказываться от всех земных благ во имя сверхнизкого использования ресурсов — пишите на asm'е.
                    А то, знаете, встречаются деятели — программку на C написать могут, а про менеджмент памяти забывают — таким можно разрешать писать только на Java с использование Garbage Collector'а.
                      +1
                      При таком подходе придется все время следить за тем, чтобы каждая задача вдруг не заняла времени больше, чем ей положено. А если ей таки потребуется времени больше — придется вводить машину состояний, чтобы разбить одно длинное действие на последовательность более коротких шагов.

                      И последовательность выполнения всегда задана, что тоже может очень быстро начать мешать.

                      Ну и ошибка в любой из задач «уложит» сразу всю систему.

                      Для простой системы — подойдет, но чем сложнее система — тем проблемнее ее будет делать в терминах квантов времени, машин состояний и последовательности вызовов.
                        0
                        Вот именно. А автор этот момент деликатно обходит стороной :)
                        У нас написана БД которая крутится на чипе линейки stm32f2xx. Да там к тому же ещё и коммуникационная часть и аппликативная.
                        Алгоритмически движок БД довольно тяжеловесен (сотни тыщ строк С кода), хотя его и заоптимизировали насколько это возможно. Поэтому время выполнения запроса отнюдь не короткое по сравнению со считыванием датчика. А уж пространство состояний, кхм… будет весьма обширно.
                        И ничего, всё работает под RTOS. Повеситься можно писать какие-то циклы и мегасвичи чтобы это всё крутилось.
                        Вообще если писать для ардуин, то циклы вполне ещё будут работать. Но такое приложение и за пару вечеров можно набросать.
                          0
                          Извините, я далек от embedded программирования, но какая может быть задача, чтобы базы данных с сотнями тыщ строк кода крутить на таком железе как stm32f2xx? Почему пришлось писать свое, а не использовать уже существующие и запускать на обычном серверном железе?
                            +2
                            1) Что за задача?
                            Наш заказчик (французское правительство) рассматривает возможность внедрения для граждан этакого персонального и весьма компактного девайса для хранения разнообразных строго конфиденциальных данных. В уже запущенной нами версии в версальском департаменте такой Secure Portable Token (SPT) позволяет хранить медицинскую карту человека и координировать работу докторов и социальных работников.
                            То есть вместо кучи бумажек у каждого человека (ну на данный момент это не у всех ещё, но это лишь вопрос времени) есть типа такой флешки, которая содержит специальным образом защищённый чип. И вся конфиденциальная информация хранится у него в кармане, а не у непонятного дяди где-то на сервере.
                            Зачем хранить данные в кармане? Мотивация такая — если хранить всё в одной центральной БД, то при её взломе страдают все пользователи. А если ломать персональную БД каждого, то это будет гораздо более трудозатратно.
                            В Нидерландах несколько лет назад пытались внедрить централизированную БД — быстро поняли что это не то.

                            2) Аппаратная часть — Это самый настоящий компутер, а не просто флешка которую можно купить на любом углу. Его подключают к любому компу-терминалу, важно лишь наличие веб-броузера и USB порта. Питание SPT берёт от USB. Сама коробочка этого SPT — всего лишь коробочка, пластик, а чип сделан в форме сим-карты, только чуть побольше в размерах.
                            У него достаточно хорошая защита — невозможен сниффинг электромагнитных сигналов например. Кроме того, на нём много специальных проводящих нитей и при обрыве хотя бы одной из них (при умышленном вторжении) чип делает харакири и приватный ключ, которым шифруется содержимое NAND, теряется.
                            В версии, уже запущенной в пилотную эксплуатацию на борту имеется 20Mhz CPU + CryptoCPU + 64Kb SRAM + 1Mb NOR + 256Mb NAND + USB.
                            Маленький размер RAM связан с криптозащитой — бОльший объём (и следовательно бОльшую площадь на кристалле) труднее защитить.

                            3) Софт
                            На всём этом крутится веб-сервер с веб-приложением а-ля CGI, а данные хранятся в реляционной БД.
                            Многие предыдущие попытки (в Нидерландах, в Америке) провалились потому что для упрощения приложения данные пытались хранить просто в файлах. Для управления сложными матрицами прав доступа и контроля структуры и целостности хранимых данных это быстро превращалось в монстра, который было очень трудно тестировать и поддерживать. К тому же сильно страдала скорость работы — все данные в NAND шифруются.
                            Поэтому без полноценной реляционной БД обойтись трудно. В любом случае придётся реализовать всё то, что уже придумано в БД.
                            Соответственно всё что вытекает наружу на терминал проходит обработку. Приложение отображает лишь то, что необходимо. К тому же владелец SPT конечно же должен авторизовать доступ к своему токену.

                            4) Почему писать своё?
                            Потому что такое пока никто не сделал. Вся теория БД заточена на хранение на HDD и на наличие мегабайтов (даже гигабайтов) оперативки. А мы движок изначально заточили микроскопическое использование RAM (умещаемся в 8 Кб!) на хранение на NAND флэше, причём БД у нас сама оптимальным образом раскидывает данные чтобы был равномерный wear leveling. Применяется индексирование криптованых данных и различные техники minimal exposure для экспонирования минимума конфиденциальных данных.
                            По индексам нами опубликовано несколько научных докладов, мы выступали на разных международных научных конференциях (VLDB, SIGMOD, BDA) с докладами, есть десятки публикаций в различных научных журналах.
                            Всё это конечно же является результатом многолетней работы, было много прототипов на которых мы отлаживали различные подходы индексирования например.
                            БД на NAND флэше с индексами и криптозащитой да чтобы всё это крутилось на embedded платформе — мы не знаем никого другого кто это делает.

                            5) STM32F2xx
                            Платформа, на которой разрабатывалась текущая версия сейчас слегка устарела и мы портировали всё на чип посвежее. Я лишь переписал драйвера периферии, а сама БД завелась с полпинка. На STM32F217 чуть попросторнее, можно достать из заначки и прикрутить несколько алгоритмов потяжелее :)
                            Конечное изделие будет содержать физическую криптозащиту, но нам для отладки вполне подходят обычные chip package и дев борды.
                              +2
                              Я отдаю себе отчёт, что то что я описал — хардкор и мало кто делает вещи такой сложности.
                              Но если вы уж выбираете кристалл с более чем полумегабайта NOR под код программы, то и сложность софта наверняка будет соответствующая.
                              А если просто светодиодиками моргать — тогда и микроконтроллер уровня STM32 не нужен, можно ардуинкой обойтись.
                              Для каждой ситуации есть своё оптимальное решение.
                                –1
                                любой контроллер можно нагрузить по самые уши без хардкора хотябы GUI :)
                          0
                          точно, поэтому вытесняющие RTOS рулят)
                            0
                            Эта моя первая статья и скорее всего я непонятно написал, раз возникают такие комментарии.
                            Об усложнении ПО при использовании такого подхода я написал в самом конце статьи в пункте «Недостаток». Этот момент я не обходил стороной, ни просто так, ни деликатно :)
                            И последовательность выполнения всегда задана, что тоже может очень быстро начать мешать.

                            Это уже зависит от автора кода. Если последовательность в автомате для какой-то одной задачи важна, то у задач разной функциональности последовательность не строгая.
                            Для простой системы — подойдет, но чем сложнее система — тем проблемнее ее будет делать в терминах квантов времени, машин состояний и последовательности вызовов.

                            Я понимаю, что не всем по душе усложнение ПО. Но при достаточном опыте работы с таким подходом проблем не замечаешь.
                            +1
                            Вы, похоже, совсем недавно программировали на delphi? :)
                              0
                              Не имел дело с delphi вообще.
                                +1
                                Меня смутил TSensor, простите :)
                                  +1
                                  Это у подглядел у старших товарищей. Так и повелось :)
                              +4
                              Возможно, через пару лет, когда вы поработаете над более серьезными проектами, вы напишете совсем другую статью, о том, почему вы используйте ту или иную RTOS, и, может быть, она даже будет платной.
                              1. На самом деле, ресурсоемкость ОСРВ это основная причина почему приходится работать без нее, и, если ее нет приходится изобретать что-то подобное, и ваш код тому пример.
                              2. Мюьтексы, семафоры… вам еще много интересного предстоит узнать ;) или изобрести, они непременно понадобится и при вашем подходе.
                              3. Ваша работа тоже стоит денег, и контроллеры их стоят и среды разработки и отладчики. А операционные системы есть бесплатные.
                              4. Если поддержки операционки нет — вы вряд ли будете переезжать на этот процессор, в ином случае, вы можете написать порт, в любом случае, ваш код без операционки будет сложно перенести на другой процессор.
                              5. А вдруг в вашем коде ошибка? Везде есть ошибки, это не повод не пользоваться программами.
                                +3
                                Абсолютно согласен. Почти все «недостатки», которые автор приписывает RTOS — это чистое авторское ИМХО, мало имеющее отношение к действительности.

                                1. Принцип разделения процессорного времени по задачам, который Вы описали, по эффективности не лучше алгоритма работы шедулера FreeRTOS, который написан на ассемблере. А возможностей, гибкости и удобства использования на порядок меньше.

                                2. Мютексы и семафоры — на самом деле все довольно просто и логично, если немножко почитать документацию по FreeRTOS.

                                3. FreeRTOS бесплатна.

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

                                5. Ошибки всегда были, есть и будут быть. Все рано где — в Вашей программе или в уже готовом коде RTOS. Сейчас уже невозможно эффективно писать сложные программы, не используя готовый код (файловые системы, USB, сжатие данных, шифрование и проч.), так почему же нужно бояться FreeRTOS, которая предоставляет разработчику большие возможности? В самописном коде гораздо больше шансов накосячить, чем встретите с ошибку в многократно протестированном коде FreeRTOS.
                                –1
                                Я всего три дня на хабре, а у меня уже появился анонимный недоброжелатель. Почти все мои посты заминусили :) Хотя может тут так принято.
                                  0
                                  Не унывайте!
                                  Вопрос поднят был интересный, дискуссия получилась. Кто-то что-то новое для себя почерпнул. Все мы разные и опыт у нас разный. Кто-то прав в споре, а кто-то нет.
                                  «RTOS or not RTOS?» — это решается в зависимости от конкретной ситуации. Когда пишешь для embedded, то шаблонные решения нужно применять с большой осторожностью. Поэтому всякого рода обобщения — «нужно всегда делать вот так, потому что это круто» — они всегда будут неверны.
                                  +1
                                  Отлично! Думаю, конечно, вы лукавите, что всё так просто. И имея дело с событиями, ожиданием той же передачи, надо будет как-то хитро планировать время, чтобы не пропускать фреймы. Но в целом очень верный и разумный подход, который показывает, что планировщик написать не столь уж сложно и невозможно.

                                  P.S. постоянная запись в EEPROM — это выглядит жутко. Зачем вы это делаете? Обычно регистров процессора, RAM хватает. Если надо писать большие массивы — FLASH, что, впрочем так же не вписывается в быструю работу и в один фрейм. При включении — выключении лишь записывать следует данные. В остальном памяти должно хватать ;)
                                    +1
                                    Я так не делаю :) Привел это в качестве примера на псевдокоде.
                                    0
                                    Немного поддержу автора:
                                    мы также использовали такой подход (каскадированные автоматы), но совсем не потому, что не знали преимуществ RTOS, или боялись мутексов и потоков.
                                    Причиной явилось то, что нам достаточно легко удалось доказать временные характеристики реакции системы.
                                    Позже плюсом оказалось то, что при анализе остановов по логам, знание циклограммы позволяло достаточно точно восстановить состояние в эти моменты и сделать выводы (например, однажды, наш анализ помог нам перейти из состояния виновные в состояние свидетели).
                                    Минусы этого подхода также очевидны: повышенная сложность разработки, которая требует определённого склада ума. Также плюсы и ограничения этого подхода обычно новому разработчику сразу не очевидны и требуется определённая работа при обучении.

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                    Самое читаемое