Опрос энкодера

Тема в разделе "Схемотехника, компоненты, модули", создана пользователем ostrov, 14 май 2016.

  1. ostrov

    ostrov Гуру

    Потребовалось применить энкодер в одном из проектов. Готовые варианты показались либо слишком громоздкими либо не учитывающими реалии типа дребезга механических контактов реального энкодера. Сделал собственную функцию циклического опроса, которую можно вызывать в loop либо по прерыванию таймера (что, пожалуй, лучше и правильнее). В примере первый вариант, если нужно опубликую и второй. Суть работы такая: некая переменная, представляя из себя по сути кольцевой буфер, хранит 4 последних состояния одного из контактов. Изменение состояния и устаканивание дребезга оценивается по набору этих данных, а именно 2 вкл и 2 выкл, после чего проверяется состояние второго контакта и в зависимости от него делается либо инкремент либо декремент положения энкодера. Получилось компактно, работает стабильно. Может кому пригодится:

    Код (C++):
    #define C1 6  // первый контакт
    #define C2 5  // второй контакт

    byte bufEnc;              // кольцевой буфер в байте
    unsigned long timerEnc;   // таймер опроса
    unsigned int timEnc = 1;  // пауза опроса (если короче проскакиевт дребезг, если длиннее проскакивают щелчки)
    int stepEnc = 0;          // состояние энкодера

    unsigned long timerPrint;

    void setup() {
      pinMode(C1, INPUT);
      pinMode(C2, INPUT);
      timerEnc = millis() + timEnc;
      timerPrint = millis() + 1000;
      Serial.begin(9600);
    }

    void loop() {
       encod ();                   // проверка состояния пинов энкодера с извлечением выводов

      if (millis() > timerPrint) { // показываем что получилось каждую секунду
        Serial.println(stepEnc);
        timerPrint = millis() + 1000;
      }
    }

    void encod() {                 // выполняется за 8мкс, при обращению к порту - за 3мк
      if (millis() < timerEnc) return;
        bufEnc = ((bufEnc << 1) | digitalRead(C1)) & 0b00001111;
        if (bufEnc == 12) {
          stepEnc += digitalRead(C2) ? 1 : -1;
        }
        timerEnc = millis() + timEnc;
    }
     
    Последнее редактирование: 14 май 2016
  2. Okmor

    Okmor Нерд

    Без использования прерываний, можете даже не пытаться достичь стабильной работы энкодера.
    Объясняю.
    Если у вас в цикле кроме опроса энкодера живет какая то еще обработка, то вы просто можете пропустить момент для чтения digitalRead(C2).
     
    Последнее редактирование: 16 май 2016
  3. ostrov

    ostrov Гуру

    Разумеется этот способ для малонагруженной программы. Такая у меня и была, опрашивала энкодер, кнопку от него и выводила на индикатор цифру, причем даже не динамически. А пропустить переключение очень запросто может, особенно если крутить бешенно. Можно правда буфер увеличить, например до 8 и искать в нем перепад с двумя вкл и двумя выкл, так будет стабильнее на мой взгляд. Но и сложнее.
     
    Последнее редактирование: 16 май 2016
  4. Okmor

    Okmor Нерд

    Вот содрал у колеги dimax на другом форуме. Просто и безглючно.
    Только я прерывание ставлю только на один вход, а не на два - так правильнее и ресурсов меньше жрет.

    Код (C++):
    volatile int enc;
    void setup() {              
    Serial.begin(9600);
    PCICR=1<<PCIE1; //разрешить пренрывание
    PCMSK1=(1<<PCINT9)|(1<<PCINT8); //выбрать входы
    }

    ISR (PCINT1_vect){
    static byte old_n=PINC&3; // маска B00000011 что б читать только нужные 2 бита
    byte new_n=PINC&3;
    if (old_n==1&&new_n==3||old_n==2&&new_n==0) {enc++;}
    if (old_n==2&&new_n==3||old_n==1&&new_n==0) {enc--;}
    old_n= new_n;
    }

    void loop() {
    Serial.println(enc);
    }
     
     
  5. ostrov

    ostrov Гуру

    Входов то все равно два надо, а прерывание по таймеру в моем случае, а не по событию.

    Да, по событию я делал, но проблема в том, что срабатывало на дребезг контактов. За один щелчек счетчик прыгал на непредсказуемое ко-во шагов. Надо либо аппаратно давить, либо придумать что то еще.
     
  6. Okmor

    Okmor Нерд

    Брал такой.
    Может у вас такой. У меня он действительно не работал. Показания скакали, то на два, то на три, то на один тик. В основном отсчитывал по два тика. - Не рекомендую.
    Подключил другой и все заработало как часы. Даже при очень быстром вращении.
    [​IMG]
     
  7. ostrov

    ostrov Гуру

    Какой то такой. Смотрел анализатором, ни одного переключения без дребезга. Хорошо хоть дребезг короткий. Вот потому и сделал измерения через интервал, вроде бы заработало. Хотя, возможно, надо просто найти хорошие энкодеры без дребезга, если такие бывают в природе.
     
  8. Okmor

    Okmor Нерд

    Там не в дребезге дело. Он выдает по два клика на одно положение. Может где то такой нужен, но я с ним нае*бался еще как. Потом забросил и взял вот такой. С ним все в порядке. Даже без конденсаторов четко отрабатывает.
    [​IMG]
     
  9. ostrov

    ostrov Гуру

    Два это еще неплохо, можно поделить на два. А вот дребезг угадать невозможно. Заказал в Китае три разных, посмотрю вот.
     
  10. Okmor

    Okmor Нерд

    Да нифига там не поделишь. Он в основном выдает по два импульса, но в 20% случаев три, или один.
    Второй работает четко - 99.9% одинарных импульсов без конденсаторов. Конденсаторы я все же поставил, мало чего.
     
  11. ostrov

    ostrov Гуру

    Для аппаратного антидребезга конденсатора мало, нужна RC цепь.
     
  12. Okmor

    Okmor Нерд

    А там что?
    На схему посмотрите. Резистор подтяжки плюс конденсатор, вот вам и RC цепь.
    Весь мир работает на энкодерах.
    "Вы не любите кошек?! Вы просто не умеете их готовить!"
     
  13. ostrov

    ostrov Гуру

    Вот пока что самым устойчивым к помехам и пропускам оказался еще более короткий код, который нужно вызывать каждые 2, а лучше 1мс:
    Код (C++):
      b1 = b2;
      b2 = digitalRead(C1);
      b3 = digitalRead(C2);
      if (b1 && !b2) stepEnc += b3 - !b3;
    где: b1, b2, b3 - переменные типа bool, stepEnc - счетчик энкодера.

    Основная проблема механического энкодера "обычного" качества в том, что если делать паузы между опросами слишком малые - будет засчитан дребезг (то есть непредсказуемое количество шагов в любую сторону), если слишком большие, то будут пропуски. То же самое касается аппаратного подавления. Приходится балансировать.
     
  14. Serezzza

    Serezzza Нуб

    А как сделать, что бы показания выводились не каждую секунду, а только если они (показания) меняются?
     
  15. ostrov

    ostrov Гуру

    Еще проще: выводятся в случае если новое показание не равно старому.
     
  16. Serezzza

    Serezzza Нуб

    Вот ещё рабочий код, поменьше, чем в 1 посте:

    #include <Encoder.h>
    Encoder Enc_1(4, 5);
    long old_pos = 0;
    void setup() {Serial.begin(9600);}
    void loop() {
    long new_pos;
    new_pos = Enc_1.read()/4;
    if (new_pos != old_pos) {
    old_pos = new_pos;
    Serial.println(old_pos);
    }
    }
     
  17. ostrov

    ostrov Гуру

    Вы скомпилируйте тот и другой и сравните какой меньше.
     
  18. Antipod

    Antipod Нуб

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

    Вложения: