Различия

Показаны различия между двумя версиями страницы.

Ссылка на это сравнение

Предыдущая версия справа и слеваПредыдущая версия
Следующая версия
Предыдущая версия
products:laboratory_iot:exp35 [2020/05/22 11:45] – [Программный код эксперимента] labuser29products:laboratory_iot:exp35 [2024/11/16 11:25] (текущий) – [Программный код эксперимента] labuser30
Строка 1: Строка 1:
-===== Эксперимент 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. Электрическая принципиальная схема эксперимента//
Строка 31: Строка 23:
 ==== Программный код эксперимента ==== ==== Программный код эксперимента ====
  
-<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
Строка 47: Строка 49:
     lcd.clear()     lcd.clear()
     lcd.putstr(str(data))     lcd.putstr(str(data))
- 
-def callback(p): 
-    global count 
-    global encB 
-    global encA 
-     
-    value_a = encA.value() 
-    value_b = encB.value() 
- 
-    if (value_a and value_b) or (not value_a and not value_b): 
-        print('+') 
-        count += 1  
-        print_lcd(count) 
-    elif (not value_a and value_b) or (value_a and not value_b): 
-        print('-') 
-        count -= 1 
-        print_lcd(count) 
  
  
Строка 70: Строка 55:
 lcd.backlight_on() lcd.backlight_on()
  
-encA.irq(trigger=Pin.IRQ_FALLING, handler=callback) 
  
 +while True:
 +    value_a = encA.value()
 +    value_b = encB.value()
  
-while True+    state = states.index((value_a, value_b)) 
-    pass+     
 +    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
 </file> </file>
  
-Сначала настраиваем прерывание: +Описываем возможные состояния конечного автомата
-<code python[enable_line_numbers="2", start_line_numbers_at="39"]> +<code python[enable_line_numbers="2", start_line_numbers_at="11"]> 
-encA.irq(trigger=Pin.IRQ_FALLINGhandler=callback)+states 
 +    (0,0), 
 +    (1,0), 
 +    (1,1), 
 +    (0,1) 
 +)
 </code> </code>
 +Список из 4 элементов. В каждом элементе первая цифра это состояние сигнала A, вторая — сигнала B.
 +Номер состояния — это индекс элемента списка. Логика переключения состояний автомата простая — состояние может смениться только на соседнее: состояние 0 на 1, 1 на 2, 2 на 3, 3 на 0. И в обратном направлении. Состояние не может измениться "перескочив" через другое. Эту логику мы опишем в программе далее.
  
-Мы настроили, что при возникновении события изменения сигнала на выводе encA с высокого на низкий (''Pin.IRQ_FALLING''будет вызываться обработчик прерывания — функция ''callback''.+В переменной ''old_state'' будем хранить последнее состояние конечного автомата, чтобы  понимать какое состояние было и какое у него теперь будет.
  
-Какие могут быть события: +В основном цикле программы мы получаем данные о текущем состоянии линий А и B
-  * Pin.IRQ_FALLING изменение с 1 на 0 +<code python[enable_line_numbers="2", start_line_numbers_at="34"]>
-  * 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)''+
- +
-Рассмотрим функцию- обработчик прерывания+
-<code python[enable_line_numbers="2", start_line_numbers_at="17"]> +
-def callback(p): +
-    global count +
-    global encB +
-    global encA +
-    +
     value_a = encA.value()     value_a = encA.value()
     value_b = encB.value()     value_b = encB.value()
 +</code>
  
-    if (value_a and value_b) or (not value_a and not value_b)+И определяем номер состояния конечного автомата, соответствующего такому состоянию А и B
-        print('+'+<code python[enable_line_numbers="2", start_line_numbers_at="37"]> 
-        count +1  +    state = states.index((value_avalue_b))
-        print_lcd(count) +
-    elif (not value_a and value_b) or (value_a and not value_b)+
-        print('-'+
-        count -= 1 +
-        print_lcd(count)+
 </code> </code>
  
-Она начинается с  +Формируем список с состояниями А и B ''(value_a, value_b)'' и  
-<code python[enable_line_numbers="2", start_line_numbers_at="18"]> +с помощью оператора ''index'' мы получаем индекс такого состояния в ''states''
-    global count + 
-    global encB +Теперь мы знаем индекс только что измеренного состояния линий. А индекс последнего состояния конечного автомата хранится в переменной ''old_state''. Теперь нам нужно понять можем ли мы из состояния записанного в ''old_state'' переключиться в новое состояние, индекс которого мы определили и записали в ''state''
-    global encA+ 
 +<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>
  
-Ключеваое слово ''global'' сообщает интерпретатору Python о томчто мы хотим использовать переменную, объявленную за пределами текущей функции. По умолчанию, все переменныесозданные внутри функции доступны (имеют //область видимости//в пределах именно этой функции. Как только функция завершается такие переменные уничтожаются. Такие переменные называются //локальными//Ключевое слово ''global'' дает возможность обратиться к //глобальной// переменной.+Если индекс нового состояния на 1 больше старого илиесли новое состояние 0, а старое 3то регистрируем переход в новое состояние. Переводим конечный автомат в новое состояние ''old_state = state''Увеличиваем на 1 счетчик ''count'', печатаем + в терминал и отображаем счетчик на дисплее
 + 
 +Аналогично для вращения в обратную сторонуЕсли индекс состояния уменьшился на один, то производим аналогичные действия.
  
-Потомкак и раньше, мы определяем текущ+Когда ты запустишь эту программу в конструкторе, то увидишь, что значение на дисплее всегда меняется сразу на 2 и в терминале появляется по два + или -. Дело в томчто наш энкодер имеет фиксацию положений (при вращении ощущаются толчки). И эта фиксация осуществляется не в каждом состоянии, а через один. Таким образом от щелчка до щелчка энкодер проходит 2 состояния. Поэтому и число изменяется на 2.