Цифровые автоматы для Qwone :)
- Войдите на сайт для отправки комментариев
Получил добро на публикацию от автора, т.с. "на свой страх и риск", ловите. Специально для Вас 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), не под руками, может попозжа.
Получил добро на публикацию от автора....
Есть ещё мой пример автомата ....
Спалился?
Архат, перелогинься. Или лучше изыди слвсем. Поверь - ну нафик тут никому не нужны твои шедефры.
Да... прямо слышу, как жыр потёк.
Нет, пример драйвера клавиатуры на базе его автомата Милли - мой собственный. :)
Собственно топик для Qwone - захочет пусть переложит на плюсы, нет и так сойдет. У меня нет намерений обсуждать далее чьи-то нездоровые фэнтези, извините.
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() просто изменяет таймауты у автомата времени светофора из примера выше.
Хреновенько, Ахат. Посмотрел
RGB моргалка (светофор).Гоняеш_kaпо функциям без толку, вKaTimeStepполяstateOk;надуманы, вобще какой накуй неОК у светофора может быть :) А вот мент его в режим желтого мигающего может всегда переключить. И обратно. Это не реализовано и в архитектуру не лезет, т.к. реакцию на события, (т.е. в какое состояние переходить при тех или иных событиях, а их в общем случае может быть дофига много разных), не получится задать структурами КА, они индивидуальны для каждого состояния. Проще говоря в реализации КА с претензией на универсальность, да и на Милли тоже, функция текущего состояния должно возвращать новое состояние как реакцию на событие.stateNoПС. "не получится задать структурами КА" - это в рамках данного форума конечно. На самом деле для каждого состояния список событий и реакций на них, т.е указатель на новое состояние. Но для тут это сложно ;)
Хреновенько, Ахат. Посмотрел
RGB моргалка (светофор).Гоняеш_kaпо функциям без толку, вKaTimeStepполяstateOk;надуманы, вобще какой накуй неОК у светофора может быть :) А вот мент его в режим желтого мигающего может всегда переключить. И обратно. Это не реализовано и в архитектуру не лезет, т.к. реакцию на события, (т.е. в какое состояние переходить при тех или иных событиях, а их в общем случае может быть дофига много разных), не получится задать структурами КА, они индивидуальны для каждого состояния. Проще говоря в реализации КА с претензией на универсальность, да и на Милли тоже, функция текущего состояния должно возвращать новое состояние как реакцию на событие.stateNoПС. "не получится задать структурами КА" - это в рамках данного форума конечно. На самом деле для каждого состояния список событий и реакций на них, т.е указатель на новое состояние. Но для тут это сложно ;)
Моей автоматной библиотеке уже года 3 как, ещё в arhat.h первая версия сидела как tsc.h. :) Да, тут гоняется указатель в функции исполнителей. Есть "предыдущая" версия (1.5 кажись), где его не было и исполнитель был без параметров. Для большинства простых применений - так тоже сойдет, в общем-то "скрипач не нужен". Но, вот когда в структуру КА "наследуется" нечто свое, то очень даже полезно иметь указатель в параметрах.
Можно считать "платой за универсальность".
В общем-то stateOk | stateNo такая же плата, но в ряде случаев пром. применения (а также, к примеру в построителях синт. деревьев, препроцессорах и т.д.) - очень даже нужная штуковина, до которой пока Qwone не добрался. :) неОК у светофора очень даже "могет быть" (лампачка пиригарела и он это способен определить к примеру). :) Да и добавить в программу поведения КА возможность менту переводить его в мигающий зеленый - точно такая же не проблема. Даже детишки в позапрошлом году такие светофоры расписывали "на ура", левой-задней пяткой.
Собственно эта версия (есть уже 3.0, почто и "дал добро") отличается от предыдущей не только "платой за универсальность", сколько выделенными автоматами Милли и Мура, до которых qwone похоже будет ползти ещё очень долго.. :)
Кстати, нулевая версия таймерных автоматов-моргалок, публиковалась тут чуть ли не в 2015 году ещё и там да, тоже нет stateOk | stateNo ..
Буду краток. С возвращением!
Упс .. не знал про 3.0 .. отписался на почту.
Logik, пасибки. Ты как-то тут тоже стал редок, увы.
В третьей версии добавил в "общих" автоматах функию слушателя потока для таймерного автомата, чем их унифицировал и теперь можно делать "массив конечных автоматов" и перебирать их одним вызовом в loop(). Кроме этого, добавил "автоматы с памятью" и попытался сделать автоматы с контекстной зависимостью, но обнаружил, что это просто надкласс автоматов с несколькими слушателями потока и/или автоматы с разбивкой состояний на группы. В общем - нинада. :)