Need help with Arduino + Rotary encoder

Australia & New Zealand Homebrewing Forum

Help Support Australia & New Zealand Homebrewing Forum:

This site may earn a commission from merchant affiliate links, including eBay, Amazon, and others.

mr_wibble

Beer Odd
Joined
21/1/09
Messages
1,114
Reaction score
231
Location
Lake Macquarie NSW
Hi-yas,


I have a rotary encoder: http://australia.rs-online.com/web/p/optical-rotary-encoders/6633182/?origin=PSF_433027
Data Sheet: http://australia.rs-online.com/web/p/optical-rotary-encoders/6633176/
I wired in two 0.10 uF capacitors across the encoder pins to ground, this helped debouncing a much cheaper (non-optical) encoder I was playing with.

I want to use it for setting the temperature on my HLT.
Eventually the Arduino will drive a relay, to switch the heating element, but that's for another post.

Right now the encoder is doing my head in.

It mostly works.
Turn the knob one way, and the set-temperature increases, turn it the other, and it decreases.

However, if you give it a good flick, sometimes the reading jumps backwards, despite a forward turn (and vice-versa).
For example, say it's already it's at 100, if I turn the knob quickly to *add* more temperature, it can jump back to say 95 and start increasing again from there.

Is it emitting signals too fast for the poor little Arduino to catch them all?
Maybe I'm dropping interrupts?

It's not a big problem, but it's rubbing my inner perfectionist in entirely the wrong way.

I haven't coded stuff this low-level since I used to do z80 assembly language as a kid.

Any help much appreciated.

Code:
/*
 * Test sketch to handle a rotary encoder for setting kettle temperature
 * NOTE: Fails to handle very fast turns
 */

#define ENCODER_PIN_A      2 /* must be pin2 for interrupt */
#define ENCODER_PIN_B      3 /* must be pin3 for interrupt */
#define ENCODER_BUTTON_PIN 4

/* Temperature is an integer for speed, divide by 10 for the real value */
#define TEMPERATURE_DEFAULT  500
#define TEMPERATURE_STEP       5
#define TEMPERATURE_MAX     1000
#define TEMPERATURE_MIN        0

volatile int temperature_setpoint = TEMPERATURE_DEFAULT;

void setup() 
{
    pinMode(ENCODER_PIN_A, INPUT); 
    pinMode(ENCODER_PIN_B, INPUT); 
    pinMode(ENCODER_BUTTON_PIN, INPUT);
    digitalWrite(ENCODER_PIN_A, HIGH);       // turn on pullup resistor
    digitalWrite(ENCODER_PIN_B, HIGH);       // turn on pullup resistor
    digitalWrite(ENCODER_BUTTON_PIN, HIGH);  // turn on pullup resistor

    // encoder pin on interrupt 0 (pin 2)
    attachInterrupt(0, encoderPinChanged, RISING);  //CHANGE);

    Serial.begin(115200);
}


void loop()
{     
    static int last_printed = 0;

    /* Only write the temperature if it changes */    
    if (abs(temperature_setpoint - last_printed) > 0)
    {
        last_printed = temperature_setpoint;

        float setpoint = (float)temperature_setpoint / 10.0;
        Serial.print("Set Temp: "); Serial.println(setpoint);
    }
    
    /* poll for encoder button presses */
    if (digitalRead(ENCODER_BUTTON_PIN) == LOW)  
    {
        temperature_setpoint = TEMPERATURE_DEFAULT;
        Serial.println("Reset Temp");
        delay(200);
    }    
}


// Interrupt on either encoder line A or B changing state
void encoderPinChanged()
{
    noInterrupts();

    int pin_a = digitalRead(ENCODER_PIN_A);
    int pin_b = digitalRead(ENCODER_PIN_B);
    
    if (pin_a == HIGH)
    {
        if (pin_b == LOW)
        {
            temperature_setpoint -= TEMPERATURE_STEP;    // CCW
            if (temperature_setpoint < TEMPERATURE_MIN)
                temperature_setpoint = TEMPERATURE_MIN;
        }
        else
        {
            temperature_setpoint += TEMPERATURE_STEP;    // CW
            if (temperature_setpoint > TEMPERATURE_MAX)
                temperature_setpoint = TEMPERATURE_MAX;
        }
    }
    else // pin_a == LOW
    {
        if (pin_b == LOW)
        {
            temperature_setpoint += TEMPERATURE_STEP;    // CW
            if (temperature_setpoint > TEMPERATURE_MAX)
                temperature_setpoint = TEMPERATURE_MAX;
        }
        else
        {
            temperature_setpoint -= TEMPERATURE_STEP;    // CCW
            if (temperature_setpoint < TEMPERATURE_MIN)
                temperature_setpoint = TEMPERATURE_MIN;
        }
    }

    interrupts();
}
 
Ok, I read a bit more on it.

Even using the "highly optimised" Encoder library, I still get the same results.
http://www.pjrc.com/teensy/td_libs_Encoder.html

One thing I read suggested getting a Bigger Knob to slow down the input speed. So maybe having to small a knob, or in this case no knob at all, does not help.

Code:
/*
 * Sketch to handle a rotary encoder for setting kettle temperature
 */

#define ENCODER_OPTIMIZE_INTERRUPTS
#include <Encoder.h>

#define ENCODER_PIN_A      2 /* must be pin2 for interrupt */
#define ENCODER_PIN_B      3 /* must be pin3 for interrupt */
#define ENCODER_BUTTON_PIN 4

#define TEMPERATURE_DEFAULT  500
#define TEMPERATURE_STEP       5
#define TEMPERATURE_MAX     1000
#define TEMPERATURE_MIN        0


Encoder big_knob(ENCODER_PIN_A,ENCODER_PIN_B);

void setup() 
{
    pinMode(ENCODER_PIN_A, INPUT); 
    pinMode(ENCODER_PIN_B, INPUT); 
    pinMode(ENCODER_BUTTON_PIN, INPUT);
    digitalWrite(ENCODER_PIN_A, HIGH);  // turn on pullup resistor
    digitalWrite(ENCODER_PIN_B, HIGH);  // turn on pullup resistor
    digitalWrite(ENCODER_BUTTON_PIN, HIGH);  // turn on pullup resistor

    Serial.begin(115200);
}


void loop()
{     
    static int last_printed = 0;
    
    int temperature_setpoint = big_knob.read();
    if (temperature_setpoint > TEMPERATURE_MAX)
    {
        temperature_setpoint = TEMPERATURE_MAX;
        big_knob.write(TEMPERATURE_MAX);
    }
    else if (temperature_setpoint < TEMPERATURE_MIN)
    {
        temperature_setpoint = TEMPERATURE_MIN;
        big_knob.write(TEMPERATURE_MIN);
    }
   
    if (abs(temperature_setpoint - last_printed) > 0)
    {
        last_printed = temperature_setpoint;

        float setpoint = (float)temperature_setpoint / 10.0;
        Serial.print("Set Temp: "); Serial.println(setpoint);
    }
    
    if (digitalRead(ENCODER_BUTTON_PIN) == LOW)  
    {
        temperature_setpoint = TEMPERATURE_DEFAULT;
        big_knob.write(TEMPERATURE_DEFAULT);
        Serial.println("Reset Temp");
        delay(100);
    }    
}


NOTE TO ADMIN: Previewing or editing post with <code> tags in them unformats your code.
 
Just to sum up - the encoder can change just too fast for the Arduino to keep up.
The Encoder library uses a bit of assembler, and even that doesn't keep up - but it does keep up better than my code.

I would have thought 16MHz would be enough, but apparently not.
My Microbee was only 2.75MHz, and that *always* kept up ;)
 
16MHz is more than fast enough, BUT that depends on what else the micro is doing and how the code is written. Roll your own interrupt driven code and you'd be able to get that processor to do another dozen processing-intensive tasks and the encoder reading with ease. If the code is a RTOS or pseudo-RTOS, it will be sluggish.
 
Just to put this to bed, the following code example works 99% OK.

The only failing is that when it's at 100% and you spin the encoder shaft quickly, it drops a little before going back to 100 (similarly for 0 in the negative direction).
With a knob on the shaft, it doesn't happen in normal use-cases.

If you're scratching your head on this, ensure your encoder is on pins 2 & 3, not anything else.
Even if you wrote the post above, you still might not remember ;)


Code:
#include <Serial.h>

#define ENCODER_PIN_A       2 /* must be pin2 for interrupt */
#define ENCODER_PIN_B       3 /* must be pin3 for interrupt */
#define ENCODER_BUTTON_PIN  8
#define ENCODER_OPTIMIZE_INTERRUPTS
#include <Encoder.h>
 
Encoder encoder(ENCODER_PIN_A,ENCODER_PIN_B);
volatile float last_position = 1000;  // big enough to force the initial paint

#define TEMPERATURE_MAX     100.0
#define TEMPERATURE_MIN       0.0
 
 
void setup()
{
    Serial.begin(115200);
    Serial.println("\n----------------------------------------");
    
    pinMode(ENCODER_PIN_A,INPUT_PULLUP);
    pinMode(ENCODER_PIN_B,INPUT_PULLUP);
    pinMode(ENCODER_BUTTON_PIN,INPUT);
    encoder.write(75 * 2);
}
 
void loop()
{
    // We range the encoder 0 to 200, but then halve that value
    // which gives us readings of 0-100 with 0.5 stepping.
    // Make sure the encoder value matches our needed range
    encoder.write(constrain(encoder.read(),TEMPERATURE_MIN,2 * TEMPERATURE_MAX));    
    float encoder_value = ((float)encoder.read()) / 2.0;
 
    if (last_position != encoder_value)
    {
        Serial.print("Encoder position: ");  Serial.println(encoder_value,2);
        last_position = encoder_value;
    }
 
    // Did we get a button press?
    if (digitalRead(ENCODER_BUTTON_PIN) == HIGH)
    {
        // debounce the button, we use a big delay because these encoder
        // buttons are rather dodgey
        delay(100);
        if (digitalRead(ENCODER_BUTTON_PIN) == HIGH)
        {
            Serial.println("BUTTON PRESS");
            delay(500); // no more clicks for a bit
        }
    }
}
 
I wrote my own rotary encoder logic, and it seems to work fine, although i never tried it at the speeds you seem to require. Anyway, it might be of use to you.

i chopped it down to just the logic, so as far as code syntax goes there may be some faults, but the logic is there, and thats what I'm offering :)

Regards
Jason

IRState1 = digitalRead(IRTrigger1);
IRState2 = digitalRead(IRTrigger2);



if (IRState1 >0)
{
IRvalue1=1; digitalWrite(IR1Triggered, HIGH); // light an LED to show that input is detected
}
else digitalWrite(IR1Triggered,LOW);
if (IRState2 >0)
{
IRvalue2=1;digitalWrite(IR2Triggered, HIGH); // light an LED to show that input is detected

}
else digitalWrite(IR2Triggered,LOW);

if(IRState1 > IRState2){
increasing = 1;} else if (IRState2 > IRState1){
decreasing=1;}

if ((IRState1 + IRState2) ==2){
if (increasing == 1){
targetTemp=targetTemp + 1;

else if (decreasing == 1){
targetTemp = targetTemp - 1;

}

// reset everything
increasing=0;
decreasing=0;
IRState1=0;
IRState2=0;
}
 
I didn't need high speeds, it was just that if you miss any of the encoder's pulses, your tracking goes base-over-apex.
The upshot of this is: Your turning clockwise, the count is going up. Suddenly it starts going back down again, then up.

It's not a good user-experience that's all.

If it becomes a problem, you can always use a bigger knob.

cheers,
-kt
 
Back
Top