Encoder irregular output

Hello

I have decided to fix an issue with a MIDI controller that I built a few years ago as the output of the encoders have always been very jittery. I am using a teensy 2.0 with 3 encoders attached to pin 4,5,6,7,8and9.

To test my encoders I uploaded on the board the "Two Knobs" example sketch (from Paul Stoffregen encoder.h library), only changing the first two lines to match the pins of two of my encoders:

Encoder knobLeft(4, 5);
Encoder knobRight(6, 7);

unfortunately I keep getting a very poor output when turning the knobs, numbers are not increased in a regular fashion. Please see below the output for one turn of the encoder (same happen when turning the right knob). From what I can see on the library web page this is not the expected output. I would be grateful to receive some advice. Thanks!

output when turning left knob one notch:

Left = 1, Right = 0
Left = 0, Right = 0
Left = 1, Right = 0
Left = 0, Right = 0
Left = 1, Right = 0
Left = 2, Right = 0
Left = 1, Right = 0
Left = 2, Right = 0
Left = 1, Right = 0
Left = 2, Right = 0
Left = 1, Right = 0
Left = 2, Right = 0
Left = 1, Right = 0
Left = 2, Right = 0
Left = 1, Right = 0
Left = 2, Right = 0
Left = 1, Right = 0
Left = 2, Right = 0
Left = 1, Right = 0
Left = 2, Right = 0
Left = 1, Right = 0
Left = 2, Right = 0
Left = 1, Right = 0
Left = 2, Right = 0
Left = 1, Right = 0
Left = 2, Right = 0
Left = 1, Right = 0
Left = 2, Right = 0
Left = 1, Right = 0
Left = 2, Right = 0
Left = 1, Right = 0
Left = 2, Right = 0
Left = 1, Right = 0
Left = 2, Right = 0
Left = 3, Right = 0
Left = 4, Right = 0
Left = 3, Right = 0
Left = 4, Right = 0
Left = 3, Right = 0
Left = 4, Right = 0
Left = 3, Right = 0
Left = 4, Right = 0
Left = 3, Right = 0
Left = 4, Right = 0
Left = 3, Right = 0
Left = 4, Right = 0
Left = 3, Right = 0

Hello fluxia

Either check this:

or this:

Have a nice day and enjoy coding in C++.

encoder-code that does not use the state-table-approach tend to have problems with bouncing and/or unreliable detection.

On my research for reliable encode-code I discovered this library

which uses the state-table-approach.
The examples delivered with the library itself use polling in function loop().
This is sufficient of you do not much more than polling the encoders.

If your code shall do more than just polling the encoders one way to do this is using a timer-interrupt for the polling.
This has the advantage that you need only one timer-interrupt to do fast encoder-reading without the need for multiple IO-pin-interrupts and without the need for interrupt-capable IO-pins.

I tested this on an arduino Uno and it worked really good
A timer-interrupt is hardware-specific to the type of microcontroller.
This means this demo-code below works only on arduino-unos.

/* Demo-Code that uses the Rotary-library from GitHub-User https://github.com/buxtronix
 * using his library https://github.com/buxtronix/arduino/tree/master/libraries/Rotary  
 * in combination with a timer-interrupt executed 10000 times per second
 * Copyright 2023 StefanL38. Licenced under the GNU GPL Version 3.
 * A T T E N T I O N ! 
 * this demo-code uses Timer2 which is used by other libraries too. 
 * This means using this code can interfere with other libraries
 * causing malfunction of both
 * 
 * As timer-interrupts are very hardware-sepcific
 * this demo-code works with Arduino-Uno R3 based on the chip AtMega328P
 * if you want to use it on other microcontrollers you will have to
 * modify setting up the timer-interrupt according to the hardware
 * of this other microcontroller
 * 
 * The demo-code simply prints the value of variable myCounter 
 * each time the value of the variable changes
 */
#include <Rotary.h>

const int  stepsPerRev = 200;


const byte CLK1 = 2;
const byte DT1  = 8;

const byte CLK2 = 3;
const byte DT2  = 11;

const byte CLK3 = 21;
const byte DT3  = 12;

Rotary rotary1 = Rotary(CLK1, DT1);
Rotary rotary2 = Rotary(CLK2, DT2);
Rotary rotary3 = Rotary(CLK3, DT3);

unsigned long myISR_TimerFrequency = 1000;
// Cnt1000, Cnt100, Cnt10 that will be incremented or decremented by rotation.
// as these variables are changed in an interrupt-service-routine
// this variable MUST !! be declared volatile to make sure 
// that it works properly !

volatile unsigned char Cnt1000 = 0;
volatile unsigned char Cnt100  = 0;
volatile unsigned char Cnt10   = 0;

long runSpeed = 0;
long last_runSpeed = 0;


void setupTimerInterrupt(unsigned long ISR_call_frequency) {
  long OCR2A_value;

  const byte Prescaler___8 = (1 << CS21);
  const byte Prescaler__32 = (1 << CS21) + (1 << CS20);
  const byte Prescaler__64 = (1 << CS22);
  const byte Prescaler_128 = (1 << CS22) + (1 << CS20);
  const byte Prescaler_256 = (1 << CS22) + (1 << CS21);
  const byte Prescaler1024 = (1 << CS22) + (1 << CS21) + (1 << CS20);

  const unsigned long CPU_Clock = 16000000;

  cli();//stop interrupts

  TCCR2A = 0;// set entire TCCR2A register to 0
  TCCR2B = 0;// same for TCCR2B
  TCNT2  = 0;//initialize counter value to 0

  TCCR2A |= (1 << WGM21); // turn on CTC mode
  TIMSK2 |= (1 << OCIE2A); // enable timer compare interrupt

  TCCR2B = Prescaler__32; 
  // OCR2A_value must be smaller than 256
  // use a different prescaler if OCR2A_value is > 256
  OCR2A_value = (CPU_Clock / ( 32 * ISR_call_frequency) )  - 1;

  OCR2A = OCR2A_value; // set the value of OCR2A

  sei();//allow interrupts
}


// timer-interrupt-service-function for AtMega328P
ISR(TIMER2_COMPA_vect) {
  processEncoders();
}


// counts up/down with each encoder-click
void processEncoders() {
  unsigned char result;
  
  result = rotary1.process();
  if (result == DIR_CW) {
    Cnt1000++;
  }
  else if (result == DIR_CCW) {
    Cnt1000--;
  }
  if (Cnt1000 < 0) {
    Cnt1000 = 0;
  }
  
  result = rotary2.process();
  if (result == DIR_CW) {
    Cnt100++;
  }
  else if (result == DIR_CCW) {
    Cnt100--;
  }
  if (Cnt100 < 0) {
    Cnt100 = 0;
  }
  
  result = rotary3.process();
  if (result == DIR_CW) {
    Cnt10++;
  }
  else if (result == DIR_CCW) {
    Cnt10--;
  }
  if (Cnt10 < 0) {
    Cnt10 = 0;
  }
}


void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("  compiled ") );
  Serial.print( F(__DATE__) );
  Serial.print( F(" ") );
  Serial.println( F(__TIME__) );
}


// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

unsigned long MyTestTimer = 0;  // Timer-variables MUST be of type unsigned long
const byte    OnBoard_LED = 13;


void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);

  if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
    digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
  }
}


void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  PrintFileNameDateTime();
  setupTimerInterrupt(myISR_TimerFrequency);
  }


void loop() {

  BlinkHeartBeatLED(OnBoard_LED, 250);

  runSpeed = Cnt1000 * 1000 + Cnt100 * 100 + Cnt10 * 10;

  // check if value has changed
  if (last_runSpeed != runSpeed) {
    last_runSpeed = runSpeed; // update last_runSpeed
    Serial.print("runSpeed=");
    Serial.println(runSpeed);
  }
}

For making it work with a teensy 2.0 which is a completely different microcontroller you will have to use hardware-specific code for teensy timer-interrupts.

So I searched for such a library and found this one

This library is downloadable from GitHub as ZIP-library or with the Arduino IDE-library manager.

This library makes using the teensy hardware-timers timer-interrupts pretty easy.

The example demo-code

shows it.

I haven't tested this myself but including the rotary.h-library and then using the function

void processEncoders()

instead of

void  TimerHandler0()

should make it work.

best regards Stefan

I have thrown together the argument-none demo-code and my 3x rotary-code.
This code does compile for arduino-ide adjusted for a teensy 4.1-board.

As the Teensy_TimerInterrupt-library claims to work for teensy 2.X, 3.X, 4.X it should work for teensy 2.0 too.

But I have not tested this demo-code with real hardware. I tested only compiling.

/****************************************************************************************************************************
  Demo-code showing how to use a teensy timer-interrupt in combination 
  with the rotary.h-library https://github.com/buxtronix/arduino/tree/master/libraries/Rotary  
  Code is based on this demo-example-code
  Argument_None.ino For Teensy boards Written by Khoi Hoang

  Built by Khoi Hoang https://github.com/khoih-prog/Teensy_TimerInterrupt
  Licensed under MIT license

  Now even you use all these new 16 ISR-based timers,with their maximum interval practically unlimited (limited only by
  unsigned long miliseconds), you just consume only one Teensy timer and avoid conflicting with other cores' tasks.
  The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers
  Therefore, their executions are not blocked by bad-behaving functions / tasks.
  This important feature is absolutely necessary for mission-critical tasks.
*****************************************************************************************************************************/
/*
   Notes:
   Special design is necessary to share data between interrupt code and the rest of your program.
   Variables usually need to be "volatile" types. Volatile tells the compiler to avoid optimizations that assume
   variable can not spontaneously change. Because your function may change variables while your program is using them,
   the compiler needs this hint. But volatile alone is often not enough.
   When accessing shared variables, usually interrupts must be disabled. Even with volatile,
   if the interrupt changes a multi-byte variable between a sequence of instructions, it can be read incorrectly.
   If your data is multiple variables, such as an array and a count, usually interrupts need to be disabled
   or the entire sequence of your code which accesses the data.
*/

#if !( defined(CORE_TEENSY) || defined(TEENSYDUINO) )
#error This code is designed to run on Teensy platform! Please check your Tools->Board setting.
#endif

// These define's must be placed at the beginning before #include "TeensyTimerInterrupt.h"
// _TIMERINTERRUPT_LOGLEVEL_ from 0 to 4
// Don't define _TIMERINTERRUPT_LOGLEVEL_ > 0. Only for special ISR debugging only. Can hang the system.
// Don't define TIMER_INTERRUPT_DEBUG > 2. Only for special ISR debugging only. Can hang the system.
#define TIMER_INTERRUPT_DEBUG         0
#define _TIMERINTERRUPT_LOGLEVEL_     3

// To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error
#include "TeensyTimerInterrupt.h"


#include <Rotary.h>

const byte CLK1 = 2;
const byte DT1  = 8;

const byte CLK2 = 3;
const byte DT2  = 11;

const byte CLK3 = 21;
const byte DT3  = 12;

Rotary rotary1 = Rotary(CLK1, DT1);
Rotary rotary2 = Rotary(CLK2, DT2);
Rotary rotary3 = Rotary(CLK3, DT3);

volatile unsigned char Cnt1000 = 0;
volatile unsigned char Cnt100  = 0;
volatile unsigned char Cnt10   = 0;

long runSpeed = 0;
long last_runSpeed = 0;

volatile uint32_t preMillisTimer0 = 0;

// For Teensy 4.0/4.1, F_BUS_ACTUAL = 150 MHz => max period is only 55922 us (~17.9 Hz)
#define TIMER0_INTERVAL_MS        50L

// You can select Teensy Hardware Timer  from TEENSY_TIMER_1 or TEENSY_TIMER_3

// Init Teensy timer TEENSY_TIMER_1
TeensyTimer ITimer0(TEENSY_TIMER_1);

// counts up/down with each encoder-click
void processEncoders() {
  unsigned char result;

  result = rotary1.process();
  if (result == DIR_CW) {
    Cnt1000++;
  }
  else if (result == DIR_CCW) {
    Cnt1000--;
  }
  if (Cnt1000 < 0) {
    Cnt1000 = 0;
  }

  result = rotary2.process();
  if (result == DIR_CW) {
    Cnt100++;
  }
  else if (result == DIR_CCW) {
    Cnt100--;
  }
  if (Cnt100 < 0) {
    Cnt100 = 0;
  }

  result = rotary3.process();
  if (result == DIR_CW) {
    Cnt10++;
  }
  else if (result == DIR_CCW) {
    Cnt10--;
  }
  if (Cnt10 < 0) {
    Cnt10 = 0;
  }
}


void setup() {
  Serial.begin(115200);
  while (!Serial);
  Serial.println("Setup-Start");

  delay(100);

  Serial.print(F("\nStarting Argument_None on "));
  Serial.println(BOARD_NAME);
  Serial.println(TEENSY_TIMER_INTERRUPT_VERSION);
  Serial.print(F("CPU Frequency = "));
  Serial.print(F_CPU / 1000000); Serial.println(F(" MHz"));

  // Interval in microsecs
  //if (ITimer0.attachInterruptInterval(TIMER0_INTERVAL_MS * 1000, TimerHandler0)) {
  if ( ITimer0.attachInterruptInterval(TIMER0_INTERVAL_MS * 1000, processEncoders) ) {
    preMillisTimer0 = millis();
    Serial.print(F("Starting ITimer0 OK, millis() = "));
    Serial.println(preMillisTimer0);
  }
  else
    Serial.println(F("Can't set ITimer0. Select another freq. or timer"));
}


void loop() {
  runSpeed = Cnt1000 * 1000 + Cnt100 * 100 + Cnt10 * 10;

  // check if value has changed
  if (last_runSpeed != runSpeed) {
    last_runSpeed = runSpeed; // update last_runSpeed
    Serial.print("runSpeed=");
    Serial.println(runSpeed);
  }
}

best regards Stefan

Thank you both.
@StefanL38 I am not sure whether polling would be good, the encoders are part of a MIDI controller that I built some time ago. the encoders are connected to 6 pin of which 4 are interrupt capable pins, so I suppose it just make sense to use the interrupt at this a point. I have loaded the last sketch you posted (thanks very much) but I got a warning saying that my board is AVR and may not be compatible so I decided not to upload on the board as it would be tragic if I had to do all the soldering again. I have no other teensy to test. I also found this GitHub - MajicDesigns/MD_REncoder: Rotary Encoder Library but I haven't looked into it yet.

@paulpaulson I have loaded your sketch on the board and I got much better results than with my previous coding. I am now trying to adapt it to my sketch so that it can transmit MIDI messages when changing values. TBH I have not done any coding for at least 5 years and I am found out that I am really struggling.... I noticed that when using the function GetDirectionName() it always gives either "Clockwise" and "Counterclockwise" but never "None", which does not look right...

Thank you both!!!

the NewEncoder library is certainly working better than the previous one, as it is less jittering. however the encoder skips a lot of numbers/steps when I turn it fast. I suppose it's the fact that the library is not using the interrupt?
this is the code I am using:

#define DEBUG
//#define MIDI_ACTIVE

#include "NewEncoder.h"

///////////////////////////ENCODERS/////////////////////////////////////////////////////////////////////////////////


NewEncoder firstEncoder;          // Create instance of the NewEncoder class
NewEncoder secondEncoder;         // Create instance of the NewEncoder class
NewEncoder thirdEncoder;          // Create instance of the NewEncoder class

const int numEnc = 3;
int encCurrState [numEnc]= {0, 0, 0};
int encPrevState [numEnc]= {0, 0, 0}; 
int direction [numEnc]= {0, 0, 0};                 // 0 = not turning, 1 = turning CCW, 2 = turning CW

void setup()
{
    firstEncoder.begin(5, 4);     // Initialize first encoder
    secondEncoder.begin(7, 6);    // Initialize second encoder
    thirdEncoder.begin(9, 8);     // Initialize third encoder

  #ifdef DEBUG
     Serial.begin(9600);
  #endif
}
unsigned long delayTime = 0;
void loop()
{
    firstEncoder.Update(); // Call this functions as frequently as possible (in this case, each loop)
    secondEncoder.Update();
    thirdEncoder.Update();
    encCurrState [0]= firstEncoder.GetSteps();
    encCurrState [1]= secondEncoder.GetSteps();
    encCurrState [2]= thirdEncoder.GetSteps();

    if (millis() - delayTime < 200) // Optimized delay
        return;
    delayTime = millis();

    for (int i = 0; i < numEnc; i++)
    if (encCurrState[i] != encPrevState[i]){
      if(encCurrState[i] > encPrevState[i]){
        direction[i] = 2;}
        else{direction[i] = 1;}
        encPrevState[i] = encCurrState[i];

      #ifdef DEBUG
      Serial.print("***Encoder N: ");
      Serial.print(i + 1); 
      Serial.print("   Steps:  ");
      Serial.print(encCurrState[i]);
      Serial.print("   turning: ");
      Serial.print(direction[i]);
      Serial.print("  *****\t");
      Serial.println();
      #endif 

    } 
    else {direction[i] = 0;
    }   
}

former text crossed out below because what I am writing there is wrong

A teensy 2.0 is

  • quoting from your 2018 pjcr-forum-thread:

the Teensy 2.0 ( ATMega32U4 ) has 4 INT External Interrupt and 8 PCINT Pin Change Interrupts.

ATMega32U4 is an AVR-microcontroller.
find a timer-interrupt library for Atmega32U4
and then combine this library with the rotary-library
.
.
PCINT is short for Pin-Change-interrupt

Which gives another oportunity to work with interrupts if you find a pin-change-interrupt-library

[quote="fluxia, post:5, topic:1184873"] I have loaded the last sketch you posted (thanks very much) but I got a warning saying that my board is AVR [/quote]

"last sketch" is a not really clear description.
There are two sketches in post # 3
and there is a sketch in post # 4

Sketch in post # 4 is for teensy
So why would you get a warning

if you have connected a teensy 2.0??

the compiler says that your board is AVR
This is completely contradictory to what you said here

You will have to clarify to what exactly microcontroller-type do you have your

And additionally adjust the arduino-IDE to print maximum verbose output for compiing and uploading and then post the compiler-log as a code-section

This will deinitely clarify what board you have adjusted in the arduino-IDE and what the exact message of the compiler is

best regards Stefan

As my own decision to do so I was searching for about 2 hours for Atmega32U4 pinchange-interrupt and Atmega32U4 timer-interrupt
and searched for arduino leonardo (which is the atmega32U4-µC)
but didn't find much useful things.

I finally found this link

Which has a demo-code that makes two led blink.
One LED in a 3 seconds and a second led in a 0.5 seconds interval.
Here is the demo-code as code-section

#define TIMER1_LED  13
#define TIMER3_LED  12

volatile boolean timer1_out = HIGH;
volatile boolean timer3_out = HIGH;

// Timer1 interrupt
ISR (TIMER1_COMPA_vect) {
  digitalWrite(TIMER1_LED, timer1_out);
  timer1_out = !timer1_out;
}

// Timer3 interrupt
ISR (TIMER3_COMPA_vect) {
  digitalWrite(TIMER3_LED, timer3_out);
  timer3_out = !timer3_out;
}

void setup() {
  pinMode(TIMER1_LED, OUTPUT);
  pinMode(TIMER3_LED, OUTPUT);
  delay(3000);

  setupTimer();
  setTimer1(3);
  setTimer3(0.5);
}

void loop() {
}

void setupTimer() {
  cli();
  initTimer1();
  initTimer3();
  sei();
}

void initTimer1() {
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  OCR1A = 10000;
  TCCR1B = bit(WGM12) | bit(CS12)| bit(CS10);  // WGM12 => CTC(Clear Timer on Compare Match), CS12 & CS10  => prescaler 1/1024
  TIMSK1 = bit(OCIE1A);                        // OCIE1A => Timer1 compare match A interrupt
}

void initTimer3() {
  TCCR3A = 0;
  TCCR3B = 0;
  TCNT3 = 0;
  OCR3A = 10000;
  TCCR3B = bit(WGM32) | bit(CS32);  // WGM32 => CTC(Clear Timer on Compare Match), CS32 => prescaler 1/256
  TIMSK3 = bit(OCIE3A);             // OCIE3A => Timer3 compare match A interrupt
}

void setTimer1(float _time) {
  long cnt = 16000000 / 1024 * _time;  // cnt = clk / prescaler * time(s)
  if(cnt > 65535) {
    cnt = 65535;         // "timer1 16bit counter over."
  }
  OCR1A = cnt;           // Output Compare Register Timer1A
  TIMSK1 = bit(OCIE1A);
}

void stopTimer1(){
    TIMSK1 = 0;
}

void setTimer3(float _time) {
  long cnt = 16000000 / 256 * _time;  // cnt = clk / prescaler * time(s)
  if(cnt > 65535) {
    cnt = 65535;        // "timer3 16bit counter over."
  }
  OCR3A = cnt;          // Output Compare Register Timer3A
  TIMSK3 = bit(OCIE3A);
}

void stopTimer3(){
    TIMSK3 = 0;
}

/*
|  CS12  |  CS11  |  CS10  |  Description                       |
|:-------|:------:|:------:|:----------------------------------:|
|   0    |    0   |    0   |  No clock source(timer stop)       |
|   0    |    0   |    1   |  clk / 1                           |
|   0    |    1   |    0   |  clk / 8                           |
|   0    |    1   |    1   |  clk / 64                          |
|   1    |    0   |    0   |  clk / 256                         |
|   1    |    0   |    1   |  clk / 1024                        |
|   1    |    1   |    0   |  External clock source no T1 pin.  |
|   1    |    1   |    1   |  External clock source no T1 pin.  |
*/

I have almost no knowledge how to set up the registers.
From this line

  long cnt = 16000000 / 1024 * _time;  // cnt = clk / prescaler * time(s)

I assume / interpret it this way
calculating 16000000 / 1024 = 15625 means if register is set too 15625

  OCR1A = 15625 ;           // Output Compare Register Timer1A

the timer-interrupt occurs once every second.
which would give a maximum resultion of 1/15625 = 0.000064 seconds = 64 microseconds.

Well 64 microseconds is a too short to execute more than a few lines of code

calculating a value of
16000000 / 1024 / 500 = 31.25 equals to a timer-interrupt-frequency of around 2 milliseconds
16000000 / 1024 * 2 / 1000
which should be fast enough for polling a fast rotating encoder

I have written this demo-code for 3 encoders. The code compiles but as I don't have a arduino leonardo (which is the same as a teensy 2.0 = atmega32U4-microcontroller)

I have not tested it with real hardware

#include <Rotary.h>

// please check if these pin-assignments are correct
// for your encoders

const byte CLK1 = 4;
const byte DT1  = 5;

const byte CLK2 = 6;
const byte DT2  = 7;

const byte CLK3 = 8;
const byte DT3  = 9;

Rotary rotary1 = Rotary(CLK1, DT1);
Rotary rotary2 = Rotary(CLK2, DT2);
Rotary rotary3 = Rotary(CLK3, DT3);

// Cnt1000, Cnt100, Cnt10 that will be incremented or decremented by rotation.
// as these variables are changed in an interrupt-service-routine
// this variable MUST !! be declared volatile to make sure 
// that it works properly !

volatile unsigned char Cnt1000 = 0;
volatile unsigned char Cnt100  = 0;
volatile unsigned char Cnt10   = 0;

long runSpeed = 0;
long last_runSpeed = 0;

unsigned long MyTestTimer = 0;  // Timer-variables MUST be of type unsigned long
const byte    OnBoard_LED = 13;


// Timer1 interrupt
ISR (TIMER1_COMPA_vect) {
  processEncoders();
}


void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  PrintFileNameDateTime();
  setupTimer();
  setTimer1(0.002); // 2 milliseconds
}

void loop() {
  BlinkHeartBeatLED(OnBoard_LED, 250);

  runSpeed = Cnt1000 * 1000 + Cnt100 * 100 + Cnt10 * 10;

  // check if value has changed
  if (last_runSpeed != runSpeed) {
    last_runSpeed = runSpeed; // update last_runSpeed
    Serial.print("runSpeed=");
    Serial.println(runSpeed);
  }
}


void setupTimer() {
  cli();
  initTimer1();
  sei();
}

void initTimer1() {
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  OCR1A = 10000;
  TCCR1B = bit(WGM12) | bit(CS12)| bit(CS10);  // WGM12 => CTC(Clear Timer on Compare Match), CS12 & CS10  => prescaler 1/1024
  TIMSK1 = bit(OCIE1A);                        // OCIE1A => Timer1 compare match A interrupt
}


void setTimer1(float _time) {
  long cnt = (16000000 / 1024) * _time;  // cnt = clk / prescaler * time(s)

  if(cnt > 65535) {
    cnt = 65535;         // "timer1 16bit counter over."
  }
  OCR1A = cnt;           // Output Compare Register Timer1A
  TIMSK1 = bit(OCIE1A);
}

void stopTimer1(){
    TIMSK1 = 0;
}

// counts up/down with each encoder-click
void processEncoders() {
  unsigned char result;
  
  result = rotary1.process();
  if (result == DIR_CW) {
    Cnt1000++;
  }
  else if (result == DIR_CCW) {
    Cnt1000--;
  }
  if (Cnt1000 < 0) {
    Cnt1000 = 0;
  }
  
  result = rotary2.process();
  if (result == DIR_CW) {
    Cnt100++;
  }
  else if (result == DIR_CCW) {
    Cnt100--;
  }
  if (Cnt100 < 0) {
    Cnt100 = 0;
  }
  
  result = rotary3.process();
  if (result == DIR_CW) {
    Cnt10++;
  }
  else if (result == DIR_CCW) {
    Cnt10--;
  }
  if (Cnt10 < 0) {
    Cnt10 = 0;
  }
}

void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("  compiled ") );
  Serial.print( F(__DATE__) );
  Serial.print( F(" ") );
  Serial.println( F(__TIME__) );
}


// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}


void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);

  if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
    digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
  }
}

/*
|  CS12  |  CS11  |  CS10  |  Description                       |
|:-------|:------:|:------:|:----------------------------------:|
|   0    |    0   |    0   |  No clock source(timer stop)       |
|   0    |    0   |    1   |  clk / 1                           |
|   0    |    1   |    0   |  clk / 8                           |
|   0    |    1   |    1   |  clk / 64                          |
|   1    |    0   |    0   |  clk / 256                         |
|   1    |    0   |    1   |  clk / 1024                        |
|   1    |    1   |    0   |  External clock source no T1 pin.  |
|   1    |    1   |    1   |  External clock source no T1 pin.  |
*/

best regards Stefan

1 Like

So This code-version is tested with a single encoder
connected to 4,5 or 6,7 or 8,9
and works.

The encoder-pins are polled at a frequency of around 15 kHz which seems fast enough for fast rotating the encoder.

/* Demo-Code that uses the Rotary-library from GitHub-User https://github.com/buxtronix
   using his library https://github.com/buxtronix/arduino/tree/master/libraries/Rotary
   in combination with a timer-interrupt executed 10000 times per second
   Copyright 2023 StefanL38. Licenced under the GNU GPL Version 3.
   A T T E N T I O N !
   this demo-code uses Timer2 which is used by other libraries too.
   This means using this code can interfere with other libraries
   causing malfunction of both

   As timer-interrupts are very hardware-sepcific
   this demo-code works with Arduino-Uno R3 based on the chip AtMega328P
   or AtMega32U4
   if you want to use it on other microcontrollers you will have to
   modify setting up the timer-interrupt according to the hardware
   of this other microcontroller

   The demo-code simply prints the value of variable myCounter
   each time the value of the variable changes
*/
#include <Rotary.h>

const byte CLK1 = 4;
const byte DT1  = 5;

const byte CLK2 = 6;
const byte DT2  = 7;

const byte CLK3 = 8;
const byte DT3  = 9;

Rotary rotary1 = Rotary(CLK1, DT1);
Rotary rotary2 = Rotary(CLK2, DT2);
Rotary rotary3 = Rotary(CLK3, DT3);

// Cnt1, Cnt2, Cnt3 that will be incremented or decremented by rotation.
// as these variables are changed in an interrupt-service-routine
// this variable MUST !! be declared volatile to make sure
// that it works properly !

volatile int Cnt1 = 0;
volatile int Cnt2 = 0;
volatile int Cnt3 = 0;

int lastCnt1 = 0;
int lastCnt2 = 0;
int lastCnt3 = 0;



void setupTimerInterrupt() {
  long OCR1A_value;

  cli();//stop interrupts

  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;// initialize counter value to 0

  TCCR1A |= (1 << WGM11);  // turn on CTC mode
  TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt

  const byte Prescaler___1 = (1 << CS10);

  TCCR1B = Prescaler___1;
  OCR1A = 1;

  sei();//allow interrupts
}


// timer-interrupt-service-function for AtMega
ISR(TIMER1_COMPA_vect) {
  processEncoders();
}


// counts up/down with each encoder-click
void processEncoders() {

  unsigned char result;

  result = rotary1.process();
  if (result == DIR_CW) {
    Cnt1++;
  }
  else if (result == DIR_CCW) {
    Cnt1--;
  }
  if (Cnt1 < 0) {
    Cnt1 = 0;
  }
  
  result = rotary2.process();
  if (result == DIR_CW) {
    Cnt2++;
  }
  else if (result == DIR_CCW) {
    Cnt2--;
  }
  if (Cnt2 < 0) {
    Cnt2 = 0;
  }

  result = rotary3.process();
  if (result == DIR_CW) {
    Cnt3++;
  }
  else if (result == DIR_CCW) {
    Cnt3--;
  }
  if (Cnt3 < 0) {
    Cnt3 = 0;
  }
}


void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("  compiled ") );
  Serial.print( F(__DATE__) );
  Serial.print( F(" ") );
  Serial.println( F(__TIME__) );
}


// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

unsigned long MyTestTimer = 0;  // Timer-variables MUST be of type unsigned long
const byte    OnBoard_LED = 13;


void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);

  if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
    digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
  }
}


void setup() {
  Serial.begin(115200);
  while (!Serial);
  Serial.println("Setup-Start");
  PrintFileNameDateTime();
  setupTimerInterrupt();
}


void loop() {

  // check if value has changed
  if (lastCnt1 != Cnt1) {
    lastCnt1 = Cnt1; // update last_runSpeed
    Serial.print("Cnt1=");
    Serial.println(Cnt1);
  }

  if (lastCnt2 != Cnt2) {
    lastCnt2 = Cnt2; // update last_runSpeed
    Serial.print("Cnt2=");
    Serial.println(Cnt2);
  }

  if (lastCnt3 != Cnt3) {
    lastCnt3 = Cnt3; // update last_runSpeed
    Serial.print("Cnt3=");
    Serial.println(Cnt3);
  }

}

best regards Stefan

1 Like

Stefan, I am ever so grateful for all your efforts! (although honestly concerned that with my poor knowledge I may not be able to make your effort fruitful! ie some of the code you wrote is far beyond my level). Thanks so much for all the time you spent on this and for writing the code. I am going to have a try and let you know the outcome !! cheers!!!!

WOWWW!!! IT'S ABSOLUTELY PERFECT!!!. it runs smoothly, does not miss a step at any speed of rotation. I will have to adapt it to my sketch now (is needs to go negative as well, then will have to output midi messages as well etc... I will need to study it to understand how it works (that's the hard part for me). I read your comments and hope that adding the midi library will not cause problems....
your are a star!! Thanks Stefan!!!!!!

This if-conditions limits the values to be zero or above

If you change this part of the code you can limit the values to any other borders you like.
Or you use the function constrain()
(constrain() - Arduino Reference)

Example if you wish the values of Cnt2 to be limited between -127 and +127

Cnt2 = constrain(Cnt2,-127,127);

Code-Version that uses constrain()

/* Demo-Code that uses the Rotary-library from GitHub-User https://github.com/buxtronix
   using his library https://github.com/buxtronix/arduino/tree/master/libraries/Rotary
   in combination with a timer-interrupt executed 10000 times per second
   Copyright 2023 StefanL38. Licenced under the GNU GPL Version 3.
   A T T E N T I O N !
   this demo-code uses Timer2 which is used by other libraries too.
   This means using this code can interfere with other libraries
   causing malfunction of both

   As timer-interrupts are very hardware-sepcific
   this demo-code works with Arduino-Uno R3 based on the chip AtMega328P
   or AtMega32U4
   if you want to use it on other microcontrollers you will have to
   modify setting up the timer-interrupt according to the hardware
   of this other microcontroller

   The demo-code simply prints the value of variable myCounter
   each time the value of the variable changes
*/
#include <Rotary.h>

const byte CLK1 = 4;
const byte DT1  = 5;

const byte CLK2 = 6;
const byte DT2  = 7;

const byte CLK3 = 8;
const byte DT3  = 9;

Rotary rotary1 = Rotary(CLK1, DT1);
Rotary rotary2 = Rotary(CLK2, DT2);
Rotary rotary3 = Rotary(CLK3, DT3);

// Cnt1, Cnt2, Cnt3 that will be incremented or decremented by rotation.
// as these variables are changed in an interrupt-service-routine
// this variable MUST !! be declared volatile to make sure
// that it works properly !

volatile int Cnt1 = 0;
volatile int Cnt2 = 0;
volatile int Cnt3 = 0;

int lastCnt1 = 0;
int lastCnt2 = 0;
int lastCnt3 = 0;



void setupTimerInterrupt() {
  long OCR1A_value;

  cli();//stop interrupts

  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;// initialize counter value to 0

  TCCR1A |= (1 << WGM11);  // turn on CTC mode
  TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt

  const byte Prescaler___1 = (1 << CS10);

  TCCR1B = Prescaler___1;
  OCR1A = 1;

  sei();//allow interrupts
}


// timer-interrupt-service-function for AtMega
ISR(TIMER1_COMPA_vect) {
  processEncoders();
}


// counts up/down with each encoder-click
void processEncoders() {

  unsigned char result;

  result = rotary1.process();
  if (result == DIR_CW) {
    Cnt1++;
  }
  else if (result == DIR_CCW) {
    Cnt1--;
  }
  Cnt1 = constrain(Cnt1,-100,100);
  
  result = rotary2.process();
  if (result == DIR_CW) {
    Cnt2++;
  }
  else if (result == DIR_CCW) {
    Cnt2--;
  }
  Cnt2 = constrain(Cnt2,-10,20);

  result = rotary3.process();
  if (result == DIR_CW) {
    Cnt3++;
  }
  else if (result == DIR_CCW) {
    Cnt3--;
  }
  Cnt3 = constrain(Cnt3,-127,127);
}


void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("  compiled ") );
  Serial.print( F(__DATE__) );
  Serial.print( F(" ") );
  Serial.println( F(__TIME__) );
}


// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

unsigned long MyTestTimer = 0;  // Timer-variables MUST be of type unsigned long
const byte    OnBoard_LED = 13;


void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);

  if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
    digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
  }
}


void setup() {
  Serial.begin(115200);
  while (!Serial);
  Serial.println("Setup-Start");
  PrintFileNameDateTime();
  setupTimerInterrupt();
}


void loop() {

  // check if value has changed
  if (lastCnt1 != Cnt1) {
    lastCnt1 = Cnt1; // update last_runSpeed
    Serial.print("Cnt1=");
    Serial.println(Cnt1);
  }

  if (lastCnt2 != Cnt2) {
    lastCnt2 = Cnt2; // update last_runSpeed
    Serial.print("Cnt2=");
    Serial.println(Cnt2);
  }

  if (lastCnt3 != Cnt3) {
    lastCnt3 = Cnt3; // update last_runSpeed
    Serial.print("Cnt3=");
    Serial.println(Cnt3);
  }

}

best regards Stefan

Great, for the moment I have just removed the if - condition altogether. I have to decide whether will use a midi range 0 to 127 to whether I will just output a midi message for CCW and one for CW direction.

This is likely to be the final code for the encoder part of my sketch. Maybe someone may want to use it if they do a similar project. Thanks again to Stefan, I would have never made it without his help.

(@StefanL38 as you you may see I have cut out some parts of the code that I did not think were needed for my sketch, it seems to work fine so hopeful I did not mess anything up...)

PS when I get more time I might ask you to explain part of the code you wrote if you don't mind. mind you that does not mean I will understand it after the explanation :smile:.

update: I have just corrected a bug that was occurring when using relative mode.
I have edited the code below with the amended version

/* Code that uses the Rotary-library from GitHub-User https://github.com/buxtronix
   using his library https://github.com/buxtronix/arduino/tree/master/libraries/Rotary
   in combination with a timer-interrupt executed 10000 times per second
   Copyright 2023 StefanL38. Licenced under the GNU GPL Version 3.
   A T T E N T I O N !
   this demo-code uses Timer2 which is used by other libraries too.
   This means using this code can interfere with other libraries
   causing malfunction of both

   As timer-interrupts are very hardware-sepcific
   this demo-code works with Arduino-Uno R3 based on the chip AtMega328P
   or AtMega32U4
   if you want to use it on other microcontrollers you will have to
   modify setting up the timer-interrupt according to the hardware
   of this other microcontroller

   This code is for three encoders. Two modes for each encoder.
   It can be set to relative mode (ie values change to 1 or 127); alternative values 
   can be set by changing the goUp and goDown constants
   it can be set to absolute mode (values aro going up and down incrementally from 0 to 127, 
   to change this limit values to different values you need to change the constraint in the code )  
   
   this program requires a TEENSY board (or similar). when defining MIDI you need to  select MIDI USB type from the Tools menu of arduino IDE 
   Define debug to output on serial monitor for debugging
   Define RELATIVE to use controller in "relative mode", otherwise it will be in "absolute mode" with values going from 0 to 127.
   PLEASE NOTE THAT THIS CODE DOES NOT INCLUDE A BUTTON TO CHANGE MODE FROM 1 TO 2. YOU NEED TO ADD THAT IF YOU WISH.
   IF YOU HAVE ENCODER WITH PUSH BUTTONS YOU CAN USE THE BUTTON ON EACH ENCODER TO CHANGE MODE FOR THAT ENCODER, WHICH WIL THEN TRANSMIT 
   ON A CONTINOUS CONTROL. ENJOY! 
   Fluxia 
*/



#include <Rotary.h>

#define DEBUG
#define RELATIVE_MODE
// #define MIDI_ACTIVE


const byte CLK1 = 4;
const byte DT1  = 5;

const byte CLK2 = 6;
const byte DT2  = 7;

const byte CLK3 = 8;
const byte DT3  = 9;

Rotary rotary1 = Rotary(CLK1, DT1);
Rotary rotary2 = Rotary(CLK2, DT2);
Rotary rotary3 = Rotary(CLK3, DT3);

// Cnt1, Cnt2, Cnt3 that will be incremented or decremented by rotation.
// as these variables are changed in an interrupt-service-routine
// this variable MUST !! be declared volatile to make sure
// that it works properly !

volatile int Cnt1 = 0;    // encoder n1 counter 
volatile int Cnt2 = 0;    // encoder n2 counter
volatile int Cnt3 = 0;    // encoder n3 counter

int lastCnt1 = 0;         // enc n1 last count 
int lastCnt2 = 0;         // enc n2 last count 
int lastCnt3 = 0;         // enc n3 last count 


const byte goUp = 127;    // relative mode encoder midi value for increments
const byte goDown = 1;    // relative mode encoder midi value for decrements

const byte midiCh = 1;    // MIDI Channel (change if you want your controller to transmit on a different midi channel)
byte midiValue;           // encoder midi value output     
byte ENCcc;               // encoder continous control number     

int modeNumb[3] = {2, 2, 2};  



void setup() {
  #ifdef DEBUG
  Serial.begin(9600);     
  Serial.println("Setup-Start");
  #endif
  setupTimerInterrupt();
}



void loop() {


}


void encoderFunction(){
  // check if value has changed
  if (lastCnt1 != Cnt1) {                                         // check if new count is different from last count 
    
    #ifdef RELATIVE_MODE                                          // if in relative mode 
      if (lastCnt1 < Cnt1){                                       // checks if encoder turned CW
      midiValue = goUp;                                           // stores controller value as set for "goUP"
      }
      if (lastCnt1 > Cnt1){                                       // checks if encoder turned CW
      midiValue = goDown;                                         // stores controller value as set for "goDown"
      }
    #else                                                         // if in relative mode
      midiValue = Cnt1;                                           // midi value is the same as count (incremented or decremented)
    #endif

    if (modeNumb[0] == 1) {                                       // Rule if first enc is in mode 1
          ENCcc = 100;                                            // sets midi continout controller number as 100

        #ifdef MIDI_ACTIVE
          usbMIDI.sendControlChange(ENCcc, midiValue, midiCh);    // sends midi message encoder continouts control, value, channel
        #endif

        #ifdef DEBUG
          Serial.print("Encoder 1, Mode 1, Midi Control: ");      // printing encoder 1 output for debugging
          Serial.print(ENCcc);
          Serial.print("\tMidi Value: ");
          Serial.print(midiValue);
          Serial.print("\t\t Midi Channel: ");
          Serial.println(midiCh);
        #endif
    }
    
    if (modeNumb[0] == 2) {                                       // Rule if first enc is in mode 1                                      
          ENCcc = 103;                                            // sets midi continout controller number as 100
        
        #ifdef MIDI_ACTIVE                                        
          usbMIDI.sendControlChange(ENCcc, midiValue, midiCh);    // sends midi message encoder continouts control, value, channel
        #endif

        #ifdef DEBUG
          Serial.print("Encoder 1, Mode 2, Midi cc: ");           // printing encoder 1 output for debugging
          Serial.print(ENCcc);
          Serial.print("\tMidi Value: ");
          Serial.print(midiValue);
          Serial.print("\t\t Midi Channel: ");
          Serial.println(midiCh);
        #endif
    }
    lastCnt1 = Cnt1;                                              // update last_runSpeed                      
  }
    
                                                                  
  if (lastCnt2 != Cnt2) {                                         // from now it's all as above for all as above for encoder 2 and 3

    #ifdef RELATIVE_MODE
      if (lastCnt2 < Cnt2){
      midiValue = goUp;  
      }
      if (lastCnt2 > Cnt2){
      midiValue = goDown; 
      }
    #else 
    	midiValue = Cnt2;
    #endif
 
    if (modeNumb[1] == 1) {                                       
          ENCcc = 101;

        #ifdef MIDI_ACTIVE
          usbMIDI.sendControlChange(ENCcc, midiValue, midiCh);    
        #endif

        #ifdef DEBUG
          Serial.print("Encoder 2, Mode 1, Midi cc: ");
          Serial.print(ENCcc);
          Serial.print("\tMidi Value: ");
          Serial.print(midiValue);
          Serial.print("\t\t Midi Channel: ");
          Serial.println(midiCh);
        #endif
    }
       
    if (modeNumb[1] == 2) {                                       
          ENCcc = 104;
        
        #ifdef MIDI_ACTIVE
          usbMIDI.sendControlChange(ENCcc, midiValue, midiCh);    
        #endif

        #ifdef DEBUG
          Serial.print("Encoder 2, Mode 2, Midi cc: ");
          Serial.print(ENCcc);
          Serial.print("\tMidi Value: ");
          Serial.print(midiValue);
          Serial.print("\t\t Midi Channel: ");
          Serial.println(midiCh);
        #endif
    }

    lastCnt2 = Cnt2; // update last_runSpeed
  }


  if (lastCnt3 != Cnt3) {
    
    #ifdef RELATIVE_MODE
      if (lastCnt3 < Cnt3){
        midiValue = goUp;  
      }
      if (lastCnt3 > Cnt3){
        midiValue = goDown; 
      }
    #else 
    	midiValue = Cnt3;
    #endif
        
    if (modeNumb[2] == 1) {                                       
          ENCcc = 102;
       
        #ifdef MIDI_ACTIVE
          usbMIDI.sendControlChange(ENCcc, midiValue, midiCh);    
        #endif

        #ifdef DEBUG
          Serial.print("Encoder 3, Mode 1, Midi cc: ");
          Serial.print(ENCcc);
          Serial.print("\tMidi Value: ");
          Serial.print(midiValue);
          Serial.print("\t\t Midi Channel: ");
          Serial.println(midiCh);
        #endif
    }
       
    if (modeNumb[2] == 2) {                                       
          ENCcc = 105;
        
        #ifdef MIDI_ACTIVE
          usbMIDI.sendControlChange(ENCcc, midiValue, midiCh);    
        #endif

        #ifdef DEBUG
          Serial.print("Encoder 3, Mode 2, Midi cc: ");
          Serial.print(ENCcc);
          Serial.print("\tMidi Value: ");
          Serial.print(midiValue);
          Serial.print("\t\t Midi Channel: ");
          Serial.println(midiCh);
        #endif
    }

    lastCnt3 = Cnt3; // update last_runSpeed
    
  }
}
// counts up/down with each encoder-click
void processEncoders() {

  unsigned char result;

  result = rotary1.process();
  if (result == DIR_CW) {
    Cnt1++;
  }
  else if (result == DIR_CCW) {
    Cnt1--;
  }
  #ifndef RELATIVE_MODE 
   Cnt1 = constrain(Cnt1,0,127);
  #endif
  
  result = rotary2.process();
  if (result == DIR_CW) {
    Cnt2++;
  }
  else if (result == DIR_CCW) {
    Cnt2--;
  }
  #ifndef RELATIVE_MODE
    Cnt2 = constrain(Cnt2,0,127);
  #endif

  result = rotary3.process();
  if (result == DIR_CW) {
    Cnt3++;
  }
  else if (result == DIR_CCW) {
    Cnt3--;
  }
  #ifndef RELATIVE_MODE
    Cnt3 = constrain(Cnt3,0,127);
  #endif
}

// timer-interrupt-service-function for AtMega
ISR(TIMER1_COMPA_vect) {
  processEncoders();
  encoderFunction();
}

void setupTimerInterrupt() {
  long OCR1A_value;

  cli();//stop interrupts

  TCCR1A = 0;// set entire TCCR2A register to 0
  TCCR1B = 0;// same for TCCR2B
  TCNT1  = 0;// initialize counter value to 0

  TCCR1A |= (1 << WGM11); // turn on CTC mode
  TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt

  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;// initialize counter value to 0

  TCCR1A |= (1 << WGM11);  // turn on CTC mode
  TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt

  const byte Prescaler___1 = (1 << CS10);

  TCCR1B = Prescaler___1;
  OCR1A = 1;

  sei();//allow interrupts
}




I'm confused. Why is timer1 set up two times and, why is timer1 referred to in comments as timer2 (first setup lines)?

Not sure what you mean with "Why is timer1 set up two times "
can you post which exact lines of code are for your

  • setup timer1 first time
  • setup timer1 second time

Timer2 at the comment at the top is a non-corrected typo.

I'm not too familiar with timers and how to configure them.
I found almost nothing how to configure hardware-timers on AtMega32U4

best regards Stefan

image

The lines highlighted in yellow are duplicated immediately below.

How about in the AtMega32U4 datasheet?

Yes I know there is a datasheet and having managed to crawl through a complete datasheet means you know pretty much about this microcontroller. But it is very tedious to understand the functions by reading a data sheet.

For looking up specs like maximum current or a propagation-delaytime datasheets are super.
But understanding how to write code by reading a datasheet??? - come on ! not really

For the record I haven't the slightest idea of what that part of the code means... :sweat_smile: