Цифровые автоматы для Qwone :)

Beginer123
Offline
Зарегистрирован: 23.11.2018

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

Заголовочный файл ka.h

//
// Библиотека конечных автоматов для Ардуино ("С")
// здесь далее КА - "конечный автомат"
//
// @author Arhat109-201902.. v.2.0
//
#ifndef _KA_H_
#define _KA_H_ 1

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

// Укорачиваем место хранения времени и экономим память:
// переопределение интервалов до 65535 мсек
typedef uint16_t KaTime;

// ****************** 1. Автоматы, управляемые временем ***************** //

// переопределяем функцию получения времени: укорачивем возвращаемое значение до 2 байт
// максимально допустимый интервал между исполнениями 65.535 сек.
#define kaMillis()   ((KaTime)millis())

// Простейший конечный автомат сначала исполняющий действие command и
// затем ожидающий его повторения через interval тиков таймера по 1 миллисекунде.
// В качестве команды может быть даже последовательность команд через ; или прямой вызов функции!
// интервалы можно задавать константно и переменными (выражениями).
// При первом вызове сразу исполняет действие и настраивает интервал до следующего исполнения
//
// @example прямая вставка исполняемого кода в фигурных скобках:
//
// everyMillis(1000,
// {
//   digitalWrite(13, 1-digitalRead(13));
//   analogWrite(10, analogRead(A0)>>4);
// });
//
// @example или прямой вызов функции: everyMillis(1000, blink(); );
//
#define everyMillis(interval, action)         \
{                                             \
  static KaTime t = 0U;                       \
  if( kaMillis() - t > ((KaTime)(interval)) ) \
  {                                           \
    t = kaMillis();                           \
    { action }                                \
  }                                           \
}

// Выделенные коды состояний для всех типов конечных автоматов тут:
// автомат "выключен":
#define KA_STATE_OFF -1
// нет нового состояния перехода (не изменять!)
#define KA_STATE_NO  -2

// ****************** 2. Автоматы времени, управляемые таблицей состояний(переходов) ***************** //

// Все действия автомата определяются интервалами между ними - разного рода "мигалки".
// Порядок: сначала вызывается команда, а потом(!) выдерживается указанная в строке пауза до указанной строки
//   "Выполнить текущую команду и ждать этот интервал до заданной строки"
// Нумерация состояний с 0
// Слушатель входного потока - тут встроен и занят только ожиданием завершения таймаута.
// Исполнитель команды возвращает bool - откуда брать новое состояние {stateOk|stateNo}

typedef struct _ka_time_ctrl KaTimeControl;

// Функции - исполнители действий автоматов времени (моргалок)
// Получает структуру своего КА на всякий случай..
// Возвращает bool, выбор следующей строки по результату из колонки: nextOk или nextNo
typedef int8_t (*KaTimeCommand)(KaTimeControl *_ka);

// Преобразователь указателей к типу "команда автомата времени"
#define ptrKaTimeCommand(ptr) ((KaTimeCommand)(ptr))

// Тип для строки таблицы состояний автомата:
typedef struct {
  KaTimeCommand command;        // команда (метод) для исполнения действий состояния
  KaTime        timeout;        // временной интервал до(!) следующего состояния (мсек)
  int8_t        stateOk;        // успешно: номер следующей команды в таблице состояний КА
  int8_t        stateNo;        // ошибка: номер следующей команды в таблице состояний КА 
} KaTimeStep;

// Преобразователь указателей к строке таблицы
#define ptrKaTimeStep(ptr) ((KaTimeStep *)(ptr))

// Собственно КА, управляемый таблицей состояний и интервалов
typedef struct _ka_time_ctrl {
  KaTime       started_at;      // момент начала текущего интервала ожидания
  KaTime       timeout;         // текущий интервал до запуска строки state
  int8_t       nextState;       // ожидаемый к исполнению номер или выключен
  KaTimeStep * table;           // таблица состояний и переходов этого КА
} KaTimeControl;

// Преобразователь указателей к управляющей структуре КА
#define ptrKaTimeControl(ptr) ((KaTimeControl *)(ptr))

// setup() или динамическая смена таблицы без изменения состояния
inline void kaTimeSetup( KaTimeControl *_ka, KaTimeStep * _table){ _ka->table = _table; }

// Принудительное изменение шага: и сразу исполняет его команду.
void kaTimeGo( KaTimeControl *_ka, uint8_t _toStep );

// В loop(): исполнение шага, если прошел интервал с предыдущего (его помним)
//   Если таблица не задана - ничего не выполняет (отключение автомата)
//   Если в таблице нет команды (==0) производит задержку до следующего выполнения, ничего не вызывает
//   Время исполнения текущей команды входит в интервал ожидания следующей!
void kaTimeRun( KaTimeControl *_ka );

// оно же для таблицы управления, размещенной во flash
void pgmTimeGo( KaTimeControl *_ka, uint8_t _toStep );
void pgmTimeRun( KaTimeControl *_ka );

// ****************** 3. Обобщенный конечный автомат состояний с потоком событий ***************** //
// Слушатель входного потока принимает решение "событие наступило"(1) или изменено значение поля event(!=0)
// kaRun() вызывает слушателя потока, если он есть и по его результату может вызвать исполнителя,
// который самостоятельно по коду события (event) И текущему состоянию (state) решает что делать и
// в какое состояние впасть теперь конечному автомату.
// Слушатель может отсутствовать в явном виде или быть обработчиком прерывания. Также может менять состояние КА.
// Исполнитель возвращает новое состояние или выключает автомат.
// Сброс события в 0 после запуска исполнителя.

typedef struct _ka_control KaControl;

// "слушатель входного потока" - функция, изменяющая "событие" в КА, может отсутствовать
// может возвращать bool(!0 - событие произошло) или напрямую изменять поле event(не ноль)
typedef int8_t (*KaListener)(KaControl *_ka);
#define ptrKaListener(ptr) ((KaListener)(ptr))

// "исполнитель КА" -- функция, получающая указатель на свой блок данных КА
// и выполняющая нужное действие по состоянию автомата. Возвращает новое состояние.
typedef int8_t (*KaCommand)(KaControl *_ka);
#define ptrKaCommand(ptr) ((KaCommand)(ptr))

typedef struct _ka_control {
  KaListener      listener;     // функция - слушатель
  KaCommand       command;      // функция - исполнитель действий КА
  int8_t          state;        // текущее состояние исполнителя, <0 - выключен
  volatile int8_t event;        // "событие" (м.б. из прерывания), ==0 - нет событий
} KaControl;
#define ptrKaControl(ptr) ((KaControl *)(ptr))

// принудительный перевод в состояние. Ничего не исполняет и сбрасывает событие!
void kaGo(KaControl *_ka, int8_t toState);
// для loop(): опрашивает слушателя потока и запускает исполнителя, который
// сам выбирает набор действий по состоянию, событию или обоим: событию и предыдущему состоянию
void kaRun( KaControl *_ka );

// ****************** 4. Конечный автомат (Милли) с таблицей состояний и потоком событий ***************** //
// Милли - имеет таблицу состояний, каждое из которых имеет набор входных событий и связанного действия.
// Исполнение действия происходит при возбуждении события слушателем входного потока.
// Переключение в новое состояние производится сразу по завершению исполнителя.
// ИТОГО: автомат Милли срабатывает на переходах между состояниями по входным событиям и состоянию - единожды.
// Слушатель и Исполнитель используются от обощенного КА и его структура как часть base (как-бы наследование в "С")
// Исполнитель команды тут возвращает bool для выбора какое состояние взять следующим: stateOk | stateNo

// Каждый элемент(шаг) списка событий содержит: событие, команду исполнителю и номера следующего состояния "ок" и "ошибка"
// состояние KA_STATE_OFF выключит автомат.
typedef struct {
  int8_t        event;
  KaCommand     command;
  int8_t        stateOk;
  int8_t        stateNo;
} KaMilliStep;
#define ptrKaMilliStep(ptr) ((KaMilliStep *)(ptr))

// строка состояния со списком событий. Каждое состояние Милли имеет свой список событий с командами и переходами
typedef struct {
  uint8_t       maxEvents;
  KaMilliStep * row;
} KaMilliRow;
#define ptrKaMilliRow(ptr) ((KaMilliRow *)(ptr))

// Таблица состояний Милли - каждая строка содержит список событий этого состояния
typedef struct {
  KaControl      base;
  KaMilliRow   * table;
} KaMilliControl;
#define ptrKaMilliControl(ptr) ((KaMilliControl *)(ptr))

// для setup(): установка таблицы состояний и переход к заданному. Ничего не исполняет!
void kaMilliSetup(KaMilliControl *_ka, KaMilliRow *_table, bool isProgmem, int8_t startState);

// управление автоматом, "встраиваемое" как исполнитель универсального КА (выше) в kaMilliSetup()
// должны получать указатель на KaMilliControl в виде базового KaControl (преобразовывать ручками!)
// типа protected (для местных функций тут) для таблицы в ОЗУ и flash соответственно:
// !!! Не есть правильно: опираемся на порядок хранения полей структур в памяти компилятором
int8_t _kaMilliDo(KaControl *_ka);
int8_t _pgmMilliDo(KaControl *_ka);

// ****************** 5. Конечный автомат (Мура) с таблицей состояний с потоком событий ***************** //
// Мура - в отличии от предыдущего имеет отдельный поток входных событий.
// Состояния автомата переключаются слушателем входного потока только по событиям, в т.ч. извне (обработчик прерываний).
// Для переключения состояний слушатель дожен вызывать xxMuraGo() .. можно указывать как слушателя базового КА
//   при работе с событиями через обработчик прерывания (установка в нем только ->base.event)
// Команда исполнителя вызывается при каждом вызове, пока не изменится состояние автомата.
// В отличии от, тут результат исполнения команды ни на что не влияет.
// Список команд исполнителя по состояниям дополнительно содержит счетчик "сколько раз исполнять" для удобства.
// счетчик == 0 - непрерывно, при каждом вызове.

// состояния внутреннего автомата-повторителя команд: "первый запуск", "исполнять всегда", "сколько задано"
#define KA_MURA_FIRST 1
#define KA_MURA_ALL   2
#define KA_MURA_COUNT 0

// список команд и повторителей (значения) по состояниям автомата
typedef struct {
    KaCommand command;
    int8_t    count;
} KaMuraStep;
#define ptrKaMuraStep(ptr) ((KaMuraStep *)(ptr))

// Автомат Мура
typedef struct {
  KaControl    base;
  int8_t     * events;  // список: событие => состояние (един для всего КА!)
  KaMuraStep * states;  // список команд и повторений по состоянию (не зависит от событий!)
  int8_t       ticks;   // текущий остаток повторений
  bool         isTicks; // вн. "состояния" повторителя
} KaMuraControl;
#define ptrKaMuraControl(ptr) ((KaMuraControl *)(ptr))

// Переключатель состояний по событию. Использовать внутри слушателя базового КА или как слушатель базового КА
// Обрабочик прерывания должен только сохранить код события в ->base.event
// На самом деле ожидает указатель на KaMuraControl
int8_t kaMuraGo(KaControl *_ka);
// Исполнитель для базового КА. Всегда возвращает KA_STATE_NO. Вызывается из kaRun()!
// На самом деле ожидает указатель на KaMuraControl
int8_t kaMuraDo(KaControl *_ka);
// аналоги для размещения таблиц управления во flash
int8_t pgmMuraGo(KaControl *_ka);
int8_t pgmMuraDo(KaControl *_ka);

#ifdef __cplusplus
}
#endif

#endif // _KA_H_

реализация ka.c

/**
 * Реализация функций для создания и управления конечными автоматами
 * @author Arhat109-201902..
 */
#include <Arduino.h>
#include <avr/pgmspace.h>

#include "ka.h"

// ************ 2. КА по времени с таблицей состояний (мигалки) *********** //

// Принудительное изменение шага: и сразу исполняет его команду.
//   Если в таблице нет команды (==0) производит задержку до следующего выполнения, ничего не вызывает
//   Время исполнения текущей команды входит в интервал ожидания следующей!
//   Если таблица не задана или next<0 - ничего не выполняет (отключение автомата)
void kaTimeGo( KaTimeControl *_ka, uint8_t _toStep )
{
  if( _ka->table )                                               // есть таблица программы КА
  {
    int8_t res = 1;                                              // нет команды - значит "всё ок".

    _ka->started_at = kaMillis();                                // время ожидания: начало тут
    KaTimeStep * ptrStep = _ka->table + _toStep;                 // идем к строке
    _ka->timeout = ptrStep->timeout;                             // ждать будем столько

    if( ptrStep->command ){ res = (ptrStep->command)(_ka); }     // исполняем команду сразу тут

    _ka->nextState = (res? ptrStep->stateOk : ptrStep->stateNo); // следующий шаг
  }
}

// В loop(): исполнение шага, если прошел интервал с предыдущего (его помним)
void kaTimeRun( KaTimeControl *_ka )
{
  KaTime skippedTime = kaMillis() - _ka->started_at; // сколько уже прошло времени

  if( (_ka->nextState != KA_STATE_OFF) && (skippedTime >= _ka->timeout) )
  {
    kaTimeGo(_ka, _ka->nextState);
  }
}

// ====== 2.1 версии для размещения таблицы управления во flash ====== //

void pgmTimeGo( KaTimeControl *_ka, uint8_t _toStep )
{
  if( _ka->table )
  {
    _ka->started_at = kaMillis();   // время ожидания пошло
    {
      int8_t          res=1;
      KaTimeStep    * ptrStep = _ka->table + _toStep;  // flash-адрес!
      KaTimeCommand   command = ptrKaTimeCommand(pgm_read_word_near( &(ptrStep->command) ));

      _ka->timeout = pgm_read_word_near( &(ptrStep->timeout) );
      if( command ){ res=(command)(_ka); }
      _ka->nextState = res?
            pgm_read_byte_near( &(ptrStep->stateOk) )
          : pgm_read_byte_near( &(ptrStep->stateNo) )
      ;
    }
  }
}

void pgmTimeRun( KaTimeControl *_ka )
{
  KaTime skippedTime = kaMillis() - _ka->started_at; // сколько уже прошло времени

  if( (_ka->nextState != KA_STATE_OFF) && (skippedTime >= _ka->timeout) )
  {
    pgmTimeGo(_ka, _ka->nextState);
  }
}

// ************ 3. КА с входным потоком событий ************ //

// принудительный перевод в состояние. Ничего не исполняет и сбрасывает событие!
void kaGo(KaControl *_ka, int8_t _toState)
{
  _ka->state = _toState;
  _ka->event = 0;
}

// для loop(): опрашивает слушателя потока и если событие произошло, то запускает исполнителя
void kaRun( KaControl *_ka )
{
  if( _ka->state != KA_STATE_OFF )                              // включен?
  {
    int8_t res = 0;                                             // по умолчанию "нет события"

    if( _ka->listener ){ res = (_ka->listener)(_ka); }          // слушатель
    if( res || _ka->event )                                     // результат или событие
    {
      int8_t newState;

      if( _ka->command ){
        newState = (_ka->command)(_ka);                         // новое состояние
        if( newState != KA_STATE_NO ){ _ka->state = newState; } // сохраняем, если новое или выключить
      }
      _ka->event = 0;                                           // обработано
    }
  }
}

// ************ 4. КА Милли с таблицей состояний и входным потоком событий ************ //

// для setup(): установка таблицы состояний и переход к заданному. Ничего не исполняет!
void kaMilliSetup(KaMilliControl *_ka, KaMilliRow *_table, bool isProgmem, int8_t _startState)
{
  _ka->table = _table;
  _ka->base.command = isProgmem ? _pgmMilliDo : _kaMilliDo;
  kaGo( &(_ka->base), _startState);
}

// Виртуализация на "С": Функция для обощенного автомата как "исполнителя Милли"
// на самом деле должна получать указатель на KaMilliControl - нужна таблица поведения автомата
int8_t _kaMilliDo( KaControl *_ka )
{
  KaMilliControl * milli = ptrKaMilliControl(_ka); // приводим явно: он и ожидается

  if( milli->table ){
    // ищем событие и пробуем запустить команду состояния
    // 1. ищем список событий текущего состояния. Он может быть "дырявым" (не все события)
    KaMilliRow  * r   = (milli->table + milli->base.state);
    KaMilliStep * e   = r->row;
    uint8_t       num = r->maxEvents;

    // 2. ищем событие в списке:
    do{
      if( e->event == milli->base.event ){       // нашли?
        int8_t res2 = 1;                         // по умолчанию выбираем stateOk

        if( e->command ){ res2=(e->command)( ptrKaControl(milli) ); }

        return res2? e->stateOk : e->stateNo;
      }
      e++;
    }while(--num);
  }
  return KA_STATE_NO;
}

// ************ 4.1 КА Милли с таблицей состояний и входным потоком во FLASH ************ //

// на самом деле принимаем KaMilliControl *! С ним и работаем далее
int8_t _pgmMilliDo( KaControl * _ka )
{
  KaMilliControl * milli = ptrKaMilliControl(_ka);

  if( milli->table )
  {
    KaMilliRow  * r  = (milli->table + milli->base.state);              // into flash!
    KaMilliStep * e  = ptrKaMilliStep(pgm_read_word_near( &(r->row) )); // into flash too!
    uint8_t      num = pgm_read_byte_near( &(r->maxEvents) );

    // 2. ищем событие в списке во flash:
    do{
      int8_t event = pgm_read_byte_near( &(e->event) );

      if( event == milli->base.event ){ // нашли?
        KaCommand command = ptrKaCommand(pgm_read_word_near( &(e->command) ));
        int8_t    stateOk = pgm_read_byte_near( &(e->stateOk) );
        int8_t    stateNo = pgm_read_byte_near( &(e->stateNo) );
        int8_t    res2    = 1; // по умолчанию stateOk

        if( command ){ res2=(command)( ptrKaControl(milli) ); }
        return res2 ? stateOk : stateNo;
      }
      e++;
    }while(--num);
  }
  return KA_STATE_NO;
}

// *********************************** 5 КА Мура *********************************** //

// переключатель сотояний по заданному событию в ->base.event
int8_t kaMuraGo(KaControl *_ka)
{
  KaMuraControl * mura = ptrKaMuraControl(_ka);

  if( mura->events ) // есть таблица состояний
  {
    mura->base.state = *(mura->events + mura->base.event);
    mura->isTicks = KA_MURA_FIRST;
    return 1; // состояние изменено!
  }
  return 0; // а не на что менять - нет таблицы
}

// PRIVATE! внутренний исполнитель..
void _muraDo(KaMuraControl *_mura, int8_t _ticks, KaCommand _command)
{
  if( _mura->isTicks == KA_MURA_FIRST ){                         // первое исполнение - кешируем повторитель заново
    _mura->ticks = _ticks;
    _mura->isTicks = (_ticks? KA_MURA_COUNT : KA_MURA_ALL);      // если не задано, то "вечно"
  }
  if( _mura->isTicks==KA_MURA_ALL || (_mura->ticks--) > 0 )      // вечно или задано повторение (уменьшаем сразу)
  {
    if( _command ){ _command( &(_mura->base) ); }                // запуск команды
  }
}

// Исполнитель для базового КА. Всегда возвращает KA_STATE_NO. Вызывается из kaRun()
int8_t kaMuraDo(KaControl *_ka)
{
  KaMuraControl * mura = ptrKaMuraControl(_ka);
  KaMuraStep    * s = mura->states + mura->base.state;             // переход на шаг состояния

  _muraDo(mura, s->count, s->command);
  return KA_STATE_NO;
}

// ************ 5.1 КА Мура с таблицей состояний и входным потоком во FLASH ************ //
int8_t pgmMuraGo(KaControl *_ka)
{
  KaMuraControl * mura = ptrKaMuraControl(_ka);

  if( mura->events )
  {
    mura->base.state = pgm_read_byte_near(mura->events + mura->base.event); // in flash!
    mura->isTicks = KA_MURA_FIRST;
    return 1;
  }
  return 0;
}

int8_t pgmMuraDo(KaControl *_ka)
{
  KaMuraControl * mura = ptrKaMuraControl(_ka);
  KaMuraStep    * s = mura->states + mura->base.state;    // переход на шаг состояния во flash!

  _muraDo(mura, pgm_read_byte_near(s->count), ptrKaCommand(pgm_read_word_near( &(s->command) )));
  return KA_STATE_NO;
}

Примеры:

1. на макросе everyMillis()

// Пример1 -- моргалка встроенным светодиодом через макрос everyMillis()

#include "ka.h"

// нога, которой мигаем:
#define pin13     13

// половина периода мигания в мсек:
#define halfTurn 500

void setup() {
  pinMode(pin13, OUTPUT);
}

void loop() {
  everyMillis(500, {
    digitalWrite(pin13, (digitalRead(pin13)? LOW : HIGH) );
  });
}

2. Пример конечного автомата, управляемого от времени (моргалки)

// пример2 -- RGB моргалка (светофор) со сложной программой по времени (пишем "снизу-вверх")

#include "ka.h"

// 4. Назначем пины светофору и делаем функцию инициализации:
#define pinRed    2
#define pinYellow 3
#define pinGreen  4

void rgbSetup(){
  pinMode(pinRed,    OUTPUT);
  pinMode(pinYellow, OUTPUT);
  pinMode(pinGreen,  OUTPUT);
}

// 3. Создаем "команды" светофора:
int8_t doRGB_Off(KaTimeControl *_ka)
{
  digitalWrite(pinRed, LOW);
  digitalWrite(pinYellow, LOW);
  digitalWrite(pinGreen, LOW);
  return 1;
}
int8_t doRGB_Red(KaTimeControl *_ka)
{
  doRGB_Off(_ka);
  digitalWrite(pinRed, HIGH);
  return 1;
}
int8_t doRGB_Yellow(KaTimeControl *_ka)
{
  doRGB_Off(_ka);
  digitalWrite(pinYellow, HIGH);
  return 1;
}
int8_t doRGB_Green(KaTimeControl *_ka)
{
  doRGB_Off(_ka);
  digitalWrite(pinGreen, HIGH);
  return 1;
}
int8_t doRGB_All(KaTimeControl *_ka)
{
  digitalWrite(pinRed, HIGH);
  digitalWrite(pinYellow, HIGH);
  digitalWrite(pinGreen, HIGH);
  return 1;
}

// генератор ошибки (примера для)
int8_t doRGB_Error(KaTimeControl *_ka)
{
  return (rand()%100 > 10? 1 : 0);
}

// 2. Создаем "программу" для светофора
// Закомментирован вариант объявления (и запуска) с размещением программы во flash
// Возврат любой командой "ошибки" (0) приведет к аварии "морганию всеми светодиодами"

// const KaTimeStep rgbProg[] PROGMEM = {
KaTimeStep rgbProg[] = {
// поля:  command,    timeout,       nextOk,      nextNo
       { doRGB_Off,       0,KA_STATE_OFF,KA_STATE_OFF}, //  0: светофор выключен и стоит в этом шаге
       { doRGB_Red,    3000,  2,  9}, //  1: включить красный на 3сек
       { doRGB_Yellow,  500,  3,  9}, //  2: включить желтый на 0.5сек
       { doRGB_Green,  1000,  4,  9}, //  3: включить зеленый на 1 сек
       { doRGB_Off,     250,  5,  9}, //  4: выключить
       { doRGB_Green,   250,  6,  9}, //  5: мигаем зеленым
       { doRGB_Off,     250,  7,  9}, //  6: выключить
       { doRGB_Green,   250,  8,  9}, //  7: мигаем зеленым
       { doRGB_Error,     0,  1,  9}, //  8: ошибка? повторяем программу или переходим на обработку ошибки

       { doRGB_All,     250, 10, 10}, //  9: Ошибка светофора! Включаем все на 0.25сек
       { doRGB_Off,     250, 11, 11}, // 10: Ошибка светофора! Выключаем всё на 0.25сек или стоп.
       { doRGB_Error,     0,  1,  0}, // 11: ошибка? повторная ошибка выключает светофор..
};

// 1. Определяем глобальную структуру для светофора (тут задан старт с первой, а не нулевой записи!):
KaTimeControl rgb = {
// поля: started_at, timeout, nextState, table
              0,        0,        1,    ptrKaTimeStep(rgbProg) // желательно явное приведение при размещении во flash
};

// ============== собственно скетч: ===================

void setup() {
  rgbSetup();
}

void loop() {
  kaTimeRun( &rgb );
//  pgmTimeRun( &rgb );
}

Есть ещё мой пример автомата Милли для клавиатуры (той самой) где происходит сканирование 4х4 клавиатуры и обработка по "нужным" клавишам на отпускание, со списком исполняемых функций на каждую клавишу или их группу: ввод числа (0-9), исправление ввода (*), завершение ввода (#) и клавиши - события (A-D), не под руками, может попозжа.

b707
Онлайн
Зарегистрирован: 26.05.2017

Beginer123 пишет:

Получил добро на публикацию от автора....

 

Есть ещё мой пример автомата ....

Спалился?

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

sadman41
Offline
Зарегистрирован: 19.10.2016

Да... прямо слышу, как жыр потёк.

Beginer123
Offline
Зарегистрирован: 23.11.2018

Нет, пример драйвера клавиатуры на базе его автомата Милли - мой собственный. :)

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

Beginer123
Offline
Зарегистрирован: 23.11.2018

Qwone, нашел какую-то версию конечного автомата Милли как обработчика клавиатуры, ловите:


// статически настраиваем пины строк (будут выходами сканирования) и столбцов (входы опроса кнопок)
uint8_t kbdRows[] = {2,3,4,5};
uint8_t kbdCols[] = {A0,A1,A2,A3};

// перед подключением настраиваемся на конкретную клавиатуру
#define KBD_MAX_ROWS 4
#define KBD_MAX_COLS 4
#define KBD_MAX_RESULT 4

#include "keyboard.h"

// Теперь доступен тип Kbd, определяем глобал "клавиатура"
Kbd glKbd;

// монитор нажимаемых клавиш:
// 0-9 -- ввод числа.
// 'A' -- время горения красного в секундах (до 99сек)
// 'B' -- время горения желтого в секуднах (до 9)
// 'C' -- время непрерывного горения зеленого в секундах (до 99)
// '*' -- исправление неверно введенной цифры (backspace)
// '#' -- сохранение ввода в нужном месте.
//
// вводим признаки какая длительность вводится сейчас:
bool isRed=false, isYellow=false, isGreen=false;
// Всего можно ввести цифр в буфер:
#define DIGIT_MAX 5
// место для хранения цифр числа (символов) (+1 символ - конец строки)
char digitInput[] = {'0','0','0','0','0', '\x0'};
// количество введенных цифр:
int8_t digitCount = 0;

// перевод номера кнопки в символ
char kbdToChar[] = {
  '1', '2', '3', 'A', '4', '5', '6', 'B', '7', '8', '9', 'C', '*', '0', '#', 'D'
};

// монитор клавиатуры - автомат Милли: реакция на входное событие (клавишу) в зависимости от состояния:

// 5. слущатель входного потока (клавиатуры) ищем только отпущенные кнопки:
int8_t kbdGetChar(KaControl *_ka)
{
  kbdScan( &glKbd );
  if( glKbd.countUpped > 0 ){
    _ka->event = (int8_t)(kbdToChar[glKbd.upped[0]]);
//    Serial.println(""); Serial.print("upped "); Serial.print(glKbd.upped[0], DEC);
//    Serial.print(", char="); Serial.println(_ka->event);
    return 1; // нашли кнопку!
  }
  return 0; // нет событий..
}

// 4. Функции - действия по отпущенной клавише:

// очистить буфер ввода и количество введенных цифр
int8_t kbdClearDO(KaControl *_ka)
{
  int8_t count = DIGIT_MAX;
  do{
    digitInput[--count] = '0';
  }while(count);
  digitCount = 0;
  return 1;
}

// Продижение младшей цифры в старший разряд, ввод в младший
int8_t kbdMovingDO(KaControl *_ka)
{
  int8_t cur = 0;
  do{
    digitInput[cur] = digitInput[cur+1];
  }while( ++cur < (DIGIT_MAX-1) );
  digitInput[cur] = _ka->event; // слушатель потока уже сложил символ в событие
  return 1;
}

// Ввод цифры в позицию переход к следующей, если есть куда
int8_t kbdInputDO(KaControl *_ka)
{
  if( digitCount < DIGIT_MAX ){
    digitInput[digitCount++] = _ka->event;
  }else{
    kbdMovingDO(_ka);
  }
//  Serial.println(digitInput);
  return 1;
}

// Исправление цифр (backspace)
int8_t kbdBackspaceDO(KaControl *_ka)
{
  if( digitCount > 0 ){
    digitCount--;
    digitInput[digitCount] = '0';
  }
  return 1;
}

// Переключение режима ввода 'A', 'B' или 'C' и очистка буфера ввода числа
int8_t kbdModeDO(KaControl *_ka)
{
  char newMode = (char)(_ka->event);

  isRed = isYellow = isGreen = false;
  switch(newMode){
    case 'A':
      isRed    = true;
//    Serial.println(""); Serial.println("mode Red:");
    break;
    case 'B':
      isYellow = true;
//    Serial.println(""); Serial.println("mode Yellow:");
    break;
    case 'C':
      isGreen  = true;
//    Serial.println(""); Serial.println("mode Green:");
    break;
  }
  return kbdClearDo(_ka);
}

// '#' (Enter) -- фиксация введенных цифр в переменной режима ввода
int8_t kbdEnterDO(KaControl *_ka)
{
  int res = atoi(digitInput);

       if( isRed    ){ rgbProg[1].timeout = res; }
  else if( isYellow ){ rgbProg[2].timeout = res; }
  else if( isGreen  ){ rgbProg[3].timeout = res; }

//  Serial.print(" timeout="); Serial.print(res, DEC);
  return 1;
}

// 3. фактически состояние одно: ждем отпускания клавиши:
const KaMilliStep stWork[] PROGMEM = {
  {'0', kbdInputDO,     1, 0},
  {'1', kbdInputDO,     1, 0},
  {'2', kbdInputDO,     1, 0},
  {'3', kbdInputDO,     1, 0},
  {'4', kbdInputDO,     1, 0},
  {'5', kbdInputDO,     1, 0},
  {'6', kbdInputDO,     1, 0},
  {'7', kbdInputDO,     1, 0},
  {'8', kbdInputDO,     1, 0},
  {'9', kbdInputDO,     1, 0},
  {'*', kbdBackspaceDO, 1, 0},
  {'#', kbdEnterDO,     1, 0},
  {'A', kbdModeDO,      1, 0},
  {'B', kbdModeDO,      1, 0},
  {'C', kbdModeDO,      1, 0},
};
// на всякий случай состояние "ошибка" - сброс всего
const KaMilliStep stError[] PROGMEM = {
  {0, kbdClearDO, 1, 1},
};

// 2. создаем таблицу состояний монитора клавиатуры
const KaMilliRow kbdStates[] PROGMEM = {
  { 1, ptrKaMilliStep(stError) },
  { 15, ptrKaMilliStep(stWork) }
};

// 1. Определяем автомат Милли:
KaMilliControl kbdMonitor = {
// поле base: listener, command, state, event
  { kbdGetChar, _pgmMilliDo, 1, 0},
  ptrKaMilliRow(kbdStates)
};

Тут ещё надо keyboard.h, но его не нашел. Там сканер самой клавиатуры kbdScan() и структура для сканирования Kbd, не влияет на понимание примера как использовать автомат Милли. Мне это понадобилось для пожарно-охранной сигнализации, модуль настройки параметров. Получился единый блок обработки кнопок с клавиатуры. Здесь kbdEnterDo() просто изменяет таймауты у автомата времени светофора из примера выше.

Logik
Offline
Зарегистрирован: 05.08.2014

Хреновенько, Ахат. Посмотрел RGB моргалка (светофор). Гоняеш _ka по функциям без толку, в KaTimeStep поля stateOk; stateNo надуманы, вобще какой накуй неОК у светофора может быть :) А вот мент его в режим желтого мигающего может всегда переключить. И обратно.  Это не реализовано и в архитектуру не лезет, т.к. реакцию на события, (т.е. в какое состояние переходить при тех или иных событиях, а их в общем случае может быть дофига много разных), не получится задать структурами КА, они индивидуальны для каждого состояния. Проще говоря в реализации КА с претензией на универсальность, да и на Милли тоже, функция текущего состояния должно возвращать новое состояние  как реакцию на событие.

ПС. "не получится задать структурами КА" - это в рамках данного форума конечно. На самом деле для каждого состояния список событий и реакций на них, т.е указатель на новое состояние. Но для тут это сложно ;)

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Logik пишет:

Хреновенько, Ахат. Посмотрел RGB моргалка (светофор). Гоняеш _ka по функциям без толку, в KaTimeStep поля stateOk; stateNo надуманы, вобще какой накуй неОК у светофора может быть :) А вот мент его в режим желтого мигающего может всегда переключить. И обратно.  Это не реализовано и в архитектуру не лезет, т.к. реакцию на события, (т.е. в какое состояние переходить при тех или иных событиях, а их в общем случае может быть дофига много разных), не получится задать структурами КА, они индивидуальны для каждого состояния. Проще говоря в реализации КА с претензией на универсальность, да и на Милли тоже, функция текущего состояния должно возвращать новое состояние  как реакцию на событие.

ПС. "не получится задать структурами КА" - это в рамках данного форума конечно. На самом деле для каждого состояния список событий и реакций на них, т.е указатель на новое состояние. Но для тут это сложно ;)

Моей автоматной библиотеке уже года 3 как, ещё в arhat.h первая версия сидела как tsc.h. :) Да, тут гоняется указатель в функции исполнителей. Есть "предыдущая" версия (1.5 кажись), где его не было и исполнитель был без параметров. Для большинства простых применений - так тоже сойдет, в общем-то "скрипач не нужен". Но, вот когда в структуру КА "наследуется" нечто свое, то очень даже полезно иметь указатель в параметрах.

Можно считать "платой за универсальность".

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

Собственно эта версия (есть уже 3.0, почто и "дал добро") отличается от предыдущей не только "платой за универсальность", сколько выделенными автоматами Милли и Мура, до которых qwone похоже будет ползти ещё очень долго.. :)

Кстати, нулевая версия таймерных автоматов-моргалок, публиковалась тут чуть ли не в 2015 году ещё и там да, тоже нет stateOk | stateNo ..

Logik
Offline
Зарегистрирован: 05.08.2014

Буду краток. С возвращением!

Beginer123
Offline
Зарегистрирован: 23.11.2018

Упс .. не знал про 3.0 .. отписался на почту.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Logik, пасибки. Ты как-то тут тоже стал редок, увы.

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