Подключение энкодера с кнопкой к Digispark

 
    05.05.2024

Разработка

    По мере разработки PWM регуляторов аппетит приходил во время еды :) Сперва было 2, потом 3, а потом и 7 скоростей. Но вот на холостом ходу хотелось иметь минимальный шум работы двигателя, то есть нужна регулировка с шагом этак в 100 или 256 (минимальная скважность digispark 1/256). Числовая панель это сложно, и не очень удобно. Так что оптимальное решение - энкодер, или крутилка по простому, такая например в колёсике мышки установлена. Само собой энкодер как правило идёт с встроенной кнопкой, вспоним ту же мышку - колёсико можно крутить, а можно и нажимать. Проблема в случае с digispark, полноценных выводов 5, а для классического варианта подключения энкодера нужно 3 вывода, один вывод получается на выход и на вспомогательные цели остаётся один вывод, что маловато, тому же индикатору tm1637 нужно два вывода.

    И вот мне в один момент пришла идея, а что если подключить в аналоговом режиме? Схема приведена ниже. Когда нажата кнопка, напряжение на выходе 0, если ни одни из контактов энкодера не замкнут, на выхоже 5 Вольт, если замкнуты оба, два резистора соединяются параллельно, иначе по одному:

Для подбора номинала резисторов, написал программу, получилось подобрать номиналы резисторов по максимально минимальной разнице в уровне между ними, это верхний резистор 1 кОм, и на выходах 1 кОм и 1.6 кОм, так вышла минимальная разница в 0.58 Вольт, чего более чем достаточно. Далее уровни отсортированы по напряжению.

    В чём же фишка энкодера? А в том, что зная предыдущее и текущее значение уровня на выводах можно однозначно сказать, в какую сторону он вращается. Из 16 возможных вариантов, 4 отпадают так они без смены уровня 1-1, 2-2, 3-3, 4-4. Далее имеем ещё 4 нелегальных перехода: 1-4, 2-3, 3-2, 4-1. Казалось бы их исключаем и всё должно работать как надо. Но гладко было на бумаге, да забыли про овраги... Проблема дребезжания контактов никуда не делась, тем более в аналоговом режиме. Я реализовал замер уровня 4 раза, далее логическим сдвигом на 2 получаем среднее арифметическое. Это и решило проблему дребезга контактов, и при этом сохранило приемлемую производительность и отзывчивость. Увеличение количества замеров только ухудшало отклик и не более того, даже начинали пропускаться положения.

    По итогу имеем комбинации вращения по часовой стрелке (на самом деле всё условно, можно перепаять резисторы или выводы и вращение поменяется наоборот) 42134, а против часовой 43124. Казалось бы и всё, а вот и нет. Дело в том, что уровень не может измениться мгновенно. И при смене уровня например 31, на какое-то время уровень будет 2. И он успешно воспримется и попытается обработаться алгоритмом. В результате, имеем следующие проблемные уровни: при вращении по часовой стрелке 4321234 есть вариант 43, эта комбинация разрешённая для обратного вращения, поэтому анализруем три последних уровня, если 343, то 3 пропускаем. По такому же принципу пропускаем 2, если последние уровни 212 В случае обратного вращения, всё гораздо проще. Обе проблемные комбинации запрещённые, и такой уровень просто пропускается. 4321234

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

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

    Сам скетч:

#include 

#define CLK 1
#define DIO 3
#define tm1637bit(b) (1 << (b))
#define _dash 0x40 //-
#define _degree 0x63

/*
 # DIO change only CLK = Low

 # Byte send start:
  CLK = High; DIO to Low
  so Idle CLK = High, DIO = High

 # After send one (9th) CLK = High with DIO = Low, then idle both High
 
 */

const byte translator[16] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x77, 0x7f, 0x39, 0x3f, 0x79, 0x71};

byte prev_prev_level = 5, prev_level = 5, tune = 0, gear = 1, prev_button = 0;

void CLK1(){
    *(byte*)0x38 |= tm1637bit(CLK);
}

void DIO1(){
    *(byte*)0x38 |= tm1637bit(DIO);
}

void CLK0(){
    *(byte*)0x38 &= 255 - tm1637bit(CLK);
}

void DIO0(){
    *(byte*)0x38 &= 255 - tm1637bit(DIO);
}

void tm1637idle(){
  DIO1();
  CLK1();
}

void tm1637cmdStart(){
  DIO0();
}

void tm1637send(byte v){
  for(byte i = 0; i < 8; i++){
    CLK0();
    if( v & 1 ){
      DIO1();
    }else{
      DIO0();
    }
    
    v >>= 1;
    CLK1();
  }
  
  CLK0();
  DIO0();
  CLK1();//9th stop CLK, wait for ACK
  CLK0();
 
}

void tm1637out(byte tablo[6], byte bright){
  tm1637cmdStart();

  tm1637send(    0x40);//0x40 mode = output, autoincrement
  
  CLK1();
  DIO1();
  DIO0();
  CLK0();
  
  tm1637send(    0xc0);//0xc0 address set command, 0..5 address

  //210543 for 6-digit
  //2345 for 4-digit
  
  tm1637send(tablo[2]);
  tm1637send(tablo[1]);
  tm1637send(tablo[0]);
  tm1637send(tablo[5]);
  tm1637send(tablo[4]);
  tm1637send(tablo[3]);

  /*
  tm1637send(tablo[2]);
  tm1637send(tablo[3]);
  tm1637send(tablo[4]);
  tm1637send(tablo[5]);*/

  CLK1();
  DIO1();
  DIO0();
  CLK0();

  tm1637send(0x80 + (bright & 15)); //0x80 control 0x8 - On, 0-7 - bright  

  tm1637idle();  
}

void setup() {
  pinMode(CLK, OUTPUT);
  pinMode(DIO, OUTPUT);
  pinMode(  2,  INPUT);//encoder

  tm1637idle();

  byte tablo[6] = {0, 0, 0, 0, 0x3f, 0x3f};//    00    
  tm1637out(tablo, 15);
}

void loop() {
  byte tablo[6] = {0, 0, 0, 0, 0, 0};
  word ADCvalue;
  byte level;

  ADCvalue = 0;
  for(byte c = 0;  c < 4; c++){
    ADC_SetInputChannel((adc_ic_t) 1); //pin2
    ADC_StartConversion();
    while( ADC_ConversionInProgress() );
    ADCvalue += ADC_GetDataRegister();
    //spend 108 mks
  }
  ADCvalue = ADCvalue >> 2;

  if( ADCvalue <= ((0 + 389) / 2) ){
    level = 0;
  }else{
    if( ADCvalue <= ((389 + 512) / 2) ){
      level = 1;
    }else{
      if( ADCvalue <= ((512 + 631) / 2) ){
        level = 2;
      }else{
        if( ADCvalue <= ((631 + 1024) / 2) ){
          level = 3;
        }else{
          level = 4;
        }
      }
    }
  }

  if( prev_level != level ){

    tablo[0] = translator[prev_prev_level];//debug
    tablo[1] = translator[prev_level];
    tablo[2] = translator[level];
    
    byte state = (prev_level << 3) + level;
    
    if( level == 0 ){//button pressed
      if( prev_button == 0 ){
        if( gear == 1 ){
          gear = 10;
        }else{
          gear =  1;
        }

        prev_button = 1;
      }
      
      prev_prev_level = 0;
      prev_level = 0;
    }else{//encoder action
      prev_button = 0;

      if(
        (prev_prev_level == 2 && prev_level == 1 && level == 2) ||
        (prev_prev_level == 3 && prev_level == 4 && level == 3) ||
        state == (2*8 + 3) || 
        state == (3*8 + 2)
      ){
        ;//incorrect sequence, skip value
      }else{//13421 clockwise
        if( prev_prev_level == 0 ){
          ;//skip after button press
        }else{
          if(
            state == (1*8 + 3) || 
            state == (3*8 + 4) || 
            state == (4*8 + 2) || 
            state == (2*8 + 1)
          ){
              tune += gear;

              if( tune > 99 ){
                tune = 99;
              }

          }else{//12431 clockwise
            if(
              state == (1*8 + 2) || 
              state == (2*8 + 4) || 
              state == (4*8 + 3) || 
              state == (3*8 + 1)
            ){
              if( tune >= gear ){
                tune -= gear;
              }else{
                tune = 0;
              }
          
            }
          }
        }

        prev_prev_level = prev_level;      
        prev_level = level;
      }      
    }

    byte c = tune;
    while( c >= 10 ){
      c -= 10;
      tablo[4]++;
    }
    tablo[4] = translator[tablo[4]];

    if( gear == 10 ){
      tablo[4] += 0x80;
    }
    tablo[5] = translator[c];
      
    tm1637out(tablo, 15);
  }
}

//val 389 512 1023 631 0

    По итогу получаем точную регулировку с индикацией. И при этом используется всего три провода. Энкодер подключен к выводу 2, так как 3 и 4 используются для usb, они подятнуты к плюсу, плюс там стабилитрон на 3.3 Вольта. Для выхода PWM можно использовать вывод 0.

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

    По наблюдениям, между фикированными положениями энкодера происходит два шага, две смены уровня. Видно чтобы срабатывание было чётче. Можно так сделать, но крутиться будет вдвое медленнее. Это по желанию.

    По кнопкаv самое правильное решение, если использовать tm1637 - это подключить кнопки к ней. Она поддерживает до 16 кнопок. Готовые платы tm1637 с индикатором и кнопками есть на Али. Как приспичит, я наверно попробую припаять к обычной плате. Ещё надо будет доработать скетч для забора состояния кнопок.

    По итогу самоделка удалась, я очень доволен. Получилось всего по одному проводу подключить энкодер, исключив большинство ошибок алгоритмически.

   

Фотогалерея