A software debouncing class for rotary encoders

Rotary encoders are notoriously difficult to get right.

Although I haven’t used this new encoder class yet, it looks very interesting. From active Teensy forum user Theremingenieur, it allows initialization with upper and/or lower limits.

You can find the code in this thread.

Implementing a simple menu interface on OLED display

While working on a project to automate environmental control in our greenhouse, I needed to implement a menu interface on a small OLED display. In this sub-project, meant to test the concept, I’ve used a Teensy 3.1, a small I2C-driven 0.96" monochrome OLED display and a rotary encoder.

Bill of materials:

  • Teensy 3.1 - the Teensy 3.1 is no longer available, but you can easily find the compatible Teensy 3.2.
  • 0.96" yellow/blue I2C OLED module - I used the version with the yellow band at the top so that it this area could act as the highlighted region of the display.
  • Rotary encoder with push-button switch - I used this one from Adafruit, but there are many options.
  • {% asset_link MC74HC14.pdf 74HC14 Schmitt Trigger inverter %} - to debounce the pushbutton.

Description

This project is a proof-of-concept for using a rotary encoder to manipulate an on-screen menu of options. A number of electronics design concepts are used here.

Circuit

There’s nothing particularly unusual about the schematic, though I’ll point out three different methods for debouncing the encoder and switch.

Rotary encoder switch schematic

First, about the rotary encoder, there are two sides with pins. One side has three pins and the other two pins. The three pin side is for the encoder and the two pin side is for the pushbutton switch. The center pin of the encoder is grounded and remaining two pins are connected to ground via a 0.01 uF capacitor. Together with the internal pullup resistors, this creates an RC filter that removes noise in the square wave pattern that the encoder should generate. It’s a form of debouncing, though we still debounce in code. We use an inverting Schmitt trigger inverter to handle debouncing on the switch side.

There’s a lot to say about Schmitt trigger debouncing, but since it has been written about extensively, I’ll just refer you to references at the end of this post for that. Same for the rotary encoder.

Code

Setup

First, we’ll initialize the the u8g2 library used to display the menu on our OLED.

#include <U8g2lib.h>

U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, SCL, SDA, U8X8_PIN_NONE);

void setup() {
    u8g2.begin();
    u8g2.enableUTF8Print();
    u8g2.setFont(u8g2_font_courB12_tr);
    u8g2.setFontMode(0);

    //  initialize our serial interface
    //  initialize our interrupts for the rotary encoder
}

Next, we setup our menu items.

static const uint8_t NUM_MENU_ITEMS = 6;
const char* menu_items[] = {
    "Day lo temp",
    "Day hi temp",
    "Nite lo temp",
    "Nite hi temp",
    "Heat ON",
    "Heat OFF"
};

And the interrupts for the rotary encoder/switch. There are many ways to read the rotary encoder. We could simply poll all the device pins, but since we have a robust interrupt capability on the Teensy, why not use that?

enum PinAssignments {
    encoderPinA = 5,   // right
    encoderPinB = 6,   // left
    selectButton = 7
};

volatile unsigned int encoderPos = 0;  // a counter for the dial
unsigned int lastReportedPos = 1;   // change management
static boolean rotating = false;    // debounce management

// interrupt service routine vars
boolean A_set = false;
boolean B_set = false;

void setup() {
    pinMode(encoderPinA, INPUT);
    pinMode(encoderPinB, INPUT);
    pinMode(selectButton, INPUT);

    // turn on pullup resistors
    digitalWrite(encoderPinA, HIGH);
    digitalWrite(encoderPinB, HIGH);
    digitalWrite(selectButton, HIGH);

    // encoder pin on interrupt A
    attachInterrupt(digitalPinToInterrupt(encoderPinA), doEncoderA, CHANGE);

    // encoder pin on interrupt B
    attachInterrupt(digitalPinToInterrupt(encoderPinB), doEncoderB, CHANGE);

    // interrupt for the switch component
    attachInterrupt(digitalPinToInterrupt(selectButton), doSelect, RISING);
}

Here, we make all of the encoder pins inputs and enable the pullup resistors. Then we have to attach the interrupts to each. We use CHANGE interrupt on the rotary encoder pins because we’re interested in interpreting any change (up or down) whereas with the select button, we just want to know about the RISING status when it is pushed.

Using the interrupt service routines

Here I used a debounce approach I’ve adopted in similar projects. I’m not entirely fond of the 1ms delay in the interrupt service routines, but on a practical level, it works.

We interrupt whenever either the A or B inputs from the encoder transition. Then we look to see whether we’ve preceded or followed the other pulse to determine if which direction the user has rotated.

For the select button, I’ve debounced solely in hardware using the Schmitt trigger circuit depicted above.

Scrolling the menu items

The last task is to scroll the menu items on the screen in response to the encoder movement. There are really two tasks here. The first is to make sure that the encoder value is bounded by the limits of the menu. The value should not go below 0 (or whatever the wraparound value is for the unsigned type.) And it should not go higher than the number of menu items - 1 (for a zero-index array of menu items.) Here’s how I do it:

void loop() {
    rotating = true;  // reset the debouncer

    if (lastReportedPos != encoderPos) {
        encoderPos = (encoderPos > NUM_MENU_ITEMS -1 )?0:encoderPos;
        Serial.print("Index:");
        Serial.println(encoderPos, DEC);
        lastReportedPos = encoderPos;

        uint8_t tempPos = encoderPos;
        u8g2.clearDisplay();
        u8g2.firstPage();
        do {
            u8g2.setCursor(0, 12);
            u8g2.print(menu_items[tempPos++]);
            for( uint8_t i = 0; i < NUM_MENU_ITEMS; i++ ) {
                if( tempPos < NUM_MENU_ITEMS) {
                    u8g2.setCursor(0,16 + (i+1)*14);
                    u8g2.print(menu_items[tempPos++]);
                }
            }

        } while ( u8g2.nextPage() );
    }
}

And that’s it. The current menu item is shown in the yellow status area of the display, a UI feature that shows its availability for selection.

If you’d like to grab the entire code, you can find it here:

References

Carl Sagan's tools for detecting baloney

Carl Sagan’s “Baloney detection kit” is arguably more important now than ever. His 9 rules for critical thinking work for science and they can work to detect political baloney, too.

Arguments from authority carry little weight — “authorities” have made mistakes in the past. They will do so again in the future. Perhaps a better way to say it is that in science there are no authorities; at most, there are experts.

Resetting the Syncthing index

I use Syncthing^[No, there’s no iOS client. I’m OK with that.] to keep my laptop, desktop, and workshop computers in sync.^[Why don’t I just use Dropbox like everyone else? I trust peer-to-peer syncing because I’m in control. I don’t know what Dropbox is up to.] At least 99.9% of the time it works perfectly. Rarely, it seems to choke because of some edge case that I’ve never been able to sort out. But it never recovers on its own. Instead, it continues to report that a remote is 99% done syncing.

Displaying Cyrillic fonts on a 128x64 OLED display

Recently I picked up a couple inexpensive 128x64 pixel OLED displays with an I2C interface. It turns out that displaying Russian text on these displays is not difficult. But it’s non-obvious. This is a brief description of how to make it work.

First, there’s a variety of these little displays and they’re all seemingly configured a little differently. I used this device for this test.

There are two options for libraries to simplify communicating with SSD1306 boards:

Reading data from Si7021 temperature and humidity sensor using Raspberry Pi

The Si7021is an excellent little device for measuring temperature and humidity, communicating with the host controller over the I2C bus. This is a quick tutorial on using the Raspberry Pi to talk to this device. If you are unfamiliar with the conceptual framework of I2C or how to enable I2C access on the Raspberry Pi, I suggest starting here. Otherwise, let’s jump in.

You are probably working with the device mounted on a breakout board. I used this one from Adafruit. There are no surprises on the pins that it breaks out - Vin, 3v out, GND, SCL and SDA. One the 40-pin P1 header of the Raspberry Pi, SDA and SCL for I2C bus 1 occupy pins 2 and 3.

RF communication between Arduino Nanos using nRF24L01

In this tutorial I’ll go through a simple example of how to get two Arduino Nano devices to talk to one another.

Materials

You’ll need the following materials. I’ve posted Amazon links just so that you can see the items, but they can be purchased in a variety of locations.

  • Arduino Nano 5V/16 MHz, or equivalent (Amazon)
  • Kuman rRF24L01+PA+LNA, or equivalent (Amazon)

About the nRF24L01+

The nRF24L01+ is an appealing device to work with because it packs a lot of functionality on-chip as opposed to having to do it all in software. There is still a lot of work to be done in code; but it’s a good balance between simplicity and functionality. It’s also inexpensive.

Using the Raspberry Pi to communicate over the I2C bus using C

I recently wrote about using the excellent bcm2835 library to communicate with peripheral devices over the SPI bus using C. In this post, I’ll talk about using the same library to communicate over the I2C bus. Nothing particularly fancy, but you’ll need to pay careful attention to the datasheet of the device we’re using. TheTSL2561 is a sophisticated little light sensor that has a very high dynamic range and is available on a breakout board from Adafruit. I’m not going to delve into the hookup of this device as you can take a look at the Adafruit tutorial for that. Note that we’re not going to use their library. (Well, I borrowed a bunch of their #define statements for device constants.)

Implementing ADC using Raspberry Pi and MCP3008

Several years ago I wrote about adding analog-to-digital capabilities to the Raspberry Pi. At that time, I used an ATtinyx61 series MCU to provide ADC capabilities, communicating with the RPi via an I2C interface. In retrospect it was much more complicated than necessary. What follows is an attempt to re-do that project using an MCP3008, a 10 bit ADC that communicates on the SPI bus.

MCP3008 device

The MCP3008 is an 8-channel 10-bit ADC with an SPI interface^[Datasheet can be found here.]. It has a 4 channel cousin, the MCP3004 that has similar operating characteristics. The device is capable of performing single-ended or differential measurements. For the purposes of this write-up, we’ll only concern ourselves with single-ended measurement. A few pertinent details about the MCP3008: