В предыдущих уроках, мы писали достаточно простые программы. Их простота заключалась в линейности логики алгоритма. В главном цикле проверялось состояние кнопки и сразу же изменялось состояние светодиода. Задержка, если необходимо, вносилась с помощью функции time.sleep(). Этот пример отлично работает, с небольшими схемами и примитивной логикой. Но если хочется чего-то большего, то старыми средствами уже не обойтись и от использования time.sleep() придется отказаться.
Ранее, чтобы проверить состояние вывода микроконтроллера мы использовали команду вроде value_a = encA.value()
. Чтобы узнать изменилось ли состояние по сравнению с предыдущей проверкой мы хранили ее результат в переменной и сравнивали. А если нам нужно отслеживать короткие изменения состояния пинов, то проверять их состояние нужно как можно чаще. Все это заставляет микроконтроллер тратить процессорное время на простую работу с сигналом.
Оказывается есть отличное решение этой проблемы. Микроконтроллер умеет аппаратно отслеживать изменения состояния выводов. Пока процессор занят чем-то по-настоящему важным за состоянием пинов следит специальная электронная схема. Как только изменение происходит, схема сигнализирует об этом процессору, который прерывает исполнение основной программы для того, чтобы обработать это прерывание — исполнить небольшой код, реагирующий на событие.
Прерывание — это сигнал (событие), который заставляет контроллер прекратить выполнение текущей задачи и приступить к исполнению другой, имеющей более высокий приоритет. После выполнения высокоприоритетной задачи, контроллер возвращается к той, которой был занят до прерывания.
Представь, что ты — это микроконтроллер и ты очень занят важным делом — читаешь эту статью. Но внезапно начинает звонить твой телефон. Ты откладываешь статью и занимаешься обработкой этого прерывания — отвечаешь на звонок и ведешь беседу. Как только беседа завершена, ты возвращаешься к чтению статьи с того места, где прервался. Примерно так устроена обработка прерываний в микроконтроллере.
Попробуем применить прерывания для работы с энкодером. Нам потребуется сделать две вещи — настроить прерывание и написать функцию-обработчик прерывания.
Оставим схему прошлого эксперимента без изменений. Всё новое будет заключаться в программе. Рисунок 1. Электрическая принципиальная схема эксперимента
from machine import I2C, Pin from esp8266_i2c_lcd import I2cLcd import time _init() DEFAULT_I2C_ADDR = 0x3F # Или 0x27 в зависимости от модели микросхемы на плате encA = Pin(13, Pin.IN) encB = Pin(12, Pin.IN) count = 0 states = ( (1,1), (0,1), (0,0), (1,0) ) old_state = 0 def print_lcd(data): lcd.clear() lcd.putstr(str(data)) def callback(p): global count global encB global encA global states global old_state value_a = encA.value() value_b = encB.value() current_state = states.index((value_a, value_b)) if (current_state - old_state == 1) or (current_state == 0 and old_state == 3): count += 1 if not (count % 2): print('+'); print_lcd(int(count/2)) old_state = current_state elif (current_state - old_state == -1) or (current_state == 3 and old_state == 0): count -= 1 if not (count % 2): print('-'); print_lcd(int(count/2)) old_state = current_state i2c = I2C(scl=Pin(5), sda=Pin(4), freq=400000) lcd = I2cLcd(i2c, DEFAULT_I2C_ADDR, 2, 16) lcd.backlight_on() encA.irq(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING, handler=callback) encB.irq(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING, handler=callback) while True: pass
Сначала настраиваем прерывания:
encA.irq(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING, handler=callback) encB.irq(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISING, handler=callback)
Мы настроили, что при возникновении события изменения сигнала на выводах encA и encB с высокого на низкий (Pin.IRQ_FALLING
) или с низкого на высокий (Pin.IRQ_RISING
) будет вызываться обработчик прерывания — функция callback
.
Какие могут быть события:
События можно комбинировать. Например, если нужна реакция и на событие Pin.IRQ_FALLING
и Pin.IRQ_RISING
, то можно записать так: trigger=(Pin.IRQ_FALLING | Pin.IRQ_RISING)
.
Рассмотрим функцию-обработчик прерывания:
def callback(p): global count global encB global encA global states global old_state value_a = encA.value() value_b = encB.value() current_state = states.index((value_a, value_b)) if (current_state - old_state == 1) or (current_state == 0 and old_state == 3): count += 1 if not (count % 2): print('+'); print_lcd(int(count/2)) old_state = current_state elif (current_state - old_state == -1) or (current_state == 3 and old_state == 0): count -= 1 if not (count % 2): print('-'); print_lcd(int(count/2)) old_state = current_state
Она начинается с
global count global encB global encA global states global old_state
Ключевое слово global
сообщает интерпретатору Python о том, что мы хотим использовать переменную, объявленную за пределами текущей функции. По умолчанию, все переменные, созданные внутри функции доступны (имеют область видимости) в пределах именно этой функции. Как только функция завершается такие переменные уничтожаются. Такие переменные называются локальными. Ключевое слово global
дает возможность обратиться к глобальной переменной.
Потом, как и раньше, мы определяем текущее состояние линий A и B энкодера. Определяем индекс состояния и проверяем можем ли мы сделать переход в данное состояние.
Как мы заметили ранее, при одном щелчке энкодера происходит смена двух состояний энкодера. Чтобы одному щелчку соответствовало изменение счетчика на 1, мы просто проверяем счетчик на четность.
if not (count % 2): print('+'); print_lcd(int(count/2))
Если число четное (нет остатка от деления на 2) то пишем в терминал + и выводим число, деленное на 2 на дисплей.
Теперь самое главное. Обратим внимание на основной цикл программы. Мы в нем ничего не делаем. Все действия, связанные с работой с энкодером, происходят в обработчике прерывания и обрабатываются независимо от основной программы. В основном цикле мы можем заняться чем-то другим. В этом и смысл использования прерываний.
В этом эксперименте мы производили настройку аппаратных средств микроконтроллера, а именно настраивали прерывания. Как мы уже узнали, работа прерываний не зависит от программы и происходит автоматически. Это значит, что обработчик прерывания будет вызываться даже после завершения работы нашей программы, ведь настройки никуда не исчезли. Это может оказать влияние на следующие эксперименты, в которых не нужны эти прерывания. Они могут мешать. Для того, чтобы следующие эксперименты проходили нормально, необходимо перезагрузить микроконтроллер. Это можно сделать выдернув на некоторое время кабель USB или нажать на кнопку Reset на плате конструктора.