Encoders Spin Us Right Round

Rotary encoders are great devices. Monitoring just a few pins you can easily and quickly read in rotation and direction of a user input (as well as many other applications). But as with anything, there are caveats. I recently had the chance to dive into some of the benefits and drawbacks of rotary encoders and how to work with them.

I often work with students on different levels of electronic projects. One student project needed a rotary encoder. These come in mechanical and optical variants. In a way, they are very simple devices. In another way, they have some complex nuances. The target board was an ST Nucleo. This particular board has a small ARM processor and can use mbed environment for development and programming. The board itself can take Arduino daughter boards and have additional pins for ST morpho boards (whatever those are).

The mbed system is the ARM’s answer to Arduino. A web-based IDE lets you write C++ code with tons of support libraries. The board looks like a USB drive, so you download the program to this ersatz drive, and the board is programmed. I posted an intro to mbed awhile back with a similar board, so if you want a refresher on that, you might like to read that first.

Reading the Encoder

The encoder we had was on a little PCB that you get when you buy one of those Chinese Arduino 37 sensor kits. (By the way, if you are looking for documentation on those kinds of boards, look here.; in particular, this was a KY-040 module.) The board has power and ground pins, along with three pins. One of the pins is a switch closure to ground when you depress the shaft of the encoder. The other two encode the direction and speed of the shaft rotation. There are three pull-up resistors, one for each output.

I expected to explain how the device worked, and then assist in writing some code with a good example of having to debounce, use pin change interrupts, and obviously throw in some other arcane lore. Turns out that was wholly unnecessary. Well… sort of.

Abstract Reasoning

I forget that the Arduino, mbed, and other similar platforms have a wealth of (often user-contributed) libraries. I did a quick search from the import dialog (see below) and found several likely-looking libraries. The highlighted item (from [Karl Zweimüller]) was updated this year, so I figured it was a safe bet. Pressing the ridiculously large import button near the top right of the screen took care of it.

With the library in the project, the question becomes: how to use it? Luckily, there’s documentation built in. When you navigate your project, some nodes are source code and some are documentation (they look like little blue documents in the navigator). Clicking on the mRotaryEncoder node shows a useful help document (shown below). What else do you need to know?

The Code

Armed with this, there are just a few lines of code to write. This one sets up the encoder:


mRotaryEncoder enc(D7,D8, D4,PullNone);

The D7, D8, and D4 constants define pin numbers (D7 and D8 are the encoder pins and D4 is the switch which I am actually not using). The PullNone prevents the CPU from enabling internal pullup resistors because the board already has them.

Next, we can assign functions that the library will call on certain events. In particular:


void cw()
{
dutycycle+=0.1;
if (dutycycle>1.0) dutycycle=1.0;
}

void ccw()
{
dutycycle-=0.1;
if (dutycycle<0.0) dutycycle=0.0;
}

You may have deduced my clever naming scheme. The cw function handles clockwise motion, and the ccw function handles the reverse. How does the library know what to call? It takes some setup in the program’s main function:


enc.attachROTCW(cw);
enc.attachROTCCW(ccw);

But Wait a Minute!

Sure, that’s simple. It is a great abstraction that handles a lot of complex issues. But what do you learn doing that? Not much. For all you can tell, the encoder might have a microprocessor on it and is sending data via some serial protocol (which, actually, isn’t a bad idea with as cheap as processors are now).

I decided it wasn’t sufficient to just roll out a canned library. I drug out the scope, and we put the encoder on channel 1 and channel 4 (it is a shame that channel 2 and channel 4 have nearly the same color on the probe rings). Here’s the result of twisting the shaft clockwise and counterclockwise:

Clockwise Rotation Counter Clockwise Rotation

The key is to look at the falling edge of the top trace. Note that in the clockwise case (left image), the bottom trace is high at that time. In the counterclockwise case (right image), it is low. You can actually detect on both edges if you like, but for most purposes, this is sufficient and it really is that simple. Watch for the edge, note the other signal’s state, and that tells you the direction. Clearly, the faster the edges come, the faster the shaft is spinning.

This is known as quadrature encoding because the two signals are 90 degrees out of phase.

Bouncy Bouncy

In an ideal world, this would be pretty easy. However, the world is far from ideal. You can see some noise on the signals if you look closely. Let’s zoom in:

You don’t want to take all of those as falling edges! You need some debouncing. Ideally, you’d wait for the signal to go low, wait a bit, make sure it was still low and then react. Then you would not look again for some other time out delay.

This can be tricky because you typically don’t want to poll for the edge. You want to use something like a pin change interrupt that calls your code when a pin changes state.

Since the mbed libraries include the source, it was easy to look at what was really going on. The library doesn’t use an interrupt. It uses a class known as PinDetect (by [Andy Kirkham]) that polls on a timer (by default, every 20mS).

The code doesn’t have any comments, so I added a few to the partial code below:


void isr(void) {
int currentState = _in->read(); // get current pin state

if ( currentState != _prevState ) { // if the state has changed
if ( _samplesTillAssert == 0 ) { // see if that was long enough
_prevState = currentState; // remember state for next time
_samplesTillHeld = _samplesTillHeldReload; // reset hold time
if ( currentState == _assertValue ) // call correct callback
_callbackAsserted.call();
else
_callbackDeasserted.call();
}
else {
_samplesTillAssert--; // state held, so keep counting down
}
}
else {
_samplesTillAssert = _samplesTillAssertReload; // reset
}
...

Remember, this code runs every 20mS. It looks to see if the input changed from last time and makes the appropriate decision.

The actual encoder object is simple given that you have debounced data. Here’s the function PinDetect calls when the main input goes low:


void mRotaryEncoder::fall(void) {
// debouncing does PinDetect for us
//pinA still low?
if (*m_pinA == 0) {
if (*m_pinB == 1) { // state of 2nd pin tells us CW or CCW
m_position++;
rotCWIsr.call(); // notify user code
} else {
m_position--;
rotCCWIsr.call(); // notify user code
}
rotIsr.call(); // call the isr for rotation; notify user code
}
}

Naturally, you might not provide all the callbacks, and that’s not a problem. The library has a similar piece of code for the rising edge, as well. You can find the entire library online if you want to browse it without creating a project.

Just to put this all in perspective, I joined the KY-040 encoder module with a KY-016 RGB LED. I selected pins on the Nucleo board, so the LED module will just plug in, but you’ll still need wires for the encoder. You can find the code on the mbed site, and the comments in the code describe the wiring.

The idea is simple. You can use the encoder to set the level of red the LED emits. Push on the shaft of the encoder, and you can control the green level. Another push sets the blue level. Subsequent pushes repeat the cycle. A simple program, but strangely entertaining to dial in strange colors (like pink or aqua). There’s a quick and dirty demo in the video, below.

Meanwhile

I often say you don’t have to know how an engine works to drive a car. But the best drives do know. By the same token, it was easy enough to use this library to read the encoder, and if all you want is results, that’s the way to go. After all, all engineering boils down to abstractions. Maybe you understand assembly language. Maybe you can design a CPU at the logic gate level. Maybe even at the device level. But you probably aren’t growing your own silicon crystals, lapping a wafer, and building the device from there. Even then, you are relying on abstractions about how electrons flow and probably a few others.

However, it probably wouldn’t hurt if you had at least a feeling for how each one of those abstractions work. I don’t think letting students avoid understanding how a rotary encoder works is a good thing. Even if you choose to use a library to hide the details, it is good to understand what is really happening under the hood.

By the way, if you are running short on pins, you might be interested in this alternate method which is truly a hack. Meanwhile, I should probably take my own medicine and build my own encoders.


Filed under: ARM, Hackaday Columns, Microcontrollers, Original Art, rants, Skills

from Hackaday http://ift.tt/2jjFE3K
via IFTTT