Shop Learn
Reading Rotary Encoders — Parallax Forums

Reading Rotary Encoders

I've been trying to write code to read the value of a rotary encoder with not much success. This code seems to mostly work but it sometimes skips values or jumps. This is code for the ESP8266 using the Arduino libraries. Any suggestions for improvements? I'm actually likely to switch to using a Propeller for this but it seems like it should be possible to do it on the ESP8266 as well.
int a0 = -1;
int b0 = -1;
volatile int Count = 0;

void setupInterrupt(int pin)
{
  attachInterrupt(digitalPinToInterrupt(pin), handleInterrupt, CHANGE);
}

int getCount()
{
  return Count;
}

ICACHE_RAM_ATTR void handleInterrupt()
{
  int a = digitalRead(ROTARY_CLOCK);
  int b = digitalRead(ROTARY_DATA);
  if (a != a0) {
    a0 = a;
    if (a == b) {
      if (Count < 99)
        ++Count;
    }
    else {
      if (Count > 0)
        --Count;
    }
  }
}

Comments

  • I thought the ESP8266 was a WiFi module
  • DigitalBob wrote: »
    I thought the ESP8266 was a WiFi module
    It is and that's why I'm using it. I want to make a gadget with various controls on it like rotary encoders, buttons, and LEDs and have it talk to another device over WiFi. The problem is, the ESP8266 doesn't have many GPIO pins so I need a "port expander". It would be nice to use a Propeller since then I can have intelligent peripherals instead of just pins that I have to poll and handle on the ESP8266. I figure I can get the Propeller to manage the rotary encoders, maybe some smart pixels, and all of the buttons and LEDs and just have the ESP8266 handle the WiFi connection.

  • If you're only triggering the interrupt on the A pin, you need to take and immediate sample of B and compare that to A. If they're different, increment the count; if they are the same, decrement the count. I will cover this in detail tomorrow.
  • What I have found and using a scope is that if you start to turn the knob slowly it will generate noise as it ticks between going forward and going backwards.

    Even on the propeller using straight code and not smart pins it will work but will sometimes go forward and then backwards.

    Simple logic states that the first pin to go low is the direction with the second pin not meaning that much but to complete the cycle.

    I was trying to built some debounce logic but was not very successful.

    I will say that the quadrature smart pin works well. The only problem is I wanted to capture the push button as well.

    Mike
  • Isn’t it possible to do what the P2 smart pin does in software on the P1?
  • David BetzDavid Betz Posts: 14,364
    edited 2020-12-30 11:49
    This is the P1 code I was using the last time I tried this. It's written in PropGCC. As I recall, it wasn't very reliable either.
    /*
     * code to read a rotary encoder
     */
    
    #include <propeller.h>
    #include "encoder.h"
    
    // require this many samples to match before accepting a new value
    #define DEBOUNCE_TARGET 2000
    
    static _COGMEM unsigned int pin;
    
    static _COGMEM unsigned int nextValue;
    static _COGMEM unsigned int nextCount;
    static _COGMEM unsigned int tempValue;
    
    static _COGMEM unsigned int lastValue;
    static _COGMEM unsigned int thisValue;
    
    _NATIVE void main(volatile struct encoder_mailbox *m)
    {
        pin = m->pin;
    
        nextValue = -1;
        nextCount = 0;
    
        lastValue = 0;
        m->value = 0;
    
        for (;;) {
    
            tempValue = (INA >> pin) & 3;
            tempValue = ((tempValue & 1) << 1) | ((tempValue & 2) >> 1); // not sure why I needed to swap the pins
    
            if (tempValue == nextValue) {
                if (++nextCount >= DEBOUNCE_TARGET) {
                    thisValue = nextValue;
                    nextCount = 0;
                }
            }
            else {
                nextValue = tempValue;
                nextCount = 0;
            }
    
            switch ((lastValue << 2) | thisValue) {
            case 0b0000:    // no movement
            case 0b0101:
            case 0b1111:
            case 0b1010:
                // nothing to do
                break;
            case 0b0001:    // clockwise
            case 0b0111:
            case 0b1110:
            case 0b1000:
                if (m->value < m->maxValue)
                    ++m->value;
                else if (m->wrap)
                    m->value = m->minValue;
                break;
            case 0b0010:    // counter-clockwise
            case 0b1011:
            case 0b1101:
            case 0b0100:
                if (m->value > m->minValue)
                    --m->value;
                else if (m->wrap)
                    m->value = m->maxValue;
                break;
            }
    
            lastValue = thisValue;
        }
    }
    
  • It looks like it works pretty well if I sample on a 1ms interrupt rather than on the edges of the clock. This is using the code in the first message of this thread.
  • I've decided that I want to try to replace the port expander chip on my ESP8266 project with a P1. I'll likely just use a simple P1 circuit with minimal components because I mostly want to expose the P1 pins to be used for buttons, encoders, and RGB LEDs. I have a question about power supplies though. This device will be powered by a rechargeable battery that supplies 7.4 volts. I need 5V for the RGB LEDs and 3.3V for the ESP8266 and the P1. Should I drive both the 5V and 3.3V regulators from the battery directly or would it be better to drive the 3.3V regulator from the output of the 5V regulator?
  • For LDO's the lower the voltage the better the efficiency as it won't generate as much heat to drop the voltage down.

    For switching it doesn't matter as long as you don't overload the output.

    Mike
  • When I use these mechanical rotary encoders, I just setup simple truth table scanning which pins are high or low, mainly for navigating a menu on my LCD. A JM quadature spin object would be good for tracking movement on a wheel etc.
  • JonnyMacJonnyMac Posts: 7,391
    edited 2021-01-01 19:35
    @"David Betz" Give this a try. The new P1 encoder object matches the P2 assembly I showed in my presentation -- with one small change. We cannot send an arbitrary value to the P2 smart pin, but we can to the P1 cog. I do this instead of keeping track of an offset. Otherwise, all of the methods are the same.

    While running this I found the cheap encoder a lot less noisy; this is probably due to the fact that the P1 is running more slowly than the P2, and this serves as a bit of a digital filter. I ported my jm_ansi code to the P1 the other day, so you can output to an ANSI/VT100 terminal. I tested with Putty on Windows (see image).

    I attempted to test with FlexProp but there was an issue. Even though I selected P1, FlexProp wanted to use jm_fullduplexserial.spin2 (P2 version) and this caused compile errors. I have attached an image from my system for @ersmith so he can check on his side.

    Happy New Year.
  • JonnyMac wrote: »
    @"David Betz" Give this a try. The new P1 encoder object matches the P2 assembly I showed in my presentation -- with one small change. We cannot send an arbitrary value to the P2 smart pin, but we can to the P1 cog. I do this instead of keeping track of an offset. Otherwise, all of the methods are the same.

    While running this I found the cheap encoder a lot less noisy; this is probably due to the fact that the P1 is running more slowly than the P2, and this serves as a bit of a digital filter. I ported my jm_ansi code to the P1 the other day, so you can output to an ANSI/VT100 terminal. I tested with Putty on Windows (see image).

    I attempted to test with FlexProp but there was an issue. Even though I selected P1, FlexProp wanted to use jm_fullduplexserial.spin2 and this caused compile errors. I have attached an image from my system for @ersmith so he can check on his side.

    Happy New Year.
    Thanks, Jon! I'll give it a try.

  • ersmithersmith Posts: 4,716
    JonnyMac wrote: »
    I attempted to test with FlexProp but there was an issue. Even though I selected P1, FlexProp wanted to use jm_fullduplexserial.spin2 (P2 version) and this caused compile errors. I have attached an image from my system for @ersmith so he can check on his side.

    The choice of language is independent of processor -- FlexProp can compile Spin2 code for the P1, as long as it doesn't use any PASM.

    Generally speaking if your top level object is a .spin file then it should be checking for .spin files first for objects. I actually see that there are some cases where that doesn't happen correctly, so perhaps that's what you've run into. I'll try to improve that logic.

    Thanks,
    Eric
  • MicksterMickster Posts: 1,824
    I don't know if this discussion is of any use but....
Sign In or Register to comment.