Saturday, June 19, 2021

Embedded C--AVR--Get an OLED display working (SSD1306 I2C Driver) (and) Comparator Application Thoughts

I had so much fun last time getting a TFT working with AVR embedded C that I thought why stop there, let's get an OLED display going as well. 

There are differences between the two display technologies (read more about that here); but OLEDs are so cheap and look so good you just have to throw one into your next project, right? 

Right???

"Let's get started!"

SSD1306 AVR C LIBRARY

The main chip found in my junkbox OLEDs isn't the same IC we coded for last time; these cheap I2C OLED displays use an SSD1306 driver. So, I had to find a C library for the 1306 (or write it myself, but who has time for that?)


Good news--there are many C and C++ libraries for the SSD1306 family; two that are popular and full featured can be found here and here.  

However I ended up using one by preston-Sundar as it seemed simple and had methods to do everything I usually need. Get that library from Github, here.




I used Atmel studio 7 to craft preston's demo main.c and then added his necessary supporting code; his code compiled and flashed without issue, and I was up and running with the POC you see above in about 20 minutes. So--it works! Cool!

COMPARING COMPARATORS

We have a working OLED display--what to do with it?  A sythesizer control voltage comparator?

Why/why not? I bounced ideas off my friends at my local synth geek club.   

Some of their thoughts:
  • Should I design and build an audio window comparator at all?  "We never use them....don't bother." Off to a bad start. 
  • If you do build one, its inputs have to be bipolar (see the "Bowal" subcircuit, post is here)
  • If you do build one, the output(s) should be -10V to 10V or so, not 0-5V 
  • Other, smarter techs (this one!) already make one for sale--again: why make this at all?
In spite of the lack of enthusiasm I jotted down ideas to create a full featured window comparator module with this 1306 display, the 10 bit A-Ds built into the Atmel 328, and an MCP4922 DAC.  But the 4922 is unipolar, these guys are challenging me to implement bipolar output....how?

BIPOLAR DAC--AD5721

Throwing money at this, I bought a few AD5721's  DACs which support +/-5V at output, and require twos complement math, and probably have other design challenges, so even if the comparator module turns out being a waste I will have worked on something new and (hopefully) learned something.  

I'll do an upcoming post and find or create an AVR C library for the AD5721 if I have time. Stay tuned.

Update: 9-2-21 After more work at the bench I will need a faster microcontroller for this project, probably an STM32, see the initial post for that here. The AD5721 library remains unfinished....

Here is a very quick mock up of a potential front panel for the comparator:


Here is a general main.c to make sure the OLED works at all:


#include <avr/io.h>
#include "i2c.h"
#include "SSD1306.h"

int main(void) {
  
    OLED_Init();  //initialize the OLED
    OLED_Clear(); //clear the display (for good measure)
    
    while (1) {
        
        OLED_SetCursor(0, 0);        //set the cursor position to                                         //(0,0)
        OLED_Printf("Hello World!"); //Print out some text

    }
    
    return 0; // never reached
}


Here is working main.c code for the comparator OLED mockup:


#include <avr/io.h>
#include "i2c.h"
#include "SSD1306.h"


int main(void) {
OLED_Init();  //initialize the OLED
OLED_Clear(); //clear the display (for good measure)
while (1) {
// this is a mock up.  Other than the OLED nothing                     //works.
    OLED_SetCursor(0, 0);      //set the cursor                                                    //position to (0, 0)
    OLED_Printf("AudioDIWHY Comparator"); //Print out                                                         //some text
    OLED_HorizontalGraph(1,50);
    OLED_SetCursor(3,103);
            OLED_Printf("IN A");
            OLED_HorizontalGraph(3,30);
            OLED_SetCursor(7,103);
            OLED_Printf("IN B");
    OLED_SetCursor(5,0);
    OLED_Printf("Mode A < x < B");
    OLED_SetCursor(5,103);
    OLED_Printf("2.2V");
}
return 0; // never reached
}


Which begs the question: should this project be built in the digital domain?  If so, it could have more whiz bang features with fewer components, but nevertheless this isn't a difficult analog circuit to conjure.  

I did a quick back of the napkin sketch of how to do this with an opamp--lots of mistakes, and no, the NPN transistors are not a realistic choice, but this is a napkin:



..,.and then simulated it using circuitlab:


The sim "works", but in the real world it would need more buffering, and a bipolar CV source for the inverting terminal of OA2.  

I might quickly layout a board based on a quad op amp, maybe 3 of them could sit behind a 2u Frac panel?  Then see if I have any real use for it?

Also--in the analog domain this could also be done with comparator ICs like this one. At the end of the day doing a basic comparator in analog is easy.

Update: I have a basic analog comparator working, roughly based on the simulation above; see the post here--but it has not proven useful in my mod synth setup. Not sure if I will pursue this any further at this point. 

OK--"not sure what I will do next".  But my initial goal--to get some OLEDs I had lying around working on my bench, using nothing but embedded C, worked right away, so I had time to kill; an AVR window comparator would be fun to build, but in reality may get little use. Asi es la vida.

Sunday, June 13, 2021

Embedded C--AVR MCU--Getting a TFT Display Working

 In the recent Arduino days, getting a small display (OLED, TFT, etc.) to work was easy:

  • Find a display with a decent Arduino sketch-ready library
  • Buy the display component for next to nothing
  • Download and install the library into the Arduino IDE (how-to here)
  • Study the display's C++ library code and its datasheet to understand how it really works **NOT**
  • Mess with examples/read tutorials
  • Bang on your sketch until your display's output looks pretty good
  • See your display in action
  • Baffle + scare your girlfriend

  • (Done!)

You can read a previous post: the "Arduino way" to do all this--here

I've moved on, having temporarily abandoned the Arduino sketch language and its familiar IDE for embedded or "Pure C", and lost a ton of blog readers in the process. 

My psychiatrist girlfriend remains--puzzled? 

Oh well.

If you're still with me, OK, we are no longer using Arduino, but we still need displays in our audio DIWHY projects--what to do?  

Dev Tools: As per my last few posts: I am using Atmel Studio 7 as my IDE, and Atmel ICE programmer, and (most recently) stripped down Atmel 328P dev boards of my own creation (post here, schematics and gerbers here) for writing and testing embedded AVR code. 

Atmel Studio 7 supports Arduino libraries (read more here--useful!), allowing us to use things like Adafruit's very popular GFX display library or PJRC's Teensy display library (here) without leaving Atmel's IDE, but what fun would that be?  

I wanted to go all-C, meaning no use of the arduino.h library at all!

Fortunately there are other open source libraries designed for coders using only Embedded C.  

This time we'll dig into one of these libraries, created by the "Long Hair hacker" then by "Barskern".  it supports TFT displays (1.44" and 128 x 160) using the popular 7735 TFT display controller chip.  

You can get this library from Github, go here

Reading through its Github page and open source code: this is a port of the aforementioned GFX library but stripped to its bare bones and written in C, not C++.  Sounds great!

Motorizing our pursuit:

Long Hair Hacker's code is primarily targeted at a specific TFT controller IC, the 7735; get the datasheet here. Fair warning: the 7735 chip is quite complex; it supports several interface protocols (6800 MPUs, IIC, SPI, etc.)  The display manufacturers usually choose only one and exposed its pins to the outside world; in my case, the board I'm using this time--a no-name clone from China--was wired up to work only with SPI.

A post where the author digs into a bit more into "how this 7735 chip works, really" is here; he gets the 7735 going with a Raspberry Pi's, not an embedded processor, but the basic ideas are the same.

My takeaway after skimming this article and the 7735 datasheet: I didn't want to write my own library; it would take too long....

I downloaded the Long Hair hacker library and loaded the example files into Atmel Studio. So far: easy.  

I compiled it--wow, no errors, no warnings! 

But when I sent this to the test rig, I got nothing.  Hello? 

Maybe it's my minimalist AVR board so I substituted a known/good UNO clone on my bench....same issue.

Turns out it was a wiring issue. To get this to work, I had to wire it on the bench as per the table below; and what is silk screened on my super affordable clone TFT display board confused me at first ("SDA" for MOSI? Oh, OK: "Serial DAta.".   

But !(Why), !Scotty, !Why?

Anyway, Here's the wiring that worked:

TFT          MPU       (or)   UNO 

VCC          5V  |            5V

GND          GND |            GND

CS           PB2 |            UNO pin 10  SS

RESET        PD7 |            UNO PIN 7

D/C          PD6 |            UNO PIN 6

SDA          PB3 |            PIN 11  MOSI

SCL          PB5 |            PIN 13   CLOCK

LED          N/C |            N/C


The column on the left is what is silkscreened on my clone TFT 128x160 display.  Your display may vary....

The center column is the pin used on an Atmel 328 MCU or "minimal" MCU.

The right column shows pins used on an UNO R3.

Also, the Long Hair Hacker/Barskern  github repository lists the DC pin on the display as "AD0" which threw me for a bit....as did the "D/C pin" itself.  What is that?  It's a logic pin needed by the 7735; it lets the display controller distinguish what is data and what are control commands. Without wiring up "D/C" the display won't work.

Fortunately for everything else the library handles all the heavy lifting.

Once wired up correct, by George, it works!  


But! what is up with the green column on the left?  

The github page says we need to modify the provided code to choose the correct display, so i figured I had the wrong one chosen, hence the green bar.  

After some trial and error, I found I had to change this in the 7735.h header file: 

//static const enum ST7735_DISPLAY_TYPE st7735_type = ST7735_RED144_JAYCAR;  COMMENT THIS!

static const enum ST7735_DISPLAY_TYPE st7735_type = ST7735_RED_18_GREENTAB;  // add this!

I think "Greentab" might mean the color of the removal tab on the clear protective cover sheet the display ships with (really?), and whatever a "JayCar" is, I don't have one. Recompiled and uploaded again; the green bar is gone.

I also added by CPU speed to Atmel Studio as a constant, see the vid here.  

OK, I compiled again and uploaded to the UNO.

Now the display looks a lot better:


.....but the font color is wrong; the code says this text should be cyan--it's not. 

i fixed this by changing some of the color definitions....BTW, I still don't understand how 2 bytes hex you see here maps to the 18 bits (3x 6bits in 3 bytes) needed by the 7735 for color, but figuring that out is for another time or even better, never.

here is what worked for me....edit 7735.h:

enum ST7735_COLORS {

ST7735_COLOR_BLACK = 0x0000,

// ST7735_COLOR_BLUE = 0x001F,

// ST7735_COLOR_RED = 0xF800,

ST7735_COLOR_RED = 0x001F,

ST7735_COLOR_BLUE = 0xF800,

ST7735_COLOR_GREEN = 0x07E0,

//ST7735_COLOR_CYAN = BLAH

//ST7735_COLOR_YELLOW = 0xFFE0,

ST7735_COLOR_CYAN = 0xFFE0,

ST7735_COLOR_YELLOW = 0x07FF,


ST7735_COLOR_MAGENTA = 0xF81F,

ST7735_COLOR_WHITE = 0xFFFF

};


Which fixed the color issue.

The last thing I worked on is the fonts--or should I say font--the library comes with just one. At its smallest display size it's still too big to use for some of my applications. So it'd be nice to fix this, or at least understand how the fonts really work so I can create a new smaller font for this library.

Let's dig in.....

The file we need to review is "free_sans.h".

It consists of two large consts; an array of the actual font data and another const that maps letters from your program to the font lookup table. 

I messed around with this a bit and found that you can change things pretty easily by changing columns of the GFXglyph table.

the declarations for structures for this can be found in st7735.h; here is one we care about:

typedef struct { // Data stored PER GLYPH

uint16_t bitmapOffset;     // Pointer into GFXfont->bitmap

uint8_t  width, height;    // Bitmap dimensions in pixels

uint8_t  xAdvance;         // Distance to advance cursor (x axis)

int8_t   xOffset, yOffset; // Dist from cursor pos to UL corner

} GFXglyph;

What this means:

  • left column: the starting position for the letter or symbol in question in the FreeSans_Bitmaps array. so, the offset for the letter in question; you are sifting through the large array FreeSans_Bitmaps[]. Note: If you duplicate a row to the freesans array it creates serious problems with the lookups, which proves in my mind we are indeed dealing with an offset?
  • Next the width (column 2) and height (column 3) of the letter or symbol we want to draw.
  • The last two columns tell the display where to position the cursor for the next font. For the Y axis, negative values means "go up along the Y axis".  This appears to be included so we can have letters like "j" that dip below the current line and not cause the next font to dip as well.

By altering this data we have control of how each FreeSans letter looks, and where the following letter starts:

The small block used to be the "# symbol.....I  put 0xFFs in the array for #, drawing a white box instead of a tick-tack-toe sign. Yep, it works.


There is another method to draw in the pixels "Behind the font" whichI didn't dig into much, but if I create a new font I'll need to understand that. 

Overall, for anyone who has time, it would be possible to change or augment the freesan.h code to create a new font (smaller or otherwise) but for now I'll live with what is provided.

One more thing: the library uses a AVR programming paradigm called "program space utilities"; e.g.: PROGMEM, which I hadn't heard of before.  This allows flash data to be written into and out of RAM as needed. The 328P has not too much RAM, so this is very useful.  I'll dig into that in a future post.

The library also makes extensive use of pointers, including something I've not seen before: pointers to structures. This is basic C, amigos. My "HOWTO" cheat sheet on pointers is here; I provide very simple examples of this interesting programming practice, mostly for myself. I should have used progmem and structure pointers for the DLFO project but I didn't know about them. Next time, Live and learn, but down the road,  right?  

See ya next time.




JTAG to SWD Converter

Readers: If you'd like to build the project featured in today's post, please go to PCBWAY's Community pages--gerber file, KiCAD ...