Go Down

Topic: Rotary Encoder KY40 with Interrupt (Read 1 time) previous topic - next topic

Abizhar

hello everyone,
 I am working on a project using KY40 rotary encoder.
I did my research around the internet, tried several examples, tested my own code, but nothing works. except for this one  this code works flawlessly :
Code: [Select]


// Used for generating interrupts using CLK signal
const int PinA = 3;

// Used for reading DT signal
const int PinB = 4;   

// Updated by the ISR (Interrupt Service Routine)
volatile int virtualPosition = 0;

void isr ()  {
  static unsigned long lastInterruptTime = 0;
  unsigned long interruptTime = millis();

  // If interrupts come faster than 5ms, assume it's a bounce and ignore
  if (interruptTime - lastInterruptTime > 5) {
    if (digitalRead(PinB) == LOW)
    {
      virtualPosition-- ;
    }
    else {
      virtualPosition++ ;
    }

    // Keep track of when we were here last (no more than every 5ms)
    lastInterruptTime = interruptTime;
  }
}


void setup() {
  // Just whilst we debug, view output on serial monitor
  Serial.begin(9600);

  // Rotary pulses are INPUTs
  pinMode(PinA, INPUT);
  pinMode(PinB, INPUT);

  // Attach the routine to service the interrupts
  attachInterrupt(digitalPinToInterrupt(PinA), isr, LOW);

  // Ready to go!
  Serial.println("Start");
}


void loop() {
  Serial.println(virtualPosition);
}



well, the problems are :
1. the code above is using LOW level interrupt trigger so it has the possibility to be triggered multiple times if the encoder is rotating super slowly (hasn't happened so far). I think using falling / raising edge triggered interrupt is more appropriate(correct me if I'm wrong). But if I do so, it won't work.
2. I'm only using Arduino as a prototype. After I make sure everything is working properly I'll switch to stm32 and stm32 can only do edge triggered interrupt (raising, falling, change).

any solutions? thanks.

robtillaart

I assume the above code works as you do debounce in software.

Most rotary encoders need 2 capacitors and 2 resistors to do debounce in hardware.
Check the datasheet for details.

to work woth CHANGE you might need to change this line

if (digitalRead(PinB) == digitalRead(PinA))
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

Abizhar

I assume the above code works as you do debounce in software.

Most rotary encoders need 2 capacitors and 2 resistors to do debounce in hardware.
Check the datasheet for details.

to work woth CHANGE you might need to change this line

if (digitalRead(PinB) == digitalRead(PinA))
added 2 capacitor and resistor, changed the code as you suggest, still not working :(

robtillaart

#3
Jan 05, 2018, 11:08 am Last Edit: Jan 05, 2018, 12:00 pm by robtillaart
Do you have a link to the datasheet?

Can you post your schematic?
Rob Tillaart

Nederlandse sectie - http://arduino.cc/forum/index.php/board,77.0.html -
(Please do not PM for private consultancy)

dougp

I did my research around the internet, tried several examples, tested my own code, but nothing works.
Was the Buxtronix approach tried?  Works well for me with no additional external passive components.
Everything we call real is made of things that cannot be regarded as real.  If quantum mechanics hasn't profoundly shocked you, you haven't understood it yet. - Niels Bohr

No private consultations undertaken!

zhomeslice

#5
Jan 06, 2018, 12:38 am Last Edit: Jan 06, 2018, 12:39 am by zhomeslice
I use simple capacitors for debounce:


This is my basic encoder code:
Code: [Select]
#define ClockPin 2 // Must be pin 2
#define DataPin 3 // Must be pin 3
#define readA bitRead(PIND,2)//faster than digitalRead()  (((value) >> (bit)) & 0x01)
#define readB bitRead(PIND,3)//faster than digitalRead()  (((value) >> (bit)) & 0x01)
volatile long count = 0;
long lastCtr;
unsigned long SpareCycles;

void Encoder(bool A) {
  (readB == A)  ? count++ : count--;
}

void setup() {
  Serial.begin(115200); //115200
  pinMode(ClockPin, INPUT);
  pinMode(DataPin, INPUT);
  /* 1 Step */
  attachInterrupt(digitalPinToInterrupt(ClockPin), [] {Encoder( true);}, RISING);
  /**/

  /*  2 step count
  attachInterrupt(digitalPinToInterrupt(ClockPin), [] {Encoder( readB);}, CHANGE);
  */
  
  /* Full 4 Step Count
  attachInterrupt(digitalPinToInterrupt(ClockPin), [] {Encoder( readB);}, CHANGE);
  attachInterrupt(digitalPinToInterrupt(DataPin ), [] {Encoder( !readA);}, CHANGE);
  */
}

void loop() {
  long Counter;
  SpareCycles++;
  noInterrupts ();
  Counter = count;
  interrupts ();
  if ((lastCtr != Counter) & (SpareCycles > 10)) {
    Serial.println(Counter);
    SpareCycles = 0;
  }
  lastCtr = Counter;
}
HC

glamo

Hello zhomeslice,

I can't compile your code "Rotary Encoder KY40 with Interrupt" on Jan,05,2018

Error line is:

attachInterrupt(digitalPinToInterrupt(ClockPin), [] {Encoder( true);}, RISING);

My Arduino version is 1.05.-r2 with UNO

Please help

zhomeslice

Hello zhomeslice,

I can't compile your code "Rotary Encoder KY40 with Interrupt" on Jan,05,2018

Error line is:

attachInterrupt(digitalPinToInterrupt(ClockPin), [] {Encoder( true);}, RISING);

My Arduino version is 1.05.-r2 with UNO

Please help
this is a lambda anonymous function 
Code: [Select]
[] {Encoder( true);}

it is a function all in its self inserted into the callback portion of the attached interrupt because the attach Interrupt can't take a value
Code: [Select]
attachInterrupt(digitalPinToInterrupt(ClockPin), [] {Encoder( true);}, RISING);

This is identical for the following MyCallbackFunction function and attachinterrupt
Code: [Select]
void MyCallbackFunction(){
   Encoder( true);
}
attachInterrupt(digitalPinToInterrupt(ClockPin),MyCallbackFunction, RISING);


Hope that helps :)
Z
attach your code and or the entire error message if I'm to determine why my original code failed to compile
This Compiles:
Quote
Sketch uses 2262 bytes (7%) of program storage space. Maximum is 32256 bytes.
Global variables use 204 bytes (9%) of dynamic memory, leaving 1844 bytes for local variables. Maximum is 2048 bytes.
code:
Code: [Select]
#define ClockPin 2 // Must be pin 2
#define DataPin 3 // Must be pin 3
#define readA bitRead(PIND,2)//faster than digitalRead()  (((value) >> (bit)) & 0x01)
#define readB bitRead(PIND,3)//faster than digitalRead()  (((value) >> (bit)) & 0x01)
volatile long count = 0;
long lastCtr;
unsigned long SpareCycles;

void Encoder(bool A) {
  (readB == A)  ? count++ : count--;
}

void setup() {
  Serial.begin(115200); //115200
  pinMode(ClockPin, INPUT);
  pinMode(DataPin, INPUT);
  /* 1 Step */
  attachInterrupt(digitalPinToInterrupt(ClockPin), [] {Encoder( true);}, RISING);
  /**/

  /*  2 step count
  attachInterrupt(digitalPinToInterrupt(ClockPin), [] {Encoder( readB);}, CHANGE);
  */
  
  /* Full 4 Step Count
  attachInterrupt(digitalPinToInterrupt(ClockPin), [] {Encoder( readB);}, CHANGE);
  attachInterrupt(digitalPinToInterrupt(DataPin ), [] {Encoder( !readA);}, CHANGE);
  */
}

void loop() {
  long Counter;
  SpareCycles++;
  noInterrupts ();
  Counter = count;
  interrupts ();
  if ((lastCtr != Counter) & (SpareCycles > 10)) {
    Serial.println(Counter);
    SpareCycles = 0;
  }
  lastCtr = Counter;
}
HC

enjoyneering

#8
Jun 14, 2019, 02:29 pm Last Edit: Jun 14, 2019, 02:29 pm by enjoyneering

zhomeslice

check this topic - Lightweight Arduino Library for Rotary Encoder
whats lighter weight than this?
Code: [Select]
#define ClockPin 2 // Must be pin 2
#define DataPin 3 // Must be pin 3
#define readA bitRead(PIND,2)//faster than digitalRead()  (((value) >> (bit)) & 0x01)
#define readB bitRead(PIND,3)//faster than digitalRead()  (((value) >> (bit)) & 0x01)
volatile long count = 0;
void Encoder(bool A) {
  (readB == A)  ? count++ : count--;
}
void setup() {
  Serial.begin(115200); //115200
  pinMode(ClockPin, INPUT);
  pinMode(DataPin, INPUT);
  /* 1 Step */
  attachInterrupt(digitalPinToInterrupt(ClockPin), [] {Encoder( true);}, RISING);
}
void loop() {
  long Counter;
  noInterrupts ();
  Counter = count;
  interrupts ();
  if ((lastCtr != Counter)) {
    Serial.println(Counter);
  }
  lastCtr = Counter;
}

Z
HC

enjoyneering

#10
Jun 18, 2019, 08:52 pm Last Edit: Jun 18, 2019, 09:16 pm by enjoyneering
whats lighter weight than this?
you are doing it wrong:
- The encoder uses the Manchester code. You must read A & B at the same time, otherwise you will have wrong counts due to noise, debounce...
- pins and ports are hardcoded and can only be used with atmega328 / 168


The only reason you get a decent result with your code is because the atmega328 & arduino framework is so slow and cannot catch the noise yet. :) But the encoder is aging & debounce will increase....

Try your code on fast Cortex MCU, for example, STM32F103xxxx or ESP8266 - you will be surprised. Ohh but you can't - pins and ports are hardcoded

zhomeslice

you are doing it wrong:
- The encoder uses the Manchester code. You must read A & B at the same time, otherwise you will have wrong counts due to noise, debounce...
- pins and ports are hardcoded and can only be used with atmega328 / 168


The only reason you get a decent result with your code is because the atmega328 & arduino framework is so slow and cannot catch the noise yet. :) But the encoder is aging & debounce will increase....

Try your code on fast Cortex MCU, for example, STM32F103xxxx or ESP8266 - you will be surprised. Ohh but you can't - pins and ports are hardcoded
So how long of a delay between the interrupt triggering (pin2) and the Reading of Pin3?
(this should be almost instantaneous.)
We know the Clock pin Just went HIGH (Pin2). the callback immediately reads the Data Pin 3 which is either HIGH or LOW. Now we know that the direction of rotation. and can make a count.
You could add some sort of debounce timer on the interrupt maybe turn off interrupts for a short period of time. the Data pin is in the middle of its pulse and shouldn't be bouncing by now. 
What do you think?
Z
Z
HC

enjoyneering

#12
Jun 19, 2019, 03:28 pm Last Edit: Jun 19, 2019, 03:38 pm by enjoyneering
I do not like software debounce timers - eats memory and cpu time. It is easier for me to add 0.1uf between each pin and ground.

As for the first question, this is how I did it:


zhomeslice

I do not like software debounce timers - eats memory and cpu time. It is easier for me to add 0.1uf between each pin and ground.

As for the first question, this is how I did it:


@enjoyneering this is interesting! Thank you.
I Have a question.
how does the shift _prevValueAB =_curvalAB << 2; get clear _prevValueAB Shifting bits further over would not match the switch case: options
_prevValueAB 00000001 <<2 changes to 00000111 <<2 changes to 00011110 changes to 01111000 and so on so this would need to be managed or we will not match. 
How do you do it?

My thoughts: maybe change 
Code: [Select]
switch ((_prevValueAB !_currValueAB))
to
switch ((_prevValueAB !_currValueAB) &0x0F) / keep only first 4 bytes


I'm working on a version of my code to match your suggestion :)
Thank you
Z
HC

enjoyneering

#14
Jun 19, 2019, 04:47 pm Last Edit: Jun 19, 2019, 04:59 pm by enjoyneering
this is not true:

_prevValueAB 00000001 <<2 changes to 00000111

the shifting works like this:

_prevValueAB 00000001 <<2 changes to 00000100

see official manual

this is how my code works:

prevAB=0b00001000

read A = 1 = 0b00000001

A << 1 = 0b00000010

read B = 1 = 0b00000001

currAB = A | B = 0b00000011

switch(prevAB | currAB ) = 0b00001011

prevAB = currAB << 2 = 0b00000011 << 2 = 0b00001100

Go Up