Rotary encoders are great input devices for electronics projects - hopefully this Instructable will inspire and help you use one in your next project.

Why write rotary encoder code?

I wanted to use a low cost rotary encoder as an input mechanism for one of my upcoming projects and was initially bewildered by the code options available to take readings from the rotary encoder and determine how many "detents" or cycles the encoder had clicked past and in what direction. I think my main sketch will need to use most of my Arduino's memory so I am avoiding the various available encoder libraries, which seemed to be difficult to make work when I tried a couple of them. They also appear to use far more of the code budget than the sketch-based code approaches discussed from here on.

If you just want to bypass the thinking behind my approach and get straight into the Instructable, feel free to skip ahead to Step 1!

Other Approaches

Several of the main sketch-based (i.e. they don't use a library) approaches are discussed in rt's blog post where they write rotary encoder code that makes the cheapest encoders usable as Arduino inputs. They also have a good example of they logic signal that the encoder produces. rt found that a timer interrupt system worked best for them but I'm concerned that the polling frequency would detract from screen update speed in the main loop of my project sketch. Given that the rotary encoder will be moving for a tiny proportion of the time I want the screen to be updating, this seems a poor match for my application.

I chose to start off using Steve Spence's code here, which was fine on it's own but appeared to really slow down when I incorporated the rest of my sketch code (which involves writing display updates to a small TFT screen). Initially I wondered if it could be because the main loop contains a debounce statement.

I then read Oleg's rotary encoder article on an interrupt service routine version of his previous post, I also thought it might be a good idea to use direct port manipulation to read both pins simultaneously and as soon as the interrupt fires. His code can be used on any input pin if the port manipulation code is rewritten. In contrast, I decided to use only the hardware interrupts on digital pins 2 and 3, so we can set interrupts to only fire on a rising edge of the pin voltage, rather than on pin voltage change, which includes falling edges. This reduces the number of times the ISR is called, distracting from the main loop.

Oleg's code uses a lookup table to reduce compiled code size to a really small size but I couldn't get reliable results which would catch very slow rotation as well as reasonably fast rotation. Bear in mind that hardware debouncing (see Step 2) can help a lot with reliability of readings but I was after a software solution to simplify the hardware build and be as portable to other hardware applications as possible.

This concludes the introduction of my challenge and considerations. In Step 2 we'll take a look at the encoder hardware, terminology and some practical considerations when you want to integrate a rotary encoder into your project.

Step 1: A Bit About Rotary Encoders

Why are rotary encoders so cool?

  1. Unlike a variable resistor/potentiometer they have infinite travel in any direction and because they produce a digital "Gray code" you can scale their readings to whatever range you like.
  2. The dual direction makes them useful for increasing or decreasing a value within a variable or navigating menus.
  3. Finally, many of these rotary encoders come with a centre push button, which can be used to select menu items, reset a counter or do anything you can think of that might suit a momentary push button.


  1. PPR: pulses per rotation - typically 12, 20 or 24. You might also see specifications for maximum rotation in rpm etc. This is probably determined by the encoder's propensity to "bounce" contacts - see below.
  2. Detent: the little click of the action as it springs to a natural rest point between pulses. There may be one detent per pulse/cycle (not equal to a rotation of the shaft) or two.
  3. Bounce: mechanical contacts inside the encoder literally bounce enough to jump off and back on a contact when rotating, potentially leading to too many readings attributed to that phase of the travel between detents.
  4. Debounce: This can be either done in hardware, perhaps with a low value ceramic capacitor between each pin and Ground, or in software, perhaps with a delay. In either case, the aim is to create a system which ignores bouncing contacts.


  1. Look out for a threaded section near the base of the shaft and a matching nut if you want to mount your encoder in a panel or enclosure.
  2. Many knobs are available for rotary encoders, with the most easily available coming in 6mm diameter shafts.
  3. Pay attention to whether your encoder shaft uses a flat face or splines to achieve a proper fit with the knob.
  4. The body of the rotary encoder may also come with a raised pin/stub, intended to mate with a small indent/hole in your panel (probably hidden by your knob) and prevent your encoder from rotating when you turn the knob. You might find you want to remove this if you can create enough friction to prevent encoder body rotation using the mounting bolt to screw the encoder in the panel or enclosure.
  5. Make sure you find out where the detent state is for your encoder and adapt your code accordingly. My example uses an encoder whose pins are both disconnected from ground and are pulled high by their respective input pullup resistors. This drives my selection of a RISING interrupt. If both pins were connected to ground when at detent, they would need code which was looking for FALLING pin voltage.

Step 2: The Circuit

The circuit is so simple. You will need:

• An ATMEGA328P based Arduino, such as the Uno, Pro Mini or Nano.
• A mechanical (as opposed to optical) quadrature rotary encoder - this is the most common kind so don't worry too much if it isn't specified. eBay and Aliexpress listings will often mention Arduino in the description and this is a good indicator that one is suitable.
• Hook-up wire/jumper leads.
• Optional: a prototyping breadboard.

First of all, look for a collection of three pins on one side of the encoder. These are the three for measuring rotation with our code. If there are two pins together on another side, these are likely to be for the centre push button. We'll ignore these for now.

Out of the three pins together, the encoder ground pin is connected to Arduino ground pin. Either of the other two pins is connected to digital pin 2 and the remaining on is connected to digital pin 3. If your direction of rotation isn't the way you'd like, just swap the two non-ground pins over.

Pins 2 and 3 are important because on the ATMEGA328P-based Arduinos they are the only pins which have the ability to detect RISING and FALLING pin change interrupts. The MEGA 2560 boards etc. have other hardware interrupt pins which can do this.

Note: In the diagram the ground pin is one of the end pins. In reality, the ground pin is often the centre pin but this is not always the case so read the datasheet or test your encoder to find out which pin is ground.

Another note: ArneTR made a good comment about not having a separately wired connection to the logic positive voltage (e.g. 5V or 3.3V) for the rotary encoder circuit shown. The Arduino can't read the rotary encoder without both a ground signal (which we have connected a wire to) and the logic voltage (sometimes annotated as Vcc or Vdd), so how can the Arduino read the logic from this encoder without a positive voltage wire? The answer is that the ATMEGA328P chip in the Arduino has a special mode you can set on the digital pins (which we are using) where a pin is automatically pulled "high" to the logic voltage by an internal resistor. Look in the code for "pinMode(pinX, INPUT_PULLUP)" to see us telling the Arduino that we want to take advantage of this mode. Once set, we only need to provide the encoder with a ground wire as the sensing wires from the digital pins are already providing the logic voltage.

ONE MORE THING... Githyuk found that a particular branded encoder didn't work with this way of doing things (ie the code below). Please see the comments section for details but in general, trying a different encoder would be a good debugging step when you have exhausted the easier/faster/cheaper steps.

Step 3: The Code

If you are not familiar with programming Arduinos, please get up to speed with this resource from Arduino themselves.

This code is free for your use (as in no cost and to be modified as you please), please attribute where you should.

/*******Interrupt-based Rotary Encoder Sketch*******
by Simon Merrett, based on insight from Oleg Mazurov, Nick Gammon, rt, Steve Spence

static int pinA = 2; // Our first hardware interrupt pin is digital pin 2
static int pinB = 3; // Our second hardware interrupt pin is digital pin 3
volatile byte aFlag = 0; // let's us know when we're expecting a rising edge on pinA to signal that the encoder has arrived at a detent
volatile byte bFlag = 0; // let's us know when we're expecting a rising edge on pinB to signal that the encoder has arrived at a detent (opposite direction to when aFlag is set)
volatile byte encoderPos = 0; //this variable stores our current value of encoder position. Change to int or uin16_t instead of byte if you want to record a larger range than 0-255
volatile byte oldEncPos = 0; //stores the last encoder position value so we can compare to the current reading and see if it has changed (so we know when to print to the serial monitor)
volatile byte reading = 0; //somewhere to store the direct values we read from our interrupt pins before checking to see if we have moved a whole detent

void setup() {
pinMode(pinA, INPUT_PULLUP); // set pinA as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
pinMode(pinB, INPUT_PULLUP); // set pinB as an input, pulled HIGH to the logic voltage (5V or 3.3V for most cases)
attachInterrupt(0,PinA,RISING); // set an interrupt on PinA, looking for a rising edge signal and executing the "PinA" Interrupt Service Routine (below)
attachInterrupt(1,PinB,RISING); // set an interrupt on PinB, looking for a rising edge signal and executing the "PinB" Interrupt Service Routine (below)
Serial.begin(115200); // start the serial monitor link

void PinA(){
cli(); //stop interrupts happening before we read pin values
reading = PIND & 0xC; // read all eight pin values then strip away all but pinA and pinB's values
if(reading == B00001100 && aFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
encoderPos --; //decrement the encoder's position count
bFlag = 0; //reset flags for the next turn
aFlag = 0; //reset flags for the next turn
else if (reading == B00000100) bFlag = 1; //signal that we're expecting pinB to signal the transition to detent from free rotation
sei(); //restart interrupts

void PinB(){
cli(); //stop interrupts happening before we read pin values
reading = PIND & 0xC; //read all eight pin values then strip away all but pinA and pinB's values
if (reading == B00001100 && bFlag) { //check that we have both pins at detent (HIGH) and that we are expecting detent on this pin's rising edge
encoderPos ++; //increment the encoder's position count
bFlag = 0; //reset flags for the next turn
aFlag = 0; //reset flags for the next turn
else if (reading == B00001000) aFlag = 1; //signal that we're expecting pinA to signal the transition to detent from free rotation
sei(); //restart interrupts

void loop(){
if(oldEncPos != encoderPos) {
oldEncPos = encoderPos;

That's it!

Step 4: Conclusion

I hope you find this code useful for your next project which uses a rotary encoder or that it has inspired you to consider a rotary encoder as an input for your next project.

Summary of the Aims

I have tried to write some code which achieves a good balance of:

  • Portability (port manipulation code is the compromise when moving to other chips)
  • Speed (port manipulation really helps)
  • Low compiled code size (port manipulation and bitmath helps)
  • Reliably records slow and fast manual rotation
  • Reduced nugatory interrupt service routine calls (using RISING interrupt and temporarily disabling interrupts)

Caveats and Ideas for Improvement

This code isn't perfect by any means and you might like to change it to use other pins. I tested this code with the sketch which was causing the most delay and least reliable readings with the other approaches discussed - I certainly haven't compared it with timers to see whose code produces fewer nugatory interrupt service routines, takes the least time to execute or filters out the highest percentage of contact bounces. Perhaps someone might like to do a benchmark test against the other approaches out there.

<p>Hi Simon,</p><p>Thank you for your code, it works better than many others. Some others also work with attachInterrupt's but use other inputs. I've tried this code with other inputs, but that did not work. Question is, can this code be used for two rotary encoder's? I am using a Arduino UNO with ATMEGA328PU. Do you have an idea or example. Thanks in advance.</p><p>RonVZ</p>
<p>Hi Simon,</p><p>This has been useful to me and I hate to nitpick, <br>but on entering an ISR function interrupts are already off, so there is <br>no need to turn them off, and turning them on at exit from your <br>function, though really not doing much harm, will lead to unnecessary <br>stack usage. And to be really pedantic you shouldn't turn them on, but <br>restore the original SREG state. But thank you all the same, it has been<br> interesting. The encoders I am using are so crappy that unfortunately <br>polling is a much better answer, but it has been an interesting journey.</p><p>Adam</p>
Thanks for the simplifications. This is where a github repository would come in handy! Anyone with an account care to commit this code? <br><br>Sorry to hear about your encoders. I think the ones I tested with were no-name eBay specials so that's surprising. No joy if you change the debounce time?
<p>At risk of being boring... I set up a sketch to log the events of CLK or DT going either high or low, and the log shows the time stamp of the event, the direction of the change, the state of the line that didn't change, and the microsecond time difference since the last event. What I found were many identical events which were high to low or low to high which directly followed each other, so the matching event had been missed even though the ISRs are short, the time difference being 8, 12 or 16 us (the granularity of the Uno being 4us). I ran it again on a Due and got the same result with smaller time differences, so even though I have capacitors in the circuit there are spikes lasting for very small times. What you end up with is odd and unpredictable interrupts taking unpredictable (and possibly excessive) amounts of time from the other processing, and you can't even work out what the signals mean. My encoders by the way are no-name eBay specials.</p><p>I'd post the logs and the sketches, but at the moment I haven't worked out how to do that nicely (a monospace font to make them readable).</p><p>My eventual solution was to assume that bouncing and spiking only happen when an electrical connection is (sort of) being made and the only thing you could trust was a high (detent on my hardware) signal, so polling and assuming that any noise in the polling debounce period indicates a signal has produced the best results for me. Very good actually, usable in fact which is not what I thought when I looked at the logs the first time.</p><p>Normally I am a big fan of interrupts, and deride polling, I wanted to believe in the interrupt driven solution, but I am a realist too, so for this application, polling it is.</p>
<p>Excellent work Simon - thanks very much for sharing. Adding to Githyuk's observations, I have also found that this works well with a two detents per pulse encoder. I previously tried a KY-040 'module' with a 20 pulse/20 detent device, and performance was too jittery for a menu system. This is a bit of a shame as the 30/15 devices are relatively expensive (~&pound;6 or more), especially if you want a switch and a threaded body. Now if we could just figure out how to get the cheapo ones to work... :-)</p>
That's a shame - I thought my encoder I used to test the system was a cheap one even though it was advertised as something better.
<p>Hello Simon, </p><p><br>For the life of me, I cannot seem to get a reading of B00000100 and therefore no flag that will increment in the CW direction. The CCW direction works awesome though, better than any code I've found so far. FWIW, I'm using the Bourns PEC 16-4215F-S0024 (24 detents, 24ppr). </p><p> <a href="http://www.digikey.ca/product-detail/en/bourns-inc/PEC16-4215F-N0024/PEC16-4215F-N0024-ND/3534275" rel="nofollow">http://www.digikey.ca/product-detail/en/bourns-inc...</a></p><p><a href="https://www.bourns.com/pdfs/PEC16.pdf" rel="nofollow">https://www.bourns.com/pdfs/PEC16.pdf</a></p><p>Great tutorial so far, just banging my head trying to understand why my Rotary Encoder is not outputting the 0100 to raise the b flag.</p>
Hi Githyuk, sorry it's not working yet. Can you confirm that this behaviour is the same at low rotation speed? This would help rule out contact bounce. You could add some hardware debouncing to be sure. <br>From not knowing much about your setup (although encoder datasheet link was a good idea), I think there might be a situation where the other pin is always high when the other one triggers the interrupt. E.g, if you read B00001000, then you can get B00001100 but never B00000100 or B00000000. If this is the case, it could be a short circuit on the wiring to the pin, pulling it HIGH, or it could perhaps be that one of the Arduino pins is connected to the encoder ground pin. The data sheet didn't seem to be clear about which pins are A, B and ground (although it would be a good first assumption that a = A, b =B and c = ground. Perhaps check the wiring for pull-up short circuits and the try different combinations of encoder wiring. Hope that helps - post pictures if you have no joy.
Ya, it doesn't matter how slow I go. I've tried it on two other encoders of the same model and I get similar results. Other encoder software does work somewhat in a CW direction, but not as smooth as yours in the CCW direction. May I ask what model of rotary encoder you used so I can look up it's spec sheet to compare quadrature output, or are all quad output encoders the same? Also, I have 1uf capacitors to contain debounce on both pins A and B. I did run a multimeter across the pins and have infinite resistance between all pins at detente. Thanks for your super quick response btw!
Hmm, I used an Alps EC11B0924802&nbsp;but I think the logic is all the same apart from whether the pins are grounded or not at detent. You might want to look at whether your reset is around B00001100 or B00000000. Other than that, I wonder if the capacitor is preventing the pin from reaching a 0 in sufficient time but I can't think what situation this could realistically occur in. Try without caps perhaps? Does it work in the same direction if you swap the arduino pins over?
<p>I did try reversing the pins and it counted up but not down.</p><p>Hmmm, your decoder has 2 detents/pulse whereas mine had 1d/p....wonder if that has something to do with it. Will try some diff decoders and get back to you. Thanks for the help thus far. Really want to use your code as I found it the smoothest by far.</p>
<p>Okay, my rotary encoders came in the mail today. Happy to report your code works flawlessly with the two detents per pulse over the one with the single detent per pulse. Maybe there is some weird timing issue going on, dunno. I sweated days over trying to figure out what went wrong. Letting you know in case you want to look at it with a different rotary encoder, or possibly just flag your instructees to the potential issue (not absolutely sure it is the detent per pulse thing, but ...). You can check it out for yourself I suppose if you haven't done so already. The new one I used today was ACZ09BR1E-20FA1-30C15P:<br><br><a href="http://www.digikey.com/product-detail/en/cui-inc/ACZ09BR1E-20FA1-30C15P/102-2229-ND/2510104" rel="nofollow">http://www.digikey.com/product-detail/en/cui-inc/A...</a><br><br><a href="http://www.cui.com/product/resource/digikeypdf/acz09.pdf" rel="nofollow">http://www.cui.com/product/resource/digikeypdf/acz...</a></p><p><br>Thanks again for your great code and public contribution. Cheers.<br></p>
Githyuk, thanks so much for the update - that's the kind of thing that will really help someone else who may be losing patience. I have edited the Instructable in Step 2 and signposted them to the details in your comments. So glad it's working well for you now!
<p>Thanks. I wouldn't say it so much a branded thing, than it is a detent thing...I'd be interested to know if any others used a one detent per pulse encoder as well. I am now using the PEC11R-4215F-S0012-ND which is the same company that made the first one I tried, however, this one has two detents per pulse and works beautifully. If anyone is reading this comment and has tried a one detent per pulse rotary encoder successfully, please let me know.</p>
Sorry, what I meant by branded was that it was a known part and therefore results should be repeatable (versus an unbranded eBay special). You're right to isolate the issue to the detent per pulse (although I'm not clear if it's 2 pulses per detent on the kind of encoder which works or 2 detents per pulse). Please show us the final results of your project.
<p>Hi Simon,</p><p>Thanks for what I hope will be a great start to a project where I need to input a number of variables for an extreme macro, stepper driven linear actuator.</p><p>Unfortunately, I a complete novice when it comes to coding and I am struggling to see how to display, on LCD, the text for the 'main menu' options. I can see that encoderPos give the numerical value for the Mode, but how do I translate this to a textual description? e.g. I need to display encoderPos = 2 as &quot;Configure Stepper&quot; so that as user I can see exactly what I am about to change. </p><p>Any insight would be much appreciated - my elderly brain not what it used to be!!</p><p>Charles</p>
Hi Charles, are you thinking of using the menu I describe in my other Instructable? I'm not sure what LCD library you plan to use but in the sketch you can try placing something along the lines of (depending on the code you use to get the lcd to display characters):<br>lcd.write(&quot;Mode selected: &quot;);<br> instead of the Serial.print() equivalent and<br>lcd.writeln(&quot;Configure stepper&quot;);<br> instead of the Serial.println(Mode); line to show what setting you are affecting in the menu. Hope that helps.
<p>Hi Simon, thanks for the swift reply. I am in fact using your other sketch with the button press / menu.</p><p>I mis-communicate perhaps. When I run the sketch and rotate the encoder I get the feedback on the serial monitor 0, 1, 2 or 3 as expected. What I need however is meaningful text descriptors on LCD (as opposed to 0,1,2,3) of what each mode represents i.e. Configure Camera, Set IN Point, Set OUT Point, Configure Stepper etc etc so that the user knows what each button press leads to.</p><p>Can you assist here?</p><p>Best </p><p>Charles</p>
In this section... <br>if(oldEncPos != encoderPos) { // DEBUGGING Serial.println(encoderPos);// DEBUGGING. Sometimes the serial monitor may show a value just outside modeMax due to this function. The menu shouldn't be affected. <br>oldEncPos = encoderPos;// DEBUGGING <br>}// DEBUGGING<br><br>Replace:<br>Serial.println(encoderPos);<br>with something like a series of if statements like... <br>if(encoderPos = 0) lcd.write(&quot;Configure Camera&quot;) ;<br>else if(encoderPos = 1) lcd.write(&quot;Set IN Point&quot;) ;<br>else if(encoderPos = 2) lcd.write(&quot;Set OUT Point&quot;) ;<br>else if(encoderPos = 3) lcd.write(&quot;Configure Stepper&quot;) ;<br><br>Let us know how you get on.
<p>Thanks Simon, I'd worked out that was the place to add my code - in the interim found what I assume is another of your projects &quot;Interactive Menus for your project with a Display and Encoder&quot; - works a treat. Between the two sketches I'm exercising the elderly grey matter!!</p><p>Many thanks again for your valuable work.</p><p>Charles</p>
Charles, I'm so glad to hear you are making headway. The other Instructable you mentioned isn't mine but that's the beauty of the community.
<p>Thanks Simon. Worked on the first try. I've added the sketch to my personal library of stuff that works that I piece together sketches from.</p>
<p>Also thought I'd have to use an RC circuit, but i don't as the switch I have (looks identical to yours from AliExpress) works perfectly with the code you have written</p>
Thank you for taking the time to share your findings with us all - especially the confirmation that you did not need hardware debouncing.
<p>Hi - I'm getting all kinds of errors trying to get this compiled. Any input? </p><p>'cli' was not declared in this scope<br>'PIND' was not declared in this scope<br>'sei' was not declared in this scope<br></p>
Which board and IDE you using?
I'm on Arduino 1.6.11 using a Due over the programming port. Thanks!
If you look back at the decisions I made, one of the trade-offs was to use port manipulation to achieve speed. This unfortunately means that you would need to rewrite the code to make it work on boards that don't use the ATMEGA328P chip. Some people have done this for the ATMEGA2560 in these comments but that was the same &quot;8 bit AVR&quot; style as the ATMEGA328P so didn't require massive code changes. The Due is a &quot;32 bit ARM&quot; style of microcontroller and you'd need to look carefully into whether it can do something that is akin to port manipulation. The good news is that the Arduino community has probably worked this out already so you are only a few searches away from finding out how feasible editing the code is in this way. The other bonus of the Due is that it processes information much faster than the ATMEGA328P so you can probably just read each encoder pin in sequence without needing port manipulation in the first place. You'd have to work out how to compare the readings to determine whether you have passed a detent and in which direction but that's exactly what had to research when I decided to make this code. cli and sei are AVR instructions to turn interrupts off and on. I'm sure you can find a way of doing this with your Due. For example, the arduino functions nointerrupts() and interrupts() will probably do the trick and may have been a better way to write my own code but I wasn't sure if these functions would result in a larger sketch or slower execution when compiled so I went for the AVR commands to play it safe. Good luck and if you make a Due version of the code, please publish it for the benefit of others!
<p>I'm a Arduino newby, I have a few projects that I have built using basic compilers and Pics, and have begun to play with arduinos... I have a project in mind that would use a handwheel like this: </p><p><a href="http://www.mpja.com/Handwheel-Digital-Encoder-100PPR-5VDC/productinfo/33106+MI/" rel="nofollow">http://www.mpja.com/Handwheel-Digital-Encoder-100P...</a></p><p>Do you think this is compatible?</p><p>One of my other hoobbies is Ham radio. I want to build a remote antenna tuner that involves turning capacitors through a range of 180 degress and a coil that requires 25 turns to move through it's range. </p><p>I have a shield that holds four stepper drivers that I plan to use to turn the parts</p>
[Usual disclaimer about responsibility for your own purchasing decisions] Yes, looks like it will be compatible with the Arduino and the code! I like the sound of your project - would you mind putting a link or photo in the comments when you're done? <br>180&deg; sounds like a good match with servos - probably cheaper and simpler than stepper motors, depending on the torque required. <br>Your 25 turn coil would probably need steppers or a motor with feedback (another rotary encoder!).<br>Good luck!
<p>Hi Simon, thanks for your great instructable. Is there a way to limit the reading from 0-255. As I would like to have the minimum 0 and maximum 100.</p><p>Kind regards</p>
Hi Rainbowfish, the first thing I would try is putting an &quot;if&quot; statement after the ecnoderPos-- or encoderPos++ lines inside each interrupt service routine to check if the new value of encoderPos has exceeded your limits and to return it to the range you want to use. <br>For example, after encoderPos--, the next line of code could be:<br><br>if(encoderPos &gt;200) {<br>encoderPos =100;<br>} <br><br>This would deal with the case where the counter has to go below 0 stored in a byte (maximum of 256 values, including 0 so would roll over from 0 to 255 if you subtract 1).<br><br>Then, for after the encoderPos++ line of code in the other interrupt service routine, to deal with values going higher than 100:<br><br>if(encoderPos &gt;100) {<br>encoderPos =0;<br>} <br><br>I hope that works for you!
<p>Thanks Simon, that works great :) </p>
That's great to hear!
<p>overly complicated...</p>
I'd love to hear how best to simplify it - simpler might make it even lighter and more effective.
The way you do that is you store the last state read from the encoder and compare the current state to the last state.<br> <br> For example: if there is a change from high,high to low,high,&nbsp; it means you're going in one direction and you can add 1 to your counter or whatever you're using it for.<br> If you go high,high, to low,low , it means you're going the other direction, and you can remove 1 from your counter.<br> <br> bit of code:<br> <br> upState = digitalRead(up);<br> &nbsp; downState = digitalRead(down);<br> &nbsp; if(lastDownState == 1 &amp;&amp; lastUpState == 1){&nbsp;&nbsp;<br> &nbsp;&nbsp;&nbsp; if(upState == 0 &amp;&amp; downState == 0){&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Detects Counter Clock Wise rotation&nbsp;<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; timer--;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // removes 1 from the existing timer count<br> &nbsp;&nbsp;&nbsp; }}<br> &nbsp;&nbsp;&nbsp;<br> &nbsp; if(lastDownState == 0 &amp;&amp; lastUpState == 0){&nbsp;&nbsp;<br> &nbsp;&nbsp;&nbsp; if(upState == 1 &amp;&amp; downState == 1 ){&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Detects Clock Wise rotation<br> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; timer++;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // adds 1 to the existing timer count<br> &nbsp;&nbsp;&nbsp; }}<br> <br> <br> upState and downState are my outputs from my encoder, and my encoder track goes like this:<br> _ :low&nbsp;&nbsp;&nbsp;&nbsp; -&nbsp; :high<br> track1:&nbsp; ______----___<br> track2:&nbsp; ___---------___<br> <br> <br> I'm using the same code (adapted a bit of course) on a 2500 step encoder, and it works a charm, even at (fairly) high speeds.<br> <br> -Didgitalpunk
<p>@Didgitalpunk, Could you put together the entire code using your solution? I'm trying to learn both ways and would be interesting in seeing it. Of course, if this is ok with SimonM83.</p><p>Thanks</p>
Absolutely fine with me!
Thanks - sorry for delay, been away. Would you put that code in your main loop? My main loop takes a while to complete so that would start missing transitions.
<p>Yesterday started playing with a rotary encoder, and it looked like another nightmare infront of me with all the different codes and wirings being suggested. Came across this code, wired it without any resistors or capacitors. Works like a charm, building my project up from this. Thanks!</p>
Great! Glad it helped.
<p>Only like to add one thing. Added a 220Ohm resistor to each of the three signal pins that lead to Arduino, still works like a charm. For Arduino longevity. Reading your instructable on Menus next. </p>
<p>As you mentioned, in my version ( I am rt from Practical Usage) of the code, I use a timer interrupt every millisecond (programmable). My interrupt routine executes in 12 micro-seconds so there is plenty of time for the Arduino to do lots of other things. I never had a problem running out of CPU power while using the rotary encoder, a display and lots of pins (50 on a Mega). Just food for thought...</p><p>Great instructable, by the way.</p>
<p>Hi rt, thank you for your blog post! Glad you like this Instructable.</p><p>You're probably right about the timer approach. I was intrigued by the external interrupt capabilities. This world of encoding I have stepped into is fascinating. I'm just looking into absolute positioning in hardware!</p>
<p>This instructable is really great, Love it!</p><p>Especially your thoughts about the rotary encoder problematic in the beginning really deepen the knowledge here.</p><p>However, I think your wiring diagram is really confusing. It only makes clear how to connect the sensory inputs. But in your sketch you don't show the connection with VDD. I suggest you add the connection with 3.3V or 5.5V as it might not get clear to beginners that the rotary encoder needs a driving current to work. Also in my setup I had to use resistors on the inputs. This could be added as well.</p>
<p>Just updated with some info in the notes on Step 2 which should help people understand the deliberate omission of separate logic voltage wiring. Thanks for the advice!</p>
<p>Hi ArneTR, I'm glad you like it! </p><p>The wiring diagram doesn't need a separate connection to VDD thanks to the use of digital Arduino pins in the INPUT_PULLUP mode, where they are pulled up to logic level inside the ATMEGA328P chip on the Arduino. However, I should explain that and will do an edit. Thanks for the tip!</p><p>In your setup, did you use the code in this Instructable? Interesting that you needed input resistors. Would you mind explaining a bit more or including a photo of a wiring diagram sketch?</p>
I've just published an Instructable which harnesses rotary encoders to navigate Arduino menus. I hope it's useful: <br><br>https://www.instructables.com/id/Easy-Arduino-Menus-for-Rotary-Encoders
<p>Now with improved code readability thanks to Pro membership and Russ' Instructable here: <a href="https://www.instructables.com/id/Put-Your-Code-in-a-Box-Properly-Indented-on-Instru/" style="">https://www.instructables.com/id/Put-Your-Code-in-a-Box-Properly-Indented-on-Instru/</a></p>

About This Instructable




More by SimonM83:Easy Arduino Menus for Rotary Encoders  Improved Arduino Rotary Encoder Reading 
Add instructable to: