KY-040 Rotary Encoders

rotary

A rotary encoder is a set of two switches arrayed on disk with graduated holes or spaces so that when the disk turns one of the pair is triggered before the other. We then check which switch triggers first in order to work out the count and direction of the turn.

Here is a very clear account of both the operation and logic of rotary encoders and the particular model we currently include in our kits for those after some greater detail.

 

rotaryenc

In the example that follows we will use our OLED display to count and track the turns on our rotary encoder and use the encoder push switch to reset the value displayed.

This will provide a useful basis for including the rotary encoder in most projects including those based on the Polygon Door Artbot kits.

The KY-040 rotary encoder comes mounted on a shield with all the appropriate electronics required to read the output (includes two pull up resistors). The shield has 5 pins; Ground, Voltage, SW (Switch), DT(Pin1), CLK (Pin2) –  (I haven’t been able to find a definitive account of what DT and CLK stand for – but I am guessing CLK stand for Clockwise – it does seem clear that they carry signal (high or low) for each with as described in the diagram above.

This post shows a very interesting reading of the pins as the turn. You can see the logic of the assemblage analysed as the dial is turned.

The DT, CLK and SW pins get connected to three digital pins (in the code below I’ve used pins 2,3 & 4) on the Arduino and of course the GND and + pins are connected to Ground and 5V pins.

Thats all the hardware we need. The code is fairly straight forward although it does introduce the structure and potential for ‘interrupts’ (see below)- and the code below assumes we are using an 64*128 OLED running via the adafruit libraries.

The below diagram shows how the CLK pin and the DT pin connect to any of the digital pins.

Screen Shot 2016-06-21 at 8.10.37 pm

The below code uses an ‘Interrupt’ in order to monitor the CLK pin for a ‘Falling’ state (when the pin moves from High to Low).  An interrupt is a function of the hardware (the arduino) and allows us to use a section of code that is triggered when a particular condition is met . That condition is determined by the ‘mode’ of the interrupt (we will use ‘Falling’ as described above – a pin has changed state from High to Low). There is more information about interrupts and how to use them herald there is a very good explanation of when they should (and shouldn’t) be used here.

A word of warning: Interrupts depend on the particular hardware being prepared to listen on particular pins! (Something we learnt the hard way). The Arduino mega has Interrupt pins on:

The arduino mea we are using in our kits has interrupt pins available at digital pins:2, 3, 18, 19, 20, 21

Only the CLK pin need be connected to an Interrupt pin if we are using this method:

#include <Wire.h> // These libraries are all here to drive the OLED screen. 
#include <Adafruit_GFX.h> // No library is required to run the KY-040 rotary ecoder shield 
#include <SPI.h>
#include <Adafruit_SSD1306.h>

#define OLED_RESET 4 

Adafruit_SSD1306 display(OLED_RESET); 


volatile boolean TurnDetected; // should be self explanatory - was a turn detected
volatile boolean up; // was the turn up or down

//volatile is used to define an integer that should not be optimised on compiling because it might be set or changed elsewhere (in ISR) in this case.

const int PinCLK=2; // Used for generating interrupts using CLK signal
const int PinDT=3; // Used for reading DT signal
const int PinSW=4; // Used for the push button switch

void isr () { // Interrupt service routine is executed when a HIGH to LOW transition is detected on CLK
 if (digitalRead(PinCLK)) // why is this ever true in ISR?? 
 up = digitalRead(PinDT);
 else
 up = !digitalRead(PinDT);
 TurnDetected = true;
}


void setup () {
 pinMode(PinCLK,INPUT); // set all our pins to input.
 pinMode(PinDT,INPUT); 
 pinMode(PinSW,INPUT);
 attachInterrupt (digitalPinToInterrupt(PinCLK),isr,FALLING); // interrupt 0 is always connected to pin 2 on Arduino UNO
 //Serial.begin (9600);
 display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // used to setup display
 display.clearDisplay();
 display.setTextColor(WHITE);
 display.println("Start");
 
}

void loop () {
 
 display.clearDisplay();
 display.setCursor(0,0);
 
 static long virtualPosition=0; // using static to stop it initialising again when decalriotn is in the loop.

 if (!(digitalRead(PinSW))) 
 { // check if pushbutton is pressed
 virtualPosition=0; // if YES, then reset counter to ZERO
 display.clearDisplay();
 display.setCursor(0,0);
 display.print ("Reset"); // Using the word RESET instead of COUNT here to find out a buggy encoder
 } 
 
 if (TurnDetected) { // do this only if rotation was detected
 if (up)
 virtualPosition--;
 else
 virtualPosition++;
 TurnDetected = false; // do NOT repeat IF loop until new rotation detected 
 }
 display.print ("Count = "); 
 display.println (virtualPosition);
 display.display();
}

You can also avoid using the interrupt option and the accompanying limits on pins using the method below. This works for the Artbot project as long as we remove the input stage from the output stage – once Artbot starts drawing we don’t want continually reads on Digital pins to slow down our code.

#include <Adafruit_GFX.h> // these things at the top just load a bunch of libraries we need to run the screen.
#include <SPI.h> // libraries are collections of code that have been written by others so we don't
#include <Wire.h> // don't have to.
#include <Adafruit_SSD1306.h> //we need these for libraries to make our screen easier to use.
#define OLED_RESET 4 // this code resets the display.
Adafruit_SSD1306 display(OLED_RESET); //

#include <AccelStepper.h>
#include <AFMotor.h>

// two stepper motors one on each port
AF_Stepper motor1(2048, 1);
AF_Stepper motor2(2048, 2);

// you can change these to DOUBLE or INTERLEAVE or MICROSTEP!
// wrappers for the first motor!
void forwardstep1() {
 motor1.onestep(FORWARD, SINGLE);
}
void backwardstep1() {
 motor1.onestep(BACKWARD, SINGLE);
}
// wrappers for the second motor!
void forwardstep2() {
 motor2.onestep(FORWARD, SINGLE);
}
void backwardstep2() {
 motor2.onestep(BACKWARD, SINGLE);
}

// Motor shield has two motor ports, now we'll wrap them in an AccelStepper object
AccelStepper stepper1(forwardstep1, backwardstep1);
AccelStepper stepper2(forwardstep2, backwardstep2);

float maxSpeedLeft = 400;
float accelerationLeft = 150;
float moveToLeft = 65;

float maxSpeedRight = 400;
float accelerationRight = 100;
float moveToRight = 1000000;

int pinA = 22; // connected to CLK
int pinB = 24; // connected to DT
int encoderPosCount = 0;
int pinALast;
int aVal;
boolean bCW;

void setup()
{
 pinMode(pinA, INPUT);
 pinMode(pinB, INPUT);
 pinALast = digitalRead(pinA);
 // dispay config
 display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // its the 0x3C that says "128 * 64"

 display.clearDisplay();
 display.setTextColor(WHITE);
 display.setTextSize(3);
 display.setCursor(10, 0); // this moves our cursor right back to the top left pixel.. we should talk about this.
 display.print("ARTBOT"); //this copies some text to the screens memory
 display.setTextSize(2);
 display.setCursor(20, 40);
 display.print("Hello :)"); //this copies some text to the screens memory
 display.display();

 display.setTextSize(3);
 
 // Motor configuration
 stepper1.setMaxSpeed(maxSpeedLeft);
 stepper1.setAcceleration(accelerationLeft);
 stepper1.moveTo(moveToLeft);

 stepper2.setMaxSpeed(maxSpeedRight);
 stepper2.setAcceleration(accelerationRight);
 stepper2.moveTo(moveToRight);

 // wait to see the above message
 delay(2000);
}

void loop()
{
 aVal = digitalRead(pinA);
 if (aVal != pinALast) {
 if (digitalRead(pinB) != aVal) {
 encoderPosCount++;
 bCW = true;
 report();
 } else {
 bCW = false;
 encoderPosCount--;
 report();
 }
 }
 
 if (stepper1.distanceToGo() == 0) {
 stepper1.moveTo(-stepper1.currentPosition());
 }
 // if (stepper2.distanceToGo() == 0) { stepper2.moveTo(-stepper2.currentPosition()); }

 //stepper1.run();
 //stepper2.run();
}

void report() {
 display.clearDisplay();
 display.setCursor(20, 40);
 display.print(encoderPosCount); //this copies some text to the screens memory
 display.display();
}