Pull to refresh

Comments 38

Есть так называемая технология автоматного программирования или проектирования. В частности способ програмирования конечных автоматов в языке C -- "Switch-технология" А. А. Шалыто.

Рекомендую к ознакомлению: http://softcraft.ru/auto/ и https://is.ifmo.ru/automata/

Там поднятые автором статьи вопросы в значительной степени давно разобраны. Обработка конечным автоматом входных событий в своём одном состоянии -- и есть "однопроходная функция".

Немного отступая, стоит ещё вспомнить язык "Дракон". Где отдельные "шампуры" -- работа программы в одном состоянии. И проход блок-схемы "шампура" та же "однопроходная функция".

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

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

Простейшим способом планирования выполнения автоматов может быть очередь сообщений: сообщение из головы очереди отправляется последовательно на обработку всем автоматам, которым оно предназначено (или которые его обрабатывают). Такой способ применён в системе Quantum Leaps (теперь state-machines.com). Данный способ имеет очевидный недостаток: подразумевается, что автомат в процессе обработки события может сам породить множество событий предназначенных другим автоматам. И такое поведение может привести к "лавине событий", переполняющей очередь сообщений.

Можно принять, что события являются атомарными: событие или произошло в прошлом и ещё не обработано, и не важно сколько раз, или событие не возникало. Событие не несёт никакой информации. Информация ассоциированная с событием, если есть, должна передаваться отдельно (через выделенные FIFO-очереди и т.п.) И очередь сообщений скорей должна превратиться в очередь с приоритетом, где очередное событие размещается в очереди только в случае если оно уже там не размещено. Тогда переполнение очереди невозможно, но происходит некоторое усложнение логики, т.к. события говорят только о факте своего возникновения, но ни не несут ассоциированную информацию, ни даже не говорят сколько раз они произошли. В принципе, всё это становится похожим на libasync или boost.asio...

Рекомендую к чтению "Механизм обмена сообщениями для параллельно
работающих автоматов (на примере системы управления турникетом): https://is.ifmo.ru/download/turn.pdf

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

Ещё нужно отметить, что распространённая в embedded-среде методика программирования с использованием т.н. Big Loop -- по сути тот же планировщик запускающий отдельные автоматы. Но крайне не эффективный: ни только нет приоритетов, так ещё и автоматы запускаются независимо от того, есть ли для них входные события или нет. Когда автоматов (задач) становится очень много, цикл начинает проворачиваться неприемлемо медленно.

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

С работой Шалыто я знаком. К стати, он есть в ВК, я с ним как-то имел наглость проконсультироваться.

С автоматным программированием я хорошо знаком но не все же сразу.

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

Но тем не менее, считаю Ваш комментарий весьма полезным. И пару формулировок позаимствую для будущей статьи.

А почему не использовать например методологию grafcet?

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

Спасибо за комментарий. Методик очень много разных существует. Чтобы дойти до некоторых, нужно пройти определённый путь. Я решил начать рассказ с самого начала: флаги и однопроходные функции. Про диаграммы в следующий раз остановлюсь подробнее.

Спасибо к слову за статью. Узнал для себя некоторые интересные моменты. Поставил бы везде плюсы, да карма хабра - зло. Аффор, пиши есчё.

Лучший способ управления дискретными лампочками без управления яркостью - это машина состояний на шаблонах. Выглядит так:

Сначала создаётся тип управляющей структуры для отдельной машины состояний и массив таких структур по количеству необходимых лампочек:

// Управляющая структура машины состояний управляемой шаблоном
typedef struct
{
    uint8_t          active;
    T_sys_timestump  last_timestump;      // Время последнего вызова автомата состояний
    uint32_t         state_duration;      // Длительность состояний в мс
    int32_t          *start_pattern_ptr;  // Указатель на массив констант являющийся цепочкой состояний (шаблоном)
                                          // Если значение в массиве = 0xFFFFFFFF, то процесс обработки завершается
                                          // Если значение в массиве = 0x00000000, то вернуть указатель на начало цепочки
    int32_t          *curr_pattern_ptr;   // Текущая позиция в цепочке состояний
    uint8_t          repeat_flag;         // Флаг принудительного повторения сигнала

} T_state_machine_control_block;

T_state_machine_control_block outs_cbl[OUTPUTS_NUM];

Потом создаётся функция запуска машины состояний для каждой отдельной лампочки


/*-----------------------------------------------------------------------------------------------------
  Инициализация шаблона для машины состояний выходного сигнала

  \param pttn    - указатель на запись шаблоне
  \param n       - номер сигнала
-----------------------------------------------------------------------------------------------------*/
void Set_output_pattern(const int32_t *pttn, uint32_t n)
{
  if (n >= OUTPUTS_NUM) return;
  if (pttn != 0)
  {
    if (outs_cbl[n].start_pattern_ptr != (int32_t *)pttn)
    {
      outs_cbl[n].start_pattern_ptr = (int32_t *)pttn;
      outs_cbl[n].curr_pattern_ptr = (int32_t *)pttn;
      Set_output_state(n,*outs_cbl[n].curr_pattern_ptr);
      outs_cbl[n].curr_pattern_ptr++;
      Get_hw_timestump(&outs_cbl[n].last_timestump);
      outs_cbl[n].curr_pattern_ptr++;
      outs_cbl[n].active = 1;
    }
    else
    {
      outs_cbl[n].repeat_flag = 1;
    }
  }
}

И создаётся сама машина обработки состояний. Эта функция должна где-то в программе периодически вызываться с частотой не менее в два раза превышающей частоту самого быстрого мигания лампочек.

/*-----------------------------------------------------------------------------------------------------
   Автомат состояний выходных сигналов

  \param tnow  - текущее машинное время в тактах процессора
-----------------------------------------------------------------------------------------------------*/
void Outputs_state_automat(T_sys_timestump *tnow)
{
  uint32_t         duration;
  uint32_t         output_state;

  for (uint32_t n = 0; n < OUTPUTS_NUM; n++)
  {
    if (outs_cbl[n].active) // Отрабатываем шаблон только если активное состояние
    {
      uint32_t dt = Timestump_diff_to_msec(&outs_cbl[n].last_timestump  , tnow);

      if (dt >= outs_cbl[n].state_duration)  // Меняем состояние сигнала при обнулении счетчика
      {
        memcpy(&outs_cbl[n].last_timestump, tnow, sizeof(T_sys_timestump));

        if (outs_cbl[n].start_pattern_ptr != 0)  // Проверяем есть ли назначенный шаблон
        {
          output_state =*outs_cbl[n].curr_pattern_ptr;   // Выборка значения состояния выхода
          outs_cbl[n].curr_pattern_ptr++;
          duration =*outs_cbl[n].curr_pattern_ptr;       // Выборка длительности состояния
          outs_cbl[n].curr_pattern_ptr++;                // Переход на следующий элемент шаблона
          if (duration != 0xFFFFFFFF)
          {
            if (duration == 0)  // Длительность равная 0 означает возврат указателя элемента на начало шаблона и повторную выборку
            {
              outs_cbl[n].curr_pattern_ptr = outs_cbl[n].start_pattern_ptr;
              output_state =*outs_cbl[n].curr_pattern_ptr;
              outs_cbl[n].curr_pattern_ptr++;
              outs_cbl[n].state_duration =*outs_cbl[n].curr_pattern_ptr;
              outs_cbl[n].curr_pattern_ptr++;
              Set_output_state(n , output_state);
            }
            else
            {
              outs_cbl[n].state_duration = duration;
              Set_output_state(n ,output_state);
            }
          }
          else
          {
            if (outs_cbl[n].repeat_flag)
            {
              outs_cbl[n].repeat_flag = 0;
              // Возврат указателя элемента на начало шаблона и повторная выборка
              outs_cbl[n].curr_pattern_ptr = outs_cbl[n].start_pattern_ptr;
              output_state =*outs_cbl[n].curr_pattern_ptr;
              outs_cbl[n].curr_pattern_ptr++;
              outs_cbl[n].state_duration =*outs_cbl[n].curr_pattern_ptr;
              outs_cbl[n].curr_pattern_ptr++;
              Set_output_state(n , output_state);
            }
            else
            {
              // Обнуляем счетчик и таким образом выключаем обработку паттерна
              Set_output_state(n , output_state);
              outs_cbl[n].active = 0;
              outs_cbl[n].start_pattern_ptr = 0;
            }
          }
        }
        else
        {
          // Если нет шаблона обнуляем состояние выходного сигнала
          Set_output_state(n, 0);
        }
      }
    }
  }
}

И наконец создаём какие угодно шаблоны для мигания лампочек

#define __ON   0 // Ноль потому что лампочки зажигаются низким уровнем сигнала 
#define _OFF   1
#define _LOOP  0
#define _STOP  0xFFFFFFFF
#define    SC  1 // Масштабирующий коэффициент 
                 // если понадобится замедлить или ускорить 
                 // все моргания одновременно

//-------------------------------------------------------------------------
// Пример шаблона для периодического тройного помаргивания

//  Шаблон состоит из массива груп слов.
//  Первое слово в группе - значение сигнала
//  Второе слово в группе - длительность интервала времени в  мс
//    интервал равный 0x00000000 - означает возврат в начало шаблона
//    интервал равный 0xFFFFFFFF - означает застывание состояния

const int32_t   OUT_3_BLINK[] =
{
  __ON, 50*SC,
  _OFF, 50*SC,
  __ON, 50*SC,
  _OFF, 50*SC,
  __ON, 50*SC,
  _OFF, 250*SC,
  _OFF, _LOOP
};


// Пример включения сигнала с заданным шаблоном тройного моргания
// В данном примере сигнал зациклиться и будет 
// продолжаться пока его не отключат сбросив флаг  outs_cbl[LAMP1].active = 0;
Set_output_pattern(OUT_3_BLINK, LAMP1);

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

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

Вот представьте себе ситуацию. Вы только начали вникать в процесс программирования. А тут такое выкатилось. Очень сложно сходу въехать. Надо к этому подбираться постепенно.

Ко мне недавно обратился знакомый. Попросил проекты для примера. Он когда-то давно учился меня программированию. А потом долго этим не занимался. Я скинул ему свою версию обработчик событий в автоматом стиле. Объяснял пол дня что да как. В итоге он все равно попросил пример по проще.

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

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

Я в сфере обмена знаниями сторонник теории фичей.
Т.е. если в вашем коде нет фичей (это нечто очень интересное изучающему), то любой код будет сложным.

Если ваши примеры управляют светофором кое как, и вы сами к тому же об этом утверждаете, то зачем это изучать? Это неинтересно. Рисунки помогают, но они не могут стать источником интереса.

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

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

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

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

Я ни кого не водил за нос. В самом начале статьи чётко обозначено, кому она адресована.

Делать функции в которые все время надо что-то передавать не так уж и удобно.
Это не фича. Проще выглядят функции у которых нет аргументов.

Дозирование фичей должно быть конечно. Не больше 7. У меня их три. В самый раз.

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

Словом, правильно ли вы представляете целевую аудиторию своей статьи?

Скорее всего вы статью не читали, а просто пролистали по диагонали. И это не удивительно.

Да, я хорошо представляю свою аудиторию. Я с интересом читал ваши статьи. И под некоторыми есть комментарии, указывающие на то, что хорошего материала начального уровня в интернете не хватает.

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

Вот это вы вообще к чему сейчас? В моем примере параметры хранятся в глобальной структуре. Переменные счётчик и флаг тоже объявлены глобально.

Раз так, то давайте конкретно по коду из вашего примера. Покажите мне в нем хотя-бы один конечный автомат?!! Кашу из иф-элсов я увидел, но машину состояний нет.

Делать функции в которые все время надо что-то передавать не так уж и удобно.
Это не фича. Проще выглядят функции у которых нет араргументов.

И тут же сами в своем примере передаете в функцию указатель на структуру. А потом ещё обращаетесь к её полям не самым наглядным способом.

Чтобы осмыслить код из вашего примера, нужно обладать определённым знанием синтаксиса и опытом в программировании. А остальным как быть?

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

Словом, правильно ли вы представляете целевую аудиторию своей статьи?

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

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

Дайте цитату на то место, где я утверждаю, что мой код работает кое-как?

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

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

Если у Вас есть сомнения в истинности моих соображений, то могу предложить Вам реальное ТЗ, чтобы Вы попробовали решить его тем способом, который продемонстрировали в своем комментарии. Сразу могу предположить, что вряд-ли это получится, т.к. 2кб программной памяти резко ограничивают количество доступных к применению "фич".

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

Мой исходник не для того чтобы опровергнуть все ваши утверждения, а просто пример машины состояний. Машины состояний они так и делаются на if-ах. Так генерирует машины например Mаtlab. Машины меняют состояния не по событиям, а по изменениям условий! А события как раз ломают логику машин состояний, вносят плохо воспринимаемую мозгом асинхронность.

Про глобальные переменные я завёл речь в плане того что вы там пообещали все перевести в классы намекая на C++ видимо и спрятать переменные внутри классов. Это сильно ухудшает отлаживаемость.

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

А метафоры как раз нужны из области машин состояний (state machine, SM). И вот этот барьер очень сложно перескочить. Нужно структуры создавать для SM, а не для объектов материального мира. Но нужно научиться редуцировать свои задачи на отдельные SM.

Задачу с кнопками и светофорами я бы редуцировал на три SM. Первая SM - это работа кнопки, а не сама кнопка , вторая SM - это менеджер между SM кнопок и SM лампочек, и третья SM это работа лампочки, а не сама модель лампочки . Масштабирование достигается мультиплицированием этих SM.

Т.е. в следующих статьях я бы вам предложил раскрыть темы:

  • дизайн кода с учётом максимальной отлаживаемости,

  • дизайн кода с учётом удобства рефакторинга,

  • дизайн кода с учётом масштабируемости.

Думаю что вот это было бы реально интересно новичкам. Про это почему-то в книгах не пишут.

Так генерирует машины например MаMаtlab.н

Не сравнивайте код, генерируемый автоматически с кодом, который надо руками писать.

Конечный автомат это в первую очередь метод проектирования. Соответственно, нужно придерживаться некоторой логики в проектировании.

Мне кажется, что Вы слишком зациклены на конечных автомата. В моем обработчике кнопки конечного автомата нет.

В начале статьи я специально написал, что статья не про конечные автоматы. И я предупредил, что статья не для вашего уровня подготовки. Чем я Вас обманул?!

Как по вашему должна выглядеть хорошая статья? "Пацаны, смотрите, тут такая фича..." и дальше дофига кода.

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

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

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

Вы используете термин state machine. Если переводить этот термин на пусский, получается конечный автомат.

Сам термин "состояние" в моей статье встречается, но без привязки к конечному автомату. В сектанты автоматчиков не состою. Считаю, что каждая методика хороша по своему. Поэтому и пытаюсь оформить цикл про разные подходы.

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

Я как раз и хочу выйти к пониманию, что КА это частный случай.

Возражу. Не сложно, а порой даже проще чем начинать с линейного программирования. Обучал сына (10-12лет) программированию конечных автоматов, и кстати, как раз на "умном светофоре". К сожалению, он уже знал алгоритмическое программирование на базе Ардублока и Лего скретчей, поэтому пришлось тоже "перестраивать" свое понимание что такое программа.

Этапы:

  1. Определяем и доводим до понимания, что каждая железка имеет определенное состояние - лампочка может быть или включена или погашена (ещё сломана)..

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

    На базе этих двух пунктов, ребенок в 10-12лет уже способен самостоятельно(!) определить граф состояний конечного автомата умного светофора с кнопкой для пешехода. Проверено на практике.

  3. И вот тут есть тонкий момент, которого нет в "академической" теории КА: входящий поток событий .. "слушается" соответствующим слушателем потока. Сам дошел не сразу, но как только сия мысль сформулировалась примерно в таком акцепте, так сразу дошло и до него КАК происходит работа конечного автомата.

    Простой светофор пошагово описан тут: https://community.alexgyver.ru/threads/programmirovanie-konechnyx-avtomatov-bez-delay.2657 Ещё не знаю, разрешено ли тут постить ссылки на сторонние ресурсы, но .. пусть будет.

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

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

Возражение касалось порядка обучения. Надо не столько перетаскивать тех кто уже погряз в "линейном программировании", а первоначально учить автоматному подходу, и уже только потом линейному. Так проще. Имел небольшой опыт обучения партнеров сына при подготовке к соревнованиям. Они были "нулевые" и конечные автоматы в упрощенном виде воспринимали сразу и сильно легче.

У Вас имеется большая статистика по применению вашего подхода в обучении?

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

И переучивать нужно и учить правильно тоже нужно. Именно переучивать, а не ломать. Поэтому я привожу понятные знакомые примеры, а затем показываю альтернативу.

Почитайте мою статью внимательно, если вы действительно интересуетесь методологией обучения.

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

Ну нет! Я понимаю, что это круто, винтажно, и это мы проходили 30 лет назад. Ну если так хотите, то ПЛИС то же самое, но в одном корпусе. Но я сторонник японской простоты и надежности. Я бы реализовал это еще проще. Генератор, счетчик, OTP ROM (осциллограммы то есть). Все просто и ничего лишнего. Конфигурирование - простая замена ROM. 3 секунды. Если хотите с извратами - EEPROM, UVEPROM.

Ну а насчет программирования.... Я еще лет эдак 20 назад выработал себе конструкцию параллельного вычисления с флагами. То есть конструкция вертится по кругу и в зависимости от флагов ветвится. И события обрабатываются либо в конкретном потоке, либо в общем. Нет никаких Delay, все реализуется на ходу. Программа ни миллисекунды не стоит. Типа псевдомногозадачность. И добавление функций просто и гибкость на высоте.

Блин, ну в самом деле. Если бы я хотел плис, то делал бы на плис. Но хочется же молодость вспомнить. Кто-то в шахматы играет, кто-то крестиком вышивает, а я "винтажной" схемотехникой занимаюсь. Отдыхаю я так.

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

Совсем без задержек код крутить я че-то "очкую". С прерываниями обмен всегда нормально проходит?

Всегда кручу программы без задержек :) Более того - стараюсь по возможности избегать вот таких delay(). Но это, конечно, уровень повыше того, для которого предназначена эта статья. У меня есть создаваемые таймеры обратного отсчета, которые уменьшаются каждую миллисекунду в прерывании. Если что-то нужно задержать на 3 секунды, я создаю (или переиспользую) таймер, задав ему значение счетчика 3000. И потом в цикле проверяю достиг ли этот таймер нуля. Достиг - хорошо, можно выполнять нужное действие и при необходимости повтора этого действия вновь взвести этот таймер :)

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

Задержка для ардуино реализована примерно таким же способом, но выполнена по блокирующему принципу. Там тоже использован аппаратный таймер, счетчик которого используется для измерения времени. В принципе, подход, аналогичный вашему, в Arduino way тоже можно организовать, есть функции millis() и micros(), возвращающие uptime. Но для Вас, скорее всего, это бесполезная информация.

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

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

Ну это понятно, не на for() же организовывать задержки (хотя иногда приходится делать и так) :)

В принципе, подход, аналогичный вашему, в Arduino way тоже можно организовать, есть функции millis() и micros(), возвращающие uptime

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

Это меня успокаивает и дает хоть какую-то надежду на предсказуемый отклик.

А без блокирующих задержек отклик будет максимально быстрым, что бывает очень полезным для реакции на внешние раздражители :)

Но скорее всего тут дело привычки.

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

Вариант программирования в статье я привел для примера. Сам я так давно уже не пишу. Тоже выделяю состояния программы и описываю их по отдельности. Делаю несложную диспетчеризацию.

Мгновенная реакция на внешние раздражители не всегда нужна. Обычно есть какая-то вилка по времени. Но все равно, это уже вкусовщина. Кто к чему привык.

Но все равно, это уже вкусовщина. Кто к чему привык.

Не спорю :)

моё небольшое замечание:
1. если время такта принять 0.5с, т.е. считать такты по 0.5 секунд, то половинки секунд уйдут и будут целые числа, описывающие текущие состояния.
не 0, 3, (4, 8.5, 9.5, 10.5), (8, 9, 10), 11,
а 0, 6, (8, 17, 19, 21), (16, 18, 20), 22.
2. и если в структуре иметь максимальное количество тактов (N), от 0..N-1, то номер можно брать по модулю, и смещение будет автоматически учитываться.

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

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

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

Этой статьи могло бы и не быть если бы создатели ардуины не придумали delay и не показали на нем пример мигания светодиодом.

В первом своем проекте на мк, пытался использовать Arduino IDE, и их библиотеки, но стукнувшись о занятый таймер при попытке напрямую инициировать ШИМ как мне надо, ушел от ардуино.

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

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

Можно данный алгоритм нарисовать на языке ДРАКОН, например, так:

Здесь тоже есть ваш бесконечный цикл, на него указывают черные треугольники.

На Хабре по языку ДРАКОН см. посты:

Я сторонник "натуральных ощущений", если вы понимаете, о чем я))) Я с ассемблера ещё начинал программировать. Хотя, я считаю визуальные языки более перспективными.

Sign up to leave a comment.