Два подхода к проектированию ПО для embedded

    Хочу немного рассказать о двух подходах проектирования ПО в embedded. Два подхода эти – c использованием суперцикла или же с использованием RTOS (Real-Time Operation System, операционная система реального времени).

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

    Надеюсь, будет интересно всем тем, кто хочет заглянуть в мир разработки для встраиваемых систем. Для тех, кто в embedded уже собаку съел, скорее всего, не будет ничего нового.

    Совсем немного теории (для тех, кто делает самые первые шаги).

    Есть у нас микроконтроллер, представляющий из себя собственно процессор, немного памяти и различную периферию, например: аналого-цифровые преобразователи, таймеры, Ethernet, USB, SPI – все это сильно зависит от контроллера и решаемых задач.

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

    А к выходу контроллера, называемому GPIO (General Purpose Input-Output) можно, к примеру, подключить светодиод (или же что-нибудь более мощное вроде моторчика, но уже через усилитель).

    Через SPI, RS232, USB и т.п. контроллер может связываться с внешним миром более сложным способом – получая и отсылая сообщения по заранее заданному протоколу.

    В 90% случаев ПО пишется на С, иногда может использоваться С++ или ассемблер. Хотя все чаще появляются возможности писать на чем-нибудь более высокоуровневом, если это не касается непосредственной работы с периферией и не требуется максимально возможное быстродействие.

    Чтобы лучше представить, с чем приходится иметь дело, вот пара примеров окружений, с которыми приходится работать: размер FLASH контроллера (аналог жесткого диска) – 16-256 килобайт, размер RAM – 64-256 килобайт! И в таком окружении реально запустить не только приложение, а еще и операционную систему реального времени с полноценной поддержкой многозадачности!

    Примеры ниже – на псевдокоде, местами очень похожем на С. Без подробностей реализации там, где это несущественно для понимания.

    Итак, подход «суперцикл».

    Программа в этом подходе выглядит проще простого:

    int main()
    {
      while(1)
      {
        doSomething();
        doSomethingElse();
        doSomethingMore();
      }
    }
    


    Бесконечный цикл, в котором контроллер последовательно делает все, что он должен делать.

    Самое интересное, конечно же, во встраиваемых системах – это работа с периферией (теми самыми АЦП, SPI, GPIO и т.д.). С внешней периферией контроллер может работать двумя способами: опрашивая или используя прерывания. В первом случае, если мы хотим, например, прочитать символ из RS232 консоли, то мы будем периодически проверять, нет ли там символа, до тех пор, пока его не получим. Во втором же случае мы настраиваем RS232 контроллер так, чтобы он генерировал прерывание в тот момент, когда появится новый символ.

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

    int main()
    {
      init_adc();
      init_gpio_as_out();
      while (1)
      {
        int temperature = readTemperature();
        if (temperature > TEMPERATURE_LIMIT)
        {
          turnLedOn();
        }
        else
        {
          turnLedOff();
        }
    }
    
    

    Пока все должно быть просто и понятно. (Функции чтения температуры и манипуляций со светодиодом приводить не буду – это не цель данной статьи).

    Но что, если нам нужно делать что-то с заданной периодичностью? В примере выше температура будет проверяться с максимально возможной частотой. А если нам, например, нужно мигать светодиодом раз в секунду? Или опрашивать датчик строго с интервалом в 10 миллисекунд?

    Тогда на помощь приходят таймеры (которые есть практически у любого микроконтроллера). Работают они так, что генерируют прерывание с заданной частотой. Мигание светодиода тогда будет выглядеть как-то так:

    volatile int interrupt_happened = 0;
    interrupt void timer_int_handler()
    {
      interrupt_happened = 1;
      clear_interrupt_condition();
    }
    
    int main()
    {
      init_timer(1_SECOND_INTERVAL, timer_int_handler);
      while (1)
      {
        if (interrupt_happened)
        {
          ledToggle();
          interrupt_happened = 0;
        }
      }
    }
    


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

    Эта самая глобальная переменная обязательно должна быть объявлена с идентификатором volatile – иначе оптимизатор может банально «выбросить» неиспользуемый с его точки зрения код.

    А если нужно будет мигать двумя светодиодами, так чтобы один мигал раз в секунду, а второй – три раза? Можно, конечно, использовать два таймера, но с таким подходом таймеров нам надолго не хватит. Вместо этого сделаем так, чтобы таймер работал с гораздо более высокой частотой, а в программе будем использовать делитель.

    volatile uint millisecond_counter = 0;
    interrupt void timer_int_handler()
    {
      ++millisecond_counter;
      clear_interrupt_condition();
    }
    
    int main()
    {
      init_timer(1_MILLISECOND_INTERVAL, timer_int_handler);
      while (1)
      {
        uint timestart1 = millisecond_counter;
        uint timestart2 = millisecond_counter;
        if (millisecond_counter – timestart1 > 1000) // 1 second interval
        {
          led1Toggle();
          timestart1 = millisecond_counter;
        }
    
        if (millisecond_counter – timestart2 > 333) // 1/3 second interval
        {
          led2Toggle();
          timestart2 = millisecond_counter;
        }
      }
    }
    
    

    Заметьте, нам не нужно следить за переполнением счетчика миллисекунд, так как применяется беззнаковый тип.

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

    И вот здесь возникнет вопрос – как реализовать такую банальную вещь, как вывод строки в консоль? Очевидное решение вроде

    void sendString(char * str)
    {
      foreach (ch in str)
      {
        put_ch(ch);
      }
    }
    


    будет в данном случае недопустимым. Строку-то оно выведет, но при этом необратимо нарушит требование опрашивать датчик со строго заданной частотой. Мы же все это делаем в одном большом цикле, где все действия выполняются последовательно, помните? А консоль – устройство медленное, и вывод строки может занять гораздо больше времени, чем требуемый интервал между последовательными опросами датчика. Пример ниже — то, как делать не надо!

    int main
    {
      while (1)
      {
        …
        if (something)
        {
          send_string("something_happened");
        }
        …
        if (10_millisecond_timeout())
        {
          value = readADC();
        }
      }
    }
    


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

    А если вдруг где-то в этом цикле закрадется ошибка – то «ляжет» вся система. В том числе и реакция на перегрузку (чего допускать очень не хотелось бы, не так ли?).

    Хотя с первой проблемой теоретически можно еще что-то сделать. Например, заменить простейшую, но долгую функцию печати строки на что-то вроде:

    int position = 0;
    int send_string(char * str)
    {
      if (position < strlen(str)
      {
        put_ch(str[position];
        ++position;
        return 1;
      }
      else
      {
        return 0;
      }
    }
    


    А простой вызов этой функции на что-то вроде:

    int main
    {
      while (1)
      {
        …
        if (something)
        {
          do_print = 1;
          position = 0;
        }
        if (do_print)
        {
          do_print = send_string("something_happened");
        }
        …
        if (10_millisecond_timeout())
        {
          value = readADC();
        }
      }
    }
    


    Мы в результате сократили время прохода одного цикла со времени, необходимого для печати целой строки, до времени, необходимого для печати одного символа. Но для этого нам пришлось вместо примитивной и всем понятной с первого взгляда функции вывода строки в консоль добавить в код две машины состояний – одну для печати (чтоб запоминать позицию), а вторую – собственно для печати, чтобы помнить, что мы сейчас печатаем строку на протяжении нескольких следующих циклов. Да здравствуют глобальные переменные, «грязные» функции, хранящие состояния, и тому подобные замечательные штуки, которые запросто и очень быстро способны превратить код в несопровождаемое спагетти.

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

    Безусловно, это все реально. Но вот тому, кому придется это все сопровождать спустя хотя бы год после написания, я не позавидую.

    Еще одна проблема дизайна «общий цикл» — сложность измерения загруженности системы. Предположим, у вас есть код:

    interrupt void external_interrupt_handler()
    {
      interrupt_happened = 1;
      clear_interrupt_condition();
    }
    
    int main()
    {
      while (1)
       {
          if (interrupt_happened)
          {
            doSomething();
            interrupt_happened = 0;
          }
       }
    }
    


    Система как-то реагирует на прерывание, приходящее извне. И вопрос – сколько таких прерываний в секунду система сможет обработать? Насколько будет занят процессор при обработке 100 событий в секунду?

    Вам будет очень сложно измерить, сколько времени было потрачено на обработку событий, а сколько – на опрос переменной «А не произошло ли прерывание?». Ведь все выполняется в одном цикле!

    И вот здесь на помощь приходит второй подход.

    Применение операционной системы реального времени.

    Проще всего ее применение проиллюстрировать на том же примере: одновременный опрос датчика с заданной частотой и вывод на консоль длинной отладочной строки.

    void SensorPollingTask()
    {
       while (1)
       {
         value = SensorRead();
         if (value > LIMIT)
         {
           doSomething();
         }
         taskDelay(10_MILLISECOND_DELAY);
      }
    }
    
    
    void DebugTask()
    {
        dbg_task_queue = os_queue_create();
        while   (1)
        {
          char * str = os_queue_read(dbg_task_queue);
          foreach (ch in str)
          {
             put_ch(ch);
          }
        }
    }
    
    void OtherTask()
    {
        other_task_init();
        …
        while(1)
        {
           …
           // we want to do a dbg_printout here
           os_queue_put("Long Debug Output String");
           …
         }
    }
    
    int main()
    {
        os_task_create(SensorPollingTask, HIGH_PRIORITY);
        os_task_create(DebugTask, LOW_PRIORITY);
        os_task_create(OtherTask, OTHER_PRIORITY);
        os_start_sheduler();
    }
    


    Как видите, в главной функции больше нет одного главного бесконечного цикла. Вместо него – отдельный бесконечный цикл в каждой задаче. (Да-да, функция os_start_sheduler(); никогда не вернет управление!). И что самое главное – у этих задач есть приоритеты. Операционная система сама обеспечит то, что нам нужно – чтобы задача с высоким приоритетом выполнялась прежде всего, а с низким – только лишь тогда, когда ей остается время.

    И если время реакции на, например, прерывание в дизайне с суперциклом будет равно в худшем случае времени выполнения всего цикла (прерывание-то случится, конечно же, сразу же, но далеко не всегда необходимые действия можно сделать непосредственно в обработчике), то время реакции в случае ОС реального времени будет равно времени переключения между задачами (которое достаточно мало, чтобы считать, что это происходит сразу же!). Т.е. прерывание произойдет в одной задаче, а сразу по его завершению мы переключимся на другую задачу, ожидающую события, «запущенного» из прерывания.

    interrupt void overcurrent_handler()
    {
      os_semaphore_give(overcurrent_semaphore);
      clear_interrupt_condition();
    }
    
    void OvercurrentTask()
    {
      os_sem_create(overcurrent_semaphore);
      while (1)
        {
          os_semaphore_take(overcurrent_semaphore);
          DoOvercurrentActions();
        }
    }
    


    Что касается измерения загрузки процессора – то и эта задача с применением ОС становится тривиальной. По умолчанию каждая ОС имеет самую прожорливую (но и самую низкоприоритетную) задачу Idle, которая выполняет пустой бесконечный цикл и получает управление лишь тогда, когда все остальные задачи неактивны. И подсчет времени, проведенного в Idle, обычно тоже уже реализован. Остается лишь его вывести в консоль.

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

    И подводя итог: если система очень простая и нетребовательная ко времени реакции, ее проще сделать по образцу «суперцикл». Если же система собирается стать большой, соединяющей в себе много разных действий и реакций, которые к тому же критичны ко времени – то альтернативы использования ОС реального времени нет.

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

    Минус же использования ОС – для ее использования требуется больше места, памяти, опыта и знаний (хотя ничего сложного там и нет, все же многозадачность априори сложнее и непредсказуемее, чем последовательно выполняющийся код). Обязательно хорошее понимание принципов работы в многозадачной среде, принципов потокобезопасного кода, синхронизации данных и многого другого.

    Для «поиграться» можно взять FreeRTOS – бесплатный проект с открытым кодом, при этом достаточно стабильно работающий и простой в освоении. Хотя не редкость и коммерческие проекты с использованием именно этой операционки.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 49

      0
      Интересно, обходил RTOS стороной, думая, что там все уж очень сложно.
      FreeRTOS взлетит на STM32?
        +1
        По всей видимости, да: www.freertos.org/a00090.html#ST
          +1
          сам её на stm32 использую, на avr кстати freertos работает очень плохо, хотя и есть порт
            0
            О, а можно подробнее про «на avr кстати freertos работает очень плохо». Я сам недавно на avr сильно ругался, но вот про freeRTOS на нем ничего плохого сказать не могу, работает без проблем.
              0
              ну флаг вам в руки, а у меня глюки были какие-то непонятные, я потом спросил, мне сказали что с поротом на avr у freertos проблемы, по крайней мере это было на iar
                0
                С GCC работает отлично (порты для XMega и Atmega). Но если это старая Атмега с небольшим объемом памяти, то возникают проблемы.
            +2
            freeRTOS отличная операционка, простая, легкая и стабильно развивается в правильном направлении )

            Совсем не согласен с фразой:
            Для «поиграться» можно взять FreeRTOS

            она не для поиграться, она для настоящих серьезных проектов. В театре Современник она отлично уже не один год управляем некоторыми приводами сценической миханики. Привода в IP-сети, стек lwIP, работает как часы )
              +1
              Под «поиграться» я всего лишь имел в виду, что она доступна бесплатно — для «попробовать» самое оно. И про серьезные проекты согласен, сами такой делаем!
            0
            Хорошая и очень наглядная статья.
            Где-то RTOS на практике применяли? Если не секрет, где?

            При желании планировщик можно реализовать и без RTOS, только хорошо железки надо знать и правильно всё инициализировать\мониторить. Как вариант делать минимальный SWPulse, перещёлкивать его, и смотреть приоритеты задач, активируя или выбрасывая Interrupt Handler'ов в планируемый фрейм.
              +1
              FreeRTOS — на текущем проекте, многоканальное измерительное устройство, большего увы рассказать не могу. Ранее работал с uCOS-II и vxWorks — FreeRTOS практически ничем не хуже аналогов.

              Планировщик самому реализовать-то можно, но зачем, если это уже многократно сделано (и оттестировано!) в RTOS?
                0
                Спасибо, интересно.

                По поводу написания планировщиков. Иногда есть особые требования, чтобы был для работающей системы исходный код, и в особых случаях, написанный по определённым правилам. Тогда бывает проще переписать свой планировщик, чем отбрасывать ненужное из кода ос (если он вообще доступен).
              0
              Работаю с STM32F2 и FreeRTOS. Вопрос к автору: вы использовали какие-нибудь реализации IP стека?
                0
                На FreeRTOS — нет, на uCOS-II использовал стек от авторов операционки. Работал без проблем — но очень медленно. На vxWorks стек встроенный, тоже пользовались, была пара подводных камней — но в целом все работало.
                  0
                  Медленная работа стека была скорее всего вызвана алгоритмом Nagle, нужно его было отключить. Мне на LwIP даже с шифрованием трафика удаётся получать скорость близкую к теоретическому потолку.
                  Борд тоже какой-то китайский uCOS, с реалтеком на PHY.
                  +1
                  На FreeRTOS успешно использовал uIP стек, со старой железкой CS8900A. Хотя если интересуют именно сетевые решения советовал бы посмотреть на Contiki OS. Один из разработчиков — главный разработчик uIP, да и в целом всю систему они позиционируют, как готовый стек для разработки связанных с сетью железок.
                    0
                    Меня более всего интересует LwIP, ибо он используется в ПО, с которым я сейчас работаю =/
                    Где-то через сутки перестает взводится прерывание Ethernet, вот грешу на драйвер LwIp который поставлялся в сборке под камень(STM32F217). Не знаю уже куда ткнуться и где искать косяк =(
                    +2
                    Плотно работаю сейчас по этой проблеме.
                    Есть несколько бордов на контроллере STM32F217, выбрал пока что LwIP (последний) который пропатчил немного — там чуть попроще сдалал с семафорами обвязку.
                    Довольно серьёзная штука крутится в FreeRTOS, проблем пока не замечено, но и доделать ещё кое-что нужно. Смотрел другие реализации сетевого стека, но увы, если нужен SSL, то на мой взгляд лучшая стартовая площадка — это LwIP. Есть хардкорщики которые свой стек пишут полностью, но это совсем другая задача.
                    Должен заметить что к сожалению качество исходников в демонстрационных примерах просто отвратительное. Во многих местах не проверяются коды возврата, неправильные комментарии (copy-paste), некоторые вещи просто не проверяются и вообще, такое ощущение что писалось это только для того частного случая что в демке показывают. Когда перешёл на этот чип, то я был очень наивен. Практически всю Board-Specific Package обвязку пришлось переписать, без этого запарился баги ловить. Скоро выложу на гитхабе, может кому пригодится.
                    0
                    Хотя бы использовали (ассемблерную) команду halt, она есть в разных вариациях на всех ЦП с прерываниями. Обычно она останавливает выполнение программы (и выключает ЦП) до прихода любого прерывания, т.о. процессор не греется.
                      +3
                      Конечные автоматы + программный таймер и в 90% случаев все будет решено им. С той же легкостью.

                      Либо простейший диспетчер + программный таймер.

                      Либо просто тупой большой case и программный таймер. В общем, программный таймер наше все :)

                      А загрузку контроллера можно очень легко определять осциллографом или стрелочным вольтметром, вот так:
                      easyelectronics.ru/avr-uchebnyj-kurs-ocenka-zagruzki-kontrollera.html
                        +1
                        Собственно это и пытался показать на примитивном примере. Вот только сопровождать конечные автоматы — не самая простая задача, нет? Код над RTOS будет сильно проще в большинстве случаев.
                          +2
                          Хорошо написанный автомат сопровождать очень легко. Более того, он в подавляющем большинстве случаев даже отладки не требует. Включил и опа, работает.
                          +1
                          это всё для простых проектов, если есть куча задач, которые нужно выполнять параллельно, то кооперативная rtos тут не справится, нужна вытесняющая. У freertos, тоже можно отлично узнать загрузку, там есть специальная задача IDLE простоя если нет задач, и там можно повесить что угодно, хоть таймер, хоть светодиод, она кстати очень помогает снизить потребление.
                            +1
                            Я ж вроде именно это и хотел сказать — что при наличии ОС измерить загрузку ЦПУ весьма просто — будь то GPIO пин с осциллографом или printf в консоли. А суперцикл с многократными опросами эту задачу сильно усложняет.
                              0
                              Проблема суперцикла в том, что там нет idle задачи которая сжирала бы неиспользованное время, в результате оно все крутится быстрей чем это надо. Но что мешает перевести тот же суперцикл на событийную систему?
                                +1
                                Проблема суперцикла в том, что неудобно реализовывать все, что связано с блокирующем чтением (IP, например), и с тем, что не возможности вытеснять долговременные задачи (вывод на экран).
                                  +2
                                  А что, прерывания уже не в моде? Если есть столь критичные к выполнению задачи, то их можно и в прерывание сунуть.
                                    +2
                                    Может быть и можно, но во-первых, не всегда, а во-вторых, зачем огород городить? Если FreeRTOS уже написана, отлажена и оттестирована, то есть смысл использовать ее для решения многих проблем. У меня тоже все программки — конечные автоматы в одном потоке, но есть и дополнительные потоки, выполняющие долгие или блокирующие операции (которые автомат может вытеснять). Получается, что если бы мне надо было использовать прерывания для критичных задач, пришлось бы сам конечный автомат в прерывание засовывать. А это сделать нельзя, потому что есть синхронизация с прерываниями, а nested interrupts на контроллере нет. Да и автомат тогда должен все делать очень быстро, чтобы не пропустить ничего (например, если во время обработки прерывания мы получим 2 символа по уарт, первый пропадет).
                                      +1
                                      Медленней реакция, больше оверхед. Да и не везде rtos влезет.

                                      В прерывании не надо ничего обрабатывать. В худшем случае нажрать буфер быстро тухнущих данных и поставить флажок, что их надо разгрести. Да хоть при следующем проходе автомата.
                                        0
                                        Какой-то пустой спор, вы пытаетесь доказать, что РТОС — бесполезная штука?
                                          +1
                                          Нет, не бесполезная. Сам юзаю с удовольствием. Но в большинстве случае можно обойтись без нее.
                                            +1
                                            Так никто обратного не утверждал же :)
                                              +1
                                              и всё же использование rtos даёт больше плюсов, например код читается проще, улучшается его структура и связность, в плане дебага гораздо проще, у IAR например вообще есть драйвер для FreeRTOS, классно работает, ну и куча других печенек
                                                0
                                                Все зависит от размера кода, на пример
                                                main()
                                                {
                                                blinkled();
                                                }

                                                читается проще без осрв
                                                  0
                                                  ой, ну если такие задачи стоят, то можно вообще одни ардуины узать), но ведь всегда хочется большего, поэтому потом без rtos никак. Мне сейчас вообще без разницы, я freertos поставил бы даже на гирлянду, привычка.
                                                    0
                                                    так ведь о том и речь, есть например контроллеры вообще_без_прерываний просто мигалки
                            0
                            Если у тебя камень уровня stm32f1xx то конечно же, сложную аппликуху ты туда всё равно не зашьёшь. В таком случае конечно можно всё написать руками как ты говоришь.

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

                            RTOS реализаций много, представленная здесь FreeRTOS — одна из них, отлично сделана, код проверен многими людьми во многих проектах.
                            +5
                            Тоже использую FreeRTOS на STM32, удобно.
                            По поводу
                            >все же многозадачность априори сложнее и непредсказуемее, чем последовательно выполняющийся код. Обязательно хорошее понимание принципов работы в многозадачной среде, принципов потокобезопасного кода, синхронизации данных и многого другого.

                            Это, конечно, верно, вот только даже без фри-ртос код все равно становится многозадачным, как только начинают юзать интеррапты. И проблемы синхронизации встают, и потокобезопасности — только во ФриРТОС они… как бы сказать… более традиционно выглядят, типичный concurrency, а когда человек начинает работать с интерраптами и без ОС, еще не сразу может обратить внимание на необходимость учитывать то, что будет с кодом, когда придет этот самый интеррапт. Да еще и свои велосипеды для синхронизации придется клепать.

                            Так что это, имхо, неоднозначный вопрос — что проще — прочесть справочник по АПИ FreeRTOS и дальше пользоваться ее мультитаскингом, или самому городить.

                            Интеррапты, Интеррапты,
                            флаги прерыванья,
                            мультитаскинг расширяет
                            кодера сознанье!
                              +3
                              Хотел бы добавить пару слов от себя.
                              Для реализации поведения «interrupt_happened = 1;» нет необходимости писать обработчик прерывания — можно напрямую проверять флаг прерывания.
                              Ну и для того чтобы упростить работу с машинами состояний можно воспользоваться Protothreads.
                                –1
                                Из статьи не понятно откуда взялись функции os_start_sheduler/os_task_create.

                                В начале раздела «Применение операционной системы реального времени.»
                                Хорошо было бы указать, что сейчас мы будем использовать функции такой-то библиотеки.
                                  +3
                                  Это функции некой абстрактной ОС. Они есть в любой ОС, делают одно и то же, только называются по-разному.
                                    +1
                                    Это не библиотека, это собственно операционная система (на что, собственно, указывают префиксы os — про то, что примеры на псевдокоде, указал в самом начале).
                                      0
                                      Я это понял, но не сразу. Указать дополнительно или хотя бы в виде комментария не будет лишним.
                                        0
                                        Просто после столь детального и понятного описания подхода с «суперциклом»,
                                        кусок кода, с непонятными вызовами, которые делают непонятно что, смотрится как-то не очень.
                                          0
                                          Статья и так немаленькая получилась, и добавить сюда еще и введение в RTOS — было бы перебором. Да и собственно написано уже все!
                                      +3
                                      Всё верно в статье написано, начинающим самое оно.
                                        0
                                        Особенность работы с прерываниями такова, что обработчик прерывания (код, который будет вызван непосредственно в тот момент, когда прерывание произойдет) должен быть как можно более коротким.
                                        — почему? В смысле я не спорю, а хочу узнать: почему обработчик прерывания должен быть коротким, если есть nested interrupts?

                                        Вообще классно написано, просто и довольно точно.
                                        А всякие комментарии типа «можно самому sheduller написать» — блин можно и РТОС самому написать :)

                                        Лично для себя использую РТОС только для упрощения расширения проекта или лепки одного проекта из двух, добавления задач.
                                          +1
                                          почему обработчик прерывания должен быть коротким, если есть nested interrupts?

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

                                          Вообще же абсолютного требования, чтобы прерывания были короткими, конечно же, нет — все очень сильно зависит от задачи и дизайна решения. Мы однажды реализовали систему так, что в прерывании проводилось 85% времени процессора при максимальной загрузке — и все стабильно работало.
                                            0
                                            чтобы не загружать прерывания обычно используется RTOS, кстати по русски это ОСРВ (Операционная система реального времени)
                                              0
                                              вот о потери следующего такого же не подумал — спасибо

                                          Only users with full accounts can post comments. Log in, please.