2

I've been trying to program my ATtiny817-XPRO to interpret input data from a rotary encoder (the Arduino module), however I'm having some trouble and can't seem to figure out what the problem is. What I'm trying to do is essentially program a digital combination lock that blinks a red LED every time the rotary encoder is rotated one "tick" (in either direction), and blinks a green LED once the right "combination" has been detected. It's a little bit more involved than that, so when I ran into trouble upon testing my code, I decided to write a simple method to help me troubleshoot/debug the problem. I've included it below:

void testRotaryInput(){
    if(!(PORTC.IN & 0b00000001)){   // if rotary encoder is turned clockwise
        PORTB.OUT = 0b00000010;     // turn on green LED
    }
    else if(!(PORTC.IN & 0b00000010)){  // if rotary encoder is turned CCW
        PORTB.OUT = 0b00000001;         // turn on blue LED
    }
    else{                           // if rotary encoder remains stationary
        PORTB.OUT = 0b00000100;     // turn on red LED
    }
    RTC.CNT = 0;
    while(RTC.CNT<16384){}          // wait 500ms
    PORTB.OUT = 0x00;               // turn LED off
    while(RTC.CNT<32768){}          // wait another 500ms
}

int main(void)
{
    PORTB.DIR = 0xFF;               // PORT B = output
    PORTC.DIR = 0x00;               // PORT C = input
    RTC.CTRLA = RTC_RTCEN_bm;       // Enable RTC
    PORTB.OUT = 0x00;               // Ensure all LEDs start turned off
                                    // ^(not necessary but I do it just in case)^

    //testLED(); <-- previous test I used to make sure each LED works upon start-up

    while(1)
    {
        testRotaryInput();
    }
}

The idea here is that whichever output line arrives at the AVR first should indicate which direction the rotary encoder was rotated, as this dictates the phase shift between the two signals. Depending on the direction of rotation (or lackthereof), a red/green/blue LED will blink once for 500ms, and then the program will wait another 500ms before listening to the rotary encoder output again. However, when I run this code, the LED will either continuously blink red for awhile or green for awhile, eventually switching from one color to the other with the occasional (single) blue blink. This seems completely random each time, and it seems to completely ignore any rotation I apply to the rotary encoder.

Things I've done to troubleshoot:

  • Hooked up both outputs of the rotary encoder to an oscilloscope to see if there's any output (everything looked as it should)

  • Used an external power supply to power the rotary encoder, as I was only reading 1.6V from the 5.0V VCC pin on my ATtiny817-XPRO when it was connected to that (I suspect this was because the LEDs and rotary encoder probably draw too much current)

  • Measured the voltage from said power supply to ensure that the rotary encoder was receiving 5.0V (I measured approx. 4.97V)

  • Checked to make sure that the circuitry is correct and working as it should

Unfortunately, none of these things eliminated the problem at hand. Thus, I suspect that my code may be the culprit, as this is my first attempt at using a rotary encoder, let alone interpreting the data generated by one. However, if my code looks like it should work just fine, I'd appreciate any heads-up on this so that I can focus my efforts elsewhere.

Could anyone shed light on what may be causing this issue? I don't think it's a faulty board because I was using these pins two nights ago for a different application without any problems. Also, I'm still somewhat of a newbie when it comes to AVRs, so I'm sure my code is far from being as robust as it could be.

Thanks!

Jacob M
  • 125
  • 2
  • 9
  • 1
    can you exclude that it is because the input changes too quickly, as in this related question: https://stackoverflow.com/q/34735/2513200? – Hulk Jun 27 '19 at 05:07
  • 1
    You could try sampling the relevant bits more quickly (every few milliseconds) and calculating and average/sum over several steps and react to that as a first debugging step. – Hulk Jun 27 '19 at 05:15
  • @Hulk Yes I think I can exclude that as a potential cause. I've tried single rotations/ticks at a time, bursts of slowly rotating it, as well as bursts of quickly rotating it and nothing has seemed to work. The two signals appear about 900us to 1500us out of phase on the scope, and with the RTC functioning at 32.768kHz (period = 30.52us) that should be more than enough time for the AVR to differentiate which signal arrives first. As per your second comment, would that approach have anything to do with the debouncing routine discussed in the post you linked? – Jacob M Jun 27 '19 at 05:34
  • 1
    kind of - it would be a naive approach to get rid of the effects of potential spurious signals. It could also be interesting to see if multiple bits can be set at the same time (i.e. it could be that green is overrepresented because you test for it first, and don't check that *only* that bit is set). If things change when you swap your tests, that would be an indication for that. – Hulk Jun 27 '19 at 05:54
  • @Hulk I think you're right about the green being overrepresented because of testing it first. I swapped the green/blue tests and sure enough the blue light started blinking instead of the green. Interestingly, though, I never see an occasional green blink when blue is blinking, just as I had initially seen occasional blue blinks whenever green was blinking. Moreover, it seems like those occasional blue blinks I initially saw have stopped happening as well. Not sure what could be causing this change in behavior, as I haven't changed the circuit since posting about this... – Jacob M Jun 27 '19 at 06:23

3 Answers3

3

Encoders can behave in various strange ways. You'll have signal bounces like with any switch. You'll have cases where several inputs may be active at once during a turn. Etc. Therefore you need to sample them periodically.

Create a timer which polls the encoder every 5ms or so. Always store the previous read. Don't accept any change to the input as valid until it has been stable for two consecutive reads. This is the simplest form of a digital filter.

Lundin
  • 155,020
  • 33
  • 213
  • 341
  • Would these signal bounces be visible on an oscilloscope? I didn’t seem to observe any such behavior on mine, but then again I’m using an Analog Discovery 2 which may not have a high enough resolution to capture such detail. Nevertheless, this is awesome advice and I will definitely give it a shot, as I’ve read that it’s generally of good practice to do this when working with mechanical encoders. I’ll give it a try tomorrow after work and post my results. I might also include an oscilloscope screenshot of the rotary encoder output, in the (very possible) event that I’m missing something. – Jacob M Jun 27 '19 at 07:12
  • @JacobM Yeah they would, if you go down to ms resolution or faster - so you don't need an amazing scope. If the encoder isn't pure mechanical but with built-in electronics, I wouldn't expect any bounces though. Keep in mind that you'll have to measure multiple channels. – Lundin Jun 27 '19 at 09:14
1

Which input is shorted does not show you what direction the encoder was turned. But the order in which they were shorted does.

Normally, rotary encoders have two outputs which are shorted to the ground pin: first shorted, then second shorted, then first released, then second released - this full sequence happened between each click. (Of course there are encoders which have additional "click" in the middle of the sequence, or have no clicks at all, but most of them do like described above).

enter image description here

So, generally speaking, each "CLICK!" movement you may consider as 4 phases:

  • 0. Both inputs are released (high) - default position
  • 1. Input A is shorted to ground (low), input B is released (high)
  • 2. Both inputs are shorted (low)
  • 3. Input A is released (high), B is shorted (low).

Rotation in one direction is a passage thru phases 0-1-2-3-0. Another direction is 0-3-2-1-0. So, whatever direction the encoder is rotated, both inputs will be shorted to ground at some particular moment.

As you can see from the picture above, usually the bouncing happens only at one of inputs. So, you may consider the bouncing as jumping between two adjacent phases, what makes the debounce much simpler.

Since those phases are changes very fast you have to pool input pins very fast, may be 1000 times per second, to handle fast rotations.

Code to handle the rotation may be as follows:

signed int encoder_phase = 0;

void pull_encoder() {
    int phase = ((PORTC.IN & (1 << INPUT_A_PINNO)) ? 0 : 1)
                ^ ((PORTC.IN & (1 << INPUT_B_PINNO)) ? 0 : 0b11);
    // the value of phase variable is 0 1 2 or 3, as described above
    int phase_shifted = (phase - encoder_phase) & 3;
    if (phase_shifted == 2) { // jumped instantly over two phases - error
        encoder_phase = 0;
    } else if (phase_shifted == 1) { // rotating forward
        encoder_phase++;
        if (encoder_phase >= 4) { // Full cycle
            encoder_phase = 0;
            handle_clockwise_rotation();
        }
    } else if (phase_shifted == 3) { // rotating backward; 
        encoder_phase--;
        if (encoder_phase <= -4) { // Full cycle backward
            encoder_phase = 0;
            handle_counterclockwise_rotation();
        }
    }
    if (phase == 0) {
        encoder_phase = 0; // reset 
    }
}
AterLux
  • 3,708
  • 2
  • 8
  • 12
  • Wouldn’t my code already check which input was shorted first, though? My thought process when programming it was that the AVR would listen to both inputs, and once *one* of them was pulled low (either phase 1 or phase 3 from your answer) the program would immediately consider this to be the first input to be shorted & jump into the appropriate while() loop. This is why I don’t even bother to have my code look at the second input once it detects one of them being shorted, because to me, all the information I’d need to know the order of the signals is which one shorts first/which loop executes. – Jacob M Jun 27 '19 at 17:06
  • ^I’m sure the logic from my previous comment is wrong somewhere, so if anyone could help me understand why it’s wrong I’d really appreciate it. – Jacob M Jun 27 '19 at 17:09
  • No, your code does not check which input was shorted FIRST. Your code just waits 1 second and then lights up a LED depending on what input is shorted. I.e. it will be "red" when the encoder is in fixed position, and whatever other color (depend on which input was shorted in that exact moment) when you're rotating, 2/3 of cases it will be "green" (because this case is checked first, so includes the case when both inputs are shorted) and 1/3 it will be "red" depend on luck. – AterLux Jun 28 '19 at 10:20
  • Thank you for this and thank your patience. I think I see where I went wrong now – Jacob M Jun 28 '19 at 14:35
1

As others have noted, mechanical encoders are subject to bouncing. You need to handle that.

The most simple way to read such an encoder would be to interpret one of the outputs (e.g. A) as a 'clock' signal and the other one (e.g. B) as the direction indicator.

You wait for a falling (or rising) edge of the 'clock' output, and when one is detected, immediately read the state of the other output to determine the direction.

After that, include some 'dead time' during which you ignore any other edges of the 'clock' signal which occur due to the bouncing of the contacts.

Algorithm:

0) read state of 'clock' signal (A) and store ("previous clock state")

1) read state of 'clock' signal (A) and compare with "previous clock state"

2) if clock signal did not change e.g. from high to low (if you want to use the falling edge), goto 1).

3) read state of 'direction' signal (B), store current state of clock to "previous clock state"

4) now you know that a 'tick' occurred (clock signal change) and the direction, handle it

5) disable reading the 'clock' signal (A) for some time, e.g. 10ms, to debounce; after the dead time period has elapsed, goto 1)

This approach is not time critical. As long as you make sure you poll the 'clock' at least twice as fast as the shortest time between the change of signal A and the corresponding change of signal B (minus bouncing time of A) you expect to see (depends on maximum expected rotation speed) it will work absolutely reliably.

The edge detection of the 'clock' signal can also be performed by a pin change interrupt which you just disable for the dead time period after the interrupt occurred. Handling bouncing contacts via a pin change interrupt is however generally not recommended because the bouncing (i.e. (very) fast toggling of a pin, can be pulses of nanoseconds duration) may confuse the hardware.

JimmyB
  • 11,178
  • 1
  • 23
  • 42