Author Topic: Rotary incremental encoders  (Read 13553 times)

0 Members and 1 Guest are viewing this topic.

Offline akisTopic starter

  • Frequent Contributor
  • **
  • Posts: 981
  • Country: gb
Re: Rotary incremental encoders
« Reply #25 on: December 20, 2014, 12:31:11 pm »
For whoever is reading this still.

The rotary encoder module arrived from ebay and finally I put in on breadboard earlier on. It is vert badly made with the actual encoder soldered at 30 degree angle to the little PCB. It is also missing one 10K pullup resistor. But I got it to work 110% reliably.

First of all it looks like this : http://www.ebay.co.uk/itm/321293171237?_trksid=p2059210.m2749.l2649&ssPageName=STRK%3AMEBIDX%3AIT

It is basically a simple rotary encoder which also has a momentary switch. The PCB contains 3 pullup resistors, one for the switch and two for the rotary encoder.

Wiring is simple: 10K pullup resistors to the 5V line (already provided on the PCB) and then a low pass filter for each pin, in my case a 10K resistor followed by a 100nF capacitor to ground.

WARNING: the momentaty switch, at least, will not function without the low pass filter. Do not try to fix it in the code, it simply sends multiple "phantom" presses.

So the basic code is as follows.

First the switch, the code would need to know if it has been pressed. Maybe also how many times - which means by the time you actually try to do something intelligent in the code, the switch may have been pressed again, so we need some sort of a queue, very similar to Windows mouse queue where clicks are stored for later processing. To keep things simple I dispensed with a queue and simply used a counter, you press the switch the counter goes up, you "read" the switch, the counter goes down. So you keep reading until the counter goes to 0. Of course it can be done more sophisticated with a queue storing the switch press as well as the time it was pressed, and like Windows mouse clicks it could also present double clicks. But it is not needed here.

So I tested the switch and it works faultlessly, does not miss a press, and does not give more presses than needed.

The code uses interrupt channel 0 (digital pin 2) on the Arduino for the switch and is set to trigger on going low. The ISR simply increments a counter could not be more simple than that.

Now for the rotary encoder.

From the two pulled-up lines, I have attached the "DT" line (whatever that means) to interrupt channel 1 (digital pin 3 on the Arduino). After some experimentation and head scratching, the ISR is also very simple, as simple as it gets. The ISR triggers on "change". I tested and unless you turn and click the encoder you will not get suprious interrupts. So if the ISR triggers it means the rotary encoder was turned either right or left (cw or ccw). After examining the values of the two data pins on each interrupt invokation, it is clear that the designer is ingenious! If the two pins are at the same value, either 0s or 1s, it means the switch was turned clockwise. If the pins have different values, it means the switch was turned counter clockwise.

Rotary movements are stored in a circular buffer / queue so that there is some history of how the switch was turned. I have tested this and it is bullet proof. It is also so simple it defies belief.


Here is the interrupt routine for the rotary encoder:

Code: [Select]
void ea_interrupt()
{
  byte ea=digitalRead(ROTARY_ENCODER_EA)==HIGH?1:0;
  byte eb=digitalRead(ROTARY_ENCODER_EB)==HIGH?1:0;
  int iRotation = (ea ^ eb ) ? 2 : 1 ;
  insert_to_rotation(iRotation);
}

"insert_to_rotation" simply inserts a rotation action into the buffer/queue, where 1 means clockwise and 2 means counter. Can't get any simpler. I do remember reading a post with some very convoluted code, that had to remember previous positions and half-positions, I am not sure how this can happen, I have literally jumped up and down on this switch and the code above is infallible. And it is a shitty switch from ebay.

I am not sure how "expensive" the two interrupts are, in terms of stealing CPU time, not my code, but the framework underneath. Would it be better to poll very often, it was suggested 500 times a second, that implies a "loop" delay of just 2ms, and it also assumes we do nothing else in the meantime. So I stick with the interrupts for the time being.

Here is ALL the code for the breadboarded encoder switch - it may help someone to just get started with one of those ebay rotary encoders:

Code: [Select]
char buf[256];

#define ROTARY_ENCODER_SW 2
#define ROTARY_ENCODER_EA 3
#define ROTARY_ENCODER_EB 4

int switch_count;
byte rotation_buffer[256];
byte rotation_head, rotation_tail;

bool ReadSwitch()
{
  if (switch_count>0)
  {
    switch_count--;
    return true;
  }
  else
    return false;
}

// 1=cw, 2=ccw
void insert_to_rotation(byte bDirection)
{
  rotation_tail++;
  if (rotation_tail>=sizeof(rotation_buffer)) rotation_tail=0;
  // fix overflow issues here, tail overtakes head
  rotation_buffer[rotation_tail]=bDirection;
}

// 0 nothing, 1=cw, 2=ccw
byte ReadRotation()
{
  if(rotation_tail!=rotation_head)
  {
    rotation_head++;
    if (rotation_head>=sizeof(rotation_buffer)) rotation_head=0;
    return rotation_buffer[rotation_head];
  }
  else
    return 0;
}

void switch_interrupt()
{
  switch_count++;
}

void ea_interrupt()
{
  byte ea=digitalRead(ROTARY_ENCODER_EA)==HIGH?1:0;
  byte eb=digitalRead(ROTARY_ENCODER_EB)==HIGH?1:0;

  //snprintf(buf,sizeof(buf),"rotation ea:%u - eb:%u\r\n",ea,eb);
  //Serial.print(buf);
 
  int iRotation = (ea ^ eb ) ? 2 : 1 ;
  insert_to_rotation(iRotation);
}

void setup()
{
  Serial.begin(115200);
  snprintf(buf,sizeof(buf),"in setup\r\n");
  Serial.print(buf);

  pinMode(ROTARY_ENCODER_SW, INPUT);
  pinMode(ROTARY_ENCODER_EA, INPUT);
  pinMode(ROTARY_ENCODER_EB, INPUT);

  switch_count=0;
  rotation_head=rotation_tail=0;

  attachInterrupt(0, switch_interrupt,FALLING);
  attachInterrupt(1, ea_interrupt,CHANGE);
}

long lLastTime=millis();

void loop()
{

  if (millis() - lLastTime > 1000)
  {
    lLastTime=millis();
    snprintf(buf,sizeof(buf),"in loop()\r\n");
    Serial.print(buf);
  }

  if (ReadSwitch())
  {
    snprintf(buf,sizeof(buf),"switch is pressed\r\n");
    Serial.print(buf);
  }

  byte rotation=ReadRotation();
  if (rotation!=0)
  {
    snprintf(buf,sizeof(buf),"rotation %s\r\n",rotation==1?"cw":"ccw");
    Serial.print(buf);
  } 

  delay(250);
}


 

Offline dannyf

  • Super Contributor
  • ***
  • Posts: 8221
  • Country: 00
Re: Rotary incremental encoders
« Reply #26 on: December 20, 2014, 12:58:33 pm »
Quote
Maybe some code helps (for ATmega):

That's not very efficient. A better approach is to read a/b channels, in combination with prior reads to index a table that determines 1) validity of rotate; and 2) direction of rotation.

Such an approach is very resistant to bounces, and allows the same velocity detection mechanism to work.
================================
https://dannyelectronics.wordpress.com/
 

Offline SL4P

  • Super Contributor
  • ***
  • Posts: 2318
  • Country: au
  • There's more value if you figure it out yourself!
Re: Rotary incremental encoders
« Reply #27 on: December 20, 2014, 12:59:46 pm »
Congratulations - you've approached it exactly the right way.

I agree that some other threads suggest ridiculous amounts of code for what requires three lines of C to handle the encoder - well done..  You may identify tiny tweaks - like non-blocking delays - to ensure clean debounces, but in principal you're on the money.

(Remember to keep (''insert_to_rotation'') as short as possible, as it forms part of the interrupt routine as currently coded (and uses stack) - or process the count value 'outside' the interrupt entirely (you have 250mS sitting idle already).

The button sounds faulty.  Worst case add 200mS delay after each button-down state change.
Pushing and/or turning won't be affected in reality.
« Last Edit: December 20, 2014, 01:03:00 pm by SL4P »
Don't ask a question if you aren't willing to listen to the answer.
 

Offline akisTopic starter

  • Frequent Contributor
  • **
  • Posts: 981
  • Country: gb
Re: Rotary incremental encoders
« Reply #28 on: December 20, 2014, 05:11:39 pm »
I think the button is OK, sometimes it bounces as most switches do (don't they?) so it needs a low pass filter and then there is absolutely no bounces. Someone else has mentioned above the importance of low pass filters anytime we are dealing with switches.

An update: another two encoders arrived from ebay today. They are 100% identical to the one I was using before. However they behave differently. On turning the encoder I now receive two codes instead of one, and that more matches the datasheet timing diagrams. Most likely the first encoder was broken and was sending me alternative codes for the same rotation direction. The change in the code is again very simple, simply interrupt on "falling" rather than on "change". So 00 means we are going right and 01 means we are going left. Can't get easier than that.
 

Offline jadew

  • Frequent Contributor
  • **
  • Posts: 472
  • Country: ro
Re: Rotary incremental encoders
« Reply #29 on: December 20, 2014, 09:40:41 pm »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf