For upcoming projects I want to include SSD1306 based displays for RP2040 based audio projects. Easy in Micropython, but what should we use for Embedded C?
|
No-name, super cheap I2C SSD1306 OLED display. Gotta love the umlaut in "menu"! |
I found a really good C library for this application. written by David Schramm, here. It gives us useful 1306 display functions without extra bloat.
This time: I get this library working on the bench: different strings showing up on the OLED based on the position of a rotary encoder.
I also get additional font.h files working with the library, from content found here.
|
Working on the bench.... |
Use your Library Card!
This is the same idea as the Atmel 328 SSD1306 C library described in
this post, but with a different MCU.
I could have written my own library for the 1306 OLED but didn't have time, so I used
this library's abstrations instead.
Documentation for this SSD1306 I2C C RP2040 library are
here.
Writing the code
First up was to get the example code working which was really easy.
I downloaded the library and code examples
here and loaded it into my usual toolset: Pico/Pico SDK and VSCODE, running on an Ubutu VM--posts for my RP2040 C toolchain begin
here.
The library's provided test code compiled first time with no issues--impressive! I had this all up and running in about 15 minutes. The example code showed some basic graphics and a few different fonts whizzing by on the OLED.
So far, so good!
On to see if I could get a rotary encoder to display "CW" and "CCW" as the encoder was rotated, well, clockwise and counter-clockwise ("anti-clockwise" in the UK, which is a much better phrase).
I had trouble with this, and it was my own fault.
Here we go....
Encoders and Hardware
I bought a bag of surplus top-shelf ALPS encoders that had incorrect documentation. These were high quality encoders at a ridiculously inexpensive
alibaba price--but as I say so often--what is my time worth really?
Turns out its pins were wired up the same as every other 3 pin encoder I have ever seen. The included photocopied documentation was just plain wrong.
Doh!
|
High quality, surplus encoders. Usually cost $3.25USD or so each, I paid something like .25c USD each. How could I fail? |
|
Sketchy Xeroxed documentation that came with the surplus ALPS parts (no designation of which ALPS encoder was on the part) had the ground on the leftmost pin--nope. Above is the correct wiring and conforms to most encoders out there. ARRRG!!! No, I can't show you a scan of the incorrect documentation, because I lit a match and burned it.
|
Wiring used to test the OLED from a rotary encoder. For power: connect 3.3V bench power to pin 39/VSYS ("-2" on some RP2040 pin diagrams); bench GND to RP2040 38/"-3"; 1306 Vcc pin to regulated 3.3V power on RP2040 (36/"-5"). PICO Pin1 is TX; hook it to RX on your serial reader.
|
|
|
Breadboard test setup |
Next it's on to this post's stupid coding mistakes.
It had to do with how I was reading the encoder.
But first, I need a few minutes to talk about how the encoder was being read by my test code....
A line of code in main.c's infinite loop sensed if the encoder state had changed at all; then, saw if A and B were the same or different. If they were the same, the encoder was turning one way, if different, the other. Read more
here, this is a pretty standard "read the encoder" algorithm. Easy, right?
Not for me. I used the UART based reboot code found in a previous post (
here), but the UART reboot code has a
scanf()which made the loop break. Until this was commented out, the encoder didn't work at all.
Time wasted: twenty minutes?
However it was the next dumb mistake that cost me an entire weekend day.
I put a sleep_ms(200) into the main loop. The idea was to...well I am not sure really why I put that in there! This made the encoder's outputs almost always read "both 1s or "both 0's"....so, the sleep statement broke things. After sleeping the main.c woke up and queried the rotaty encoder to see if the encoder state had changed but it was--too late!! I had to catch the encoder's lows and highs right when they change...which means the main.c loop had to always run unimpeded.
Took me a while to find the errant single line of code and get rid of it.
I will go into more detail about encoders and RP2040 in a future post. The encoder read really should to be done with interrupts, not a loop. But not now--fixed--onward.
OK, with the encoder sorted, it was time to move on to the OLED itself.
CHANGING AND MANAGING FONTS
There is a string function in the library that allows you to argue in the font you want to use--cool!
ssd1306_draw_string_with_font(&disp, 8, 24, 2, fonts[2], str1);
And the sample library provides 4 fonts, but 3 of them weren't that useful to me, more designed to show off some of the odd characters you can display on a 1306 vs. things I would use in a project.
Good news, you can get more fonts for the library, using content found
here.
Even better, the fonts repo has PNG files that show how the fonts will look on the OLED once compiled. Very cool!
You have to edit the .h files a bit to make them work with the library--not too hard--here are my bench notes describing how to edit them:
/* how to use font.h included here
See https://jared.geek.nz/2014/jan/custom-fonts-for-microcontrollers#drawing-fonts
--#include the font in main.c
#include "fivexfive_font.h" //name of edited font file
--call the font in main.c (add it to fonts array
so add the font file (without the .h!) at the end of this array in main.c
const uint8_t *fonts[6]= {acme_font, bubblesstandard_font, crackers_font, BMSPA_font, renew_font,fivebyfive_font};)
then call it: fonts[5] eg:
ssd1306_draw_string_with_font(&disp, 8, 24, 2, fonts[5], str1);
You also have to modify the .h font files to work at all, or else the imported font files won't compile.
In general, Use existing [font.h] files included with the 1306 library as an example of how to format the new .h files:
in the font.h file get rid this line:
const unsigned char font[96][6]
and use this instead
const unsigned char fivebyfive[ ] = {
where fivebyfive is the name of the font file without the .h extension, elmo!
for the first row use this
8, 7, 0, 32, 126,
where 7 is the number of columns in the array (usually 6 or 7) and the 0 is extra # of spaces you want between letters.
Get rid of the extra curlies in the guts of the array data,i.e., all the rows with 0x00, 0x70, 0x00,0x00, 0x70, 0x00 but leave a single curly at the beginning and a curly and ; end of each font
.h "how will the letter look?" data.
save everything and compile
Test it.
*/
DEBOUNCING THE ENCODER
It became obvious quickly that some better debouncing of the encoder was needed. The CW and CCW track some of the time, but not all the time..
I found an interesting "software filter" idea (for Arduino--but should work for any MCU)
here.
My adaptation of this:
static uint16_t stateA=0,counter=0;
stateA=(stateA<<1) | gpio_get(ENC_A) | 0xe000;
if (stateA==0xf000)
{
//trap encoder stuff here.
}
StateA fills up LSB to MSB; then we see if the LSB values are jumping around.
If so, don't do anything. Once it's stablized, proceed.
I have had mixed results with this code addition. An RC filter is still needed to get rid of chatter and only slower turns of the encoder are seen. I continue to bounce around.
OUTTRO
In case you missed it--you can get the code I used in this post from github,
here.
|
Victory! |
With this set, the "CW" and "CCW" strings look good in various fonts. I still had some encoder issues however as I describe above. Most of the time the correct string is shown on the OLED, I figure when it displays incorrect data it is because better debouncing for the encoder is needed.
Again I will dig into encoders and encoder debouncing in greater detail in a future post.
Until then, code well and live!