If your seeing this page without having set up a TFT screen with Arduino or NodeMCU then first see either of these two guides
Colour LCD with NodeMCU ESP8266
There is also a supporting video for this page:
So we have a colour LCD on which (thanks to various graphics libraries) we can draw lines, triangles, text etc. But adding colour graphics is possible but information (at time of writing) is rather limited. There are various examples of pulling images from an SD card (either built onto the display board, or added separately), but what if you just want a few icons etc? Having spent some time doing research most guides mentioned using GIMP to convert the image to text data you can use in your program but the data being outputted was not suitable (I noted others having similar problems) without further messing about with it. Despite following the guides to the letter I could not get the data into my program in a format that would work. However further research eventually turned up a web page called RinkyDink Electronics (http://www.rinkydinkelectronics.com). This site deals in LCD modules and they have written a handy converter to change your graphics (in png, jpeg and gif format) to a raw format that the LCD screen understands.
Software you will need
You will need to appropriate driver for your screen, in this case for the ST7735 based screens you can use my altered Adafruit driver (see this article as to why it had to be altered slightly)
If you are using a different screen with different driver chip on board then obviously you will have to add your own library for that screen.
You will also need the Adafruit graphics library, even if you’ve already got it you need to make sure your version supports the drawRGBBitmap function, if your not sure then remove the one you have and install the latest one from
With these two installed we can move on…
The format of most LCD Colour screens.
Most cheap screens will be advertised as having around 64,000 to 260,000+ colours. It’s usually up to the software driver to select a range and for small MCU’s that is usually 65536 colours (due to memory and speed issues of the SPI bus and MCU). That means each individual pixel can be one of any of these colours. For each single pixel we wish to control we need to send the appropriate number to the screen. To store a number that could be anything between 0 and 65535 we need 16 bits or two full bytes (1 byte being 8 bits). If you are not confident in bits,bytes and hex it may be an idea to look at some tutorials on line or if you wish, skip ahead to “Converting Images” section as the technical details are not essential to getting graphics onto your display.
The Primary Colours
Our eyes are only actually sensitive to three basic colours; red,green and blue; having separate receptors at the back of the retina for these colours. But mixing these three colours together in different intensities gives us all the colours we are used to that we see every day. Displays (whether it’s your TV or phone or a small LCD) have three tiny colours on the screen per pixel (red,green and blue or RGB). They are so small that you will need a good magnifying glass to see them. By varying the intensity of these it too can display many colours. The amount of colours depends on the different intensity levels it can set the small colour segments to that together represent a single pixel. If each colour segment only 2 levels, i.e. either on or off, then the maximum number of different colours would be 8. Look below at the three bits, one each for red, green and blue.
RGB
0 0 0 : All off, would be black
0 0 1 : Just Blue
0 1 0 : Just Green
1 0 0 : Just Red
0 1 1 : Green and blue gives Cyan
1 0 1 : Red and blue gives Magenta
1 1 0 : Red and Green gives Yellow
1 1 1 : All switched on gives White
And that’s all possible values of these 3 bits. If we used 2 bits per colour then we would need 6 bits per pixel which takes up more memory but means we can then have four different intensities of each colour (RGB) giving a range of 64 colours, decimal values 0 to 63. Back in the early 80’s memory was more limited and that’s why you often see systems with maximum colours between 8 and 16.
So we said that typically these cheap screens can handle 65536 colours (0-65535). This requires 16 bits to store (or two 8 bit bytes). But if we’ve got 3 colours (RGB) then that doesn’t divide equally! And no, it doesn’t so they set aside 5 bits for red, 6 for Green and 5 for blue adding up to 16 bits in total. So red and blue have 32 different levels of intensity but green has 64. Apparently the reason for giving green the best deal is because biologically our eyes can see more different shades of green than we can for red and blue, so having more levels for green makes sense.
So each pixel on our screen needs two bytes to be able to display the colour and they are laid out as follows (in what is termed RGB565 format or sometimes just 565 format).
MSB and LSB stand for Most Significant Byte and Least Significant Byte respectively. When you send these two bytes you need to send them either MSB first or LSB first and it all depends on what the device your talking to requires, if it wants MSB first then that’s what is sent over the SPI. This is a level of detail that you need not concern yourself with, you only need to appreciate that each pixel of colour on the screen requires two bytes to define the colour that it is.
The Code
Below is our sample skeleton code (do not copy this yet as it will not do anything!).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include <Adafruit_GFX.h> // Core graphics library #include <XTronical_ST7735.h> // Hardware-specific library #include <SPI.h> // set up pins we are going to use to talk to the screen #define TFT_DC D4 // register select (stands for Data Control perhaps!) #define TFT_RST D3 // Display reset pin, you can also connect this to the Arduino reset // in which case, set this #define pin to -1! #define TFT_CS D2 // Display enable (Chip select), if not enabled will not talk on SPI bus Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); void setup(void) { tft.init(); // initialize a ST7735S chip, tft.setRotation(0); // values 0-3 set to 1 of 4 different orientations tft.fillScreen(ST7735_BLACK); // clear to black } void loop() { // do nothing here for the demo } |
We now need to get our image data into our program.
Converting Images
So most of your graphics will be in one form or another, perhaps jpeg, if, png etc. etc. We need to get these into the format the display can work with, so they need converting. The format required is known as RGB565 (mentioned above if you read that section). There are several guides giving instructions to do this using the popular free “GIMP” image editing package. However I’m not sure if something has changed within the package itself or something else but the format output could not be put into our code and work with LCD screens. Further searching revealed this website
http://www.rinkydinkelectronics.com
On this site you will find an online converter tool, but at time of writing it had a small error (which can be worked around). It also had an offline tool which didn’t suffer from the online tools error. The direct link to the online tool is
http://www.rinkydinkelectronics.com/t_imageconverter565.php
So for our example we are going to use the following graphic;
Download this graphic to your computer. It’s a simple save icon, still associated with a floppy disk 🙂 We want this to display on our screen wherever we want, so we need to embed the data for it (in RGB565 format) into our code. So choose whether to use the online tool or download the suite of programs. This is called UTFT.zip and is available on this page
http://www.rinkydinkelectronics.com/library.php?id=51
Upon extracting this zip file the converter is located at \UTFT\Tools\ImageConverter565.exe
For our demo I will use the online converter tool (as some people will reluctant to run “exe” programs from the internet). On this web page (http://www.rinkydinkelectronics.com/t_imageconverter565.php) click “Choose File” and choose the “Save Icon” file you downloaded from this site earlier. Click “Make File”, this will take you to another page, click “Click here to download your file“. It will download as “name.c” where name is the name of the image file you uploaded (“DiskIcon” if you used mine). Open this file in your text editor of your choice and you should see this code;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// Generated by : ImageConverter 565 Online // Generated from : DiskIcon.png // Time generated : Sun, 22 Oct 17 16:31:14 +0200 (Server timezone: CET) // Image Size : 16x16 pixels // Memory usage : 512 bytes #if defined(__AVR__) #include <avr/pgmspace.h> #elif defined(__PIC32MX__) #define PROGMEM #elif defined(__arm__) #define PROGMEM #endif const unsigned short DiskIcon[256] PROGMEM={ 0x8D38, 0xF7DF, 0x53D7, 0x4397, 0x3B57, 0x3B57, 0x3B57, 0x3B57, 0x3B57, 0x3B57, 0x3B57, 0x3B57, 0x4397, 0x5C37, 0xA578, 0xC618, // 0x0010 (16) pixels 0x74B8, 0xCEFE, 0xCEFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xBE7D, 0x6479, 0x9578, // 0x0020 (32) pixels 0x4BD7, 0xCEFE, 0x7D7C, 0xFFFF, 0xFFFF, 0x6479, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xBE7D, 0xBE7D, 0x4397, // 0x0030 (48) pixels 0x4397, 0xCEFE, 0x7D3C, 0xFFFF, 0xFFFF, 0x6479, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7D3C, 0xC6BD, 0x3356, // 0x0040 (64) pixels 0x3B57, 0xCEFE, 0x7D3C, 0xFFFF, 0xFFFF, 0x6479, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7D3C, 0xAE3C, 0x3356, // 0x0050 (80) pixels 0x3B57, 0xCEFE, 0x7D3C, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x74FB, 0xA5FC, 0x3316, // 0x0060 (96) pixels 0x3B57, 0xC6BE, 0x7D3C, 0x6479, 0x6479, 0x6479, 0x6CB9, 0x6CB9, 0x6CB9, 0x6479, 0x6479, 0x6479, 0x6479, 0x74FA, 0x9DBB, 0x3316, // 0x0070 (112) pixels 0x3B57, 0xC6BE, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x74FB, 0x74FA, 0x74FA, 0x74FA, 0x74FA, 0x9DBB, 0x3316, // 0x0080 (128) pixels 0x3B57, 0xC6BD, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x74FB, 0x74FA, 0x74FA, 0x74FA, 0x74FA, 0x95BB, 0x3315, // 0x0090 (144) pixels 0x3B57, 0xBE7D, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x74FA, 0x74FA, 0x74FA, 0x74FA, 0x74FA, 0x74FA, 0x8D7A, 0x3315, // 0x00A0 (160) pixels 0x3B57, 0xBE7D, 0x7D3C, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x6CBA, 0x8D3A, 0x3315, // 0x00B0 (176) pixels 0x3B57, 0xB67D, 0x7D3C, 0xFFFF, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0xFFFF, 0x6CB9, 0x8539, 0x3315, // 0x00C0 (192) pixels 0x3B57, 0xB67D, 0x7D3C, 0xFFFF, 0xC6F7, 0xC6F7, 0xC6F7, 0xC6F7, 0xC6F7, 0xC6F7, 0xC6F7, 0xC6F7, 0xFFFF, 0x6CB9, 0x84F9, 0x3315, // 0x00D0 (208) pixels 0x3B57, 0xB67D, 0x7D3C, 0xFFFF, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0xFFFF, 0x6479, 0x7CF8, 0x3314, // 0x00E0 (224) pixels 0x4B97, 0xAE3C, 0xAE3C, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7CF8, 0xDF1E, 0x3B55, // 0x00F0 (240) pixels 0xDF1E, 0x6437, 0x4397, 0x3356, 0x3356, 0x3316, 0x3316, 0x3316, 0x3315, 0x3315, 0x3315, 0x3315, 0x3315, 0x3314, 0x3B15, 0x53D5, // 0x0100 (256) pixels }; |
Note: The error with the online tool mentioned earlier (if it’s not been fixed at time of writing) only occurs for graphics of certain widths. The width of this graphic is OK and will convert fine.
The data above consists of all the pixels and their colour definitions as two bytes together (a short integer). Altogether there are 256 shorts (16 pixels across by 16 down), giving a total of 512 bytes – as there are two bytes for every short. If you are using the Arduino or NodeMCU (within the Arduino IDE) then you can ignore the commented lines. Here is the completed code below that you can transfer to your system.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
#include <Adafruit_GFX.h> // Core graphics library #include <XTronical_ST7735.h> // Hardware-specific library #include <SPI.h> // set up pins we are going to use to talk to the screen #define TFT_DC D4 // register select (stands for Data Control perhaps!) #define TFT_RST D3 // Display reset pin, you can also connect this to the Arduino reset // in which case, set this #define pin to -1! #define TFT_CS D2 // Display enable (Chip select), if not enabled will not talk on SPI bus Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); const unsigned short DiskIcon[256] PROGMEM={ 0x8D38, 0xF7DF, 0x53D7, 0x4397, 0x3B57, 0x3B57, 0x3B57, 0x3B57, 0x3B57, 0x3B57, 0x3B57, 0x3B57, 0x4397, 0x5C37, 0xA578, 0xC618, // 0x0010 (16) pixels 0x74B8, 0xCEFE, 0xCEFE, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xBE7D, 0x6479, 0x9578, // 0x0020 (32) pixels 0x4BD7, 0xCEFE, 0x7D7C, 0xFFFF, 0xFFFF, 0x6479, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xBE7D, 0xBE7D, 0x4397, // 0x0030 (48) pixels 0x4397, 0xCEFE, 0x7D3C, 0xFFFF, 0xFFFF, 0x6479, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7D3C, 0xC6BD, 0x3356, // 0x0040 (64) pixels 0x3B57, 0xCEFE, 0x7D3C, 0xFFFF, 0xFFFF, 0x6479, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7D3C, 0xAE3C, 0x3356, // 0x0050 (80) pixels 0x3B57, 0xCEFE, 0x7D3C, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x74FB, 0xA5FC, 0x3316, // 0x0060 (96) pixels 0x3B57, 0xC6BE, 0x7D3C, 0x6479, 0x6479, 0x6479, 0x6CB9, 0x6CB9, 0x6CB9, 0x6479, 0x6479, 0x6479, 0x6479, 0x74FA, 0x9DBB, 0x3316, // 0x0070 (112) pixels 0x3B57, 0xC6BE, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x74FB, 0x74FA, 0x74FA, 0x74FA, 0x74FA, 0x9DBB, 0x3316, // 0x0080 (128) pixels 0x3B57, 0xC6BD, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x74FB, 0x74FA, 0x74FA, 0x74FA, 0x74FA, 0x95BB, 0x3315, // 0x0090 (144) pixels 0x3B57, 0xBE7D, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x7D3C, 0x74FA, 0x74FA, 0x74FA, 0x74FA, 0x74FA, 0x74FA, 0x8D7A, 0x3315, // 0x00A0 (160) pixels 0x3B57, 0xBE7D, 0x7D3C, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x6CBA, 0x8D3A, 0x3315, // 0x00B0 (176) pixels 0x3B57, 0xB67D, 0x7D3C, 0xFFFF, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0xFFFF, 0x6CB9, 0x8539, 0x3315, // 0x00C0 (192) pixels 0x3B57, 0xB67D, 0x7D3C, 0xFFFF, 0xC6F7, 0xC6F7, 0xC6F7, 0xC6F7, 0xC6F7, 0xC6F7, 0xC6F7, 0xC6F7, 0xFFFF, 0x6CB9, 0x84F9, 0x3315, // 0x00D0 (208) pixels 0x3B57, 0xB67D, 0x7D3C, 0xFFFF, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0x8E2C, 0xFFFF, 0x6479, 0x7CF8, 0x3314, // 0x00E0 (224) pixels 0x4B97, 0xAE3C, 0xAE3C, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x7CF8, 0xDF1E, 0x3B55, // 0x00F0 (240) pixels 0xDF1E, 0x6437, 0x4397, 0x3356, 0x3356, 0x3316, 0x3316, 0x3316, 0x3315, 0x3315, 0x3315, 0x3315, 0x3315, 0x3314, 0x3B15, 0x53D5, // 0x0100 (256) pixels }; void setup(void) { tft.init(); // initialize a ST7735S chip, tft.setRotation(0); tft.fillScreen(ST7735_BLACK); tft.drawRGBBitmap(56,56,DiskIcon,16,16); } void loop() { // nothing to do } |
and here is a photo of the disk icon on screen, click to enlarge. Please ignore the slight blurriness.
The line : tft.drawRGBBitmap(56,56,DiskIcon,16,16); does the hard work, the first two parameters are X,Y coordinates. The second the pointer to the data and the next two the width and height of the graphic.
Graphic take up lots of memory
As can be seen this simple small icon uses up 512bytes of memory (0.5k)and so we put it in program memory (using the PROGEM) directive instead of static ram (where your variables are stored at run time). Even so it wouldn’t take many graphics to seriously eat into an Arduino’s 32K program memory and so in this demo I used the NodeMCU and it’s more generous 1024K program memory. If you are serious about using embedded graphics then you need to choose a micro-controller that has enough on board memory to handle them. Otherwise you would need to look at pulling graphics from an SD card. More on that in a future article. But if speed is an issue then embedded is most likely your only option.