320x240 pixel colour TFT LCDs with the ILI9341 controller are very common but I've only found one library that takes advantage of the colour when it comes to rendering small text (anti-aliasing).
Most existing libraries are bloated or not fast enough to render an entire screen of text at a reasonable speed.
This minimalist library aims to address these issues and will be my Arduino screen driver for my Hopper project.
Hopper & Character UIThe Hopper project requires a text UI for the shell, the editor, the debugger, etc. I like the ubiquitous and affordable 320x240 TFTs and figured with a 5x10 character cell I could get a text display of 64 columns by 24 rows. This should be enough for my purposes.
I settled on two blank pixel rows and one blank pixel column between characters so the actual font cell is only 4x8 pixels.
Colour and Anti-AliasingTypical ILI9341 libraries use RGB565 (16 bits). The symmetry of RGB444 (12 bits) makes it simpler to blend foreground and background colours (anti-aliasing) so my font template uses a non-linear 16 tone palette (4 bpp). I blend RGB444 foreground and background colours using this tone palette for each pixel in the character. Then I transform from RGB444 to RGB565 for rendering.
Creating the FontI use a C# Windows Forms application on Windows to generate my font template (from Consolas) and used Hopper on Windows to create some test data (for now).
The font is captured at 4 bits per pixel in what I refer to as "tones". You can think of them as 16 shades of gray (even though they are actually not linearly mapped to 0..255 when blending colours).
Font template size: 4 bpp, 94 characters, 4x8 size = 1504 bytes.
It is easier to visualize in monochrome since blending black and white into gray is trivial:
The same principle applies when blending arbitrary foreground and background colours using the same 4 bpp font template, just with slightly more arithmetic (see blendPixelColor(..) in the source):
The resulting image is surprisingly good considering the characters are only 4 pixels wide and 8 pixels tall:
I made several reasonable assumptions for the Arduino part. For example, I'm assuming an SPI interface (not parallel) and I'm assuming hardware SPI with transaction support. My favourite little microcontroller is the Lolin Wemos D1 Mini (but it should be easy to port this to other 32 bit 3.3V MCUs).
If you are playing in the 5V Arduino UNO environment, then this project is not for you (the TFT displays are 3.3V and, while there are plenty of tutorials out there to hook them up to a 5V MCU, this is not one of them).
WiringI have several variations of TFT displays from multiple manufacturers and not only are they all labled, they all have the same pin order (so far). Since I'm using hardware SPI, I can assume that MOSI, MISO and SCK are well defined for your MCU. Here is the list of connections in LCD pin order:
- VCC - 3.3V
- GND - GND
- CS - SPI chip select (see below)
- RESET (see below) - optional, but I choose to use the hardware reset
- DC - data / control (see below)
- MOSI - connect to your MCU's hardware MOSI pin
- SCK - connect to your MCU's hardware SCK (SPI clock) pin
- LED - connect to 3.3V (LED back light)
- MISO - connect to your MCU's hardware MISO pin
There are only 3 pins that you need to select on your MCU. The rest are obvious/pre-defined. These 3 pin choices are passed to the constructor of the TinyTextTFT
driver object. I used D2, D3 and D4 on my Wemos D1 Mini:
I butchered the Adafruit ILI9341 library as a starting point for communicating with the LCD and stripped it down to the bare minimum of what I needed (only hardware SPI for example). Support Adafruit's work by purchasing your hardware from them.
This library is just two small files: TinyTextTFT.h
and TinyTextTFT.cpp
and has no dependencies except the usual Arduino SPI library. My library source is included in the github repo (see attachments) along with a sample sketch that includes some test data to display.
Place them in a subfolder of your Arduino libraries
folder like this in order for the Arduino IDE to easily find them:
From your Arduino sketch you need only include SPI and this library to get going.
The TinyTextTFT
class has very few public methods and is mostly self-explanatory.
Typical setup looks like this. The library is designed so that SPI activity is deferred till the call to Begin (the constructor only initializes members of TinyTextTFT
).
To use the screen in landscape orientation use SetRotation(1)
or SetRotation(3)
. By default it is in portrait orientation. Height(), Width(), Columns()
and Rows()
methods will return the extents of the screen in pixels and character cells respectively depending on the current orientation.
Other than that, there are only two methods to use:
FillScreen(uint16_t color)
DrawChar(uint8_t col, uint8_t row, char chr, uint16_t foreColor, uint16_t backColor)
They don't need much explanation other than that colour is defined as 12 bit RGB444 (not 16 bit RGB565).
I included some commented-out code that would be useful to extend the library as required (like writePixel(..)
, for example).
Along with the library I've also included a sample sketch in the github repo (see attachments) with some test data to demonstrate the functionality. Photographs don't do justice to how well the text renders.
In terms of speed, it takes less than 200ms to render a full screen of text on my Wemos D1 Mini. For comparison, the generic text functionality in the Adafruit library takes about 900ms to render the same text at 'scale' 1 (not anti-aliased, not quite small enough, without proper descenders and .. not as pretty).
So, I'm happy with 200ms for now. I'm hoping for a bit more speed when I port to a RP2040 MCU ..
Comments