Connecting a rotary encoder Mar 2017

This exploration is about connecting a rotary encoder switch for use as an infinitely adustable up/down controller. The basic idea is that there are two switches inside, which generate pulses. In the simple rotary knobs, these two switches are open in the “click” position, and closed in a very specific way in between click positions (also known as “detents”).

Note that this article isn’t about “here’s a library, plug it in and you’re done”, but a summary of the steps taken to make it work in Forth on a JeeNode Zero, gradually improving the code and making it do more things. In short: it’s about the journey!

The unit below has a common middle connection, with the switches on the left and right pins:

In Forth code, this can be represented as:

PA5 constant ENC-A
PA3 constant ENC-B
PA4 constant ENC-C  \ common

The trick is to tie the C pin to ground, and enable internal pull-ups on the A and B pins:

IMODE-HIGH ENC-A io-mode!
IMODE-HIGH ENC-B io-mode!
OMODE-PP   ENC-C io-mode!  ENC-C ioc!

And of course the first thing to try, is simply to verify that something is happening. So let’s write a small loop which reads out the A and B pins periodically and prints them out. In Mecrisp Forth, loops need to be inside a definition, which we’ll call it read-enc here:

: read-enc
  begin
    cr ENC-A io@ . ENC-B io@ .
    500 ms
  again ;

Now we can type read-enc to start the loop, and very slowly turn the knob, given that the switch changes only appear between the clicks:

-1 -1
-1 -1
-1 0
0 0
0 0
0 0
0 -1
-1 -1
-1 -1

Hey, look, there’s something going on! Looks like this hookup is working!

To understand the logic of this, we need to look at the description in the datasheet:

This particular encoder has 24 detents per full rotation (others may have 12), and we can see that there are four edges between each detent / click. Most importantly, the direction of the rotation determines the pattern:

And that’s exactly the point of this encoding. Note also that only one pin changes at a time, no matter which way the knob is turned, or how fast. This is called Gray code, and it’s extremely useful in real-world signal processing, because it eliminates nasty non-deterministic timing problems (a bit like race conditions in software).

Enough theory. We need to turn these pulses into counter changes, because the real goal is to use the knob to increase or decrease a software counter when turned.

There are many different ways to do this, but let’s start as simple as possible: a continuous loop, checking the pin states, and a little lookup, based on previous and current values of the A and B pins. Here’s a modified version of read-enc, which uses some bit shifting tricks:

: read-enc
  %11  \ previous state, stays on the stack
  begin
    2 lshift  \ prev pins in bits 3 and 2
    ab-pins tuck  \ new pins, also save as previous for next cycle
    or  \ combines prev-a/prev-b/curr-a/curr-b into a 4-bit value

    \ process this 4-bit value and leave only prev state on stack
    case
      %0001 of -1 step endof
      %0010 of  1 step endof
      %0100 of  1 step endof
      %0111 of -1 step endof
      %1000 of -1 step endof
      %1011 of  1 step endof
      %1101 of  1 step endof
      %1110 of -1 step endof
    endcase
  again ;

On each iteration through the loop, we move the A & B bits 2 to the left, then put the new values of the A & B pins into bits 1 and 0, respectively. This is based on some extra code:

1000 variable counter

: ab-pins ( -- n )  \ read current A & B pin state as bits 1 and 0
  ENC-A io@ %10 and  ENC-B io@ %01 and  or ;

: step ( n -- )  counter +!  cr counter @ . ;

As Forth is strictly bottom up, counter, ab-pins, and step must be defined before run-enc.

The step word will increment or decrement the counter, and print it out. So now, read-enc will print changing counter values when the knob is rotated, and stay silent otherwise. We’ve just implemented the basics of a rotary switch decoder!

But that’s still a bit boring, isn’t it? Let’s hook up a 128x64 OLED and display the counter:

This requires some extra code (and the flib/any/digits.fs font for the nice large bitmaps). Only the step word needs to be changed (the full code can be found on GitHub):

: step ( n -- )  counter +!  counter @ shownum ;

To start things up properly, we now need to type in a few more commands:

lcd-init clear display read-enc

And that’s it. Working code for each of these can be found in the rot1.fs, rot2.fs, and rot3.fs source files on GitHub. If you try this out, you’ll need to resend core.fs before the rot3.fs rotary encoder demo can be used, since it was modified to include the digits.fs bitmaps.

Unfortunately, there’s a problem when using the OLED: the knob appears to work, but only when turned very slowly. Normal turn rates seem to cause only erratic changes in the counter value. The reason for this is quite simple - it will be explained (and fixed) in the next article…

PS. Due to the way the pixels are mapped, the exact same code also works on 128x32 OLEDs:

(apologies for the low image quality: these were taken without flash, using a compact camera)

Weblog © Jean-Claude Wippler. Generated by Hugo.