Encoder connection to Arduino and full-featured processing code for it

An encoder is a device that converts mechanical movement or angular changes in position into a digital signal. The article discusses the most popular incremental encoder EC11 with a button in the DIY community. When it rotates, TTL signals are generated in the form of pulses at outputs A and B, phase-shifted by 90 degrees. Thus, with its help, you can determine the direction and speed of rotation, as well as calculate the angle of rotation. Unlike potentiometers, the KY-040 encoder is much more reliable and durable.

Few details

Compiling one of the projects using encoder. I could not find a code for Arduino that fulfills all my conditions. Since the project needs to process the following commands: “Rotation without pressing”, “Rotation with pressing”, “Press” and “Long press”, and also requires stable operation of the encoder. Sketches using one pin with the INT0 or INT1 interrupt work disgustingly and when the encoder shaft rotates, a lot of errors fly out. The code without using interrupts works stably, but it does not work in the background, it needs to be embedded in the body of the main program, which in turn leads to inopportune triggering of the handler and gaps in the rotation of the encoder. The situation is even worse with the processing of pressing with the rotation of the encoder shaft and the usual one with pressing. I had to write my own processing code that eliminates the problems described above. I did not programmatically deal with the bounce of contacts, as this leads to processing delays. It is easier and safer to use ceramic capacitors.

Diagram of connecting the encoder to Arduino

To read signals from the EC-11 outputs, you need to use the three digital inputs of the Arduino. In the wiring diagram, I used Arduino pins that I rarely used in my projects (A1, A2 and A3). There are no external pull-up resistors as I used the internal pull-up of the microcontroller. Capacitors are needed to damp the contact bounce impulses. If you have a new and good encoder, then you can do without them. But in any case, a capacitor will be required for the button, since its bounce is inevitable.

Components used in the circuit:

Arduino nano – 1 PC.
Encoder EC11 -1 PC.
Connecting Reins – 4 things.
Ceramic capacitors 0.1 μF – 3 pcs.

Sketch for Arduino

In order to monitor the change in the position of the encoder in the background, I use the PCINT1 interrupt. All functions are processed in an interrupt, the handler, depending on the action that has taken place, changes the enc_state variable. If the value of the variable enc_state = 0 – nothing happened, enc_state = 1 – the ecocoder rotated without pressing, enc_state = 2 – the ecocoder rotated with a click, enc_state = 3 – there was a click on the button, enc_state = 4 – there was a long press on the button, the interruption will be to be triggered every time when the state of the inputs changes, both from high to low and vice versa. That is, with one click of the encoder, the interrupt will be triggered 4 times. Or 2 times for each of the inputs. But the handler will issue a turn signal only 1 time for all 4 interrupts.
The handler code writes to the lastcomb variable the state of the inputs to which the encoder is connected at each operation. And it waits for a state when outputs A and B are shorted to GND, this is a guaranteed signal that the encoder is rotating. After this signal is received, the handler checks in which direction the rotation was. To do this, it compares its previous value from the lastcomb variable and, depending on the shift phase, will determine in which direction the rotor turned. As I wrote earlier, the most difficult thing is to track the button click.
Since I did not plan to use certain timings, because they inevitably lead to long delays in the work of the handler and the main program, or require the use of a timer, of which there are only 3 in the microcontroller. they are usually never enough. The actual problem was to separate the “press and then rotate” from a simple press. In the end, as you can already see, I solved this problem. I did not begin to optimize the code, because everything works and everything suits me. For clarity, in the code, all actions with the encoder are displayed in the Serial monitor of the Adruino IDE program.

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

#define btn_long_push 1000   // Длительность долинного нажатия кнопки
volatile uint8_t lastcomb=7, enc_state, btn_push=0;
volatile int enc_rotation=0, btn_enc_rotate=0;
volatile boolean btn_press=0;
volatile uint32_t timer;

void setup() 
  pinMode(A1,INPUT_PULLUP); // ENC-A
  pinMode(A2,INPUT_PULLUP); // ENC-B

  PCICR =  0b00000010; // PCICR |= (1<<PCIE1); Включить прерывание PCINT1
  PCMSK1 = 0b00001110; // Разрешить прерывание для  A1, A2, A3

void loop() 
    if(enc_state==1) // Если экодер вращался без нажатия
    Serial.print("Вращение без нажатия ");
    enc_state=0; //обнуляем статус энкодера
  if(enc_state==2) // Если экодер вращался с нажатием
    Serial.print("Вращение с нажатием ");
    enc_state=0; //обнуляем статус энкодера
  if(enc_state==3) // Если было нажатие на кнопку
    Serial.println("Нажатие кнопки ");
    enc_state=0; //обнуляем статус энкодера
    if(enc_state==4) // Если было длинное нажатие на кнопку
    Serial.println("Длинное нажатие кнопки ");
    enc_state=0; //обнуляем статус энкодера

ISR (PCINT1_vect) //Обработчик прерывания от пинов A1, A2, A3
  uint8_t comb = bitRead(PINC, 3) << 2 | bitRead( PINC, 2)<<1 | bitRead(PINC, 1); //считываем состояние пинов энкодера и кнопки

 if (comb == 3 && lastcomb == 7) btn_press=1; //Если было нажатие кнопки, то меняем статус
 if (comb == 4)                         //Если было промежуточное положение энкодера, то проверяем его предыдущее состояние 
    if (lastcomb == 5) --enc_rotation; //вращение по часовой стрелке
    if (lastcomb == 6) ++enc_rotation; //вращение против частовой
    enc_state=1;                       // был поворот энкодера    
    btn_enc_rotate=0;                  //обнулить показания вращения с нажатием
   if (comb == 0)                      //Если было промежуточное положение энкодера и нажатие, то проверяем его предыдущее состояние 
    if (lastcomb == 1) --btn_enc_rotate; //вращение по часовой стрелке
    if (lastcomb == 2) ++btn_enc_rotate; //вращение против частовой
    enc_state=2;                        // был поворот энкодера с нажатием  
    enc_rotation=0;                     //обнулить показания вращения без нажатия
    btn_press=0;                         //обнулить показания кнопки

   if (comb == 7 && lastcomb == 3 && btn_press) //Если было отпусание кнопки, то проверяем ее предыдущее состояние 
    if (millis() - timer > btn_long_push)         // проверяем сколько прошло миллисекунд
      enc_state=4;                              // было длинное нажатие 
    } else {
             enc_state=3;                    // было нажатие 
      btn_press=0;                           //обнулить статус кнопки
  timer = millis();                       //сброс таймера
  lastcomb = comb;                        //сохраняем текущее состояние энкодера


The result of the code made me happy and now I can continue working on my new project, which I will post here soon. I hope you liked this short article and you can use my experience in your homemade products.
If you have any questions or comments, write them in the comments. I will be happy to answer them.

Similar Posts

Leave a Reply