| Предыдущая версия справа и слеваПредыдущая версияСледующая версия | Предыдущая версия |
| products:laboratory_iot:exp35 [2020/05/22 11:27] – [Эксперимент] labuser29 | products:laboratory_iot:exp35 [2024/11/16 11:25] (текущий) – [Программный код эксперимента] labuser30 |
|---|
| ===== Эксперимент 35. Прерывания ===== | ===== Эксперимент 35. Конечные автоматы ===== |
| |
| В предыдущих уроках, мы писали достаточно простые программы. | Коне́чный автома́т — абстрактный автомат, число возможных внутренних состояний которого конечно. Если говорить проще, то с помощью конечного автомата описываются состояния какого либо объекта и переходы между этими состояниями. Например, светофор можно описать с помощью конечного автомата. |
| Их простота заключалась в линейности логики алгоритма. В главном цикле проверялось состояние кнопки и сразу же изменялось | |
| состояние светодиода. Задержка, если необходимо, вносилась | |
| с помощью функции time.sleep(). Этот пример отлично работает, с | |
| небольшими схемами и примитивной логикой. Но если хочется | |
| чего-то большего, то старыми средствами уже не обойтись и от | |
| использования time.sleep() придется отказаться. | |
| |
| Ранее, чтобы проверить состояние вывода микроконтроллера мы использовали команду вроде ''value_a = encA.value()''. Чтобы узнать изменилось ли состояние по сравнению с предыдущей проверкой мы хранили ее результат в переменной и сравнивали. А если нам нужно отслеживать короткие изменения состояния пинов, то проверять их состояние нужно как можно чаще. Все это заставляет микроконтроллер тратить процессорное время на простую работу с сигналом. | {{ :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 |}} | {{ :products:esp-iot:exp14_sch.png?nolink |}} |
| //Рисунок 1. Электрическая принципиальная схема эксперимента// | //Рисунок 1. Электрическая принципиальная схема эксперимента// |
| ==== Программный код эксперимента ==== | ==== Программный код эксперимента ==== |
| |
| <file python Exp35[enable_line_numbers="2", start_line_numbers_at="1"]> | <file python Exp35.py[enable_line_numbers="2", start_line_numbers_at="1"]> |
| | from time import sleep_ms, ticks_ms |
| from machine import I2C, Pin | from machine import I2C, Pin |
| from esp8266_i2c_lcd import I2cLcd | from esp8266_i2c_lcd import I2cLcd |
| _init() | _init() |
| |
| DEFAULT_I2C_ADDR = 0x3F | DEFAULT_I2C_ADDR = 0x3F # Или 0x27 в зависимости от модели микросхемы на плате |
| |
| encA = Pin(13, Pin.IN) | encA = Pin(13, Pin.IN) |
| encB = Pin(12, Pin.IN) | encB = Pin(12, Pin.IN) |
| | |
| | states = ( |
| | (0,0), |
| | (1,0), |
| | (1,1), |
| | (0,1) |
| | ) |
| | |
| | old_state = 0 |
| |
| count = 0 | count = 0 |
| lcd.putstr(str(data)) | lcd.putstr(str(data)) |
| |
| def callback(p): | |
| global count | |
| global encB | |
| global encA | |
| |
| print('start') | 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_a = encA.value() |
| value_b = encB.value() | value_b = encB.value() |
| |
| if (value_a and value_b) or (not value_a and not value_b): | state = states.index((value_a, value_b)) |
| print('+') | |
| count += 1 | if (state - old_state == 1) or (state == 0 and old_state == 3): |
| | count += 1 |
| | print('+'); |
| print_lcd(count) | print_lcd(count) |
| elif (not value_a and value_b) or (value_a and not value_b): | old_state = state |
| print('-') | elif (state - old_state == -1) or (state == 3 and old_state == 0): |
| count -= 1 | count -= 1 |
| | print('-'); |
| print_lcd(count) | print_lcd(count) |
| print('stop') | old_state = state |
| | </file> |
| |
| | Описываем возможные состояния конечного автомата: |
| | <code python[enable_line_numbers="2", start_line_numbers_at="11"]> |
| | states = ( |
| | (0,0), |
| | (1,0), |
| | (1,1), |
| | (0,1) |
| | ) |
| | </code> |
| | Список из 4 элементов. В каждом элементе первая цифра это состояние сигнала A, вторая — сигнала B. |
| | Номер состояния — это индекс элемента списка. Логика переключения состояний автомата простая — состояние может смениться только на соседнее: состояние 0 на 1, 1 на 2, 2 на 3, 3 на 0. И в обратном направлении. Состояние не может измениться "перескочив" через другое. Эту логику мы опишем в программе далее. |
| |
| i2c = I2C(scl=Pin(5), sda=Pin(4), freq=400000) | В переменной ''old_state'' будем хранить последнее состояние конечного автомата, чтобы понимать какое состояние было и какое у него теперь будет. |
| lcd = I2cLcd(i2c, DEFAULT_I2C_ADDR, 2, 16) | |
| lcd.backlight_on() | |
| |
| encA.irq(trigger=Pin.IRQ_FALLING, handler=callback) | В основном цикле программы мы получаем данные о текущем состоянии линий А и B: |
| | <code python[enable_line_numbers="2", start_line_numbers_at="34"]> |
| | value_a = encA.value() |
| | value_b = encB.value() |
| | </code> |
| |
| | И определяем номер состояния конечного автомата, соответствующего такому состоянию А и B: |
| | <code python[enable_line_numbers="2", start_line_numbers_at="37"]> |
| | state = states.index((value_a, value_b)) |
| | </code> |
| |
| while True: | Формируем список с состояниями А и B ''(value_a, value_b)'' и |
| pass | с помощью оператора ''index'' мы получаем индекс такого состояния в ''states''. |
| </file> | |
| |
| Сначала настраиваем прерывание: | Теперь мы знаем индекс только что измеренного состояния линий. А индекс последнего состояния конечного автомата хранится в переменной ''old_state''. Теперь нам нужно понять можем ли мы из состояния записанного в ''old_state'' переключиться в новое состояние, индекс которого мы определили и записали в ''state''. |
| <code python[enable_line_numbers="2", start_line_numbers_at="42"]> | |
| encA.irq(trigger=Pin.IRQ_FALLING, handler=callback) | <code python[enable_line_numbers="2", start_line_numbers_at="39"]> |
| | if (state - old_state == 1) or (state == 0 and old_state == 3): |
| </code> | </code> |
| |
| Мы настроили, что при изменении сигнала на выводе encA с высокого на низкий (''Pin.IRQ_FALLING'') будет вызываться обработчик прерывания — функция ''callback''. | Если индекс нового состояния на 1 больше старого или, если новое состояние 0, а старое 3, то регистрируем переход в новое состояние. Переводим конечный автомат в новое состояние ''old_state = state''. Увеличиваем на 1 счетчик ''count'', печатаем + в терминал и отображаем счетчик на дисплее. |
| | |
| | Аналогично для вращения в обратную сторону. Если индекс состояния уменьшился на один, то производим аналогичные действия. |
| | |
| | Когда ты запустишь эту программу в конструкторе, то увидишь, что значение на дисплее всегда меняется сразу на 2 и в терминале появляется по два + или -. Дело в том, что наш энкодер имеет фиксацию положений (при вращении ощущаются толчки). И эта фиксация осуществляется не в каждом состоянии, а через один. Таким образом от щелчка до щелчка энкодер проходит 2 состояния. Поэтому и число изменяется на 2. |