Новичкам: Автоматное программирование микроконтроллеров.

Arhat109
Offline
Зарегистрирован: 26.05.2015

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

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

Это главный недостаток программирования микроконтроллеров "в лоб". ТАК НЕ ДЕЛАЮТ, если хочется "хорошо и шустро".

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

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

О том КАК преобразовать программу "в лоб" в стиль автоматного программирования очень доходчиво изложено тут:

http://ru.wikipedia.org/wiki/%D0%90%D0%B2%D1%82%D0%BE%D0%BC%D0%B0%D1%82%...

Я также не смогу.

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

Привествуется: дополнения, помощь в описании подхода (могу чего и упустить), вопросы в тему и полезные ответы на них.

Сообщения "зачем", "нафиг надо", "автор дурак" и т.д. НЕ ОТНОСЯЩИЕСЯ к вопросу темы -- надо рассматривать ИСКЛЮЧИТЕЛЬНО как отсутствие мозга у написавшего. :)

Arhat109
Offline
Зарегистрирован: 26.05.2015

Пример кода Blink.ino в этом стиле (часть1 - явное переключение состояний КА):

/*
 * Пример автоматного программирования.
 * Светодиод - как конечный автомат имеет состояния: 
 *     LED_OFF (выключен, ожидание включения)
 *     LED_ON  (включен, ожидание выключения)
 * 
 * Событием, изменяющим состояние является счечик времении.
 */

#define pinLed            13 // ножка платы со встроенным светодиодом
#define WAIT_ON         1000 // длительность состояния "ожидание выключения" мсек.
#define WAIT_OFF        1000 // длительность состояния "ожидание включения" мсек.

enum states { LED_OFF, LED_ON } state;
unsigned long ledTimeout;

// используем это для начальной настройки конечного автомата (далее КА)
void setup() {                
  pinMode(pinLed, OUTPUT);           // ножку светодиода настраиваем как "это выход"
  state = LED_OFF;                   // начальное состояние КА - выключен, ждем включения
  ledTimeout = millis() + WAIT_OFF;  // время, когда должно произойти включение
}

// используем это как "шаг цикла КА":
void loop() {
  if( millis() >= ledTimeout ){         // если событие, переключающее КА - наступило:
    switch (state ){                    // выбираем действие согласно текущего состояния:
      case LED_OFF:                     // было "выключен":
        digitalWrite(pinLed, HIGH);     // .. включаем светодиод
        state = LED_ON;                 // .. меняем состояние КА
        ledTimeout = millis() + WAIT_ON;// .. и устанавливаем время следующей смены состояний.
        break;
      case LED_ON:                      // .. было "включен"
        digitalWrite(pinLed, LOW);      // .. выключаем светодиод
        state = LED_OFF;                // .. и меняем состояние КА
        ledTimeout = millis()+ WAIT_OFF;// .. устанавливаем время следующей смены состояний
        break;
    }
  }
}

 

Puhlyaviy
Puhlyaviy аватар
Offline
Зарегистрирован: 22.05.2013

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

// the setup выполняеться один раз при включении
void setup() {                
  // включаем дырдочку 13 куда подключили светик как выход
  pinMode( 13 , OUTPUT);     
}

// loop повторяется снова и снова и снова и снова и с....
void loop() {
  digitalWrite(led, HIGH);   // включаем светик
  delay(1000);               // ждем секунду
  digitalWrite(led, LOW);    // выключаем светик
  delay(1000);               // ждем секунду

 

Arhat109
Offline
Зарегистрирован: 26.05.2015

Продолжу разбор этого примера.

И так, "как такое получилось"? А просто:

Задача "включить светодиод на 1 сек, потом выключить на 1 сек. Повторять пока не надоест".

Фактически "устройство" - это светодиод. Протокол работы предписывает 2 состояния "включен" и "выключен". А также предписывает 2 времени задержки изменения состояния: "по 1 сек". Отсюда, когда сетодиод горит - то он на просто "включен" и надо ещё подождать заданное время, чтобы его выключить - то есть это состояние правильнее обозвать как "ждем выключения", соответственно и наоборот.

Итого: есть устройство, которое имеет 2 состояния: ждет включения или ждет выключения. И 2 метода изменения состояния: "включить" и "выключить". В примере выше, сами методы - собраны в единый блок switch(){} и не выделены из кода. Это - следующий этап перевода скетча в "автоматный стиль".

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

... продолжение следует. :)

Клапауций 999
Offline
Зарегистрирован: 06.06.2015

Arhat109 пишет:

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

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

вот этим бредом окончательно мосги набекрень снесёшь неофитам и просто идиотам...

*компьютер не контроллер потому, что состоит из контроллеров, которые управляют периферией, кторой управляют по протоколам, но нам же известно, что у компьютера нет периферии - он шлёт электрические сигналы непосредсвенно в мосг ползателю, а шо протоколы? - протоколы в топку.

**набираю послушников в секту Святаго Контроллера для приношения в жертву Святаму Компьютеру.

Arhat109
Offline
Зарегистрирован: 26.05.2015

Можете резать посты, но флуд в теме - озвучен: "это сигнал, что у автора мозгов- нет".

Puhlyaviy
Puhlyaviy аватар
Offline
Зарегистрирован: 22.05.2013

Arhat109 пишет:

Можете резать посты, но флуд в теме - озвучен: "это сигнал, что у автора мозгов- нет".

продолжаем веселую игру?

кто следующий тут напишет, тот лох!

Arhat109
Offline
Зарегистрирован: 26.05.2015

Кто следующий напишет НЕ В ТЕМУ - да, даже хуже... впрочем, вы сами определитесь и поможете остальным в такой ориентировке. :)

Клапауций 999
Offline
Зарегистрирован: 06.06.2015

Arhat109 пишет:

Кто следующий напишет НЕ В ТЕМУ - да, даже хуже... впрочем, вы сами определитесь и поможете остальным в такой ориентировке. :)

ок. давай проведём тестирование:

сейчас я пишу В ТЕМУ?

Puhlyaviy
Puhlyaviy аватар
Offline
Зарегистрирован: 22.05.2013

Arhat109 пишет:

Кто следующий напишет НЕ В ТЕМУ - да, даже хуже... впрочем, вы сами определитесь и поможете остальным в такой ориентировке. :)

все, видишь, нужно признать что ты ЛОХ! И ничего с этим не поделаешь!

Puhlyaviy
Puhlyaviy аватар
Offline
Зарегистрирован: 22.05.2013

Клапауций 999 пишет:

ок. давай проведём тестирование:

сейчас я пишу В ТЕМУ?

 странная фигня какая то.. если пишу не в ЭТУ тему, то сообщения тут не появляются... 

Arhat109
Offline
Зарегистрирован: 26.05.2015
/*
 * Пример автоматного программирования. Часть 2.
 *
 * Светодиод - как конечный автомат имеет состояния:
 *     LED_OFF (выключен, ожидание включения)
 *     LED_ON  (включен, ожидание выключения)
 *
 * Событием, изменяющим состояние является счечик времении.
 */
	 
#define pinLed            13 // ножка платы со встроенным светодиодом
#define WAIT_ON         1000 // длительность состояния "ожидание выключения" мсек.
#define WAIT_OFF        1000 // длительность состояния "ожидание включения" мсек.
 
enum states { LED_OFF, LED_ON } state;
unsigned long ledTimeout;

// выделяем методы изменения состояний в отдельные функции:

// Метод "включить":
void to_on(){
        digitalWrite(pinLed, HIGH);     // .. включаем светодиод
        state = LED_ON;                 // .. меняем состояние КА
        ledTimeout = millis() + WAIT_ON;// .. и устанавливаем время следующей смены состояний.
}

// Метод "выключить":
void to_off(){
        digitalWrite(pinLed, LOW);      // .. выключаем светодиод
        state = LED_OFF;                // .. и меняем состояние КА
        ledTimeout = millis()+ WAIT_OFF;// .. устанавливаем время следующей смены состояний
}

// используем это для начальной настройки конечного автомата (далее КА)
void setup() {               
  pinMode(pinLed, OUTPUT);           // ножку светодиода настраиваем как "это выход"
  state = LED_OFF;                   // начальное состояние КА - выключен, ждем включения
  ledTimeout = millis() + WAIT_OFF;  // время, когда должно произойти включение
}
 
// используем это как "шаг цикла КА":
void loop() {
  if( millis() >= ledTimeout ){         // если событие, переключающее КА - наступило:
    switch (state ){                    // выбираем действие согласно текущего состояния:
      case LED_OFF:                     // было "выключен":
        to_on();
        break;
      case LED_ON:                      // .. было "включен"
        to_off();
        break;
    }
  }
}

 ... собственно код автомата - упростился и стал "универсальнее": есть состояние LED_OFF (выключен) - меняем его методом "включить" и наборот.

Ещё можно заметить, что ОБА метода имеют "общую часть" по изменению состояния автомата и установке следующего периода ожидания.

То есть, имеем 2 направления улучшения этого кода:

1. Выделение матрицы состояний и переходов, что приведет к полной универсальности кода самого автомата (пример есть в статье) ... оставим на "продолжение следует";

2. Выделение кода изменения состояний из самих методов. Тут он - одинаков.

Puhlyaviy
Puhlyaviy аватар
Offline
Зарегистрирован: 22.05.2013

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

обясняю на пальцах основную идею успешного обучения... правда придеться тебе обьяснять еще проще чем я это делал дочери, когда она занималась с отстающими подругами ( как результат пересдали математику на 85-90 балов, заместо 65 ранее)

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

Аммммм Короче если хочешь гопникам обьяснить как работает компьютер, то опустись на уровень гопников и мысли их понятиями.

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

Arhat109
Offline
Зарегистрирован: 26.05.2015
/*
 * Пример автоматного программирования. Часть 3.
 * Светодиод - как конечный автомат имеет состояния:
 *     LED_OFF (выключен, ожидание включения)
 *     LED_ON  (включен, ожидание выключения)
 *
 * Событием, изменяющим состояние является счечик времении.
 */
	 
#define pinLed            13 // ножка платы со встроенным светодиодом
#define WAIT_ON         1000 // длительность состояния "ожидание выключения" мсек.
#define WAIT_OFF        1000 // длительность состояния "ожидание включения" мсек.
 
typedef enum states { LED_OFF, LED_ON } LedStates;

LedStates     state;
unsigned long ledTimeout;

// выделяем методы изменения состояний в отдельные функции:

// Вспомогательный метод для "смены состояний" этого протокола:
void set_state( LedStates _new_state )
{
        state = _new_state;
        ledTimeout = millis() + (_new_state == LED_OFF? WAIT_OFF : WAIT_ON);
}

// Метод "включить":
void to_on()
{
        digitalWrite(pinLed, HIGH);     // .. включаем светодиод
        set_state(LED_ON);              // .. меняем состояние КА
}

// Метод "выключить":
void to_off()
{
        digitalWrite(pinLed, LOW);      // .. выключаем светодиод
        set_state(LED_OFF);             // .. и меняем состояние КА
}

// используем это для начальной настройки конечного автомата (далее КА)
void setup() {               
  pinMode(pinLed, OUTPUT);           // ножку светодиода настраиваем как "это выход"
  state = LED_OFF;                   // начальное состояние КА - выключен, ждем включения
  ledTimeout = millis() + WAIT_OFF;  // время, когда должно произойти включение
}
 
// используем это как "шаг цикла КА":
void loop() {
  if( millis() >= ledTimeout ){         // если событие, переключающее КА - наступило:
    switch (state ){                    // выбираем действие согласно текущего состояния:
      case LED_OFF:                     // было "выключен":
        to_on();
        break;
      case LED_ON:                      // .. было "включен"
        to_off();
        break;
    }
  }
}
 

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

... продолжение следует. :)

Клапауций 999
Offline
Зарегистрирован: 06.06.2015

Arhat109 пишет:

... продолжение следует. :)

куда следует продолжение?

Puhlyaviy
Puhlyaviy аватар
Offline
Зарегистрирован: 22.05.2013

Клапауций 999 пишет:

Arhat109 пишет:

... продолжение следует. :)

куда следует продолжение?

туда

Arhat109
Offline
Зарегистрирован: 26.05.2015

Небольшая реорганизация скетча: изменение состояний вынесено из методов управления светодиодом в блок изменения шага конечного автомата. Это для наглядности, что весь скетч теперь представляет собой методы управления согласно протоколу И блок управления КА.

/*
 * Пример автоматного программирования. Часть 3.
 * Светодиод - как конечный автомат имеет состояния:
 *     LED_OFF (выключен, ожидание включения)
 *     LED_ON  (включен, ожидание выключения)
 *
 * Событием, изменяющим состояние является счечик времении.
 */
	 
#define pinLed            13 // ножка платы со встроенным светодиодом
#define WAIT_ON         1000 // длительность состояния "ожидание выключения" мсек.
#define WAIT_OFF        1000 // длительность состояния "ожидание включения" мсек.

// вводим определение типа для перечисления состояний, чтобы можно было
// состояние передавать как параметр:
typedef enum states { LED_OFF, LED_ON } LedStates;

LedStates     state;
unsigned long ledTimeout;

// выделяем методы изменения состояний в отдельные функции:

// Вспомогательный метод для "смены состояний" этого протокола:
void set_state( LedStates _new_state )
{
        state = _new_state;
        ledTimeout = millis() + (_new_state == LED_OFF? WAIT_OFF : WAIT_ON);
}

// Метод "включить":
void to_on()
{
        digitalWrite(pinLed, HIGH);     // .. включаем светодиод
}

// Метод "выключить":
void to_off()
{
        digitalWrite(pinLed, LOW);      // .. выключаем светодиод
}

// используем это для начальной настройки конечного автомата (далее КА)
void setup() {               
  pinMode(pinLed, OUTPUT);           // ножку светодиода настраиваем как "это выход"
  state = LED_OFF;                   // начальное состояние КА - выключен, ждем включения
  ledTimeout = millis() + WAIT_OFF;  // время, когда должно произойти включение
}
 
// используем это как "шаг цикла КА":
void loop() {
  if( millis() >= ledTimeout ){ // если событие, переключающее КА - наступило:
    LedStates new_state;

    switch (state ){            // выбираем действие согласно текущего состояния:
      case LED_OFF:             // было "выключен":
        to_on();                // .. включаем светодиод
        new_state = LED_ON;     // .. следующее состояние "Включен, ждем выключения"
        break;
      case LED_ON:              // .. было "включен"
        to_off();               // .. выключаем светодиод
        new_state = LED_OFF;    // .. следующее состояние "Выключен, ждем включения"
        break;
    }

    set_state(new_state);       // .. меняем состояние КА.
  }
}
 

Собственно теперь мне кажется стало виднее, что вся логика управления конечным автоматом, не зависит от устройства управления и сосредоточена в одном блоке IF(){}. Да и она фактически "типовая":

Пришло время? Смотрим на текущее состояние и выполняем метод его изменения, устанавливаем следующее состояние.

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

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

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

Puhlyaviy
Puhlyaviy аватар
Offline
Зарегистрирован: 22.05.2013

dimax пишет:

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

все ты тоже теперь троль и против прогресса :)

Клапауций 999
Offline
Зарегистрирован: 06.06.2015

кароче - резюмирую поток сознания ТС:

навички, скетчи пишем так

void setup() {}

void loop() {
IF(){}
IF(){}
IF(){}
IF(){}
}

а, не так

void setup() {}

void loop() {}

*ТС, убейся ап стену бетонную, пока тебя не покарал бесконечный делай.

Arhat109
Offline
Зарегистрирован: 26.05.2015

Ну вот и настало время описать эту самую "таблицу переходов".

У нас есть 2 состояния:

"включен, ожидаем отключения" -- по событию надо выключить,

и "выключен, ожидаем включения" -- по событию надо включить.

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

Всё это можно описать такой структурой:

typedef struct{
  LedStates     newState;
  void        (*command)(void);
  unsigned long timeout;
} TimeStateItem;

И соответственно, всю таблицу переходов можно описать массивом:

TimeStateItem   ledStates[2] = {
   { LED_ON, to_on, WAIT_ON}
  ,{ LED_OFF, to_off, WAIT_OFF}
};

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

Ну и ещё, надо заметить, что теперь речь идет не о проверке самого состояния, а только его номера... соответственно, теперь переменная state определяет номер текущего состояния в таблице и только.

В качестве "итого" получаем:

/*
 * Пример автоматного программирования. Часть 4.
 * Светодиод - как конечный автомат имеет состояния:
 *     LED_OFF (выключен, ожидание включения)
 *     LED_ON  (включен, ожидание выключения)
 *
 * Событием, изменяющим состояние является счечик времении.
 */
	 
#define pinLed            13 // ножка платы со встроенным светодиодом
#define WAIT_ON         1000 // длительность состояния "ожидание выключения" мсек.
#define WAIT_OFF        1000 // длительность состояния "ожидание включения" мсек.

// вводим определение типа ячейки хранения состояний автомата:
typedef struct{
  uint8_t       state;          // номер следующего состояния в таблице переходов
  void        (*command)(void); // команда (метод) для исполнения действий состояния
  unsigned long timeout;        // временной интервал следующего состояния (мсек)
} TimeStateItem;

// Собственно таблица переходов нашего конечного автомата (нумерация с 0!):
TimeStateItem   ledTable[2] = {
   { 1, to_on, WAIT_ON}         // "выключен": включить, перейти к состоянию "включен" с задержкой
  ,{ 0, to_off, WAIT_OFF}       // "включен": всё наоборот, выключаем, переходим к пред. состоянию.
};

unsigned int  state;            // текущий номер состояния светодиода
unsigned long ledTimeout;       // текущий временной интервал ожиданий

// выделяем методы изменения состояний в отдельные функции:

// Метод "включить":
void to_on()
{
        digitalWrite(pinLed, HIGH);     // .. включаем светодиод
}

// Метод "выключить":
void to_off()
{
        digitalWrite(pinLed, LOW);      // .. выключаем светодиод
}

// используем это для начальной настройки конечного автомата (далее КА)
void setup() {               
  pinMode(pinLed, OUTPUT);           // ножку светодиода настраиваем как "это выход"
  state = 0;                         // НОМЕР(!) начального состояние КА в таблице
  ledTimeout = millis() + WAIT_OFF;  // время, когда должно произойти включение
}
 
// используем это как "шаг цикла КА":
void loop() {
  if( millis() >= ledTimeout ){ // если событие, переключающее КА - наступило:
    TimeStateItem new_state;    // определяем место для хранения структуры следующего состояния

    new_state = ledTable[state];               // читаем состояние из таблицы переходов
    new_state.command();                       // исполняем команду состояния
    state = _new_state.state;                  // устанавливаем следующее состояние
    ledTimeout = millis() + new_state.timeout; // и его время ожидания
  }
}
 

Как видим, код в loop() больше совсем не зависит от задач, решаемых нашим автоматом. Вся его логика сосредоточена в таблице ledTable и методах управления. Как видим "пропал" и вспомогательный метод изменения состояния, поскольку он теперь точно также НЕ ЗАВИСИТ от задачи.

 ... продолжение следует (как собрать КА по начальной задаче не делая таких постепенных изменений)

Arhat109
Offline
Зарегистрирован: 26.05.2015

А пока, несколько замечаний для программистов:

1. Код можно оформить в класс ООП и он станет значительно красивше. Тут сознательно использован только С, как и в примере из википедии; по-просту, таки сделать библиотеку, которая должна была присутствовать в Ардуино ИЗНАЧАЛЬНО. :)

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

3. Если таблица переходов неизменяемая, то префикс progmem позволяет запихать её в программную память;

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

5. Методы изменений могут получать некий указатель на структуру данных и соответсвенно могут изменять свое поведение в зависимости от переданного объекта данных;

6. Легко можно дополнить этот код "ещё одним автоматом" (например поворот сервы на угол или свисток кому-нибудь по Wi-Fi). В простейшем примере, покажу позже;

7. Таблицу переходов можно снабдить вероятностью соответствующего перехода. В этом случае можно реализовать "вероятностное поведение" автомата. Значения вероятностей можно изменять (если в SRAM, а не во FLASH ... впрочем там тоже можно) по результатам некоторой "весовой функции успеха достижения цели" ... получим самообучающийся автомат. :)

8. Каждое устройство - есть такой автомат. Смешанное управление несколькими устройствами - это "автомат автоматов" и только. :)

P.S. практически ни один из п1-8 не требует сильных изменений в loop()... :)

Разбирайтесь, на сегодня - "хватит". :)

Arhat109
Offline
Зарегистрирован: 26.05.2015

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

Продолжение и окончание вопроса:

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

В части этого рассмотрения, код библиотеки на С будет примерно таким:

1. Это файл заголовка библиотеки TimeStateControl.h

// ==================== то, что должно было быть определено в Wiring ====================== //
/**
 * Time State Control Simple Library. Prefix for this: TSC_ (tsc_)
 * 
 * Библиотека создания простых конечных автоматов с задержками исполнения по времении
 * в миллисекундах через функцию millis().
 * 
 * @author Arhat109 at 2015-07-18
 * @see Arhat.lib::examples/TSC_Blink/TSC_Blink.ino
 */
typedef unsigned long TSC_Time;      // переопределение "на будущее": не всегда нужен long
typedef void (*TSC_Command)(void);   // вводим определение типа "команда КА" тут просто процедура без параметров

// вводим определение типа ячейки хранения состояний автомата:
typedef struct {
  uint8_t       state;          // номер следующего состояния в таблице переходов
  TSC_Command   command;        // команда (метод) для исполнения действий состояния
  TSC_Time      timeout;        // временной интервал следующего состояния (мсек)
} TSC_Step;

// вводим определение типа для хранения текущего состояния конечного автомата (КА)
typedef struct {
  uint8_t       state;          // текущий номер исполняемого состояния КА
  TSC_Time      timeout;        // текущий интервал, которого надо дождаться
  TSC_Step *    table;          // таблица переходов этого КА
} TSC_Control;

void tsc_init( TSC_Control *_tsc, TSC_Step * _table, uint8_t _state, TSC_Time _wait );
void tsc_next( TSC_Control *_tsc, uint8_t _state, TSC_Time _wait );
void tsc_step( TSC_Control *_tsc );

Это файл реализации функций библиотеки time_state_control.c

// ======== TimeStateControl.c -- реализация библиотеки: ======== //
include "TimeStateControl.h"
// метод сохранения текущих данных для конечного автомата:
void tsc_init( TSC_Control *_tsc, TSC_Step * _table, uint8_t _state, TSC_Time _wait )
{
    _tsc->table = _table;                 // сохраняем теблицу переходов для этого КА
    tsc_next(_tsc, _state, _wait);        // устанавливаем его текущее состояние в начальное
}

// метод перехода к следующему шагу конечного автомата:
void tsc_next( TSC_Control *_tsc, uint8_t _state, TSC_Time _wait )
{
    _tsc->state = _state;                      // устанавливаем следующее состояние
    _tsc->timeout = millis() + _wait;          // и его время ожидания
}

// метод "шаг цикла КА":
// параметр - указатель на состояние заданного КА.
void tsc_step( TSC_Control *_tsc )
{
  if( millis() >= _tsc->timeout )         // если событие, переключающее КА - наступило:
  {
    TSC_Step * current;                   // определяем место хранения структуры состояния КА

    current = _tsc->table + _tsc->state;  // и находим "текущее состояние КА"

    current->command();                            // исполняем команду
    tsc_next(_tsc, current->state, current->timeout); // и устанавливаем следующий шаг КА
  }
}

И вот теперь, собственно скетч в автоматном стиле.

Имеем задачу: "включить светодиод, подождать 1 сек., выключить и подождать ещё 1 секунду. Повторять пока не надоест".

Имеем константы: ножка светодиода - 13, задержка включенного состояния - 1000 миллисекунд, задержка выключенного состояния - 1000 миллисекунд.

Так и пишем:

#define pinLed            13 // ножка платы со встроенным светодиодом
#define WAIT_ON         1000 // длительность состояния "ожидание выключения" мсек.
#define WAIT_OFF        1000 // длительность состояния "ожидание включения" мсек.

Далее, обнаруживаем что у нас "итого" два состояния: "выключен, ждем включения" и "включен, ждем выключения".

Создаем таблицу переходов:

// Таблица переходов нашего конечного автомата (нумерация с 0!):
TSC_Step        ledTable[2] = {
   { 1, to_on, WAIT_ON}         // "выключен": включить, перейти к состоянию "включен" с задержкой
  ,{ 0, to_off, WAIT_OFF}       // "включен": всё наоборот, выключаем, переходим к пред. состоянию.
};

Обнаруживаем что в таблице требуются два действия: to_on (включить) и to_off (выключить).

Создаем их:

void to_on() { digitalWrite(pinLed, HIGH); }     // .. включаем светодиод
void to_off(){ digitalWrite(pinLed, LOW); }      // .. выключаем светодиод

И наконец, описываем текущие переменные этого конечного автомата,

TSC_Control     ledControl;     // тут будем хранить текущие данные этого КА

... вставляем код его начальной настройки:

{               
  pinMode(pinLed, OUTPUT);                        // ножку светодиода настраиваем как "это выход"
  tsc_init(&ledControl, ledTable, 0, WAIT_OFF);  // добавляем КА и задаем его начальное состояние
}

и собственно исполняемая часть:

void loop()
{
  tsc_step( &ledControl );
}

Итого, скетч в стиле автоматного программирования "в сборе":

// ============================== описание КА "blink" =================================== //
// Собственно содержимое Arhat.lib::TSC_Blink/TSC_Blink.ino

/*
 * Светодиод - как конечный автомат имеет состояния:
 *     LED_OFF (выключен, ожидание включения)
 *     LED_ON  (включен, ожидание выключения)
 *
 * Событием, изменяющим состояние является счечик времении.
 */

#define _ARHAT_ 1
#include "arhat.h"

#define pinLed            13 // ножка платы со встроенным светодиодом
#define WAIT_ON         1000 // длительность состояния "ожидание выключения" мсек.
#define WAIT_OFF        1000 // длительность состояния "ожидание включения" мсек.

// Таблица переходов нашего конечного автомата (нумерация с 0!):
TSC_Step        ledTable[2] = {
   { 1, to_on, WAIT_ON}         // "выключен": включить, перейти к состоянию "включен" с задержкой
  ,{ 0, to_off, WAIT_OFF}       // "включен": всё наоборот, выключаем, переходим к пред. состоянию.
};

// Методы, исполняемые при изменении состояния КА:
void to_on() { digitalWrite(pinLed, HIGH); }     // .. включаем светодиод
void to_off(){ digitalWrite(pinLed, LOW); }      // .. выключаем светодиод

TSC_Control     ledControl;     // тут будем хранить текущие данные этого КА

// используем это для начальной настройки конечного автомата (далее КА)
void setup()
{               
  pinMode(pinLed, OUTPUT);                        // ножку светодиода настраиваем как "это выход"
  tsc_init(&ledControl, ledTable, 0, WAIT_OFF);  // добавляем КА и задаем его начальное состояние
}

// просто вызываем выполнение текущего шага КА, если получится.
void loop()
{
  tsc_step( &ledControl );
}

... и никаких delay() ... не требуется. :)

Надо дополнительно управлять серводвигателем? Да не проблема!

Смотрим на задачу, выделяем состояния сервы, методы управления. Создаем таблицу переходов между состояниями и запускаем следом "ещё один КА".

... и никаких проблем "подружить светодиод с сервой"...

Для программистов:

9. Опять же можно заметить, что никто не мешает обрабатывать отдельные состояния устройства в обработчиках прерываний, системах разделяемого времени и т.д. Достаточно грамотно изменять текущее состояние (консистентно). И только. Если "где-то", "кто-то" изменил состояние КА, то на очередном шаге КА ... просто продолжит исполнение далее с того места, куда его "втыкнули" (переставили указатель номера текущего состояния).

Новичкам:

1. Данный пример, было бы замечательно подкрепить чтением теории конечных автоматов. Там много математики, но на самом деле она примитивна. Если чего непонятно - смотреть в этот пример и пример по ссылке на википедию. Они слегка отличаются подходами:

В вики - пример изменения шага "по событию" - в данном случае чтению символа. Тут - "по времени".

2. Для работы КА тут использована функция millis(). Она достаточно нетороплива (впрочем и работает не совсем корректно), и сделать КА с задержками в 1..10 микросекунд так не получится. Простая замена на micros() - "пройдет" далеко не всегда. Уже надо будет учитывать время срабатывания библиотечных функций КА... или "где-то" вставлять небольшие задержки "вручную"... в общем "решать по месту".

P.S.

За сим, предпочитаю откланятся и распрощаться с троллями этого быдло-форума. Сегодняшнее "обсуждение" наглядно показало, что "слаще морковки" ("гарвардского картиночного образования" (с) команда Пухлявых) они точно ничего не видели, и КАК программировать микроконтроллеры - тупо НЕ ЗНАЮТ.

Puhlyaviy
Puhlyaviy аватар
Offline
Зарегистрирован: 22.05.2013

Arhat109 пишет:

За сим, предпочитаю откланятся и распрощаться с троллями этого быдло-форума. Сегодняшнее "обсуждение" наглядно показало, что "слаще морковки" ("гарвардского картиночного образования" (с) команда Пухлявых) они точно ничего не видели, и КАК программировать микроконтроллеры - тупо НЕ ЗНАЮТ.

скупая мужская слеза.. на кого ж ты нас покинул..

конечно не знаем. куда уж нам.. мы если програмит чего. то только пачками :)

http://arduino.ru/forum/proekty/vklyuchatel-vyklyuchatel-s-mozgom-i-orga...

bwn
Offline
Зарегистрирован: 25.08.2014

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

Arhat109
Offline
Зарегистрирован: 26.05.2015

Ну и вот примерно такой результат получается в ИДЕ:

Итого имеем 1556 байт кода вместо 1518 оригинальных. 16 байт оперативной памяти, вместо 8. Вполне нормальный результат для возможности программировать скетчи на "протокольном" уровне. "Как сказано - так и записали".

Пользуйтесь. :)

Puhlyaviy
Puhlyaviy аватар
Offline
Зарегистрирован: 22.05.2013

bwn пишет:

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

(достал карандаш и записал еще одного члена групировки "Пухлявый & Со")

ты зря разоряешься. он не понимает. думает что мы шутим. 

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

Arhat109
Offline
Зарегистрирован: 26.05.2015

Дополняйте, пишите доходчивее. Изложил "как умел". То, что тут изложено - восстановлено "по памяти" в силу отъезда ровно за то время, что указано в постах ... между походами "на пляж", "в магазинчик" и т.д. :)

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

В общем, далее без меня. Думаю справитесь. :)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Arhat109, по мне, так больно сложно. Почему бы например, таблицу переходов не представить классической матрицей состояний? Код сокращается почти до нуля и всё упрощается.

Puhlyaviy
Puhlyaviy аватар
Offline
Зарегистрирован: 22.05.2013

ЕвгенийП пишет:

Arhat109, по мне, так больно сложно. Почему бы например, таблицу переходов не представить классической матрицей состояний? Код сокращается почти до нуля и всё упрощается.

Ваши оппоненты пользуются тем, что они умнее. Это нечестно.

Arhat109
Offline
Зарегистрирован: 26.05.2015

Ну, положим код "до нуля" не сокращается... исполнительную часть всё одно а делать придется. И она НЕ уменьшается ни при каком подходе. Тут - это два метода по одному оператору: to_on() и to_off().

Уменьшаются "накладные расходы"? Возможно. Но за счет существенного на мой взгляд сужения функциональности.

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

Этот пример, куда как красивее выглядит на С++. Класс TSC_COntrol можно переопределять в сторону всех тех пунктов, что были описаны и ... тупо иметь "всё разнообразие" конечных автоматов " в одном флаконе". :)

P.S.

... а впрочем, выкладывайте свои решения. Тема для этого как раз и создана.

Arhat109
Offline
Зарегистрирован: 26.05.2015

Я все-таки откланяюсь. Тем кто хочет научится программировать микроконтроллеры, таки советую ЧИТАТЬ литературу. В ней нет ничего "заумного" или сложного. Порой за развесистыми мат. выкладками стоит вот такая вот "банальщина" в коде. До сентября меня точно не будет, а там уже и пароль забуду... :)

Puhlyaviy
Puhlyaviy аватар
Offline
Зарегистрирован: 22.05.2013

Arhat109 пишет:

... а впрочем, выкладывайте свои решения. Тема для этого как раз и создана.

хммм. попробую еще раз. какие решения выкладывать? если тема никому не интересна! новички не осилят, а разбирающие не часто пользуются ардуино ИДЕ..  щас открою тебе страшную тайну. у АТМЕЛ есть Атмел студио... береш до слез знакомый С++ и шпариш километры кода как тебе хочеться...

Maverik
Offline
Зарегистрирован: 12.09.2012

А зачем вообще столько букав ? Весь этот многостраничный опус можно уместить в одну простую фразу.  Если нужно, чтобы во время ожидания, программа делала что-то еще, то delay() не подходит, вместо него записываем время начала задержки в переменную и в цикле проверяем, достигнут период задержки или нет. И всё ! Не нужно никаких ссылок на пулемёты Макарова и машины Тьюринга. И не нужно столько пафоса )) В этом же цикле пишем нужный рабочий код. Прикол в том, что в большинстве случаев начинающим и НЕ НУЖНО, чтобы во время delay() делалось что-то еще.  Мне самописная функция задержки понадобилась по факту только тогда, когда начал использовать вочдог, и для того, чтобы сбрасывать счетчик вочдога во время задержки и сделал свою функцию. 

И еще - по классам  и структурам. Эти штуки, безусловно, полезные, но где документировано, в каком объеме ардуина поддерживает стандарты C++ ? А нигде. Какие-то фишки работают даже недокументированно, другие, которые вроде бы должны работать - не работают. Я как-то хотел перейти на IDE N.Beans и стал из функций делать методы классов. Засада пришла очень быстро при адаптировании методов прерываний. Оказалось, что там нужно интенсивно прыгать с бубном. ((( А жаль...  Вы вообще в какой среде пишете код для ардуин ?

Puhlyaviy
Puhlyaviy аватар
Offline
Зарегистрирован: 22.05.2013

Maverik пишет:
  Вы вообще в какой среде пишете код для ардуин ?

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

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

Maverik
Offline
Зарегистрирован: 12.09.2012

Вообще описание всевозможных делеев-без-делея есть в любом учебнике ардуины, напр. на официальном сайте в десятке вариантов http://playground.arduino.cc/Code/AvoidDelay  . Удивить на эту тему сейчас пытаются только avr- кодом где с регистрами работают напрямую, и то даже это уже особо никого не удивляет, т.к. новички иногда приходят с других микроконтроллеров, где с этим делом всё пожестче, и удивить их тем более нечем )) 

Мне вообще ближе С чем С++, была просто одна хотелка - хотелось быстро и удобно подключать функции к новым проектам. Т.е. просто деалать собственные библиотеки.Но связавшись с полноценным С++ в ардуино-варианте получил пляски с бубном, а плясать их было не интересно. Там нужно вгружаться серьезно. Так эта мысль и потухла. Атмел Студио ставил разумеется, вообще все оболочки перепробовал, Эклипс, НетБинс, АтемлСтудио, АВР Студио еще кучу, всего и не упомню. АтмелСтудио тормозил аццки на моем ноутбуке особенно при загрузке.

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

Иногда отключаю стандартный редактор и пишу в Notepad++. 

nevkon
Offline
Зарегистрирован: 20.01.2015

to Arhat109

Решение довольно интересное в итоге получилось. Если система делается, то она должна быть отказоустойчива. У millis() и micros() есть цикличность - сброс в 0 через определенные интервалы времени. Если система должна работать месяцами без перезагрузки, то как решаете эту проблему? Я делал функцию:

unsigned long get__prev_millis(unsigned long interval) {
  unsigned long tmp;
  tmp=millis();
  if (tmp>4294965000-interval) {
    tmp=0;
  }
  return tmp;
}

Может есть более простые/правильные решения?