When it comes to microcontrollers reading a rotary encoder, I often see it being done using interrupts. While that is not necessarily the wrong thing to do, it can crowd the interrupt handler with something that's not really critical functionality, especially if microcontroller can handle it within its base loop just as effectively.
To start with, the code is available here and consists of two exported functions:
void rotary_init(void);
enum ROTARY_DIRECTION rotary_getDirection(void);
If we disregards rotary_init
which just initializes a few variables, all the significant code is in rotary_getDirection
function. To use it, simply call this function once in a while from the main loop, and it will return one of three values (ROTARY_DIRECTION_NONE
, ROTARY_DIRECTION_LEFT
, or ROTARY_DIRECTION_RIGHT
) corresponding to the detected movement.
The heart of the functionality is in the following code:
uint8_t currRotState = getRotaryState();
if (currRotState != lastRotState) {
histRotState = (uint8_t)(histRotState << 2) | currRotState;
lastRotState = currRotState;
uint8_t filteredRotState = histRotState & 0x3F;
if (filteredRotState == 0x07) { return ROTARY_DIRECTION_LEFT; }
if (filteredRotState == 0x0B) { return ROTARY_DIRECTION_RIGHT; }
}
return ROTARY_DIRECTION_NONE;
First, we get the state in binary for each of the 2 contacts available. Thus, the output can be either 0x00
, 0x01
, 0b10
, or 0b11
, depending on which contacts are closed. If the state has changed compared to the previous reading, we append it to the variable used to store previous states. As each state is 2 bits long, this means we can easily fit all 4 states in a single 8-bit value by shifting it 2 bits at a time.
If your switch is new and shiny, this miis where you might stop. However, in the real world, switches are dirty and thus can skip a state or two. For example, one of my rotary encoders would skip a state every once in a while. This means that a proper code would simply make experience worse as time goes by.
However, due to redundancies in how the encoder functions, detecting three neighboring states still gives you plenty of information to go by without misidentifying the direction of scrolling. In my code, detection starts when both encoder contacts are active (i.e. 0b11
state). On all encoders I worked with (admitely, mostly PEC12R series from Bourns), this actually nicely aligns with steps and almost perfectly filters wear-induced noise.
And yes, you can adjust the code slightly to make it run in an interrupt routine if you are so inclined.