===== Эксперимент 35. Конечные автоматы ===== Коне́чный автома́т — абстрактный автомат, число возможных внутренних состояний которого конечно. Если говорить проще, то с помощью конечного автомата описываются состояния какого либо объекта и переходы между этими состояниями. Например, светофор можно описать с помощью конечного автомата. {{ :products:esp-iot:states.png?nolink |}} Видно, что из состояния 1 (красного сигнала) светофор может перейти только в состояние 2 (красный + желтый), означающий скорое включение зеленого. Из состояния 2 светофор может перейти только в состояние 3 (зеленый сигнал). После зеленого всегда идет желтый сигнал (состояние 4), который сменяется красным (состояние 1). Главное, что у данного конечного автомата есть 4 состояния и мы знаем из какого состояния в какое он может переходить. Конечные автоматы иногда очень полезны для описания состояний электроники. Возьмем тот же инкрементальный энкодер. Снова посмотрим на график сигналов от него: {{ :encoder.png?nolink |}} По графику видно, что состояния у энкодера не меняются хаотично. Они меняются только последовательно. Если крутить ручку в одну сторону, то состояния сменяются 0-1-2-3-0..., а если в другую, то 0-3-2-1-0... Зная это мы можем отфильтровывать ложные показания из-за дребезга контактов и точно отслеживать импульсы. Попробуем обрабатывать сигналы энкодера с помощью конечного автомата. ==== Схема эксперимента ==== {{ :products:esp-iot:exp14_sch.png?nolink |}} //Рисунок 1. Электрическая принципиальная схема эксперимента// {{ :products:esp-iot:exp14_mon.png?direct&600 |}} //Рисунок 2. Монтажная схема эксперимента// ==== Программный код эксперимента ==== #include #define ENC_A 13 #define ENC_B 12 #define DEFAULT_I2C_ADDR 0x3F // Или 0x27 в зависимости от твоей платы IoT LCDI2C_Generic lcd(DEFAULT_I2C_ADDR, 16, 2); bool states[4][2] = { {0,0}, {1,0}, {1,1}, {0,1} }; bool value_a = 0; bool value_b = 0; int count = 0; int state = 0; int state_old = 0; void printLcd(int number) { lcd.clear(); lcd.print(number); } int index(bool a, bool b){ for (int i = 0; i < 4; i++){ if (states[i][0] == a and states[i][1] == b) { return i; } } return -1; } void setup() { Serial.begin(9600); lcd.init(); lcd.setBacklight(0); pinMode(ENC_A, INPUT); pinMode(ENC_B, INPUT); } void loop() { value_a = digitalRead(ENC_A); value_b = digitalRead(ENC_B); state = index(value_a, value_b); if ((state - state_old == 1) or (state == 0 and state_old == 3)) { count++; Serial.println("+"); printLcd(count); state_old = state; } else if ((state - state_old == -1) or (state == 3 and state_old == 0)) { count--; Serial.println("-"); printLcd(count); state_old = state; } } Описываем возможные состояния конечного автомата: bool states[4][2] = { {0,0}, {1,0}, {1,1}, {0,1} }; Двумерный массив 4×2. Первая цифра это состояние сигнала A, вторая — сигнала B. Номер состояния — это индекс элемента списка. Логика переключения состояний автомата простая — состояние может смениться только на соседнее: состояние 0 на 1, 1 на 2, 2 на 3, 3 на 0. И в обратном направлении. Состояние не может измениться "перескочив" через другое. Эту логику мы опишем в программе далее. В переменной ''state_old'' будем хранить последнее состояние конечного автомата, чтобы понимать какое состояние было и какое у него теперь будет. В основном цикле программы мы получаем данные о текущем состоянии линий А и B: value_a = digitalRead(ENC_A); value_b = digitalRead(ENC_B); И определяем номер состояния конечного автомата, соответствующего такому состоянию А и B с помощью написанной нами функцией ''index()'': state = index(value_a, value_b); Функция ''index(a, b)'' принимает два параметра типа ''bool'', осуществляет поиск соответствующего элемента в массиве states и возвращает индекс найденного элемента. Ключевое слово ''return'' прерывает работу функции и возвращает указанное после него значение. int index(bool a, bool b){ for (int i = 0; i < 4; i++){ if (states[i][0] == a and states[i][1] == b) { return i; } } return -1; } Зачем же нужна строка ''return -1;'' в конце функции, если в цикле ''for'' в любом случае будет найден и возвращен индекс? Компилятор не на столько умный и не знает об этом, но для всех функций обязательно возвращение значения, иначе программа не скомпилируется. Поэтому мы возвращаем заведомо нереальное значение, т.к. в нашем случае строка ''return -1;'' никогда не будет выполнена. Теперь мы знаем индекс только что измеренного состояния линий. А индекс последнего состояния конечного автомата хранится в переменной ''state_old''. Теперь нам нужно понять можем ли мы из состояния записанного в ''state_old'' переключиться в новое состояние, индекс которого мы определили и записали в ''state''. if ((state - state_old == 1) or (state == 0 and state_old == 3)) { Если индекс нового состояния на 1 больше старого или, если новое состояние 0, а старое 3, то регистрируем переход в новое состояние. Переводим конечный автомат в новое состояние ''state_old = state''. Увеличиваем на 1 счетчик ''count'', печатаем + в терминал и отображаем счетчик на дисплее. Аналогично для вращения в обратную сторону. Если индекс состояния уменьшился на один, то производим аналогичные действия. Когда ты запустишь эту программу в конструкторе, то увидишь, что значение на дисплее всегда меняется сразу на 2 и в терминале появляется по два + или -. Дело в том, что наш энкодер имеет фиксацию положений (при вращении ощущаются толчки). И эта фиксация осуществляется не в каждом состоянии, а через один. Таким образом от щелчка до щелчка энкодер проходит 2 состояния. Поэтому и число изменяется на 2.