Различия

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

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

Следующая версия
Предыдущая версия
products:laboratory_iot_c:exp36 [2024/11/17 13:20] – создано labuser30products:laboratory_iot_c:exp36 [2024/11/17 17:04] (текущий) – [Программный код эксперимента] labuser30
Строка 4: Строка 4:
 Их простота заключалась в линейности логики алгоритма. В главном цикле проверялось состояние кнопки и сразу же изменялось Их простота заключалась в линейности логики алгоритма. В главном цикле проверялось состояние кнопки и сразу же изменялось
 состояние светодиода. Задержка, если необходимо, вносилась состояние светодиода. Задержка, если необходимо, вносилась
-с помощью функции time.sleep(). Этот пример отлично работает, с+с помощью функции ''delay()''. Этот пример отлично работает, с
 небольшими схемами и примитивной логикой. Но если хочется небольшими схемами и примитивной логикой. Но если хочется
 чего-то большего, то старыми средствами уже не обойтись и от чего-то большего, то старыми средствами уже не обойтись и от
-использования time.sleep() придется отказаться.+использования ''delay()'' придется отказаться.
  
-Ранее, чтобы проверить состояние вывода микроконтроллера мы использовали команду вроде ''value_a = encA.value()''. Чтобы узнать изменилось ли состояние по сравнению с предыдущей проверкой мы хранили ее результат в переменной и сравнивали. А если нам нужно отслеживать короткие изменения состояния пинов, то проверять их состояние нужно как можно чаще. Все это заставляет микроконтроллер тратить процессорное время на простую работу с сигналом.+Ранее, чтобы проверить состояние вывода микроконтроллера мы использовали команду вроде ''value_a = digitalRead(ENC_A)''. Чтобы узнать изменилось ли состояние по сравнению с предыдущей проверкой мы хранили ее результат в переменной и сравнивали. А если нам нужно отслеживать короткие изменения состояния пинов, то проверять их состояние нужно как можно чаще. Все это заставляет микроконтроллер тратить процессорное время на простую работу с сигналом.
  
 ==== Прерывания ==== ==== Прерывания ====
Строка 19: Строка 19:
  
 ==== Эксперимент ==== ==== Эксперимент ====
-Попробуем применить прерывания для работы с энкодером. Нам потребуется сделать две вещи — настроить прерывание и написать функцию- обработчик прерывания.+Попробуем применить прерывания для работы с энкодером. Нам потребуется сделать две вещи — настроить прерывание и написать функцию-обработчик прерывания.
  
 ==== Схема эксперимента ==== ==== Схема эксперимента ====
Строка 31: Строка 31:
 ==== Программный код эксперимента ==== ==== Программный код эксперимента ====
  
-<file python Exp35[enable_line_numbers="2", start_line_numbers_at="1"]> +<file arduino Exp36.py[enable_line_numbers="2", start_line_numbers_at="1"]> 
-from machine import I2C, Pin +#include <LCDI2C_Multilingual.h>
-from esp8266_i2c_lcd import I2cLcd +
-import time +
-_init()+
  
-DEFAULT_I2C_ADDR 0x3F Или 0x27 в зависимости от модели микросхемы на плате+#define ENC_A 13 
 +#define ENC_B 12   
 +#define DEFAULT_I2C_ADDR 0x3F // Или 0x27 в зависимости от твоей платы IoT
  
-encA = Pin(13Pin.IN) +LCDI2C_Generic lcd(DEFAULT_I2C_ADDR162)
-encB = Pin(12Pin.IN)+
  
-count = 0+bool states[4][2] 
 +  {0,0}, 
 +  {1,0}, 
 +  {1,1}, 
 +  {0,1} 
 +};
  
-states +volatile int count = 0; 
-    (1,1), +volatile int state = 0; 
-    (0,1), +volatile int state_old = 0;
-    (0,0), +
-    (1,0+
-)+
  
-old_state = 0+void printLcd(int number) { 
 +  lcd.clear(); 
 +  lcd.print(number); 
 +}
  
-def print_lcd(data): +int index(bool a, bool b){ 
-    lcd.clear() +  for (int i = 0; i < 4; i++){ 
-    lcd.putstr(str(data))+    if (states[i][0] == a and states[i][1] == b
 +      return i; 
 +    } 
 +  } 
 +  return -1; 
 +}
  
-def callback(p): +void ICACHE_RAM_ATTR callback() { 
-    global count +  bool value_a = digitalRead(ENC_A); 
-    global encB +  bool value_b = digitalRead(ENC_B);
-    global encA +
-    global states +
-    global old_state+
  
-    value_a encA.value(+  state index(value_a, value_b);
-    value_b = encB.value()+
  
-    current_state = states.index((value_a, value_b)) +  if ((state state_old == 1) or (state == 0 and state_old == 3)) { 
-     +    count++; 
-    if (current_state  old_state == 1) or (current_state == 0 and old_state == 3): +    if (not(count % 2)){ 
-        count += 1 +      Serial.println("+"); 
-        if not (count % 2): +      printLcd(count / 2); 
-            print('+'); +    } 
-            print_lcd(int(count/2)) +    state_old = state; 
-        old_state = current_state +  } 
-    elif (current_state  old_state == -1)  or (current_state == 3 and old_state == 0): +  else if ((state state_old == -1)  or (state == 3 and state_old == 0)) { 
-        count -= 1 +    count--; 
-        if not (count % 2): +    if (not(count % 2)){ 
-            print('-'); +      Serial.println("-"); 
-            print_lcd(int(count/2)) +      printLcd(count / 2); 
-        old_state current_state+    } 
 +    state_old state; 
 +  } 
 +}
  
 +void setup() {
 +  lcd.init(); 
 +  lcd.setBacklight(0); 
 +  pinMode(ENC_A, INPUT);
 +  pinMode(ENC_B, INPUT);
  
-i2c = I2C(scl=Pin(5), sda=Pin(4)freq=400000+  attachInterrupt(digitalPinToInterrupt(ENC_A), callbackCHANGE); 
-lcd = I2cLcd(i2c, DEFAULT_I2C_ADDR, 2, 16) +  attachInterrupt(digitalPinToInterrupt(ENC_B), callback, CHANGE); 
-lcd.backlight_on() +} 
- +  
-encA.irq(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISINGhandler=callback+void loop() {    
-encB.irq(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISINGhandler=callback+}
- +
- +
-while True: +
-    pass+
 </file> </file>
  
 Сначала настраиваем прерывания: Сначала настраиваем прерывания:
-<code python[enable_line_numbers="2", start_line_numbers_at="56"]> +<code python[enable_line_numbers="2", start_line_numbers_at="64"]> 
-encA.irq(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISINGhandler=callback) +  attachInterrupt(digitalPinToInterrupt(ENC_A), callback, CHANGE); 
-encB.irq(trigger=Pin.IRQ_FALLING | Pin.IRQ_RISINGhandler=callback)+  attachInterrupt(digitalPinToInterrupt(ENC_B), callback, CHANGE);
 </code> </code>
  
-Мы настроили, что при возникновении события изменения сигнала на выводах encA и encB с высокого на низкий (''Pin.IRQ_FALLING''или с низкого на высокий (''Pin.IRQ_RISING''будет вызываться обработчик прерывания — функция ''callback''.+Мы настроили при помощи указание события ''CHANGE'', что при возникновении события изменения сигнала на выводах ENC_A и ENC_A с высокого на низкий или с низкого на высокий  будет вызываться обработчик прерывания — функция ''callback''.
  
 Какие могут быть события: Какие могут быть события:
-  * Pin.IRQ_FALLING изменение с 1 на 0 +  * FALLING изменение с 1 на 0 
-  * Pin.IRQ_RISING изменение с 0 на 1 +  * RISING изменение с 0 на 1 
-  * Pin.IRQ_LOW_LEVEL наличие логического 0 +  * CHANGE любое изменение 
-  * Pin.IRQ_HIGH_LEVEL наличие логической 1+  * LOW наличие логического 0 
 +  * HIGH_LEVEL наличие логической 1
  
-События можно комбинироватьНапример, если нужна реакция и на событие ''Pin.IRQ_FALLING'' и ''Pin.IRQ_RISING'', то можно записать так: ''trigger=(Pin.IRQ_FALLING | Pin.IRQ_RISING)''.+Обратите внимание на новое ключевое слово ''volatile''Все переменные изменяемые из функции вызываемой прерыванием должны быть объявлены с этим ключевым словомНеобходимость обусловлена особенностями работой с памятью микроконтроллера.   
 +<code arduino[enable_line_numbers="2", start_line_numbers_at="16"]> 
 +volatile int count = 0; 
 +volatile int state = 0; 
 +volatile int state_old = 0; 
 +</code>
  
-Рассмотрим функцию- обработчик прерывания: +Рассмотрим функцию-обработчик прерывания, она в свою очередь объявлена с атрибутом ICACHE_RAM_ATT. Причина аналогична указанной для переменных выше.   
-<code python[enable_line_numbers="2", start_line_numbers_at="26"]> +<code arduino[enable_line_numbers="2", start_line_numbers_at="34"]> 
-def callback(p): +void ICACHE_RAM_ATTR callback() { 
-    global count +  bool value_a = digitalRead(ENC_A); 
-    global encB +  bool value_b = digitalRead(ENC_B);
-    global encA +
-    global states +
-    global old_state+
  
-    value_a encA.value(+  state index(value_a, value_b);
-    value_b = encB.value()+
  
-    current_state = states.index((value_a, value_b)) +  if ((state state_old == 1) or (state == 0 and state_old == 3)) { 
-     +    count++; 
-    if (current_state  old_state == 1) or (current_state == 0 and old_state == 3): +    if (not(count % 2)){ 
-        count += 1 +      Serial.println("+"); 
-        if not (count % 2): +      printLcd(count / 2); 
-            print('+'); +    } 
-            print_lcd(int(count/2)) +    state_old = state; 
-        old_state = current_state +  } 
-    elif (current_state  old_state == -1)  or (current_state == 3 and old_state == 0): +  else if ((state state_old == -1)  or (state == 3 and state_old == 0)) { 
-        count -= 1 +    count--; 
-        if not (count % 2): +    if (not(count % 2)){ 
-            print('-'); +      Serial.println("-"); 
-            print_lcd(int(count/2)) +      printLcd(count / 2); 
-        old_state current_state+    } 
 +    state_old state; 
 +  } 
 +}
 </code> </code>
  
 Она начинается с  Она начинается с 
-<code python[enable_line_numbers="2", start_line_numbers_at="27"]> +<code arduino[enable_line_numbers="2", start_line_numbers_at="35"]> 
-    global count +  bool value_a = digitalRead(ENC_A); 
-    global encB +  bool value_b = digitalRead(ENC_B);
-    global encA +
-    global states +
-    global old_state+
 </code> </code>
  
-Ключеваое слово ''global'' сообщает интерпретатору Python о том, что мы хотим использовать переменную, объявленную за пределами текущей функции. По умолчанию, все переменные, созданные внутри функции доступны (имеют //область видимости//) в пределах именно этой функции. Как только функция завершается такие переменные уничтожаются. Такие переменные называются //локальными//. Ключевое слово ''global'' дает возможность обратиться к //глобальной// переменной.+Здесь мы впервые объявляем переменные внутри функции. По умолчанию, все переменные, созданные внутри функции доступны (имеют //область видимости//) в пределах именно этой функции. Как только функция завершается такие переменные уничтожаются. Такие переменные называются //локальными//, а объявленные в основной программе  — //глобальными//.
  
 Потом, как и раньше, мы определяем текущее состояние линий A и B энкодера. Определяем индекс состояния и проверяем можем ли мы сделать переход в данное состояние. Потом, как и раньше, мы определяем текущее состояние линий A и B энкодера. Определяем индекс состояния и проверяем можем ли мы сделать переход в данное состояние.
Строка 154: Строка 164:
 Как мы заметили ранее, при одном щелчке энкодера происходит смена двух состояний энкодера. Чтобы одному щелчку соответствовало изменение счетчика на 1, мы просто проверяем счетчик на четность.  Как мы заметили ранее, при одном щелчке энкодера происходит смена двух состояний энкодера. Чтобы одному щелчку соответствовало изменение счетчика на 1, мы просто проверяем счетчик на четность. 
  
-<code python[enable_line_numbers="2", start_line_numbers_at="40"]> +<code arduino[enable_line_numbers="2", start_line_numbers_at="42"]> 
-         if not (count % 2): +    if (not(count % 2)){ 
-            print('+'); +      Serial.println("+"); 
-            print_lcd(int(count/2))+      printLcd(count / 2)
 +    }
 </code> </code>
  
Строка 164: Строка 175:
 Теперь самое главное. Обратим внимание на основной цикл программы. Мы в нем ничего не делаем. Все действия, связанные с работой с энкодером, происходят в обработчике прерывания и обрабатываются независимо от основной программы. В основном цикле мы можем заняться чем-то другим. В этом и смысл использования прерываний. Теперь самое главное. Обратим внимание на основной цикл программы. Мы в нем ничего не делаем. Все действия, связанные с работой с энкодером, происходят в обработчике прерывания и обрабатываются независимо от основной программы. В основном цикле мы можем заняться чем-то другим. В этом и смысл использования прерываний.
  
-<WRAP center round important 60%> +<WRAP center round info 80%> 
-В этом эксперименте мы производили настройку аппаратных средств микроконтроллера, а именно настраивали прерывания. Как мы уже узнали, работа прерываний не зависит от программы и происходит автоматическиЭто значит, что обработчик прерывания будет вызываться даже после завершения работы нашей программы, ведь настройки никуда не исчезли. Это может оказать влияние на следующие эксперименты, в которых не нужны эти прерывания. Они могут мешать. Для того, чтобы следующие эксперименты проходили нормально, **необходимо перезагрузить микроконтроллер**. Это можно сделать выдернув на некоторое время кабель USB или нажать на кнопку Reset на плате конструктора.+[[https://arduino.ru/Reference/Volatile|Подробнее о ключевом слове volatile в Arduino]] 
 + 
 +[[https://arduino.ru/Reference/AttachInterrupt|Подробнее о прерываниях в Arduino]] 
 </WRAP> </WRAP>
 +
 +