===== Эксперимент 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. Монтажная схема эксперимента//
==== Программный код эксперимента ====
from time import sleep_ms, ticks_ms
from machine import I2C, Pin
from esp8266_i2c_lcd import I2cLcd
_init()
DEFAULT_I2C_ADDR = 0x3F # Или 0x27 в зависимости от модели микросхемы на плате
encA = Pin(13, Pin.IN)
encB = Pin(12, Pin.IN)
states = (
(0,0),
(1,0),
(1,1),
(0,1)
)
old_state = 0
count = 0
def print_lcd(data):
lcd.clear()
lcd.putstr(str(data))
i2c = I2C(scl=Pin(5), sda=Pin(4), freq=400000)
lcd = I2cLcd(i2c, DEFAULT_I2C_ADDR, 2, 16)
lcd.backlight_on()
while True:
value_a = encA.value()
value_b = encB.value()
state = states.index((value_a, value_b))
if (state - old_state == 1) or (state == 0 and old_state == 3):
count += 1
print('+');
print_lcd(count)
old_state = state
elif (state - old_state == -1) or (state == 3 and old_state == 0):
count -= 1
print('-');
print_lcd(count)
old_state = state
Описываем возможные состояния конечного автомата:
states = (
(0,0),
(1,0),
(1,1),
(0,1)
)
Список из 4 элементов. В каждом элементе первая цифра это состояние сигнала A, вторая — сигнала B.
Номер состояния — это индекс элемента списка. Логика переключения состояний автомата простая — состояние может смениться только на соседнее: состояние 0 на 1, 1 на 2, 2 на 3, 3 на 0. И в обратном направлении. Состояние не может измениться "перескочив" через другое. Эту логику мы опишем в программе далее.
В переменной ''old_state'' будем хранить последнее состояние конечного автомата, чтобы понимать какое состояние было и какое у него теперь будет.
В основном цикле программы мы получаем данные о текущем состоянии линий А и B:
value_a = encA.value()
value_b = encB.value()
И определяем номер состояния конечного автомата, соответствующего такому состоянию А и B:
state = states.index((value_a, value_b))
Формируем список с состояниями А и B ''(value_a, value_b)'' и
с помощью оператора ''index'' мы получаем индекс такого состояния в ''states''.
Теперь мы знаем индекс только что измеренного состояния линий. А индекс последнего состояния конечного автомата хранится в переменной ''old_state''. Теперь нам нужно понять можем ли мы из состояния записанного в ''old_state'' переключиться в новое состояние, индекс которого мы определили и записали в ''state''.
if (state - old_state == 1) or (state == 0 and old_state == 3):
Если индекс нового состояния на 1 больше старого или, если новое состояние 0, а старое 3, то регистрируем переход в новое состояние. Переводим конечный автомат в новое состояние ''old_state = state''. Увеличиваем на 1 счетчик ''count'', печатаем + в терминал и отображаем счетчик на дисплее.
Аналогично для вращения в обратную сторону. Если индекс состояния уменьшился на один, то производим аналогичные действия.
Когда ты запустишь эту программу в конструкторе, то увидишь, что значение на дисплее всегда меняется сразу на 2 и в терминале появляется по два + или -. Дело в том, что наш энкодер имеет фиксацию положений (при вращении ощущаются толчки). И эта фиксация осуществляется не в каждом состоянии, а через один. Таким образом от щелчка до щелчка энкодер проходит 2 состояния. Поэтому и число изменяется на 2.