Это старая версия документа!


Эксперимент 36. Прерывания

В предыдущих уроках, мы писали достаточно простые программы. Их простота заключалась в линейности логики алгоритма. В главном цикле проверялось состояние кнопки и сразу же изменялось состояние светодиода. Задержка, если необходимо, вносилась с помощью функции time.sleep(). Этот пример отлично работает, с небольшими схемами и примитивной логикой. Но если хочется чего-то большего, то старыми средствами уже не обойтись и от использования time.sleep() придется отказаться.

Ранее, чтобы проверить состояние вывода микроконтроллера мы использовали команду вроде value_a = encA.value(). Чтобы узнать изменилось ли состояние по сравнению с предыдущей проверкой мы хранили ее результат в переменной и сравнивали. А если нам нужно отслеживать короткие изменения состояния пинов, то проверять их состояние нужно как можно чаще. Все это заставляет микроконтроллер тратить процессорное время на простую работу с сигналом.

Прерывания

Оказывается есть отличное решение этой проблемы. Микроконтроллер умеет аппаратно отслеживать изменения состояния выводов. Пока процессор занят чем-то по-настоящему важным за состоянием пинов слежит специальная электронная схема. Как только изменение происходит, схема сигнализирует об этом процессору, который прерывает исполнение основной программы для того, чтобы обработать это прерывание — исполнить небольшой код, реагирующий на событие.

Прерывание — это сигнал (событие), который заставляет контроллер прекратить выполнение текущей задачи и приступить к исполнению другой, имеющей более высокий приоритет. После выполнения высокоприоритетной задачи, контроллер возвращается к той, которой был занят до прерывания.

Представь, что что ты — это микрокоонтроллер и ты очень занят важным делом — читаешь эту статью. Но внезапно начинает звонить твой телефон. Ты откладываешь статью и занимаешься обработкой этого прерывания — отвечаешь на звонок и ведешь беседу. Как только беседа завершена, ты возвращаешься к чтению статьи с того места, где прервался. Примерно так устроена обработка прерываний в микроконтроллере.

Эксперимент

Попробуем применить прерывания для работы с энкодером. Нам потребуется сделать две вещи — настроить прерывание и написать функцию- обработчик прерывания.

Схема эксперимента

Оставим схему прошлого эксперимента без изменений. Всё новое будет заключаться в программе. Рисунок 1. Электрическая принципиальная схема эксперимента

Рисунок 2. Монтажная схема эксперимента

Программный код эксперимента

Exp35
  1. from machine import I2C, Pin
  2. from esp8266_i2c_lcd import I2cLcd
  3. import time
  4. _init()
  5.  
  6. DEFAULT_I2C_ADDR = 0x3F
  7.  
  8. encA = Pin(13, Pin.IN)
  9. encB = Pin(12, Pin.IN)
  10.  
  11. count = 0
  12.  
  13. states = (
  14. (1,1),
  15. (0,1),
  16. (0,0),
  17. (1,0)
  18. )
  19.  
  20. old_state = 0
  21.  
  22. def print_lcd(data):
  23. lcd.clear()
  24. lcd.putstr(str(data))
  25.  
  26. def callback2(p):
  27. global count
  28. global encB
  29. global encA
  30. global states
  31. global old_state
  32.  
  33. value_a = encA.value()
  34. value_b = encB.value()
  35.  
  36. current_state = states.index((value_a, value_b))
  37.  
  38. if (current_state - old_state == 1) or (current_state == 0 and old_state == 3):
  39. count += 1
  40. if not (count % 2):
  41. print('+');
  42. print_lcd(int(count/2))
  43. old_state = current_state
  44. elif (current_state - old_state == -1) or (current_state == 3 and old_state == 0):
  45. count -= 1
  46. if not (count % 2):
  47. print('-');
  48. print_lcd(int(count/2))
  49. old_state = current_state
  50.  
  51.  
  52. i2c = I2C(scl=Pin(5), sda=Pin(4), freq=400000)
  53. lcd = I2cLcd(i2c, DEFAULT_I2C_ADDR, 2, 16)
  54. lcd.backlight_on()
  55.  
  56. encA.irq(trigger=Pin.IRQ_FALLING|Pin.IRQ_RISING, handler=callback2)
  57. encB.irq(trigger=Pin.IRQ_FALLING|Pin.IRQ_RISING, handler=callback2)
  58.  
  59.  
  60. while True:
  61. pass

Сначала настраиваем прерывание:

  1. encA.irq(trigger=Pin.IRQ_FALLING, handler=callback)

Мы настроили, что при возникновении события изменения сигнала на выводе encA с высокого на низкий (Pin.IRQ_FALLING) будет вызываться обработчик прерывания — функция callback.

Какие могут быть события:

  • Pin.IRQ_FALLING изменение с 1 на 0
  • Pin.IRQ_RISING изменение с 0 на 1
  • Pin.IRQ_LOW_LEVEL наличие логического 0
  • Pin.IRQ_HIGH_LEVEL наличие логической 1

События можно комбинировать. Например, если нужна реакция и на событие Pin.IRQ_FALLING и Pin.IRQ_RISING, то можно записать так: trigger=(Pin.IRQ_FALLING | Pin.IRQ_RISING).

Рассмотрим функцию- обработчик прерывания:

  1. def callback(p):
  2. global count
  3. global encB
  4. global encA
  5.  
  6. value_a = encA.value()
  7. value_b = encB.value()
  8.  
  9. if (value_a and value_b) or (not value_a and not value_b):
  10. print('+')
  11. count += 1
  12. print_lcd(count)
  13. elif (not value_a and value_b) or (value_a and not value_b):
  14. print('-')
  15. count -= 1
  16. print_lcd(count)

Она начинается с

  1. global count
  2. global encB
  3. global encA

Ключеваое слово global сообщает интерпретатору Python о том, что мы хотим использовать переменную, объявленную за пределами текущей функции. По умолчанию, все переменные, созданные внутри функции доступны (имеют область видимости) в пределах именно этой функции. Как только функция завершается такие переменные уничтожаются. Такие переменные называются локальными. Ключевое слово global дает возможность обратиться к глобальной переменной.

Потом, как и раньше, мы определяем текущ