Demo for Sparkfun's RPG with internal RGB led using interrupts

Dependencies:   mbed PinDetect

Using a Rotary Pulse Generator (RPG) or Rotary Encoder

This is a demo for Sparkfun's RPG (rotary pulse generator or rotary encoder) with an internal RGB led using interrupts to read the 2-bit encoder signal. By monitoring these output bits, the amount and direction of rotation of the knob by the user can be determined. Pushing the knob down activates a pushbutton that can be used to change modes in a device. See comments in main.cpp for more details and pin hookups. The RPG is mounted on Sparkfun's breakout board. The code uses the encoder count to dim the built-in red LED using PWM. The RPG's shaft pushbutton is also debounced using interrupts.

While simple software polling (programmed I/O) could be used to read the encoder bits, interrupts are more efficient in this application since it would be necessary to sample and read the two encoder bits a couple hundred times a second when someone spins the knob. Since the vast majority of the time bits are not changing at all, constantly polling this fast wastes a lot of processor time and power!

https://os.mbed.com/media/uploads/4180_1/rpg_skNKbNa.jpg

https://os.mbed.com/media/uploads/4180_1/reinside.jpg

Inside view of a RPG (rotary encoder) showing encoder wheel (silver teeth on left) and dual switch contacts (right) from https://www.robotroom.com/Counter5.html. Two parallel contacts are often used to increase reliability and reduce contact bounce.

https://os.mbed.com/media/uploads/4180_1/rpg2.jpg This style RPG has one toothed encoder wheel but the contacts are at different angles for the phase shift between the two output bits.

https://os.mbed.com/media/uploads/4180_1/incremental_directional_encoder.gif

Encoder animation of A and B output bits from Wikipedia

https://os.mbed.com/media/uploads/4180_1/rpgtt.png

The truth table is stored in a predefined C array:

const int lookup_table[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};

An interrupt routine is called whenever one of the two bits changes state and the truth table in an array is checked to determine how to change the count (i.e.+1, -1, 0). The encoder uses Gray code (only 1 bit changes at a time), if no bits or two bits change it may be switch contact noise and the count is not changed (i.e., a "0") in the truth table. See http://makeatronics.blogspot.com/2013/02/efficiently-reading-quadrature-with.html for a detailed explanation of the RPG encoder counting algorithm using interrupts with a lookup table.

The header pins are spaced so far apart that all of the jumper wire pins are under the PCB in the normal breadboard locations for an IC, but if you put in across a power bus (power bus under middle of PCB) as seen in the photo below, you will have room to connect the jumper wires after inserting the breakout (otherwise pins for jumper wires are all under the breakout board).

https://os.mbed.com/media/uploads/4180_1/20220118_141642.jpg

Parts used:

https://www.sparkfun.com/products/15141 https://www.sparkfun.com/products/11722 https://www.sparkfun.com/products/10597

Wiring

RPG breakoutmbed LPC1768
A encoder output bitp14
B encoder output bitp15
C encoder commongnd
+ LED+ for RGB LEDVout 3.3
R LED-p21
G LED-p22
B LED-p23
SW pushbuttonp16

This same scheme of two phase shifted encoder output bits to determine CW or CCW rotation is often used for feedback in motor control systems. In this application, it is called a quadrature encoder (QE). In fast motors, an optical signal is used to read the bits from the encoder wheel instead of a mechanical switch contact as in this RPG. The mbed LPC1768 has a built-in hardware quadrature encoder interface with a counter that would be nice to use for the RPG, but it does not come out on the module's limited breadboard pins.

main.cpp

Committer:
4180_1
Date:
2022-01-13
Revision:
1:5c21ef62c975
Parent:
0:3eea8ad2dbbc

File content as of revision 1:5c21ef62c975:

#include "mbed.h"
#include "PinDetect.h"
//See http://makeatronics.blogspot.com/2013/02/efficiently-reading-quadrature-with.html
//for a detailed explanation of the RPG encoder counting algorithm
//uses Sparkfun RPG with RGB led on breakout board (#15141,11722,10597)
//place RPG PCB across a breadboard power bus strip for easier pin hookup!
InterruptIn RPG_A(p14,PullUp);//encoder A and B pins/bits use interrupts
InterruptIn RPG_B(p15,PullUp);
PinDetect RPG_PB(p16); //encode pushbutton switch "SW" on PCB
//PWM setup for RGB LED in enocder
PwmOut red(p21);//"R" pin
PwmOut blue(p22);//"G" pin
PwmOut green(p23);//"B" pin
//Note: also tie RPG PCB "C" pin to ground, "+" pin to 3.3
//mbed status leds
DigitalOut ledPB(LED1);
DigitalOut red_adjust_mode(LED2);
DigitalOut green_adjust_mode(LED3);
DigitalOut blue_adjust_mode(LED4);
//Serial pc(USBTX,USBRX);
volatile int old_enc = 0;
volatile int new_enc = 0;
volatile int enc_count = 0;
//Instead of a slow 16 case statement use a faster table lookup of truth table
//index table with (previous encoder AB value<<2 | current current encoder AB value) 
//to find -1(CCW),0,or 1(CW) change in count each time a bit changes state
//Always want Interrupt routines to run fast!
//const puts data in read only Flash instead of RAM
const int lookup_table[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
//Encoder bit change interrupt service routine
//called whenever one of the two A,B encoder bits change state
void Enc_change_ISR(void)
{
    new_enc = RPG_A<<1 | RPG_B;//current encoder bits
    //check truth table for -1,0 or +1 added to count
    enc_count = enc_count + lookup_table[old_enc<<2 | new_enc];
    old_enc = new_enc;
}
//debounced RPG pushbutton switch callback
void PB_callback(void)
{
    ledPB= !ledPB;
}
int main()
{
//turn off built-in RPG encoder RGB led
    red = 1.0;//PWM value 1.0 is "off", 0.0 is full "on"
    green = 1.0;
    blue = 1.0;
//current color adjust set to red
    red_adjust_mode = 1;
//debounce RPG center pushbutton
    RPG_PB.mode(PullDown);
    RPG_PB.attach_deasserted(&PB_callback);
    RPG_PB.setSampleFrequency();
// generate an interrupt on any change in either encoder bit (A or B)
    RPG_A.rise(&Enc_change_ISR);
    RPG_A.fall(&Enc_change_ISR);
    RPG_B.rise(&Enc_change_ISR);
    RPG_B.fall(&Enc_change_ISR);
    while (true) {
        // Scale/limit count to 0..100
        if (enc_count>100) enc_count = 100;
        if (enc_count<0) enc_count = 0;
        red = 1.0 - enc_count/100.0;
//        pc.printf("%D\n\r",enc_count);
    }
}