Debouncing a rotary encoder with gray code

Hi,

I need to debounce a rotary encoder with gray code. When I was polling the encoder everything was working fine. Now I need to implement a more complex code and was advised to use interrupts. Debouncing an encoder with interrupts doesn’t seem so easy.

I have an encoder with a switch, so my pins are

SW at Pin 2
CLK at Pin 3
DT at Pin 4

I am using the ProMini which only has two interrupt pins (2 and 3). I figured I needed to use one interrupt pin for the switch, so for the encoder itself I have only one interrupt pin. Debouncing the switch was fairly easy. I just put a delay of 10ms and read the value again to make sure, that I am not reading a bounce. For the turning mechanism of the encoder it doesn’t work like this. The problem is that I don’t have no background knowledge of digital systems.

Can anyone provide an easy to understand implementation that I can use?

Cheers

It is not easy to debounce interrupts and no solution will fit all (or even most) cases. If polling worked so well before, why go to the trouble?

gunjah292:
Hi,

I need to debounce a rotary encoder with gray code. When I was polling the encoder everything was working fine. Now I need to implement a more complex code and was advised to use interrupts. Debouncing an encoder with interrupts doesn’t seem so easy.

I have an encoder with a switch, so my pins are

SW at Pin 2
CLK at Pin 3
DT at Pin 4

I am using the ProMini which only has two interrupt pins (2 and 3). I figured I needed to use one interrupt pin for the switch, so for the encoder itself I have only one interrupt pin. Debouncing the switch was fairly easy. I just put a delay of 10ms and read the value again to make sure, that I am not reading a bounce. For the turning mechanism of the encoder it doesn’t work like this. The problem is that I don’t have no background knowledge of digital systems.

Can anyone provide an easy to understand implementation that I can use?

Cheers

When I needed to read rotary encoders I looked at the output on an oscilloscope and discovered that the output was very clean so didn’t need debouncing, and that I needed to sample it at least once every millisecond. From my experience, if you wait 10ms you won’t read the output at all. Here is my code, which is written in XC8 (PIC). Sorry, it’s ages since I wrote this so you will have to make sense of it. As far as I remember I called this function every millisecond (and made sure that happened using interrupts).

//Reads rotary position encoders connected to port p and leaves delta in associated RPE_input_delta. 1 change for each encoder step.
void RPE_read(unsigned char p) {
    const char p_mask[4] = {0xfc, 0xf3, 0xcf, 0x3f};
    const char RPE_input_mask[4][4] = {{0xfe, 0xfd, 0xfc, 0xff}, {0xfb, 0xf7, 0xf3, 0xff}, {0xef, 0xdf, 0xcf, 0xff}, {0xbf, 0x7f, 0x3f, 0xff}};
    char p_masked; static char p_last; char i;
    if (p != p_last) {
        p_last = p;
        for (i = 0; i < zone_count; i++) {
            p_masked = p | p_mask[i];
            if (p_masked == 0xff) {
                RPE[i].index = 0;
            }
            switch (RPE[i].index) {
                case 0:
                    if (p_masked == RPE_input_mask [i][0]) {
                        RPE[i].index = 1;
                    }
                    if (p_masked == RPE_input_mask [i][1]) {
                        RPE[i].index = 4;
                    }
                    break;
                case 1:
                    if (p_masked == RPE_input_mask [i][2]) {
                        RPE[i].index = 2;
                    }
                    break;
                case 2:
                    if (p_masked == RPE_input_mask [i][1]) {
                        RPE[i].index = 3;
                        RPE[i].RPE_input_delta++;
                    }
                    break;
                case 3:
                    if (p_masked == RPE_input_mask [i][3]) {
                        RPE[i].index =0;
                    }
                    break;
                case 4:
                    if (p_masked == RPE_input_mask [i][2]) {
                        RPE[i].index = 5;
                    }
                    break;
                case 5:
                    if (p_masked == RPE_input_mask [i][0]) {
                        RPE[i].index = 6;
                        RPE[i].RPE_input_delta--;
                    }
                    break;
                case 6:
                    if (p_masked == RPE_input_mask [i][3]) {
                        RPE[i].index = 0;
                    }
            }
        }
    }
}

I hope that’s useful.

gunjah292:
Now I need to implement a more complex code and was advised to use interrupts.

Generally bad advice, but with no actual information about your code, it is hard to say.

gunjah292:
I just put a delay of 10ms and read the value again to make sure, that I am not reading a bounce.

Well, "just put a delay of 10ms" is a very dubious statement. You most certainly cannot use "delay()" inside an interrupt for two reasons - it won't work, and if it did, it would defeat the whole purpose of an interrupt.

You poll the decoder lines with every loop() cycle. If you see a change, you make a note of the status and the millis() time. On each successive pass, you review the lines against that status; if it reverts you cancel the status and consider it bounce or noise. If however the new status persists for an advance of the millis() of 5, you consider it stable and act on it. In the case of the quadrature encoder, you follow the same process independently for the two lines.

This means that a change is only considered valid where it persists for every pass through the loop within a 5 ms interval. If it should happen that the alternate line also changes within that 5 ms interval, its state is only validated 5 ms after it is stable so the all-important sequence of the two changes is preserved.

This is the best possible way to accurately read the encoder. If the bounce time were to exceed the step time of the encoder, it would be completely impossible to decode it in any case, so there is necessarily a limit on just how fast the encoder can be read. You clearly cannot use delay() or while loops anywhere in your code, it is based on the loop() running without impediment.

And you could attempt to implement the debounce algorithm using interrupts and millis() (which is valid inside an interrupt) but you would of course need to reliably detect every pin change - difficult if as a result of a bounce, it happens within the interrupt execution itself - in order to reject changes that did not persist for the required 5 ms, and more to the point, once the change is stable, it no longer generates interrupts, so you cannot know when it has timed out at 5 ms. You have an inherent paradox.

gunjah292:
Hi,

I need to debounce a rotary encoder with gray code.

No you don't. Debouncing is not needed, that's the whole point of using a Gray code.
So long as you read the pins more frequently or as frequently as they genuinely change,
you'll get correct counts coming out.

With a correctly functioning encoder any contact bounce only affects one pin at a time,
causing jitter between two successive output counts - such jitter is always valid with
a mechanical encoder anyway (due to vibration or dirty contacts).

What you might want is a small amount of hysteresis to prevent jittering outputs propagating
to the rest of the system.

So for instance if the count goes:

5 5 5 6 5 6 6 5 6 5 6 6 6 6 7
you want to see output
5 5 5 6 6 6 6 6 6 6 6 6 6 6 7
(one count of hysteresis hides the back-and-fro so long as its always the same two values).

To implement this just record the two latest counts and don't change you output count unless
the new input count is distinct from the two cached ones.