Go Down

Topic: Rotary Encoder (Read 4416 times) previous topic - next topic

CrossRoads

What's the best library for a rotary encoder? I have A on D9 and B on D8, so I'm thinking something that uses PCINT1 and PCINT0 would be good.

Thanks!
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

CrossRoads

Ok, I need some help. IDE 1.06, Uno, PC Win7Pro.

I started from this Playground info:
Code: [Select]

Interrupt Library (the Encoder interrupts the processor).
Utilizes any of the ATmega328p pins via the PinChangeInt library.
by GreyGnome

The AdaEncoder library was created for working with basic 2-pin quadrature encoders such as the following:
https://www.adafruit.com/products/377
http://www.sparkfun.com/products/9117
From the Introduction:
This library interfaces with 2-pin encoders (2 pins A and B, then a common pin C). It does not indicate every state change, rather, it reports only when the decoder is turned from one detent position to the next. It is interrupt-driven and designed to be fast and easy to use. The interrupt routine is lightweight, and the programmer is then able to read the direction the encoder turned at their leisure (within reason; what's important is that the library is reasonably forgiving). The library is designed to be easy to use (it bears repeating :-) ) and it is reasonably immune to switch bounce.

See the project page at: http://code.google.com/p/adaencoder/
See a speed discussion at: http://code.google.com/p/adaencoder/wiki/Speed
See the PinChangeInt library project page at: http://code.google.com/p/arduino-pinchangeint/

Here's an example with two encoders connected. Encoder a is connected to pins 2 and 3, b is connected to 5 and 6:

#include <PinChangeInt.h> // necessary otherwise we get undefined reference errors.
#include <AdaEncoder.h>

#define a_PINA 2
#define a_PINB 3
#define b_PINA 5
#define b_PINB 6

int8_t clicks=0;
char id=0;

void setup()
{
  Serial.begin(115200);
  AdaEncoder::addEncoder('a', a_PINA, a_PINB);
  AdaEncoder::addEncoder('b', b_PINA, b_PINB);  
}

void loop()
{
  encoder *thisEncoder;
  thisEncoder=AdaEncoder::genie(&clicks, &id);
  if (thisEncoder != NULL) {
    Serial.print(id); Serial.print(':');
    if (clicks > 0) {
      Serial.println(" CW");
    }
    if (clicks < 0) {
       Serial.println(" CCW");
    }
  }
}

And put the parts for 1 encoder into my sketch on pins 9 (A) & 8 (B).
Went to the two websites and put the libraries into my Prefences path library folder, so I have this for a sketch:
Code: [Select]


/*
Interrupt Library (the Encoder interrupts the processor).
 Utilizes any of the ATmega328p pins via the PinChangeInt library.
 by GreyGnome
 
 The AdaEncoder library was created for working with basic 2-pin quadrature encoders such as the following:
 https://www.adafruit.com/products/377
 http://www.sparkfun.com/products/9117
 From the Introduction:
 This library interfaces with 2-pin encoders (2 pins A and B, then a common pin C). It does not indicate every state change, rather, it reports only when the decoder is turned from one detent position to the next. It is interrupt-driven and designed to be fast and easy to use. The interrupt routine is lightweight, and the programmer is then able to read the direction the encoder turned at their leisure (within reason; what's important is that the library is reasonably forgiving). The library is designed to be easy to use (it bears repeating :-) ) and it is reasonably immune to switch bounce.
 
 See the project page at: http://code.google.com/p/adaencoder/
 See a speed discussion at: http://code.google.com/p/adaencoder/wiki/Speed
 See the PinChangeInt library project page at: http://code.google.com/p/arduino-pinchangeint/
 
 Here's an example with two encoders connected. Encoder a is connected to pins 2 and 3, b is connected to 5 and 6:
 */
// modified for just 1 encoder on 9 & 8

#include <ooPinChangeInt.h> // necessary otherwise we get undefined reference errors.
#include <AdaEncoder.h>
#include <SPI.h>

#define a_PINA 9
#define a_PINB 8

int8_t clicks=0;
char id=0;

// test of other pins - press start to light LEDs, press select to turn off LEDs
byte redLed = 14;
byte greenLed = 15;
byte yellowLed = 16;
byte selectPin = 3;
byte startPin = 2;

byte shiftRCK = 19;
byte shiftSCK = 18;
byte shiftSerial = 17;

byte fontArray [] = {
  0b00111111, // 0
  0b00000110, // 1
  0b01011011, // 2
  0b01001111, // 3
  0b01100110, // 4
  0b01101101, // 5
  0b01111101, // 6
  0b00000111, // 7
  0b01111111, // 8
  0b01101111, // 9
  0b00000000, // blank
};
byte digit1 = 0;
byte digit2 = 1;

void setup(){
  pinMode (shiftSCK, OUTPUT);
  pinMode (shiftRCK, OUTPUT);
  pinMode (shiftSerial, OUTPUT);
  pinMode (redLed, OUTPUT);
  pinMode (greenLed, OUTPUT);
  pinMode (yellowLed, OUTPUT);
  pinMode (selectPin, INPUT_PULLUP);
  pinMode (startPin, INPUT_PULLUP);
  Serial.begin(115200);
  AdaEncoder::addEncoder('a', a_PINA, a_PINB);
  SPI.begin();
}
void loop(){
  if (digitalRead(startPin) == LOW){
    digitalWrite (greenLed, HIGH);
    digitalWrite (redLed, HIGH);
    digitalWrite (yellowLed, HIGH);
  }
  if (digitalRead (selectPin) == LOW){
    digitalWrite (greenLed, LOW);
    digitalWrite (redLed, LOW);
    digitalWrite (yellowLed, LOW);
  }

  encoder *thisEncoder;
  thisEncoder=AdaEncoder::genie(&clicks, &id);
  if (thisEncoder != NULL) {
    Serial.print(id);
    Serial.print(':');
    if (clicks > 0) {
      Serial.println(" CW");
      digit1 = digit1 + 1;
      if (digit1 == 10){
        digit1 = 0;
      }
      digit2 = digit2 + 1;
      if (digit2 == 10){
        digit2 = 0;
      }
      digitalWrite (shiftRCK, LOW);
      shiftOut(shiftSerial, shiftSCK, MSBFIRST, fontArray[digit2]);  
      shiftOut(shiftSerial, shiftSCK, MSBFIRST, fontArray[digit1]);
      digitalWrite (shiftRCK, HIGH);

    }
    if (clicks < 0) {
      Serial.println(" CCW");
      if (digit1 == 0){
        digit1 = 10;
      }
      digit1 = digit1 - 1;
      if (digit2 == 0){
        digit2 = 10;
      }
      digit2 = digit2 - 1;
      digitalWrite (shiftRCK, LOW);
      shiftOut(shiftSerial, shiftSCK, MSBFIRST, fontArray[digit2]);  
      shiftOut(shiftSerial, shiftSCK, MSBFIRST, fontArray[digit1]);
      digitalWrite (shiftRCK, HIGH);

    }
  }
}


Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

CrossRoads


So my /libraries folder now has a folder /AdaEncoder and /ooPinChangeInt, and ByteBuffer, cbiface, and cppfix.
Closed the IDE and restarted. In my Contributed libraries, I see AdaEncoder and ooPinChangeInt.

When I compile I get these errors:
Code: [Select]
In file included from Rev6CardTest.ino:21:
C:\Users\CrossRoadsFencing.CrossRoads\Documents\Arduino\ArduinoStuff\libraries\ooPinChangeInt/ooPinChangeInt.h:94:32: error: ../cbiface/cbiface.h: No such file or directory
In file included from Rev6CardTest.ino:21:
C:\Users\CrossRoadsFencing.CrossRoads\Documents\Arduino\ArduinoStuff\libraries\ooPinChangeInt/ooPinChangeInt.h:162: error: 'CallBackInterface' has not been declared
C:\Users\CrossRoadsFencing.CrossRoads\Documents\Arduino\ArduinoStuff\libraries\ooPinChangeInt/ooPinChangeInt.h:171: error: ISO C++ forbids declaration of 'CallBackInterface' with no type
C:\Users\CrossRoadsFencing.CrossRoads\Documents\Arduino\ArduinoStuff\libraries\ooPinChangeInt/ooPinChangeInt.h:171: error: expected ';' before '*' token
C:\Users\CrossRoadsFencing.CrossRoads\Documents\Arduino\ArduinoStuff\libraries\ooPinChangeInt/ooPinChangeInt.h:189: error: 'CallBackInterface' has not been declared
C:\Users\CrossRoadsFencing.CrossRoads\Documents\Arduino\ArduinoStuff\libraries\ooPinChangeInt/ooPinChangeInt.h:190: error: 'CallBackInterface' has not been declared
C:\Users\CrossRoadsFencing.CrossRoads\Documents\Arduino\ArduinoStuff\libraries\ooPinChangeInt/ooPinChangeInt.h:303: error: 'CallBackInterface' has not been declared
C:\Users\CrossRoadsFencing.CrossRoads\Documents\Arduino\ArduinoStuff\libraries\ooPinChangeInt/ooPinChangeInt.h: In member function 'void PCintPort::enable(PCintPort::PCintPin*, int*, uint8_t)':
C:\Users\CrossRoadsFencing.CrossRoads\Documents\Arduino\ArduinoStuff\libraries\ooPinChangeInt/ooPinChangeInt.h:307: error: 'class PCintPort::PCintPin' has no member named 'pinCallBack'
C:\Users\CrossRoadsFencing.CrossRoads\Documents\Arduino\ArduinoStuff\libraries\ooPinChangeInt/ooPinChangeInt.h: At global scope:
C:\Users\CrossRoadsFencing.CrossRoads\Documents\Arduino\ArduinoStuff\libraries\ooPinChangeInt/ooPinChangeInt.h:324: error: 'CallBackInterface' has not been declared
C:\Users\CrossRoadsFencing.CrossRoads\Documents\Arduino\ArduinoStuff\libraries\ooPinChangeInt/ooPinChangeInt.h:364: error: 'CallBackInterface' has not been declared
C:\Users\CrossRoadsFencing.CrossRoads\Documents\Arduino\ArduinoStuff\libraries\ooPinChangeInt/ooPinChangeInt.h: In member function 'void PCintPort::PCint()':
C:\Users\CrossRoadsFencing.CrossRoads\Documents\Arduino\ArduinoStuff\libraries\ooPinChangeInt/ooPinChangeInt.h:444: error: 'class PCintPort::PCintPin' has no member named 'pinCallBack'
In file included from Rev6CardTest.ino:22:
C:\Users\CrossRoadsFencing.CrossRoads\Documents\Arduino\ArduinoStuff\libraries\AdaEncoder/AdaEncoder.h: At global scope:
C:\Users\CrossRoadsFencing.CrossRoads\Documents\Arduino\ArduinoStuff\libraries\AdaEncoder/AdaEncoder.h:120: error: expected class-name before '{' token
Rev6CardTest.ino: In function 'void setup()':
Rev6CardTest:68: error: cannot call member function 'void AdaEncoder::addEncoder(char, uint8_t, uint8_t)' without object
Rev6CardTest.ino: In function 'void loop()':
Rev6CardTest:83: error: 'encoder' was not declared in this scope
Rev6CardTest:83: error: 'thisEncoder' was not declared in this scope

What more do I need to do to get the libraries recognized?
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

jurs

What's the best library for a rotary encoder? I have A on D9 and B on D8, so I'm thinking something that uses PCINT1 and PCINT0 would be good.
Best practice reading rotary encoders depends on the type of rotary encoder (and your actual application, of course).

What type of rotary encoder is it?

Is it a small mechanical rotary encoder with bouncing signals?
Or is it a opto-electronical rotary encoder which creates bounce-free signals from its own electronic circuit?

With opto-electronical (non-bouncing) rotary encoders you can use nearly every code and library linked in the playground and will have good success with it.

With mechanical (bouncing) rotary encoders all the playground code using 'hardware interrupt' or 'pinchange interrupt' is pure crap.

CrossRoads

Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

CrossRoads

What I want to do is cycle thru 00 to FF on the display to select a file, no high speed motor shaft tracking or anything critical.
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

jurs

It's this one
http://www.digikey.com/product-detail/en/PEC12R-2125F-N0012/PEC12R-2125F-N0012-ND/4499641
with the filter circuit on page 2 of its drawing
http://www.bourns.com/data/global/pdfs/PEC12R.pdf
OK, that's a simple mechanical rotary encoder which creates less (when new) or more (when old and worn out) bouncing while rotating.

All code linked in the playground that uses hardware or pinchange interrupts will lead to less or more crappy results.

The filter circuit will make things better, because that helps preventing bouncing and less bouncing helps crappy code in working better with such encoders.

I'd strongly suggest either polling such encoders or use timer interrupts.

Polling of rotary encoders would be suitable, if the loop function is always running at a high speed with no blocking times and no slow operations, let's say the loop function never takes more than one or two milliseconds to complete. In that case: Just poll the inputs of the rotary encoder and count accordingly to the state up and down.

The other possibility would be the usage of timer interrupts. Set up a timer that generates let's say 500 or 1000 interrupts per second. Then in the interrupt handling read the inputs and count accordingly to the state up and down.

How many of such encoders would you like to use in the application?
Just one? More than one (how many total)?
Would polling be applicable to your application (loop functions always faster than 2ms)?
Or do you have a timer left that could be used for the rotary encoder(s)?


ian332isport

I always had good results with this one:

Ben Buxton's rotary encoder page.

Ian.

CrossRoads

#8
Apr 24, 2015, 04:35 pm Last Edit: Apr 24, 2015, 04:36 pm by CrossRoads
I just have the one, nothing else going on until a number is selected, after which the encoder is ignored.

I'll take a look at the Ben Buxton page too when I get home.

Thanks.
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

Riva

Here is code I wrote to test use and encoder for setting date/time on an RTC prior to including in my WordClock.

Code: [Select]
#include <Wire.h>
#include <RealTimeClockDS3231.h>

//Enumerations
const int eHours = 0;
const int eMinutes = 1;
const int eDay = 2;
const int eMonth = 3;
const int eYear = 4;
const int eDOW = 5;

//Constants
const int encButton = 5;                         //Rotary Encoder button pin (11)
const int encoder0PinA = 6;                      //Rotary Encoder A pin (12)
const int encoder0PinB = 7;                      //Rotary Encoder B pin (13)

const long debounceDelay = 90;                   //Button debounce time
const long adjustDelay = 10000;                  //10 Second Adjust timeout

//Globals
char formatted[] = "0000-00-00 00:00:00x";

void setup() {
  //  Wire.begin();
  pinMode (encoder0PinA,INPUT);
  digitalWrite(encoder0PinA,HIGH);               //Enable pullup resistor
  pinMode (encoder0PinB,INPUT);
  digitalWrite(encoder0PinB,HIGH);               //Enable pullup resistor
  pinMode (encButton,INPUT);                     //Encoder button is set to input
  Serial.begin(9600);
}

void loop() {
  long loopTime = millis();                 //Read timer
  int mainDelay = 1000;                      // Default main loop delay

  while ((millis() - loopTime) < mainDelay) {

    if (checkButton(encButton)){
      while (checkButton(encButton)){             //Wait for button to be released
      };
      delay(100);
      digitalWrite(13, HIGH);   // set the LED on
      //Serial.println("Button On");
      eAdjust();
      //Serial.println("Button Off");
      digitalWrite(13, LOW);   // set the LED on
    }

  }

  RTC.readClock(); //Read the current date and time
  RTC.getFormatted2k(formatted);
  Serial.println(formatted);
}

// Called from main loop when encoder button pressed
void eAdjust(){
  RTC.readClock();   //Read the current date and time
  int value = readEncoder();                      //Init encoder readback value and prime encoder sub (result discarded)
  int eMode = eHours;                             //Set mode to eHours
  doDisplay(eMode);                               //Display Hours

  long eTime = millis();   //Get current CPU time for adjust loop timeout
  while ((millis() - eTime) < adjustDelay) {

    if (checkButton(encButton)){                  //Is encoder button pressed?
      // Mode button pressed
      //Serial.println("Button Pressed");
      while (checkButton(encButton)){             //Wait for button to be released
      };
      delay(100);                                 //Slight delay to allow for button debounce
      //Serial.println("Button Released");
      eMode++;           //Increment mode
      if (eMode > eDOW){                          //End of modes?
        // Mode cycled past end
        RTC.setSeconds(0);                        //Zero seconds
        RTC.setClock();                           //Set the clock
        //Serial.println("Mode Exit");
        return;                                   //Return
      }
      RTC.getFormatted2k(formatted);
      Serial.println(formatted);
      doDisplay(eMode);                   //Display the new mode
      eTime = millis();                   //Reset adjust loop timout
    }

    if (millis() % 10 == 0){                      //Only read every 10mS
      int result = readEncoder();                 //Read encoder. Returns -1,0,1
      //Serial.print("Encoder: ");
      if (!result == 0){   //Encoder <> 0
        //Encoder wheel was turned
        //Serial.print("Encoder Turned");
        switch (eMode){
        case eHours:                              //Mode eHours
          value = RTC.getHours() + result;        //Read hours and add encoder direction
          RTC.setHours(value % 24);               //Store new hours after legalizing
          break;                                  //No further processing
        case eMinutes:
          value = RTC.getMinutes() + result;
          RTC.setMinutes(value % 60);
          break;
        case eDay:
          value = RTC.getDate() + result;
          RTC.setDate(value % 31);
          break;
        case eMonth:
          value = RTC.getMonth() + result;
          RTC.setMonth(value % 12);
          break;
        case eYear:
          value = RTC.getYear() + result;
          RTC.setYear(value % 99);
          break;
        case eDOW:
          value = RTC.getDayOfWeek() + result;
          RTC.setDayOfWeek(value % 7);
          break;
        default:                                  //What to do if mode unknown
          break;                                  //Nothing
        }
        doDisplay(eMode);                         //Display new mode
        eTime = millis();                         //Reset adjust loop timout
      }
    }
  }
  //Timed out
  //RTC.setClock();
  Serial.println("Timeout");
}

void doDisplay(int Mode){                         //Display relevent data for given mode
  Serial.print("Mode ");
  Serial.print(Mode);
  Serial.print(": ");
  switch (Mode){
  case eHours:
    Serial.println(RTC.getHours());
    break;
  case eMinutes:
    Serial.println(RTC.getMinutes());
    break;
  case eDay:
    Serial.println(RTC.getDate());
    break;
  case eMonth:
    Serial.println(RTC.getMonth());
    break;
  case eYear:
    Serial.println(RTC.getYear());
    break;
  case eDOW:
    Serial.println(RTC.getDayOfWeek());
    break;
  default:
    break;
  }
}

// Button = pin number to read
int checkButton(int Button) {
  int buttonState = digitalRead(Button);          // Read button
  if (buttonState == HIGH) {                      // If button pressed then wait a bit to allow for debounce
    long Time = millis();
    while ((millis() - Time) < debounceDelay) {   
    }
    buttonState = digitalRead(Button);            // Read button again
    return buttonState;                           // Return button state
  }
  else {                                          //Button not pressed so no need to wait for bebounce
    return LOW;
  }
}

int readEncoder() {
  static int encoder0PinALast = LOW;
  int eDir = 0;
  int n = digitalRead(encoder0PinA);
  if ((encoder0PinALast == LOW) && (n == HIGH)) {
    if (digitalRead(encoder0PinB) == LOW) {
      eDir = -1;
      //Serial.print("-1");
    }
    else {
      eDir = 1;
      //Serial.print("1");
    }
  }
  encoder0PinALast = n;
  //Serial.println(eDir);
  return eDir;
}

Don't PM me for help as I will ignore it.

cattledog

Here is a example using polling with debounced digital read of the pins. It is designed to read every quadrature change. If your encoder has detents at every change, it will work. If the detents are not at every change, the code can be modified to increment/decrement only in the detent positions. The encoder you referenced has different options for the detents.

Code: [Select]
//Based on code from: http://bildr.org/2012/08/rotary-encoder-arduino/
//uses quadrature bit pattern from current and previous reading

//Changes
//Polled rather than interrupts
//Added start up position check to make index +1/-1 from first move
//Add bounce2 and debounce of digitalReads

#define encoderPinA  3  
#define encoderPinB  2
#define buttonPin 5

#include <Bounce2.h>
// Instantiate three Bounce objects for all pins with digitalRead
Bounce debouncerA = Bounce();
Bounce debouncerB = Bounce();
Bounce debouncer5 = Bounce();

int lastEncoded = 0;
int encoderValue = 0;
int lastencoderValue = 0;

void setup() {
  Serial.begin (115200);

  pinMode(encoderPinA, INPUT_PULLUP);
  pinMode(encoderPinB, INPUT_PULLUP);
  pinMode(buttonPin, INPUT_PULLUP);

  debouncerA.attach(encoderPinA);
  debouncerA.interval(5);
  debouncerB.attach(encoderPinB);
  debouncerB.interval(5);
  debouncer5.attach(buttonPin);
  debouncer5.interval(5);

  //get starting position
  debouncerA.update();
  debouncerB.update();

  int lastMSB = debouncerA.read();
  int lastLSB = debouncerB.read();

  Serial.print("Starting Position AB  " );
  Serial.print(lastMSB);
  Serial.println(lastLSB);

  //let start be lastEncoded so will index on first click
  lastEncoded = (lastMSB << 1) |lastLSB;

}

void loop(){

  debouncerA.update();
  debouncerB.update();

  int MSB = debouncerA.read();//MSB = most significant bit
  int LSB = debouncerB.read();//LSB = least significant bit

  int encoded = (MSB << 1) |LSB; //converting the 2 pin values to single number

  int sum  = (lastEncoded << 2) | encoded; //adding it to the previous encoded value

  //test against quadrature patterns CW and CCW
  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) encoderValue ++;
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) encoderValue --;

  lastEncoded = encoded; //store this value for next time

  if(encoderValue != lastencoderValue){
    Serial.print("Index:  ");
    Serial.print(encoderValue);
    Serial.print('\t');

    Serial.print("Old-New AB Pattern:  ");

    for (int i = 3; i >= 0; i-- )
    {
      Serial.print((sum >> i) & 0X01);//shift and select first bit
    }

    Serial.println();

    lastencoderValue=encoderValue;
  }

  //reset index
  debouncer5.update();
  if(debouncer5.read()==LOW){
    encoderValue=0;
  }

}


jurs

I just have the one, nothing else going on until a number is selected, after which the encoder is ignored.
Here is a little 'poor mans library for rotary encoders', which requires no library installation.
Supports: As many rotary encoders as you like (or let's say max. 6 without speed improvements)
Requires: Timer2 to generate 1000 timer interrupts per second
Inner working: Reads and evaluates the pin states during timer interrupts

Here is the example sketch and how to use:
Code: [Select]

// rotary encoder demo by 'jurs' for Arduino Forum
// This is the code for the main "sketch"

#include "encoder.h"
#define BAUDRATE 115200L // serial baud rate

void setup() {
  Serial.begin(BAUDRATE);
  Serial.println();
  Serial.println("Good night and good luck!"); // print some Test-Message at beginning
  beginEncoders();
}

void printEncoders()
{ // print current count of each encoder to Serial
  for (int i=0; i<NUMENCODERS; i++)
  {
    Serial.print(encoder[i].count);
    Serial.print('\t');
  }
  Serial.println();
}

void loop() {
  if (updateEncoders()) printEncoders();
}


The library is just a file for a new "Tab". Create a "Tab" named "encoder.h" and copy this code into the Tab:
Code: [Select]

// rotary encoder include file by 'jurs' for Arduino Forum
// This is the code for a new "Tab" within the sketch with Tab name "encoder.h"
#include <Arduino.h>
struct rotary_t {byte pinA; byte pinB; int count;};

rotary_t encoder[]={
 {8,9},
};  

#define NUMENCODERS (sizeof(encoder)/sizeof(encoder[0]))

volatile byte state_ISR[NUMENCODERS];
volatile int8_t count_ISR[NUMENCODERS];

void startTimer2()  // start TIMER2 interrupts
{
  noInterrupts();
  // Timer 2 CTC mode
  TCCR2B = (1<<WGM22) | (1<<CS22)  | (1<<CS20);
  TCCR2A = (1<<WGM21);
  OCR2A = 124;   // 249==500,  124==1000 interrupts per second
                 // 63 ==2000,  31==4000
                 // 15 ==8000,   7==16000
  TIMSK2 = (1<<OCIE2A); // enable Timer 2 interrupts
  interrupts();
}

void stopTimer2() // stop TIMER2 interrupts
{
  noInterrupts();
  TIMSK2 = 0;
  interrupts();
}

int8_t readEncoder(byte i)
{ // this function is called within timer interrupt to read one encoder!
  int8_t result=0;
  byte state=state_ISR[i];
  state= state<<2 | (byte)digitalRead(encoder[i].pinA)<<1 | (byte)digitalRead(encoder[i].pinB);
  state= state & 0xF;   // keep only the lower 4 bits
  /* // next two lines would be code to read 'quarter steps'
  if (state==0b0001 || state==0b0111 || state==0b1110 || state==0b1000) result= -1;
  else if (state==0b0010 || state==0b1011 || state==0b1101 || state==0b0100) result= 1;
  */
  // next two lines is code to read 'full steps'
  if (state==0b0001) result= -1;
  else if (state==0b0010) result= 1;
  state_ISR[i]= state;
  return result;
}

void beginEncoders()
{ // active internal pullup resistors on each encoder pin and start timer2
  for (int i=0; i<NUMENCODERS; i++)
  {
    pinMode(encoder[i].pinA, INPUT_PULLUP);
    pinMode(encoder[i].pinB, INPUT_PULLUP);
    readEncoder(i); // Initialize start condition
  }
  startTimer2();
}

boolean updateEncoders()
{ // read all the 'volatile' ISR variables and copy them into normal variables
  boolean changeState=false;
  for (int i=0; i<NUMENCODERS; i++)
  {
    if (count_ISR[i]!=0)
    {
      changeState=true;
      noInterrupts();
      encoder[i].count+= count_ISR[i];
      count_ISR[i]=0;
      interrupts();
    }
  }
  return changeState;
}


ISR(TIMER2_COMPA_vect)  // handling of TIMER2 interrupts
{
  for (int i=0; i<NUMENCODERS; i++)
  {
    count_ISR[i]+= readEncoder(i);
  }
}


This file is prepared to use one encoder on pin-8 and pin-9.
If you need more than one encoder or different pins, change the "encoder[]" array data accordingly.
Just connect the mechanical rotary encoder pins to the Arduino:
- no external pull resistors required
- no external filter circuit required
- internal Atmega pullups will be activated

The example sketch does just one simple thing: If any rotation is detected, print count values of all defined encoders into one line of the serial monitor.

The following functions and variables in "encoder.h" are intended to be used in sketches:
Code: [Select]
 beginEncoders(); ==> Usually used in the setup() function to enable pins and timer
 updateEncoders(); ==> Call regularly from the loop function to update count
 encoder[i].count  ==> after updateEncoders(); this int will contain the current count value

The 'updateEncoders();' function returns a 'false' if no change on any of the encoders had occurred, or it returns 'true' if any count value on any encoder has been changed since the function was last called.

The pushbutton available on some mechanical encoders is not supported by this simple library and needs to be connected and handled as a normal pushbutton.

If you want to deactivate the rotary encoder, just make a call to "stopTimer2();"

CrossRoads

jurs,
That works great with my encoders.
How do I use it to count up from 00 to FF then wrap back to 00, and similarly count from 00 and wrap up to 255?
Right now I just have the code dumbly counting 0 to 9 and back to 0 to show the hardware is working, while the encoder counts merrily up & down on the serial monitor.
I tried changing the encoder.h code to have the count wrap there, but not successfully.
Then it dawned on me that I really wanted the +1, -1 returned back to the program so the program could do something with it, and I can't figure out how to get that #.
Thanks
Robert

Code: [Select]

// added rotary encoder demo by 'jurs' for Arduino Forum
// This is the code for the main "sketch"

#include "encoder.h" // unchanged except for swapping 8, 9 to be B, A

#include <SPI.h> // (will be used with SD card eventually)

// test of pins - press start to light LEDs, press select to turn off LEDs
byte redLed = 14;
byte greenLed = 15;
byte yellowLed = 16;
byte selectPin = 3;
byte startPin = 2;

byte shiftRCK = 19;
byte shiftSCK = 18;
byte shiftSerial = 17;

byte fontArray [] = {
  0b00111111, // 0
  0b00000110, // 1
  0b01011011, // 2
  0b01001111, // 3
  0b01100110, // 4
  0b01101101, // 5
  0b01111101, // 6
  0b00000111, // 7
  0b01111111, // 8
  0b01101111, // 9
  0b00000000, // blank
};
byte digit1 = 0;
byte digit2 = 1;

void setup(){
  pinMode (shiftSCK, OUTPUT);
  pinMode (shiftRCK, OUTPUT);
  pinMode (shiftSerial, OUTPUT);
  pinMode (redLed, OUTPUT);
  pinMode (greenLed, OUTPUT);
  pinMode (yellowLed, OUTPUT);
  pinMode (selectPin, INPUT_PULLUP);
  pinMode (startPin, INPUT_PULLUP);
  Serial.begin(115200);

  SPI.begin();
  beginEncoders();
}

void printEncoders()
{ // print current count of each encoder to Serial
  for (int i=0; i<NUMENCODERS; i++)
  {
    Serial.print(encoder[i].count);
    Serial.print('\t');
  }
  Serial.println();
}

void loop(){
  if (digitalRead(startPin) == LOW){
    digitalWrite (greenLed, HIGH);
    digitalWrite (redLed, HIGH);
    digitalWrite (yellowLed, HIGH);
  }
  if (digitalRead (selectPin) == LOW){
    digitalWrite (greenLed, LOW);
    digitalWrite (redLed, LOW);
    digitalWrite (yellowLed, LOW);
  }


  digit1 = digit1 + 1;
  if (digit1 == 10){
    digit1 = 0;
  }
  digit2 = digit2 + 1;
  if (digit2 == 10){
    digit2 = 0;
  }
  digitalWrite (shiftRCK, LOW);
  shiftOut(shiftSerial, shiftSCK, MSBFIRST, fontArray[digit2]); 
  shiftOut(shiftSerial, shiftSCK, MSBFIRST, fontArray[digit1]);
  digitalWrite (shiftRCK, HIGH);

delay(300);

if (updateEncoders()) printEncoders();
}

Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

jurs

#13
Apr 25, 2015, 12:30 pm Last Edit: Apr 25, 2015, 12:38 pm by jurs
How do I use it to count up from 00 to FF then wrap back to 00, and similarly count from 00 and wrap up to 255?
If you just want an 8-bit counter that is wrapping over at 255/0 instead of the default 16-bit int counter, you simply can use the lower byte of the counter:
Code: [Select]

void loop() {
  if (updateEncoders())
  {
    byte myCount=encoder[0].count; // use only lower byte of counter
    char buf[21];
    snprintf(buf,sizeof(buf),"0x%02X %3d",myCount,myCount);
    Serial.println(buf); // Same byte formatted in hex and decimal
  }
}

CrossRoads

jurs,
How do I get just the +1, -1 back? That's what I want, so can I update two 0 to F counters and send the updated count to two shift registers.
Code: [Select]

// added rotary encoder demo by 'jurs' for Arduino Forum
// This is the code for the main "sketch"

#include "encoder.h"

#include <SPI.h>

// test of pins - press start to light LEDs, press select to turn off LEDs
byte redLed = 14;
byte greenLed = 15;
byte yellowLed = 16;
byte selectPin = 3;
byte startPin = 2;

byte shiftRCK = 19;
byte shiftSCK = 18;
byte shiftSerial = 17;

byte fontArray [] = {
  0b00111111, // 0
  0b00000110, // 1
  0b01011011, // 2
  0b01001111, // 3
  0b01100110, // 4
  0b01101101, // 5
  0b01111101, // 6
  0b00000111, // 7
  0b01111111, // 8
  0b01101111, // 9
  0b01110111, // A
  0b01111100, // b
  0b00111001, // C
  0b01011110, // d
  0b01111001, // E
  0b01110001, // F
  0b00000000, // blank
};
byte lowerDigit = 0;
byte upperDigit = 0;

void setup(){
  pinMode (shiftSCK, OUTPUT);
  pinMode (shiftRCK, OUTPUT);
  pinMode (shiftSerial, OUTPUT);
  pinMode (redLed, OUTPUT);
  pinMode (greenLed, OUTPUT);
  pinMode (yellowLed, OUTPUT);
  pinMode (selectPin, INPUT_PULLUP);
  pinMode (startPin, INPUT_PULLUP);
  Serial.begin(115200);

  SPI.begin();
  beginEncoders();
}

void printEncoders()
{ // print current count of each encoder to Serial
  for (int i=0; i<NUMENCODERS; i++)
  {
    Serial.print(encoder[i].count);
    Serial.print('\t');
  }
  Serial.println();
}
void loop(){
  if (digitalRead(startPin) == LOW){
    digitalWrite (greenLed, HIGH);
    digitalWrite (redLed, HIGH);
    digitalWrite (yellowLed, HIGH);
  }
  if (digitalRead (selectPin) == LOW){
    digitalWrite (greenLed, LOW);
    digitalWrite (redLed, LOW);
    digitalWrite (yellowLed, LOW);
  }

  if (updateEncoders()){

    // +1 returned
    if (+1 returned){     // << how to get that?
      lowerDigit = lowerDigit + 1;
      if (lowerDigit == 0x10){
        lowerDigit = 0;
        upperDigit = upperDigit +1;
        if (upperDigit == 0x10){
          upperDigit = 0;
        }
      }
    }
    // -1 returned
    if (-1 returned){     // << how get that?
      if ((lowerDigit == 0)&& (upperDigit == 0)){  // rollover upper, lower from 00 to FF
        lowerDigit = 0x0F;
        upperDigit = 0x0F;
      }
      if ( (lowerDigit == 0) && (upperDigit >0)){ // rollover lower from 0 to F and decrement upper by 1
        lowerDigit = 0x0F;
        upperDigit = upperDigit - 1;
      }
      if (lowerDigit >0){                       // decrement lower
        lowerDigit = lowerDigit -1;
      }
    }
 // now update the display
    digitalWrite (shiftRCK, LOW);
    shiftOut(shiftSerial, shiftSCK, MSBFIRST, fontArray[upperDigit]); 
    shiftOut(shiftSerial, shiftSCK, MSBFIRST, fontArray[lowerDigit]);
    digitalWrite (shiftRCK, HIGH);

  }
}


Thanks
Robert
Designing & building electrical circuits for over 25 years.  Screw Shield for Mega/Due/Uno,  Bobuino with ATMega1284P, & other '328P & '1284P creations & offerings at  my website.

Go Up