Saturday, August 17, 2024

Debouncing a Rotary Encoder Revisited

Readers: If you want to build the project featured in today's post, please go to PCBWAY's Community pages--gerber file (main board) ready to download and/or fabricate, KiCAD pro/pcb/schem files and a BOM are here.  

You can also help out this site immensely by checking out PCBWAY using the link here. Thanks!

----------------------


Rotary encoder debouncing is alive and well: here, here and here for instance.

This post I try out an interesting debounce algorithm, silencing my rotary encoder's chattering teeth once and for all, using a small bespoke development board centered around the Raspberry Pi Pico and its RP2040 MCU.

Good news: the debounce algorithm works great.  

 


 
This started when DIY mensch Christer Janson emailed me, postulating a way to do encoder debounce using pin state as opposed to more the traditional methods: using hardware (example here) or waiting a few milliseconds after initial contact to let the chatter subside (here). 

I was baffled, his idea seemed too far out.

A few days later Christer emailed me again with a link to Marco Pinterec's website where this unfamiliar debounce paradigm is discussed. 

Ralph Bacon's video (here) shows Marco's idea in action.

"Sit on it and rotary"



To (over)simplify: the algo reads the encoder's state once pins have settled into a physically possible condition and only then outputs value indicating that the encoder is incrementing or decrementing.

Christer took the code from Marco's site and turned it into C++ classes. One class can be used when an interrupt is fired ("CCJISREncoder"), also a inherited class for polling encoder(s) using an infinite main()  loop ("CCJPollEncoder").  

Cool! 

Get the code from Christer's website here.

You can get my Raspberry Pi RP2040 port of the polling algo on Github, here; the ISR version with a main.c example is here.

GETTING IT TO WORK....

This was a simple one right?

Not at first. 

Before working on debouncing of any kind I strugged for 3+ evenings (!!) getting the Pico's serial TX pin 0 connected to an Ubuntu Linux PC running Minicom to display output. 

I have been using serial UART with Pico dev boards, SBC's, and ATMega328's since about 2019 without issue.
 
For this project I cracked open a batch of new PICOs; 4 boards total. 

I found that one new PICO could output a 112K serial stream for a few seconds before the stream stopped, while the other three didn't output serial at.  

Hello???? 

Of course I was using "hello snotrocket" code to test the output; I knew the code worked, having uploaded and tested it on earlier Picos without issue.

I tried everything--in the most logical manner I could--different computers, different USB ports, different GPIO pins, different USB cables, a CH340 based clone instead of an FTDI for serial to USB, all to isolate what worked and what didn't; no luck. 

It had to be the new batch of Picos!

Pico of crap? Usually not. But four from Amazon had a problem with breakfast carbs: i.e., serious cereal issues.




Adafruit's FTDI F(r)iend! converts RX/TX serial to USB.  Easy to use, well documented; works on Linux OS without extra drivers. Highly recommended, but only if the board you hook it has a working serial TX output, dammit.


After a lot of Internet research, scribbling notes, more Internet research, looking at scope output, reading many SDK pages, and still more Internet research, I guessed that I had some sort of pullup or pulldown issue, not needed in all my previous Pico dev boards, but somehow, for these four new ones, necessary.

Mind you, this was a guess.

I put a 10K resistor between TX and ground--still broken.  

But to my amazement: when I put the 10K from TX to ground--FIXED.  For all four!

Speculation: my older Picos had different component tolerances regarding output impedance, so they worked without the 10K pulldown, but this newest batch, nope--pull down resistors for TX were obviously needed.

Oddly, I couldn't find anyone else on the Internet with the same issue. 

Maybe most DiWHYers use the USB cable based Arduino terminal window instead of GPIO pins for serial, so, if it didn't work, well, no one cared.

Maybe I got a few lemons from Amazon and no one else did?   

I will never know. 

Anyway...I soldered a 10K resistor between TX and ground. I incorporated this mod into the gerber I uploaded to the PCBWAY community site. If you build this board and are using serial pin GPIO 0 (TX) for output, you might want to use a 10K 1206 resistor for R6.  

A potential solution and it ain't pretty.


I figured some sort of pulldown method in code could also be put to the test but that's for another time.

THE CCJ ALGORITHM

Christer's code demonstrates his deep knowledge of C++, using protected classes, virtual functions, inheritance, and more.  

It was written for AVR/Arduino so I had to change things like "digitalRead()" to C SDK functions for the RP2040. 

How to use it?....if you are calling the algo from an infinite "while" loop there is not a lot more than this:

CCJPollEncoder encoder1; //instantiate


int main() {
    
   stdio_init_all(); //initialize printf
   encoder1.init(10,15); //initialize GPIO pins
 

//In a main{} infinite loop:

            if (encoder1.check())  // we call CCJ class method
            {
               int a = encoder1.value();     
               printf("Value is: %d \n",a);  // print out inc/dec                                                 //value via serial
            }

For Christer's "CCJPollEncoder" library I initialized two GPIO pins and ran two function calls; when check() returned a true, value() picked up an incremented or decremented value based on the rotary encoder's state. 

But, with serial I/O working again, could I make it go? 




At first I saw nothing, no serial data, no increment, no decrement, even when the PICO could stream "hello wheaties" all day long running other firmware.

I reached out to Christer for help. 

He reviewed my main.cpp file and suggested removing gratuitous LED flashes I put into main() since they might be clobbering the serial stream.
  
He emailed me this clever "only flash your LED when nothing else is going on" snippet to use instead:

//before main

uint64_t blinkTime = 300000;
bool ledState = 0;

//inside main()

absolute_time_t lastBlink = get_absolute_time();

while (2) {
        absolute_time_t curTime = get_absolute_time(); // current                                                             //time
        // Check time since last blink, if time since last blink is          //larger than blinkTime, then blink.
        if ((absolute_time_diff_us(lastBlink, curTime)) > blinkTime)
        {
            lastBlink = get_absolute_time(); // update blink time
            ledState = 1-ledState;                 // Flip ledState                                                     //between 0 and 1
            gpio_put(LED_PIN, ledState);    // update led

(Wow! This is cool. A million and one uses--Telling you: Christer has the skills to pay the bills.  ).

With Christer's improved LED flash mini-algorithm the CCJEncoder class worked immedately and worked great.  

I could see the rotary encoder's output increment (CW) and decrement (CCW).  

And best: the encoder debounce was--perfect.  

I mean Abso-frigging--lootly--perfect. Never a false positive. never an issue.  Never a missed beat. Perfect!!!

update 8-24-24: I ported Christer's "ISR" class to RP2040; tested it working as well. If you want to use RP2040 GPIO interrupts to grab encoder data, vs. infinitely polling, you'll want this version--get the C++ .h file and example main.c at github: here.
 


"seems working"

 

BUILDING A DEV BOARD FOR YER PICO DEV BOARD

If you follow this blog you know I hate breadboarding, so I moved the project to a printed circuit board, adding functionality to the PICO, fabricated by my ever patient and supportive sponsor, PCBWAY.  

 
Shout out to PCBWAY for sponsoring this blog. If you have PCB, 3D printing, metalworking, you name it, needs, please check 'em out.





Populated--working!


OUTTRO


There is a long way to go with this one...I want to code a simple encoder driven user interface for the OLED for the ongoing RP2040 frequency reader project.  

All of this in the middle of busy times at my day job...but I will get it done right?

"burned up sir"

One more tidbit before going back to work...in the midst of my issues with unreliable serial ports I took no mercy on one of the 4 misbehaving boards.

I got out my rework station, melted the crap out of a PICO, then literally threw the tiny thing out the window.  

This didn't make me more productive, but it felt pretty good. 

I had a tech boss long ago who liked to say: "anything can be fixed". 

Sorry, not this time.

1 comment:

  1. That poor Pico looks like it went through hell! Glad you were able to endure the trials and get something working. Which is good news for future products.

    ReplyDelete

4 x 1 Mono Mixer--A Confidence Builder

Readers: If you want to build the project featured in today's post, please go to PCBWAY's Community pages--gerber file (main board);...