Arduino – Rotary encoder (Keyes KY-040)
Hardware
1 2 3 4 5 6 7 |
Position Bit1 Bit2 - - - - - - - - - - - Step1 0 0 1/4 1 0 1/2 1 1 3/4 0 1 Step2 0 0 |
Rotating the encoder one step takes the signal a full period, starting at A and B both in the HIGH state, both changing to LOW, and both changing back to HIGH again. If you apply the logic on both transitions, you will do a double +1 for every clock-wise transition:
1. A and B are both HIGH (initial state)
2. A changes to LOW, B is still HIGH, so +1 because A changed and B != A
3. B changes to LOW, no effect
4. A and B are now both LOW
5. A changes back to HIGH, B is still LOW, so +1 because A changed and B != A
6. B changes to HIGH, no effect
7. A and B are both HIGH (final state)
For every counter-clock-wise transition, you will instead do -1 twice:
1. A and B are both HIGH (initial state)
2. B changes to LOW, no effect
3. A changes to LOW, B is already LOW, so -1 because A changed and B == A
4. A and B are now both LOW
6. B changes to HIGH, no effect
5. A changes back to HIGH, B is already HIGH, so -1 because A changed and B == A
7. A and B are both HIGH (final state)
Pinout (Keyes KY-040 module)
Aansluiten
Sluit de module aan volgens onderstaand schema:
Rotary Encoder pin: | Arduino Pin: |
---|---|
1 (GND) | GND |
2 (+) | +5V |
4 (DT) | 3 |
5 (CLK) | 2 |
Polling of Interrupts?
Je kan de Rotary Encoder op 2 manieren uitlezen: polling of met interrupts.
Polling
Polling doe je door de stand of beweging van de encoder uit te lezen in de LOOP, het nadeel hiervan is dat je tijdens de loop niet veel kan doen omdat dat de telling verstoord en de pulsen niet goed geteld worden, met polling is het wellicht wel mogelijk om meerdere Enscoders aan te sluiten op een Arduino UNO.
Interrupts
Met interrupts kun je een vallende of stijgende puls waarnemen en een subroutine laten uitvoeren die dan de pulsen telt, op deze manier is het tellen van de pulsen veel nauwkeuriger, het nadeel is dat je 2 interrupts nodig hebt per encoder en dus maar 1 kan aansluiten op een Arduino UNO (3 op de MEGA).
Ps. Bij de Keyes module is 1 “klik/stap” 4 pulsen/tellingen.
Script #1 Polling
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
// Rotary Encoder Inputs #define CLK 2 #define DT 3 #define SW 4 int counter = 0; int currentStateCLK; int lastStateCLK; String currentDir =""; unsigned long lastButtonPress = 0; void setup() { // Set encoder pins as inputs pinMode(CLK,INPUT); pinMode(DT,INPUT); pinMode(SW, INPUT_PULLUP); // Setup Serial Monitor Serial.begin(9600); // Read the initial state of CLK lastStateCLK = digitalRead(CLK); } void loop() { // Read the current state of CLK currentStateCLK = digitalRead(CLK); // If last and current state of CLK are different, then pulse occurred // React to only 1 state change to avoid double count if (currentStateCLK != lastStateCLK && currentStateCLK == 1){ // If the DT state is different than the CLK state then // the encoder is rotating CCW so decrement if (digitalRead(DT) != currentStateCLK) { counter --; currentDir ="CCW"; } else { // Encoder is rotating CW so increment counter ++; currentDir ="CW"; } Serial.print("Direction: "); Serial.print(currentDir); Serial.print(" | Counter: "); Serial.println(counter); } // Remember last CLK state lastStateCLK = currentStateCLK; // Read the button state int btnState = digitalRead(SW); //If we detect LOW signal, button is pressed if (btnState == LOW) { //if 50ms have passed since last LOW pulse, it means that the //button has been pressed, released and pressed again if (millis() - lastButtonPress > 50) { Serial.println("Button pressed!"); } // Remember last button press event lastButtonPress = millis(); } // Put in a slight delay to help debounce the reading delay(1); } |
Script #2 “INTERRUPT”
Onderstaand script leest de encoder uit via een interrupt subroutine en geeft de waarde weer via de console:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
/* roto_jw4.ino -- JW, 29 September 2015 -- * A 4-state state-machine implementation of rotary * encoding for KY-040 rotary knobs. The state-machine picture at * https://e2e.ti.com/support/microcontrollers/hercules/f/312/t/318762 * in a Feb 4, 2014 7:40 PM post by Anthony Seely shows counts * increasing on transitions 10 -> 11 -> 01 -> 00 -> 10 and * decreasing on transitions the other way. Transitions between 00 * and 11 or 10 and 01 are invalid. This code detects valid * transitions by (abOld xor abNew) equaling 1 or 2. It detects * up-count events by the tri-bit value ABA' (where A' is the new * reading on pin A) being equal to 1, 2, 5, or 6 (a bit mask of * 0x66), and down-count events by ABA' being equal to 0, 3, 4, or 7 * (a bit mask of 0x99). * * On a KY-040 unit I tested, there are 30 detent positions per turn. * With this unit the code generates 60 counts per turn, which can be * seen individually as one turns the rotor slowly. Odd counts * appear between detents, even counts at detents. * * Set quadrature-signal pin numbers, via PinA and PinB constants. * Set IPINMODE to INPUT_PULLUP if there are no external pull-ups * on encoder AB pins, else set IPINMODE to INPUT */ enum { PinA=2, PinB=3, IPINMODE=INPUT }; static byte abOld; // Initialize state volatile int count; // current rotary count int old_count; // old rotary count void setup() { pinMode(PinA, IPINMODE); pinMode(PinB, IPINMODE); attachInterrupt(0, pinChangeISR, CHANGE); // Set up pin-change interrupts attachInterrupt(1, pinChangeISR, CHANGE); abOld = count = old_count = 0; Serial.begin(9600); Serial.println("Starting Rotary Encoder Test"); } // On interrupt, read input pins, compute new state, and adjust count void pinChangeISR() { enum { upMask = 0x66, downMask = 0x99 }; byte abNew = (digitalRead(PinA) << 1) | digitalRead(PinB); byte criterion = abNew^abOld; if (criterion==1 || criterion==2) { if (upMask & (1 << (2*abOld + abNew/2))) count--; else count++; // upMask = ~downMask } abOld = abNew; // Save new state } void loop() { if (old_count != count) { Serial.println(count); old_count = count; } } |
Output
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Starting Rotary Encoder Test 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 14 13 12 |
Script #3 “POLLING” met bibliotheek
Wat heb je nodig?
1) Arduino Rotary encoder bibliotheek
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <Encoder.h> // Change these two numbers to the pins connected to your encoder. // Best Performance: both pins have interrupt capability // Good Performance: only the first pin has interrupt capability // Low Performance: neither pin has interrupt capability Encoder myEnc(2, 3); // avoid using pins with LEDs attached void setup() { Serial.begin(9600); Serial.println("Basic Encoder Test:"); } long oldPosition = -999; void loop() { long newPosition = myEnc.read(); if (newPosition != oldPosition) { oldPosition = newPosition; Serial.println(newPosition); } } |
Output
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
Basic Encoder Test: 0 1 2 3 4 5 6 7 8 9 10 11 10 11 12 13 14 13 14 15 14 |
Script #4 “POLLING”
Onderstaand vind je een eenvoudig script zonder bibliotheken (bron g7nbp.blogspot.nl)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
int clock = 2; // Define encoder pin A int data = 3; // Define encoder pin B int count = 0; // pre-init the count to zero int c = LOW; // pre-init the state of pin A low int cLast = LOW; // and make its last val the same - ie no change int d = LOW; // and make the data val low as well void setup() { pinMode (clock,INPUT); // setup the pins as inputs pinMode (data,INPUT); Serial.begin (9600); // and give some serial debugging } void loop() { c = digitalRead(clock); // read pin A as clock d = digitalRead(data); // read pin B as data if (c != cLast) { // clock pin has changed value... now we can do stuff d = c^d; // work out direction using an XOR if ( d ) { count--; // non-zero is Anti-clockwise }else{ count++; // zero is therefore anti-clockwise } Serial.print ("Jog:: count:"); Serial.println(count); cLast = c; // store current clock state for next pass } } |
Output
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Jog:: count:-1 Jog:: count:0 Jog:: count:1 Jog:: count:2 Jog:: count:1 Jog:: count:2 Jog:: count:3 Jog:: count:4 Jog:: count:5 Jog:: count:4 Jog:: count:5 Jog:: count:4 Jog:: count:3 Jog:: count:2 Jog:: count:1 Jog:: count:0 Jog:: count:-1 Jog:: count:-2 Jog:: count:-3 |
Bronnen:
arduino.stackexchange.com
heliosoph.mit-links.info
henrysbench.capnfatz.com
martoparts.nl
playground.arduino.cc