Использование поворотных энкодеров с прерываниями смены контактов

Для проекта я использую Arduino Uno и два (3-контактных) поворотных энкодера. Поскольку цикл занимает немного больше времени, а точность важна, я бы хотел использовать прерывания для считывания значений с поворотных энкодеров. Кроме того, контакты 2 и 3 заняты, поэтому я не могу использовать аппаратные прерывания.

Я использую библиотеку Rotary (ссылка на github) для поворотных энкодеров и библиотеку EnableInterrupt (ссылка на github) для внешних прерываний. Библиотека Rotary включает пример прерывания, но мне не удалось изменить его для работы с прерываниями смены контакта. Это мое текущее состояние (тестовый код):

#include <EnableInterrupt.h>
#include <Rotary.h>

Rotary r = Rotary(2, 3);

void setup() {
  Serial.begin(9600);

  /* Original interrupt setup:
  PCICR |= (1 << PCIE2);
  PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);
  sei();
  */

  enableInterrupt(2, interruptFunction, CHANGE);
  enableInterrupt(3, interruptFunction, CHANGE);
}

void loop() {

}

void interruptFunction() {

  Serial.println("Int. called");

  unsigned char result = r.process();
  if (result == DIR_NONE) {
    // ничего не делать
  }
  else if (result == DIR_CW) {
    Serial.println("ClockWise");
  }
  else if (result == DIR_CCW) {
    Serial.println("CounterClockWise");
  }
}

Каждый раз, когда я поворачиваю энкодер на один шаг, функция interruptFunction вызывается несколько раз (около 2–4), но вызов r.process() никогда не возвращает значение.

В настоящее время я предполагаю, что это как-то связано с поворотным энкодером, выдающим код Грея, но я не совсем уверен, чем мой подход отличается от примера с поворотным прерыванием, использующим аппаратные прерывания.

Надеюсь, вы сможете помочь!

, 👍2

Обсуждение

Избегайте Serial в ISR. Проверьте онлайн-документацию., @Mikael Patel

Я знаю об этом, я просто использовал предоставленный (и рабочий) пример в качестве основы для устранения возможных источников сбоя. Я, конечно, изменю его для своего производственного кода., @jfkint

Функция process() библиотеки Rotary не является функцией блокировки и только считывает состояние двух контактов и вычисляет результат. Как вы можете сказать _"но вызов r.process() никогда не возвращает значение."_?, @J. Piquard


5 ответов


1

У меня тоже была эта проблема. Я часами преследовал свой хвост на юго-западе. Я делал ISR нереентерабельными (предполагая, что они были случайными), я выполнял устранение дребезга ПО и т. д. Я даже смотрел на сигнал на осциллографе, но не видел каких-либо значительных рикошетов. В конце концов я добавил конденсаторы 0,1 мкФ на входы, и это решило все мои проблемы с ISR. Так что не доверяйте производителям энкодеров, будет дребезг сигнала, и ваш Arduino ОБНАРУЖИТ его и выдаст несколько прерываний. Удачи с вашим проектом! ЛДП

,

> У меня тоже была такая проблема. несправедливо обвинять эту топологию в проблеме кодирования. Но ваше замечание о красоте аппаратного устранения дребезга по-прежнему актуально, и его часто игнорируют новички, желающие поставить свою жизнь на программное устранение дребезга., @dannyf


1

Каждый раз, когда я поворачиваю энкодер на один шаг, функция interruptFunction получает вызывается несколько раз (около 2~4)

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

Например, когда прерывание вызывается в первый раз, отключите прерывания по смене контакта. Затем установите таймер, который будет запускать прерывание по истечении его времени (время должно быть больше, чем время возврата, обычно несколько мс или более). Затем в прерывании таймера сначала введите свой код:

  unsigned char result = r.process();
  if (result == DIR_NONE) {
    // ничего не делать
  }
  else if (result == DIR_CW) {
    Serial.println("ClockWise");
  }
  else if (result == DIR_CCW) {
    Serial.println("CounterClockWise");
  }

end повторно включить прерывания смены контакта.

  enableInterrupt(2, interruptFunction, CHANGE);
  enableInterrupt(3, interruptFunction, CHANGE);

но вызов r.process() никогда не возвращает значение.

r.process() ВСЕГДА возвращает значение. Может быть, вы имели в виду, что он возвращает DIR_NONE ?

,

1

проверьте библиотеку, чтобы убедиться, что она устраняет дребезг ввода.

Если нет, напишите свой собственный код. обычно подходы на основе конечного автомата очень устойчивы к отказу переключателя.

,

-1

Правильное решение — использовать схему подавления дребезга. MC14490 представляет собой шестигранную схему подавления дребезга и идеально подходит для такого типа приложений. Нет необходимости блокировать прерывания или устанавливать таймеры. Он доступен либо как SMD, либо через отверстие.

,

Это неплохое устройство... за исключением того, что версия SMD дорогая (~ 11 долларов США), а версия со сквозным отверстием больше не производится. Есть и другие, более дешевые, меньшие по размеру, более простые и практичные способы устранения дребезга контактов., @TDHofstetter


1

Исправление кода, 7 октября — аккуратный код согласно комментариям. Исправление кода 7 октября - правильное чтение поворотного энкодера

Использует прерывания смены контакта, чтобы разрешить увеличение и уменьшение двух поворотных энкодеров в процедуре обслуживания прерывания с использованием Arduino (в моем случае nano) с чипом ATMEGA328P. Необходимо добавить 2 x 104 (колпачки 0,1 мкФ) на поворотном энкодере между каждым контактом и центральным контактом заземления, чтобы остановить дребезг, не работает без добавления конденсаторов. Не нуждается ни в какой библиотеке. Просмотрел учебник: прерывания смены контактов на Arduino от makecourse, который помог понять прерывания смены контактов, поможет посмотреть это, если у вас другая версия arduino, поэтому узнайте, какие цифровые выводы относятся к каким прерываниям.

          //********************************************* * поворотный энкодер ********************************************************** *************
          #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))    //быстрый способ очистки бита в регистре
          #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))     //быстрый способ установить бит в регистре
          
          
          #define Encoder1pin 3 //соответствует PCINT19
          #define Encoder2pin 4 //соответствует PCINT20
          #define Encoder3pin 5 //соответствует PCINT21
          #define Encoder4pin 6 //соответствует PCINT22
          #define Encoder5pin 7 //соответствует PCINT23

          unsigned long encoderPos1 = 100;
          unsigned long oldEncPos1 = 0; 
          unsigned long encoderPos2 = 100; //эта переменная хранит наше текущее значение положения энкодера. Измените на int или uin16_t вместо byte, если вы хотите записать больший диапазон, чем 0-255
          unsigned long oldEncPos2 = 0; //сохраняет последнее значение положения энкодера, чтобы мы могли сравнить с текущими показаниями и посмотреть, изменилось ли оно (чтобы мы знали, когда печатать на последовательный монитор)
          
          volatile byte portDstatus;
          
          volatile bool readingEncoder1pin = 0;
          volatile bool readingEncoder2pin = 0;
          volatile bool readingEncoder3pin = 0;
          volatile bool readingEncoder4pin = 0;
          volatile bool lastreadingEncoder1pin = 0;
          volatile bool lastreadingEncoder2pin = 0;
          volatile bool lastreadingEncoder3pin = 0;
          volatile bool lastreadingEncoder4pin = 0;
          
          void setup() {
          
            Serial.begin(9600);
          
          
          //*************************************************** *** энкодер ******************************************************* ************************************
            pinMode(Encoder1pin, INPUT);//определить вывод как INPUT
            digitalWrite(Encoder1pin, HIGH);//подключаем внутренний подтягивающий резистор — это даст ВЫСОКОЕ значение, если переключатель разомкнут.
            pinMode(Encoder2pin, INPUT);
            digitalWrite(Encoder2pin, HIGH);
            pinMode(Encoder3pin, INPUT);
            digitalWrite(Encoder3pin, HIGH);
            pinMode(Encoder4pin, INPUT);
            digitalWrite(Encoder4pin, HIGH);
            pinMode(Encoder5pin, INPUT);
            digitalWrite(Encoder5pin, HIGH);
          
          
            // настройка контактов прерывания: // мне нужно было установить все это, иначе последовательный монитор не считывал байт правильно -
                                               // и это не работает.
          
            sbi (PCICR, PCIE2); // разрешить прерывание PCI2);
            sbi (PCMSK2,PCINT23); // устанавливаем биты управления прерываниями для контактов 7-3. Эти биты управляют тем, могут ли выводы вызывать прерывание или нет.
            sbi (PCMSK2, PCINT22);
            sbi (PCMSK2, PCINT21);
            sbi (PCMSK2, PCINT20);
            sbi (PCMSK2, PCINT19); // мы не хотим устанавливать управление прерываниями для контактов 0 или 1, так как это последовательное чтение.
          
          //*************************************************** ******************* ВЕЛ ****************************** *********************************************
          }
          
          void loop() 
          
              {
               }
          
           
          
          
          ISR(PCINT2_vect) // Процедура обслуживания прерывания. Этот код выполняется, когда на выводе происходит изменение состояния
          {
          
            
          
                portDstatus=PIND;
               readingEncoder1pin = bitRead(portDstatus,3);
               readingEncoder2pin = bitRead(portDstatus,4);
             if(lastreadingEncoder1pin == 1 && lastreadingEncoder2pin == 0 && readingEncoder1pin == 1 && readingEncoder2pin == 1)
              {
                  encoderPos1 ++;
                   lastreadingEncoder1pin = readingEncoder1pin;
                   lastreadingEncoder2pin = readingEncoder2pin;
              }
          
              else if(lastreadingEncoder1pin == 0 && lastreadingEncoder2pin == 1 && readingEncoder1pin == 1 && readingEncoder2pin == 1)
              {
                encoderPos1 --;
                  lastreadingEncoder1pin = readingEncoder1pin;
                  lastreadingEncoder2pin = readingEncoder2pin;;
              }
              else
                {
                   lastreadingEncoder1pin = readingEncoder1pin;
                   lastreadingEncoder2pin = readingEncoder2pin;
                }
          
          
    // чтение второго энкодера:
          
          
          
               readingEncoder3pin = bitRead(portDstatus,5);
               readingEncoder4pin = bitRead(portDstatus,6);
          
             if(lastreadingEncoder3pin == 1 && lastreadingEncoder4pin == 0 && readingEncoder3pin == 1 && readingEncoder4pin == 1)
              {
                  
                  encoderPos2 ++;
                   lastreadingEncoder3pin = readingEncoder3pin;
                   lastreadingEncoder4pin = readingEncoder4pin;
              }
          
              else if(lastreadingEncoder3pin == 0 && lastreadingEncoder4pin == 1  && readingEncoder3pin == 1 && readingEncoder4pin == 1)
              {
                encoderPos2 --;
                  lastreadingEncoder3pin = readingEncoder3pin;
                  lastreadingEncoder4pin = readingEncoder4pin;;
              }
              else
                {
          
                   lastreadingEncoder3pin = readingEncoder3pin;
                   lastreadingEncoder4pin = readingEncoder4pin;
                }
             // последовательная печать только для тестирования, а затем удаление из ISR
          Serial.print(encoderPos1);
          Serial.println(encoderPos2);
        
             }
,

У этого кода есть несколько проблем: 1. portDstatus установлен, но никогда не используется. Для согласованности было бы лучше прочитать PIND только один раз, а затем получить отдельные показания вывода из portDstatus. 2. lastportDstatus никогда не устанавливается и не используется. 3. encoderPos и oldEncPos могут переполняться, поэтому они должны быть без знака, чтобы избежать неопределенного поведения. 4. (последний)readingEncoder[12]pin не обязательно должен быть long, должно быть достаточно byte. 5. Только encoderPos должен быть volatile, предполагая, что он будет использоваться в loop()., @Edgar Bonet

(продолжение...) 6. Подтягивания можно включить с помощью pinMode(pin, INPUT_PULLUP);, что понятнее, чем использование digitalWrite(). 7. Нет смысла включать PCINT на контактах 5, 6 и 7, если энкодер подключен только к контактам 3 и 4. 8. Логика декодирования выглядит ошибочной, например, lastreadingEncoder1pin не должен быть энкодера 1 вывод действительно был нулевым. 9. Использование Serial.println() в ISR вызывает проблемы., @Edgar Bonet

У вас есть исправленные проблемы 1–4. Большой! Еще небольшая проблема с логикой энкодера: 8′. Если энкодер колеблется между readingEncoder(1,2)pin = (1,0) и (1,1), позиция будет постоянно увеличиваться. И он будет уменьшаться, если колеблется как (0,1) ↔ (1,1). Это можно исправить, уменьшив (1,1) → (1,0) вместо (0,1) → (1,1). А затем: 7′. Необходимо включить только PCINT{20,22}, чтобы избежать лишних прерываний., @Edgar Bonet