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.




No comments:

Post a Comment

EFM/KORG770 VCF--BUILD: yes, SOUND: no

Ahoy!  This time, I wanted to refine the quick prototyping idea discussed midway through this previous post : Minimal breadboarding--I hate ...