Pages: [1] 2 3   Go Down
Author Topic: max with a rotary encoder  (Read 8706 times)
0 Members and 1 Guest are viewing this topic.
0
Offline Offline
Newbie
*
Karma: 0
Posts: 37
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

im trying to get my arduino to work with this encoder i bought:

http://cgi.ebay.co.uk/ws/eBayISAPI.dll?ViewItem&ssPageName=STRK:MEBTOX:IT&item=270130801383&_trksid=p3984.cTODAY.m238.lVI

ive used the code from the playground but im not sure on what i need in max.. or is there a better way of doing this? should i do the counting in max?
Logged

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6250
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Can you post a sketch with just your encoder code? It may help if you removed the Max code to focus just on the encoder until we get that going. You can send information on the encoder status using Serial.print.

After you get the encoder working you can add in the Max code.

You should close the duplicate thread so responses don't get split between the two.
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 37
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

#define encoder0PinA  2
#define encoder0PinB  4

volatile unsigned int encoder0Pos = 0;

void setup() {


  pinMode(encoder0PinA, INPUT);
  digitalWrite(encoder0PinA, HIGH);       // turn on pullup resistor
  pinMode(encoder0PinB, INPUT);
  digitalWrite(encoder0PinB, HIGH);       // turn on pullup resistor

  attachInterrupt(0, doEncoder, CHANGE);  // encoder pin on interrupt 0 - pin 2
  Serial.begin (9600);
  Serial.println("start");                // a personal quirk

}

void loop(){
// do some stuff here - the joy of interrupts is that they take care of themselves
}


void doEncoder(){
  if (digitalRead(encoder0PinA) == HIGH) {   // found a low-to-high on channel A
    if (digitalRead(encoder0PinB) == LOW) {  // check channel B to see which way
                                             // encoder is turning
      encoder0Pos = encoder0Pos - 1;         // CCW
    }
    else {
      encoder0Pos = encoder0Pos + 1;         // CW
    }
  }
  else                                        // found a high-to-low on channel A
  {
    if (digitalRead(encoder0PinB) == LOW) {   // check channel B to see which way
                                              // encoder is turning  
      encoder0Pos = encoder0Pos + 1;          // CW
    }
    else {
      encoder0Pos = encoder0Pos - 1;          // CCW
    }

  }
  Serial.println (encoder0Pos, DEC);          // debug - remember to comment out
                                              // before final program run
  // you don't want serial slowing down your program if not needed
}

thats the encoder code im using straight off the playground, but i dont understand where im getting these weird numbers, ive got 5v in the middle a resistor and then gnd in the left and right.
Logged

0
Offline Offline
Full Member
***
Karma: 0
Posts: 207
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Do you need some "debounce" type coding in there?  You may be having trouble at the transistions.

Mike
Logged

Australia
Offline Offline
Jr. Member
**
Karma: 0
Posts: 99
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

If you pay $100 for your industrial-grade encoder you get nicely conditioned, pristine square waves out of your 2 outputs. A $2 encoder, however, is really two (three if it has a push-button) mechanical switches in a housing, and they are surprisingly bouncy. The Playground code is good for the former, but not the latter. Have a read of http://www.ganssle.com/debouncing.pdf, you will see (page 16) that using interrupts on the output of undebounced switches is a really bad idea unless you are sure the flip-flops in your micro can handle the bounce rise-times & pulse widths.

The solution is to use the debounce code in Ganslee document (page 20), triggered on a 1 ms timer interrupt. I've spent the last few evenings getting the following code to work, and it's really reliable. I've used the FrequencyTimer2 lib from the Playground, but since you have a fixed frequency you can probably hack it down significantly. The core routine (the timer interrupt) appears below, I have omitted the timer setup.

Note that there are four transitions that you can update the value of encoder0Pos on, I am only using one because my encoder goes through all 4 states in one "click", so I get one step per "click". Uncomment the others if yours is different. Note also that encoder0Pos needs to be declared volatile, this prevents the compiler from caching its value if you set it then read it back immediately (the interrupt may have triggered & the value updated between the two operations, you want the updated value, not the one you just wrote).

Add another element to the State[] array and process it in the same manner if you want to debounce the push-button as well. My encoder doesn't have one, so neither does my code.

The code assumes that A & B are connected to pins configured as input ( pinMode(encoder0PinA, INPUT); ), with the internal pullups enabled ( digitalWrite(encoder0PinA, HIGH); ), and the common pin to ground.


Code:
#define encoder0PinA  2
#define encoder0PinB  4

volatile int encoder0Pos = 0;

// Service routine called by a timer interrupt
void DebounceSwitch()
{
  static unsigned char rawEncoder0A = 1;
  static unsigned char rawEncoder0B = 1;
  static unsigned char debouncedEncoder0A = 1;  // debounced state of encoder0PinA
  static unsigned char debouncedEncoder0B = 1;  // debounced state of encoder0PinB
  static uint16_t State[] = { 0, 0  }; // Current debounce status of encoder pins { A, B }

  rawEncoder0A = digitalRead(encoder0PinA);
  rawEncoder0B = digitalRead(encoder0PinB);

  // Encoder Pin A
  State[0]=(State[0]<<1) | rawEncoder0A | 0xe000;
  if(State[0]==0xf000) { // High --> Low Transition
    if (!debouncedEncoder0B) {   // check channel B to see which way
      // encoder is turning  
      encoder0Pos = encoder0Pos + 1;          // CW
    }
    else {
      encoder0Pos = encoder0Pos - 1;          // CCW
    }
    debouncedEncoder0A = 0;
  }
  if(State[0]==0xefff) { // Low --> High Transition
    //if (!debouncedEncoder0B) {  // check channel B to see which way
    //  // encoder is turning
    //  encoder0Pos = encoder0Pos - 1;         // CCW
    //}
    //else {
    //  encoder0Pos = encoder0Pos + 1;         // CW
    //}
    debouncedEncoder0A = 1;
  }

  // Encoder Pin B
  State[1]=(State[1]<<1) | rawEncoder0B | 0xe000;
  if(State[1]==0xf000) { // High --> Low Transition
    //if (debouncedEncoder0A == HIGH) {   // check channel A to see which way
    //  // encoder is turning  
    //  encoder0Pos = encoder0Pos + 1;          // CW
    //}
    //else {
    //  encoder0Pos = encoder0Pos - 1;          // CCW
    //}
    debouncedEncoder0B = 0;
  }
  if(State[1]==0xefff) { // Low --> High Transition
    //if (debouncedEncoder0A == HIGH) {  // check channel A to see which way
    //  // encoder is turning
    //  encoder0Pos = encoder0Pos - 1;         // CCW
    //}
    //else {
    //  encoder0Pos = encoder0Pos + 1;         // CW
    //}
    debouncedEncoder0B = 1;
  }

  // Clamp
  if (encoder0Pos < 0) encoder0Pos = 0;
  if (encoder0Pos > 32) encoder0Pos = 32;

}

Hope this helps,

Sam.
Logged

0
Offline Offline
Jr. Member
**
Karma: 0
Posts: 53
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi, i've bought an ALPS STEC12E rotary encoder 24/24. I've wired it up like this:

Code:
    |      |
  --------------
  |            |
|||  encoder   |||| GND
  |            |
  --------------
   |     |    |  
Pin 2   +5V  Pin 4

Pin 2/4 are digital Pins.
I've used the code from crimony above like this:

Code:
void setup()
{
  pinMode(encoder0PinA, INPUT);
  pinMode(encoder0PinB, INPUT);

  attachInterrupt(0, DebounceSwitch, CHANGE);

  Serial.begin(9600);
}

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

But nothing happens. All it prints is "0". What did i wrong?

thx Sunny
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 13
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Did you manage to get this working?

I can't get crimony's code to work either - returns a lot of errors when checked.

Anyway, I think you need the common to ground:

Code:
 --------------
  |                |
  |  encoder   |
  |                |
  --------------
   |       |      |  
Pin 2   GND  Pin 4


And turn on pull-ups:

Code:
void setup()
{
  pinMode(encoder0PinA, INPUT);
  digitalWrite(encoder0PinA, HIGH);
  pinMode(encoder0PinB, INPUT);
  digitalWrite(encoder0PinB, HIGH);

  attachInterrupt(0, DebounceSwitch, CHANGE);

  Serial.begin(9600);
}

void loop()
{  
  Serial.println(encoder0Pos);
}
Logged

0
Offline Offline
Jr. Member
**
Karma: 0
Posts: 53
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

i got it working, but did write my "own" little library based on some others... and yes, it has to be connected to ground. Heres the library code:

cpp-header-file:
Code:
/******************************************************************************
 *  RotaryEncoder.h - Arduino library for reading RotaryEncoder encoders.
 *  Version 0.90
 ******************************************************************************/

#ifndef RotaryEncoder_h
#define RotaryEncoder_h

#define DIGITAL_PINS (13)

class RotaryEncoder
{
    public:
            RotaryEncoder(int encoderPin1, int encoderPin2, int buttonPin);

            void minimum(int min);
            int minimum(void);
            void maximum(int max);
            int maximum(void);
            void nominimum(void);
            void nomaximum(void);

            int position(void);
            void position(int pos);

            int pressed(void);

            static void isr(void);

    private:
            int _readEncoderPins(void);
            int _readButtonPin(void);
            int _encoderPin1, _encoderPin2, _buttonPin;

            volatile int _position;

            int _min, _max;
            bool _usingmin, _usingmax, _buttonState, _buttonPrevious;

            volatile int _previous, _time;

            static RotaryEncoder* _instance;
};

inline void RotaryEncoder::minimum(int min)
{
    _min = min;
    _usingmin = 1;

    //  adjust position if lower than new minimum
    //_position = max(_position, min);
      _position = _position > min ? _position : min;
}

inline int RotaryEncoder::minimum()
{
    return _min;
}

inline void RotaryEncoder::maximum(int max)
{
    _max = max;
    _usingmax = 1;

    //  adjust position if higher than new maximum
    //_position = min(_position, max);
      _position = _position < max ? _position : max;
}

inline int RotaryEncoder::maximum()
{
    return _max;
}

inline void RotaryEncoder::nominimum(void)
{
    _usingmin = 0;
}

inline void RotaryEncoder::nomaximum(void)
{
    _usingmax = 0;
}


inline int RotaryEncoder::position(void)
{
    return _position;
}

inline void RotaryEncoder::position(int pos)
{
    _position = pos;
}

inline int RotaryEncoder::pressed(void)
{
    return _buttonState;
}
#endif // RotaryEncoder_h

cpp-file:
Code:
/******************************************************************************
 *  RotaryEncoder.cpp - Arduino library for reading RotaryEncoder encoders.
 *  Version 0.90
 ******************************************************************************/

#include "RotaryEncoder.h"

extern "C" {
      #include <inttypes.h>
      #include <avr/interrupt.h>
      #include "WConstants.h"      //all things wiring / arduino
}

const int _quadrature [4][4] = {
    { 0, 0, 0, 0 },            //  00 -> 10 is silent CW
    { 1, 0, 0, 0 },      //  01 -> 00 is CW
    { -1, 0, 0, 0 },      //  10 -> 11 is CW
    { 0, 0, 0, 0 }            //  11 -> 01 is silent CW
};

RotaryEncoder * RotaryEncoder::_instance;

RotaryEncoder::RotaryEncoder(int encoderPin1, int encoderPin2, int buttonPin)
 : _encoderPin1(encoderPin1), _encoderPin2(encoderPin2), _buttonPin(buttonPin), _position(0), _min(0), _max(0), _usingmin(0), _usingmax(0), _buttonState(0), _buttonPrevious(0), _time(0) // constructor initializer list
{
    pinMode(encoderPin1, INPUT);
    pinMode(encoderPin2, INPUT);
      pinMode(buttonPin, INPUT);
    digitalWrite(encoderPin1, HIGH);      //  activate internal pullups
    digitalWrite(encoderPin2, HIGH);
      digitalWrite(buttonPin, HIGH);

    _previous = _readEncoderPins();      //  read initial position

    TIMSK2 |= (1 << TOIE2);            //  enable timer 2 overflow interrupt

    _instance = this;
}

inline int RotaryEncoder::_readEncoderPins(void)
{
    return digitalRead(_encoderPin2) << 1 | digitalRead(_encoderPin1);
}

inline int RotaryEncoder::_readButtonPin(void)
{
      return digitalRead(_buttonPin) == HIGH ? 0 : 1;
}

inline void RotaryEncoder::isr(void)
{
      //____________________________________________
      //                                Read Encoder
      int quadbits = _instance->_readEncoderPins();

      if (quadbits != _instance->_previous)
      {
            int position = _instance->_position + _quadrature[_instance->_previous][quadbits];

            //  limit to minimum if assigned
            position = _instance->_usingmin ? max(_instance->_min, position) : position;

            //  limit to maximum if assigned
            _instance->_position = _instance->_usingmax ? min(_instance->_max, position) : position;

            _instance->_previous = quadbits;
      }

      //____________________________________________
      //                                 Read Button
      int reading = _instance->_readButtonPin();
      
      // if we just pressed the button (i.e. the input went from LOW to HIGH),
      // and we've waited long enough since the last press to ignore any noise...  
      if (reading != _instance->_buttonPrevious)// && millis() - _instance->_time > 100)
      {
            _instance->_buttonState = reading;

            // ... and remember when the last button press was
            //_instance->_time = millis();    
      }

      _instance->_buttonPrevious = reading;
}


ISR(TIMER2_OVF_vect)
{
    RotaryEncoder::isr();
}

Ciao Sunny
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 13
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Cheers for the code!

Can you give an example of a sketch to implement this?

Not sure how I can include this in my loop.

Cheers

Gareth
Logged

0
Offline Offline
Jr. Member
**
Karma: 0
Posts: 53
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

how you run it:

Code:
#include "RotaryEncoder.h"

RotaryEncoder rotary(3, 4, 2);

void setup()
{
  Serial.begin(9600);
  
  rotary.minimum(0);
  rotary.maximum(100);
  rotary.position(20);
}


void loop()
{
  if(rotary.pressed())
  {
    Serial.print("Sensibility: ");
    Serial.print(rotary.position());
    Serial.print("%\n");
    delay(200);
  }
}

Ciao Sunny
Logged

0
Offline Offline
God Member
*****
Karma: 2
Posts: 854
Arduino rocks!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
i got it working, but did write my "own" little library based on some others.

Nice looking library.  Has anyone used it yet and can confirm it works?

Now for extra credit, I'm already using timer2 elsewhere in my code, and there's no way I can get around using it.  Anyone have suggestions for modifying this library?

The interval I'm using on timer2 changes over a wide range of possible values, so I can't really share the timer.
Logged

0
Offline Offline
Edison Member
*
Karma: 8
Posts: 1411
Arduino rocks
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

What practical application are you using the rotary encoder in? I've got a bunch of them myself, small and good looking, but I cannot figure out a good use for them (actually, I always find a software way to get around using them).
Logged

0
Offline Offline
God Member
*****
Karma: 2
Posts: 854
Arduino rocks!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

This project will generate precision timing pulses (with a frequency set by the rotary encoder), and the precision part is why I have to use the timer interrupt.  It also has to drive a character LCD.

Next project will be almost identical hardware, but it will be in intervalometer for Nikon cameras with the encoder to select time between pictures.
Logged

0
Offline Offline
Edison Member
*
Karma: 8
Posts: 1411
Arduino rocks
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

OK, I see.
Now, I think you would need some feedback for the rotary encoder setting anyway, on an LCD, or some flashing light or something. In this case, can't you have just a push button, and handle the setting through software? (That is, push the button until you reach the desired frequency, and when you reach maximum go back to 0).
Logged

0
Offline Offline
God Member
*****
Karma: 2
Posts: 854
Arduino rocks!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

That is basically what a rotary encoder is except that as you turn the knob it keeps pressing the button for you.

It's a user interface design choice.  There is never an instance where you can't replace a rotary encoder with buttons but there's many cases where the enoder is nicer...volume knob on an amp, jog wheel on a cell phone, etc.  In this case, I have a range of a couple of hundred values, it will be tedious with buttons, but it's just 10 revolutions of the knob for 200 ticks.

In this case, I need coarse and fine adjustments, the encoder has a built in button (push in the knob), so I can have it work as push for coarse leave out for fine.  That's harder to do intuitively with buttons.

I don't know why it makes a difference having an LCD to give feedback.  The encoder doesn't have a "zero" position anyway.  As long as I turn clockwise the number should increase (until the end condition) and counterclockwise should decrease (until the other end condition).  In this case, I'll probably just have the number stay put at the end, not wrap around, or maybe go into a function menu.
Logged

Pages: [1] 2 3   Go Up
Jump to: