1

I'm using this incremental rotary encoder with a rotating disc.

Here is my system:

enter image description here

I will explain the system briefly and above is the illustration for that:

The rotary encoder(blue) is attached/fixed to a larger disc(yellow) concentrically at its origin, so both the encoder and the disc rotate together the same angle. The disc is rotating on a fixed plane(gray). The plane has a fixed point mark(red X) on its surface. There is also a fixed red arrow on the disc.

Each time before the system is powered, I will manually rotate the disc such that its red arrow will be aligned to the red X mark on the surface. This adjustment each time at the beginning will provide the reference point.

After this adjustment I want to output the actual angles via Arduino.

I tried the following example code from Arduino website:

#define encoder0PinA  2
#define encoder0PinB  4

volatile unsigned int encoder0Pos = 0;

void setup() { 


  pinMode(encoder0PinA, INPUT); 
  digitalWrite(encoder0PinA, HIGH);       // turn on pull-up resistor
  pinMode(encoder0PinB, INPUT); 
  digitalWrite(encoder0PinB, HIGH);       // turn on pull-up 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 pinA and pinB are both high or both low, it is spinning
   * forward. If they're different, it's going backward.
   *
   * For more information on speeding up this process, see
   * [Reference/PortManipulation], specifically the PIND register.
   */
  if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
    encoder0Pos++;
  } else {
    encoder0Pos--;
  }

  Serial.println (encoder0Pos, DEC);
}

/* See this expanded function to get a better understanding of the
 * meanings of the four possible (pinA, pinB) value pairs:
 */
void doEncoder_Expanded(){
  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
}

But this code increases one count for each encoder tick at CW direction, and decreases one count for each tick at CCW direction. It keeps increasing from zero to 65535 in CW and decrease from 65535 to 0 in CCW. I want to translate this code such that I can output the angle in degrees.

My encoder says its ppr is 15 and 30 ticks. This code increase/decrease one count for each tick.

Any idea how to extract the angle from this setup? (It doesn't have to use the specific code I provided.)

5
  • 2
    if it's 30ppr then every count is 12 degrees. So multiply the number by 12. The do a modulo 360 (` x = x % 360`), so you only get values from 0 to 360. But not that you can only know the angle difference from when the sketch first started. So you have to set the disk to the zero position before turning on the Arduino.
    – Gerben
    Jan 20, 2017 at 15:23
  • 1
    Serial.println() can cause problems when used within an ISR. Set a flag instead, and print from loop() Jan 20, 2017 at 17:29
  • @jwpat7 How can I implement that setting flag and outputting from void? I also experience two outputs at some single encoder ticks. Can that be because of bouncing?
    – floppy380
    Jan 21, 2017 at 17:07
  • Declare a flag, eg volatile byte cchange;, and set it true in the ISR. In loop(), say if (cchange) { cchange=false; printstuff(); } ¶ Re double counts, the ISR shown is slow, which can mess up counting. If your ISR were to catch all the edges and treat them correctly, eg via a state machine like in my answer to question 32572, bouncing would cancel out. Jan 21, 2017 at 20:38
  • How could this code be modified to use an indexed encoder to reset the counter to 0 every time it hit the index. I would like to use this to track a rotating mast for direction finding.
    – Bkukuk62
    Sep 24, 2018 at 17:34

2 Answers 2

1

Gerben's comment is mostly right. The shortest way to adapt the behavior of that sketch to your setup is by multiplying the result by 24 (not 12, since it is 15 pulses - 30 dents, but each channel varies 15 times) and then making the modulo:

if (digitalRead(encoder0PinA) == digitalRead(encoder0PinB)) {
    encoder0Pos += 24;
} else {
    encoder0Pos -= 24;
}
encoder0Pos = encoder0Pos % 360;

BUT... the code you posted has some problems.

  1. "The joy of interrupts is that they take care of themselves" is true, but relying on interrupts always has some drawbacks you MUST take into account. Mainly you are blocking the rest of the program (screwing up the timings for instance). I prefer to use the interrupts only when strictly needed (and this is not the case)
  2. You are performing a potentially long function (serial interaction) into an interrupt. Totally wrong
  3. You are using HALF the steps you can potentially use (since you are checking only one input, you are ignoring half the transitions).

To fix these, I propose you to rewrite the program to check in the loop the buttons status. Then, since they are mechanical switches, I'm going to use the Bounce2 library to debounce the inputs.

This, alongside with solving the previous 3 points, will also allow you to use any arbitrary pin on the board.

#include <Bounce2.h>

#define encoder0PinA  2
#define encoder0PinB  4
#define STEP_SIZE 12

byte encoder0Pos = 0;

Bounce debouncerA = Bounce();
Bounce debouncerB = Bounce();

void setup()
{
    debouncerA.attach(encoder0PinA, INPUT_PULLUP);
    debouncerA.interval(5); // interval in ms
    debouncerB.attach(encoder0PinB, INPUT_PULLUP);
    debouncerB.interval(5); // interval in ms

    Serial.begin (9600);
    Serial.println("start");                // a personal quirk

} 

void loop()
{
    // Read the status of the inputs
    debouncerA.update();
    debouncerB.update();

    int8_t EncVariation = 0;

    if (debouncerA.rose())
    { // if input A changed from low to high, it was CW if B is high too
        EncVariation = (debouncerB.read()) ? 1 : -1;
    }
    else if (debouncerA.fell())
    { // if input A changed from high to low, it was CW if B is low too
        EncVariation = (debouncerB.read()) ? -1 : 1;
    }
    else if (debouncerB.rose())
    { // if input B changed from low to high, it was CCW if A is high too
        EncVariation = (debouncerA.read()) ? -1 : 1;
    }
    else if (debouncerB.fell())
    { // if input B changed from high to low, it was CCW if B is low too
        EncVariation = (debouncerA.read()) ? 1 : -1;
    }

    if (EncVariation != 0)
    {
        encoder0Pos = (encoder0Pos + EncVariation * STEP_SIZE) % 360;

        Serial.println (encoder0Pos, DEC);
    }
}

Disclaimer: I haven't tested this, but I'm pretty confident this will work.

I highly suggest you to use the Bounce2 library, since it also has many helper functions which shorten the code a lot. See here for instructions on how to get and use it. I'm just a happy user of this library, and I'm not linked in any way to the author ;)

3
  • This code works better I dont see erratic outputs. Is it also possible to output the direction CCW or CW. I mean this code will work but I also want an LED to turn on when the encoder rotates CCW. How can it be done? Thanks in advance
    – floppy380
    Jan 26, 2017 at 14:46
  • oh i actually noticed EncVariation indicates the direction:)
    – floppy380
    Jan 26, 2017 at 15:18
  • @doncarlos yes, just check it and it tells you whether it is turning (!= 0) and the direction. By the way, if the answer fully answers your question please mark it as accepted; otherwise just ask ;)
    – frarugi87
    Jan 26, 2017 at 17:29
1

As noted in a comment, treating the numbers modulo 360° will put results into the range you want.

Note, however, that you can (and should) avoid use of long arithmetic by taking the pulse count modulo 30 before multiplying by 12°.

To compensate for the reference point adjustment, have the program set your tick count to correspond to 0° when the arrows align. If you set the base count to some value that's a multiple of 30, say 120, and store it in an unsigned byte, the code would look like so:

angle = 12*(count%30);

Since the base count 120 is a multiple of 30, it falls out automatically when the modulus is taken. To make this work in the long run, you would set the encoder count to 120 whenever the arrows align (or to (current%30)+120 if required to adjust it at other times).

If your base count were 0 instead of 120 and the count an unsigned integer instead of a byte, when the rotor goes CCW the modulus counts would have 15 extra counts. For example, a count of 0 correctly converts to 0°, but a count of 65535 is 15 modulo 30, so converts to 180° instead of 348°.

Note, there is no need to debounce rotary encoder inputs if a proper state machine is used; and debouncing slows processing to the point that ticks will be lost. See my answer to question 32572 which includes code for reading several encoders active at once.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.