Kinda Small Displays - Part 2

About: We are passionate programmers and embedded devices developers from Deggendorf, Germany. The AZ-Delivery Vertriebs GmbH, was established in 2016 with the purpose to offer our customers high-quality microelect...

This is part two in our series of “Introducing small displays for Arduino and Raspberry Pi”. We already created part 1 where we introduced our OLED - I2C dispalys and wrote little bit about history of lcd diplays. In this Instructable we will introduce our LCD displays, 16x2 LCD, 4x20 LCD, 128x64 LCD, Nokia 5110 LCD display, 1.77’’ TFT - SPI display, and 1.8’’ TFT - SPI display with SD card reader.

For this Instructable we will need:

We have free Quick Starter Guides for UNO R3, and for breadboard. We also have free Quick starter Guides for 16x2 LCD, 128x64 LCD, 1.77’’ display and 1.8’’ display.

Well let’s explain what is an LCD and LCD - TFT.

LCD or Liquid Crystal Display is a device that uses liquid crystals to block light coming from backlight of display. Liquid Crystals doesn’t emit light directly, but block light coming form backlight and produce images in color or monochrome. We will use I2C serial interface for LCD to communicate with first three LCD displays, and for fourth Nokia LCD we use SPI communication protocol to communicate with its driver chip.

TFT LCD - Thin Film Transistor Liquid Crystal Display is obviously variant of LCDs with Thin Film Transistor technology.. Daa. We will not go into details, but TFT improves readability and addressability of each pixel in display, thus it improving contrast. Both TFT displays we are offering are color displays, enabling 2 on 16 colors or actually 65536 colors. Both also has 128x160 pixels, and are driven by IC driver ST7735. We use SPI communication protocol to communicate with this driver chip for both displays. The only difference is SD card reader.

Ok. Many libraries that are used, show you how to draw lines, rectangles, triangles, circles, any formatted texts on display. That is so boring, but that is only we can do, because our ATMega328 microcontroller on Arduino Uno is not enough (low memory) to work with dynamic images (animations or videos). So we draw lines boxes etc. We can also display static images (bitmap) but it is so slow that is used only for examples. We will write one example for images and for it we will need external memory like SD card, SPI SD card reader or a display with SD card reader, like 1.8’’ TFT - SPI display.

We hope one day to create one instructable for easy displaying images and videos on these displays but that is one day maybe. xD

In many future projects that we are going to make with these displays, we will need line or two of text, few rectangles, and that is it. So we will cover these boxes, triangles etc. in detail in this instructable.

Logic of these TFT displays can work on 5V, but we recommend you to use 3.3V (use logic level converters). In this instructable we used four channel Sparkfun bidirectional logic level converter.

Step 1: Libraries

For two LCDs, 16x2 and 20x04 we won’t need any additional library except the ones that come with Arduino IDE by default.

For 128x64 LCD we will use U8g2lib library that we used in our previous Instructable post. There are instructions how to download it and add it to your IDE.

We will use Adafruit ST7735 library for both TFT displays. There is standard TFT library that comes with Arduino IDE which is also developed by Adafruit, but it is retired, so we will download new one. To download new library for TFT displays, go to Tools > Manage Libraries, and in search box type ST7735, and download Adafruit ST7735 and ST7789 Library by Adafruit. With it comes several sketch examples, that we will modify for our displays.

For Nokia LCD we downloaded this library for this site. With it comes several sketch examples that we will slightly modify. When you download .zip file, in your Arduino IDE go to Sketch > Include library > Add .ZIP Library… and add downloaded .zip file.

Step 2: Test Sketch for LCD 16x2 and 20x04

These two displays have predefined places for characters that they a displaying. As you can guess it, 16x2 lcd, has 2 rows and 16 columns, and each cell is one place for character. And for 20x04 LCD, there are 4 rows and 20 columns.

For testing these displays we will use libraries that come with Arduino IDE, Wire.h for I2C communication, LCD.h for lcds, and LiquidCrystal_I2C.h for I2C serial interface for LCD. Connect everything like on diagram (connection diagram is same for both displays), and copy-paste this sketch to your Arduino IDE (just uncomment first three include):

#include // <Wire.h>
#include // <LCD.h>
#include // <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27,2,1,0,4,5,6,7,3, POSITIVE);
String msg = "AZ-Delivery.de";</p><p>void setup() {  
      lcd.begin(16,2);
      //lcd.begin(20,4); //uncomment this line for LCD 4x20, and comment first line above
  
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.setBacklight(HIGH);
  
      Serial.begin(9600); // Starts the serial communication
}

void loop() {    
      lcd.setCursor(3,0);
      lcd.print(msg);    
      lcd.setCursor(3,1);
      lcd.print(msg);
      //  uncomment next 4 lines for LCD 4x20
      //  lcd.setCursor(0,2);
      //  lcd.print(msg);
      //  lcd.setCursor(0,3);
      //  lcd.print(msg);
   
      delay(1);
}

Here we first define lcd object, where we pass several arguments. Let's explain them:

First, 0x27 is a hexadecimal address of I2C serial interface, this is used for I2C communication. Next 8 numbers are for LCD: 2,1,0,4,5,6,7,3 which are pins of LCD: Enable, Read/Write, Register Select, Data 4, Data 5, Data 6, Data 7, backlight pin. The last argument is “POSITIVE” or “NEGATIVE”, and that is enumeration, used for backlight, with this we can change polarity of back light using I2C serial interface for LCD (LED+ and LED- pins on display).

In setup() function we start LCD by initializing lcd object, calling lcd.begin(16, 2) for 16x2 display, and for 4x20 display we call lcd.begin(20, 4). To clear lcd buffer we use lcd.clear() function, this clears display of any character.

To set cursor on specific location we use lcd.setCursor(0, 0) function, where first argument is X position of first character and second is Y position of character. X direction starts on left and goes to right, and Y direction starts from top, and goes to bottom. So for 16x2 display, can be from 0 to 15 and Y from 0 to 1. But for 4x20 display, X starts from 0 and ends at 19, and Y starts from 0 and ends on 3. You can use numbers outside this ranges, this will print text outside display, sometimes you want this sometimes not, so be very careful with this, because it mess things up.

With lcd.setBacklight(HIGH) we turn on backlight, and if we pass LOW as argument, it will be turned off. This works if we send POSITIVE as last argument when we create lcd object. If we pass NEGATIVE as last argument when we create lcd object, to turn on lcd backlight we have to use setBacklight(LOW), and to turn off we use setBacklight(HIGH). This is because we change polarity of backlight LED.

On I2C serial interface we have small blue potentiometer trimmer, and with it we can adjust contrast of the display. If your LCD shows only squares, or just turn on backlight, you should move this potentiometer to adjust contrast, because in most cases this is the problem.

To print text on display we use lcd.print() and pass string or char argument. Before we use this function we have to set cursor position, because default is X = 0, and Y = 0.

And that is it. You can scroll text on this displays, by changing X or Y position of cursor in one of loop functions. We will not cover this, because we cover it in our previous instructable post.

Step 3: Test Sketch for 128x64 LCD

Here we will use U8g2lib.h library. We used it in our previous instructable post for OLED displays.. So nothing new, just new constructor, for new display. Connect everything like on diagram, and copy-paste this sketch to your IDE.

#include "U8g2lib.h"
U8G2_ST7920_128X64_F_SW_SPI u8g2(U8G2_R0, /*clock=*/ 13, /*data=*/ 11, /*CS=*/ 10, /*reset=*/ 8);

const uint8_t rook_bitmap[] U8X8_PROGMEM = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xff, 0xff,
   0x7f, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0x07, 0x00, 0x00,
   0x80, 0xff, 0xff, 0xff, 0xff, 0x0f, 0x00, 0x00, 0xc0, 0xff, 0xff, 0xff,
   0xff, 0x3f, 0x00, 0x00, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00,
   0xf0, 0x0f, 0x00, 0x00, 0x80, 0xff, 0x03, 0x00, 0xf8, 0x03, 0xfe, 0xff,
   0x0f, 0xfe, 0x07, 0x00, 0xf8, 0xf1, 0xff, 0xff, 0x0f, 0xf8, 0x0f, 0x00,
   0xfc, 0xf0, 0xff, 0xff, 0x0f, 0xe0, 0x1f, 0x00, 0x7c, 0xf8, 0xff, 0xff,
   0x0f, 0x80, 0x3f, 0x00, 0x7e, 0xfe, 0xff, 0xff, 0x0f, 0x00, 0x7f, 0x00,
   0x3e, 0xfe, 0xff, 0xff, 0x0f, 0x00, 0xfe, 0x00, 0x3e, 0xff, 0x00, 0xc0,
   0x0f, 0x00, 0xfc, 0x01, 0x3e, 0x3f, 0x00, 0xc0, 0x0f, 0x00, 0xf8, 0x01,
   0xbe, 0x3f, 0x00, 0xc0, 0x0f, 0x00, 0xf0, 0x03, 0xbe, 0x1f, 0x00, 0xc0,
   0x0f, 0x00, 0xf0, 0x07, 0xbe, 0x1f, 0x00, 0xc0, 0x0f, 0x00, 0xe0, 0x07,
   0xbe, 0x1f, 0x00, 0xc0, 0x0f, 0x00, 0xc0, 0x0f, 0xbe, 0x1f, 0x00, 0xc0,
   0x0f, 0x00, 0xc0, 0x0f, 0xbe, 0x1f, 0x00, 0xc0, 0x0f, 0x00, 0x80, 0x1f,
   0xbe, 0x1f, 0x00, 0xc0, 0x0f, 0x00, 0x80, 0x1f, 0xbe, 0x1f, 0x00, 0xc0,
   0x0f, 0x00, 0x00, 0x1f, 0xbe, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x1f,
   0xbe, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x3e, 0xbe, 0xff, 0xff, 0xff,
   0xff, 0xff, 0x7f, 0x3e, 0xbe, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x3e,
   0xbe, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x3e, 0xbe, 0xff, 0xff, 0xff,
   0xff, 0xff, 0x7f, 0x3e, 0xbe, 0x1f, 0x00, 0xc0, 0x0f, 0xc0, 0x3f, 0x3e,
   0xbe, 0x1f, 0x00, 0xc0, 0x0f, 0xe0, 0x1f, 0x3e, 0xbe, 0x1f, 0x00, 0xc0,
   0x0f, 0xf0, 0x0f, 0x3e, 0xbe, 0x1f, 0x00, 0xc0, 0x0f, 0xf8, 0x07, 0x3e,
   0xbe, 0x1f, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x3e, 0xbe, 0x1f, 0x00, 0xc0,
   0x0f, 0xfe, 0x01, 0x3e, 0xbe, 0x1f, 0x00, 0xc0, 0x0f, 0xff, 0x00, 0x3e,
   0x3e, 0x00, 0x00, 0x00, 0x80, 0x7f, 0x00, 0x3e, 0x3e, 0x00, 0x00, 0x00,
   0xc0, 0x3f, 0x00, 0x3e, 0x3e, 0x00, 0x00, 0x00, 0xe0, 0x1f, 0x00, 0x3f,
   0x7e, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0x1f, 0x7c, 0x00, 0x00, 0x00,
   0xf8, 0x07, 0x00, 0x1f, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x1f,
   0xf8, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x80, 0x0f, 0xf8, 0x01, 0x00, 0x00,
   0xff, 0x00, 0x80, 0x0f, 0xf0, 0x01, 0x00, 0x80, 0x7f, 0x00, 0xc0, 0x0f,
   0xf0, 0x03, 0x00, 0xc0, 0x3f, 0x00, 0xc0, 0x07, 0xe0, 0x03, 0x00, 0xe0,
   0xff, 0xff, 0xef, 0x07, 0xe0, 0x07, 0x00, 0xe0, 0xff, 0xff, 0xef, 0x03,
   0xc0, 0x0f, 0x00, 0xe0, 0xff, 0xff, 0xff, 0x03, 0xc0, 0x1f, 0x00, 0xe0,
   0xff, 0xff, 0xff, 0x01, 0x80, 0x3f, 0x00, 0xe0, 0xff, 0xff, 0xff, 0x01,
   0x00, 0x7f, 0x00, 0xe0, 0xff, 0xff, 0xff, 0x00, 0x00, 0xfe, 0x00, 0x00,
   0x00, 0x00, 0x7f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0, 0x3f, 0x00,
   0x00, 0xf8, 0x07, 0x00, 0x00, 0xf0, 0x1f, 0x00, 0x00, 0xf0, 0x3f, 0x00,
   0x00, 0xfc, 0x0f, 0x00, 0x00, 0xe0, 0xff, 0x00, 0xc0, 0xff, 0x03, 0x00,
   0x00, 0x80, 0xff, 0x0f, 0xf8, 0xff, 0x01, 0x00, 0x00, 0x00, 0xff, 0xff,
   0xff, 0x7f, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x1f, 0x00, 0x00,
   0x00, 0x00, 0xf0, 0xff, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff,
   0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 
};

void draw(int i) {
    u8g2.setFont(u8g2_font_inb21_mf);
    u8g2.drawStr(i, 40, "AZ-Delivery.de");
}

void picture(void) {
    u8g2.drawXBMP(0, 0, 64, 64, rook_bitmap);
    u8g2.drawXBMP(64, 0, 64, 64, rook_bitmap);
}

void setup(void) {
    u8g2.begin();
}

void loop(void) {
    for(int i = 55; i > -85; i--) {
        u8g2.clearBuffer();
        draw(i-50);
        u8g2.sendBuffer();   
        delay(15); 
    }  
  
    u8g2.clearBuffer();
    picture();
    u8g2.sendBuffer();
    delay(3500); 
}

First we scroll text “AZ-Delivery.de” and than display two .xbm images.

Blue potentiometer on connection diagram is used for adjusting contrast of LCD. With this connection diagram we use LCD in serial mode (not 4bit or 8bit parallel mode). We do this by driving PSB pin to GND (pin 15 of display). This means, that we only use one pin for controling LCD, other pins are for power, contrast and setting this mode.

Here we used nothing new. Everything is explained in our previous instructable post, even how to make xbm images from any image. Here you can use any funciton explained in our previous instructable post.

Step 4: Test Sketch for Both TFTs

For 1.77'' TFT display connect everything like on connection diagram (for 1.8'' TFT connecteion diagram) and than copy-paste this sketch to your Arduino IDE. This sketch is a sketch form Adafruit library but heavily modified because of easy readability ( File > Examples > Adafruit ST7735 and ST7789 Library > graphicstest ). Just uncomment first three include lines.

#include // <Adafruit_GFX.h> // Core graphics library
#include // <Adafruit_ST7735.h> // Hardware-specific library for ST7735
#include // <SPI.h>

#define TFT_CS        10
#define TFT_RST        8
#define TFT_DC         9

// For and 1.77" TFT with ST7735 use:
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

float p = 3.1415926;
void setup(void) {
    // Use this initializer if using a 1.77" TFT screen:
    tft.initR(INITR_GREENTAB);     // Init ST7735S chip, green tab
    //tft.initR(INITR_BLACKTAB);     // Init ST7735S chip, black tab
    uint16_t time = millis();
    tft.fillScreen(ST77XX_BLACK);
    time = millis() - time;
    delay(500);
}

void loop() {
    // large block of text
    tft.fillScreen(ST77XX_BLACK);
    testdrawtext("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur adipiscing ante sed nibh tincidunt feugiat. Maecenas enim massa, fringilla sed malesuada et, malesuada sit amet turpis. Sed porttitor neque ut ante pretium vitae malesuada nunc bibendum. Nullam aliquet ultrices massa eu hendrerit. Ut sed nisi lorem. In vestibulum purus a tortor imperdiet posuere. ", ST77XX_WHITE);
    delay(5000);

    // tft print function!
    tftPrintTest();
    delay(4000);

    // a single pixel
    tft.drawPixel(tft.width()/2, tft.height()/2, ST77XX_GREEN);
    delay(2000);

    // optimized lines
    testdrawlines();
    delay(2000);
  
    testdrawrects();
    delay(2000);
    testdrawcircles();
    delay(2000);
    testroundrects();
    delay(2000);
    testdrawtriangles();
    delay(2000);
}

void testdrawtext(char *text, uint16_t color) {
    tft.setCursor(0, 0);
    tft.setTextColor(color);
    tft.setTextWrap(true);
    tft.print(text);
}

void testdrawlines() {
    tft.fillScreen(ST77XX_BLACK);
    tft.drawFastHLine(0, tft.height()-1, tft.width(), ST77XX_YELLOW);
}

void testdrawrects() {
    tft.fillScreen(ST77XX_BLACK);
    tft.drawRect(5, 5, tft.width()-9, tft.height()-9, ST77XX_BLUE);
    tft.fillRect(20, 20, tft.width()-41, tft.height()-41, ST77XX_BLUE);
}

void testdrawcircles() {
    tft.fillScreen(ST77XX_BLACK);
    tft.drawCircle(tft.width()/2, tft.height()/2, 25, ST77XX_RED);
    tft.fillCircle(tft.width()/2, tft.height()/2, 15, ST77XX_RED);
}

void testdrawtriangles() {
    tft.fillScreen(ST77XX_BLACK);
    tft.drawTriangle(1, tft.height()-1, tft.width()/2, 1, tft.width()-1, tft.height()-1, ST77XX_GREEN);
    tft.fillTriangle(15, tft.height()-11, tft.width()/2, 25, tft.width()-15, tft.height()-11, ST77XX_GREEN);
}

void testroundrects() {
    tft.fillScreen(ST77XX_BLACK);
    tft.drawRoundRect(5, 5, 118, 150, 15, ST77XX_RED);
    tft.fillRoundRect(15, 15, 98, 130, 5, ST77XX_RED);
}

void tftPrintTest() {
    tft.setTextWrap(false);
    tft.fillScreen(ST77XX_BLACK);
    tft.setCursor(0, 30);
    tft.setTextColor(ST77XX_RED);
    tft.setTextSize(1);
    tft.println("Hello World!");
    tft.setTextColor(ST77XX_YELLOW);
    tft.setTextSize(2);
    tft.println("Hello World!");
    tft.setTextColor(ST77XX_GREEN);
    tft.setTextSize(3);
    tft.println("Hello World!");
    tft.setTextColor(ST77XX_BLUE);
    tft.setTextSize(4);
    tft.print(1234.567);
    delay(1500);
    tft.setCursor(0, 0);
    tft.fillScreen(ST77XX_BLACK);
    tft.setTextColor(ST77XX_WHITE);
    tft.setTextSize(0);
    tft.println("Hello World!");
    tft.setTextSize(1);
    tft.setTextColor(ST77XX_GREEN);
    tft.print(p, 6);
    tft.println(" Want pi?");
    tft.println(" ");
    tft.print(8675309, HEX); // print 8,675,309 out in HEX!
    tft.println(" Print HEX!");
    tft.println(" ");
    tft.setTextColor(ST77XX_WHITE);
    tft.println("Sketch has been");
    tft.println("running for: ");
    tft.setTextColor(ST77XX_MAGENTA);
    tft.print(millis() / 1000);
    tft.setTextColor(ST77XX_WHITE);
    tft.print(" seconds.");
}

First thing that we have to change from original sketch is to switch TFT_RST and TFT_DC pins, TFT_RST > Digital pin 8, and TFT_DC > Digital pin 9. If (when you upload this sketch to your arduino), on TFT screen you see some line in one corner, than you have to comment this line:

tft.initR(INITR_GREENTAB);

and uncomment:

tft.initR(INITR_BLACKTAB);

We use tft.fillScreen(ST77XX_BLACK) to fill whole screen with black color. This is used for back color of the screen on which we will draw our text, lines, rectangles, etc. This color is library predefined color, and you can use any hex number for 16 bit colors or use one of predefined colors:

  • ST77XX_BLACK
  • ST77XX_WHITE
  • ST77XX_RED
  • ST77XX_GREEN
  • ST77XX_BLUE
  • ST77XX_CYAN
  • ST77XX_MAGENTA
  • ST77XX_YELLOW
  • ST77XX_ORANGE

In loop() function we call all functions that we defined. There are two kind functions for drawing objects, one is for drawing for example empty triangle, and second is for drawing triangle but filled with color. To draw for example triangle, we use function drawTriangle() which accepts 7 arguments, first 6 are X and Y positions of three corner dots of triangle, and last is the color of edge lines. To draw filled triangle we use fillTriangle() function which accepts the same arguments as drawTriangle() function. It is similar for drawRect() and fillRect() but here we have 5 arguments, first 4 are X and Y position of top left corner dot, and bottom right corner dot of rectangle and fifth is for color. Also it is similar for drawCircle() and fillCircle() which accepts 4 arguments, where first two are X and Y position of center, third argument is radius, and fourth is for color. X direction starts from left and goes on right of the screen, and Y direction starts on top and goes to bottom of the screen.

For printing text we use several functions. First is setTextWrap() and we pass one argument to it, true or false, which enables or disables text wrapping. Text wrapping is if text is longer than the width of screen, and we set text wrapping to true, all text that is longer than a width of screen will go in the next line. If text wrapping is disabled, text which is outside of the screen will stay there.

Before printing text, we have to set position of starting point, or cursor. We do this with function setCursor() which accepts two arguments, X and Y position.

We use setTextColor() function to set color to text. This function accepts one argument which is for color.

We use setTextSize() function to change size of text. This function accepts one integer argument, which value is in range between 0 and 4. You can use higher values, but that is too big forour TFT displays.

To print text we use print() or println() functions. The difference is that println() function prints text and set cursor to new line, and print() function prints text, and cursor is immediately after last printed character. Both functions can be used in few ways. If we have string message variable, we can pass that as an argument to print() function to display it on the screen. If we have integer variable which we want to display, we can pass it as single argument to print() function and it will be printed. Here we have optional second argument which is for base of numerical system (DEC, HEX, OCT). If we want to print integer number 10 in hexadecimal, we pass 10 as first argument, and HEX as second argument, and on screen we will get "A", which is 10 in hexadecimal numerical system. If we want to display float or double variable we pass this variable for first argument to print() function, and optional second argument is integer number which represents how much digits after decimal point will be displayed.

Step 5: Displaying Images on 1.8'' TFT

Connect everything like on diagram, and copy-paste this sketch to your Arduino IDE. This is sketch File > Examples > Adafruit ST7735 and ST7789 Library > seesaw_shield18_test, but it is modified so we can use it with our 1.8’’ TFT display with SD card reader. Just uncomment first 4 include lines.

#include // <SPI.h>
#include // <SD.h>
#include // <Adafruit_GFX.h>
#include // <Adafruit_ST7735.h>


// TFT display and SD card will share the hardware SPI interface.
// Hardware SPI pins are specific to the Arduino board type and
// cannot be remapped to alternate pins.  For Arduino Uno,
// Duemilanove, etc., pin 11 = MOSI, pin 12 = MISO, pin 13 = SCK.
#define SD_CS    4  // Chip select line for SD card
#define TFT_CS  10  // Chip select line for TFT
#define TFT_DC   9  // Data/command line for TFT 
#define TFT_RST  8  // Reset line for TFT

Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

void setup(void) {
    Serial.begin(9600);  
    // Initialize 1.8" TFT
    tft.initR(INITR_BLACKTAB);   // initialize a ST7735S chip, black tab
    Serial.println("TFT OK!");
    tft.fillScreen(ST77XX_BLACK);
    Serial.print("Initializing SD card...");
    if (!SD.begin(SD_CS)) {
        Serial.println("failed!");
    } else {
        Serial.println("OK!");
        File root = SD.open("/");
        printDirectory(root, 0);
        root.close();
        bmpDraw("/cat.bmp", 0, 0);
    }
    // used to test back colors
    delay(100);
    tft.fillScreen(ST77XX_RED);
    delay(100);
    tft.fillScreen(ST77XX_GREEN);
    delay(100);
    tft.fillScreen(ST77XX_BLUE);
    delay(100);
    tft.fillScreen(ST77XX_BLACK);
 
    tft.setTextSize(1);
    tft.setTextColor(ST77XX_WHITE);
    tft.setCursor(0, 0);
    tft.print("success");
}

void loop() {
    tft.setRotation(1);
    bmpDraw("/spb-1.bmp", 0, 0);
    delay(5000);
}

// This function opens a Windows Bitmap (BMP) file and
// displays it at the given coordinates.  It's sped up
// by reading many pixels worth of data at a time
// (rather than pixel by pixel).  Increasing the buffer
// size takes more of the Arduino's precious RAM but
// makes loading a little faster.  20 pixels seems a
// good balance.

define BUFFPIXEL 20
void bmpDraw(char *filename, uint8_t x, uint16_t y) {
    File     bmpFile;
    int      bmpWidth, bmpHeight;   // W+H in pixels
    uint8_t  bmpDepth;              // Bit depth (currently must be 24)
    uint32_t bmpImageoffset;        // Start of image data in file
    uint32_t rowSize;               // Not always = bmpWidth; may have padding
    uint8_t  sdbuffer[3*BUFFPIXEL]; // pixel buffer (R+G+B per pixel)
    uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
    boolean  goodBmp = false;       // Set to true on valid header parse
    boolean  flip    = true;        // BMP is stored bottom-to-top
    int      w, h, row, col;
    uint8_t  r, g, b;
    uint32_t pos = 0, startTime = millis();
 
    if((x >= tft.width()) || (y >= tft.height())) return;

    Serial.println();
    Serial.print(F("Loading image '"));
    Serial.print(filename);
    Serial.println('\'');

    // Open requested file on SD card
    if ((bmpFile = SD.open(filename)) == NULL) {
        Serial.print(F("File not found"));
        return;
    }

    // Parse BMP header
    if(read16(bmpFile) == 0x4D42) { // BMP signature
        Serial.print(F("File size: ")); Serial.println(read32(bmpFile));
        (void)read32(bmpFile); // Read & ignore creator bytes
        bmpImageoffset = read32(bmpFile); // Start of image data
        Serial.print(F("Image Offset: ")); 
        Serial.println(bmpImageoffset, DEC);
        // Read DIB header
        Serial.print(F("Header size: ")); 
        Serial.println(read32(bmpFile));
        bmpWidth  = read32(bmpFile);
        bmpHeight = read32(bmpFile);
        if(read16(bmpFile) == 1) { // # planes -- must be '1'
            bmpDepth = read16(bmpFile); // bits per pixel
            Serial.print(F("Bit Depth: ")); 
            Serial.println(bmpDepth);
            if((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed
               goodBmp = true; // Supported BMP format -- proceed!
               Serial.print(F("Image size: "));
               Serial.print(bmpWidth);
               Serial.print('x');
               Serial.println(bmpHeight);
               // BMP rows are padded (if needed) to 4-byte boundary
               rowSize = (bmpWidth * 3 + 3) & ~3;   
               // If bmpHeight is negative, image is in top-down order.
               // This is not canon but has been observed in the wild.
               if(bmpHeight < 0) {
                   bmpHeight = -bmpHeight;
                   flip = false;
               }
               // Crop area to be loaded
               w = bmpWidth;
               h = bmpHeight;
               if((x+w-1) >= tft.width())  w = tft.width()  - x;
               if((y+h-1) >= tft.height()) h = tft.height() - y;
               // Set TFT address window to clipped image bounds
               tft.startWrite();
               tft.setAddrWindow(x, y, w, h);
               for (row=0; row<h; row++) { // For each scanline...
                  // Seek to start of scan line.  It might seem labor-
                  // intensive to be doing this on every line, but this
                  // method covers a lot of gritty details like cropping
                  // and scanline padding.  Also, the seek only takes
                  // place if the file position actually needs to change
                  // (avoids a lot of cluster math in SD library).
                  if(flip) // Bitmap is stored bottom-to-top order (normal BMP)
                      pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
                  else     // Bitmap is stored top-to-bottom
                      pos = bmpImageoffset + row * rowSize;
                  if(bmpFile.position() != pos) { // Need seek?
                      tft.endWrite();
                      bmpFile.seek(pos);
                      buffidx = sizeof(sdbuffer); // Force buffer reload
                   }
                   for (col=0; col= sizeof(sdbuffer)) { // Indeed
                       bmpFile.read(sdbuffer, sizeof(sdbuffer));
                       buffidx = 0; // Set index to beginning
                       tft.startWrite();
                   }
                   // Convert pixel from BMP to TFT format, push to display
                   b = sdbuffer[buffidx++];
                   g = sdbuffer[buffidx++];
                   r = sdbuffer[buffidx++];
                   tft.pushColor(tft.color565(r,g,b));
                } // end pixel
            } // end scanline
            tft.endWrite();
            Serial.print(F("Loaded in "));
            Serial.print(millis() - startTime);
            Serial.println(" ms");
          } // end goodBmp
       }
    }
    bmpFile.close();
    if(!goodBmp) Serial.println(F("BMP format not recognized."));
}

// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.

uint16_t read16(File f) {
    uint16_t result;
    ((uint8_t *)&result)[0] = f.read(); // LSB
    ((uint8_t *)&result)[1] = f.read(); // MSB
    return result;
}

uint32_t read32(File f) {
    uint32_t result;
    ((uint8_t *)&result)[0] = f.read(); // LSB
    ((uint8_t *)&result)[1] = f.read();
    ((uint8_t *)&result)[2] = f.read();
    ((uint8_t *)&result)[3] = f.read(); // MSB
    return result;
}</p><p>void printDirectory(File dir, int numTabs) {
    while (true) {
        File entry =  dir.openNextFile();
        if (! entry) {
           // no more files
           break;
    }
    for (uint8_t i = 0; i < numTabs; i++) {
        Serial.print('\t');
    }
    Serial.print(entry.name());
    if (entry.isDirectory()) {
        Serial.println("/");
        printDirectory(entry, numTabs + 1);
    } else {
        // files have sizes, directories do not
        Serial.print("\t\t");
        Serial.println(entry.size(), DEC);
    }
    entry.close();
  }
}

In this sketch we remove anything that has nothing with tft display and sd card reader.

On microSD card, we put two images, cat.bmp, and spb-1.bmp, that we previously scaled to fit 128x64 screen and converted them to .bmp file format. For this you can use Gimp. First open image in Gimp, then to scale image in gimp go to Image > Scale Image and change width to 128 and hit enter. If height value is not less than 64, than cancel that and start scaling again, but this time, first change height to 64 and hit enter, after which width will be less than 128. To finish scaling press Scale button. To save image as .bmp, press CTRL+SHIFT+E or go to File > Export as, and save it with name NAME.bmp.

At the beginning of sketch we define 4 macros, so that we can connect two devices, TFT diplay and SD card reader on hardware SPI on Arduino board. Then we create tft object, and in setup() function we initialize it, and here we also initialize sd card reader, and print cat.bmp image on the screen.

Then we test few colors, filling screen with them, after which we print text success on screen.

In loop() function we set screen orientation with setRotation() function, which accepts one integer argument in range from 0 to 3. Then we use bmpDraw() function to display spb-1.bmp image. This function accepts three arguments, first is location of image on microsd card “/spb-1.bmp”. If your image is named differently, than this will be “/name_of_image.bmp”. Second and third arguments are X and Y position of top left corner of image.

After loop function, there are definitions of bmpDraw() function and several help functions used in bmpDraw(). We won’t cover this in this Instructable post. These functions are same as in original sketch File > Examples > Adafruit ST7735 and ST7789 Library > seesaw_shield18_test and it is not of interest for our examples.

Step 6: Test Sketch for Nokia LCD

Connect everything like on diagram. Make sure to use logic level converter. Logic of this display can work on 5V but it is recommendable to use 3.3V. Copy-paste this sketch to your Arduino IDE. Be sure to uncomment first include. This sketch is modified from sketch: File > Examples > LCD5110 > Arduino (AVR) > LCD5110_Graph_Demo.

/*  LCD pin  - Arduino pin (via logic level converter)
 *      CLK  - Pin 8
 *      DIN  - Pin 9
 *      DC   - Pin 10
 *      RST  - Pin 11
 *      CE   - Pin 12
 *      
 *      VCC  - 3.3V
 *      GND  - GND
 **********************************************
 *      LIGHT - GND > turn ON backlight
 *      LIGHT - 3.3V > turn OFF backlight
*/

#include // <LCD5110_Graph.h>

LCD5110 lcd(8,9,10,11,12);

extern uint8_t SmallFont[];
extern unsigned char TinyFont[];</p><p>const uint8_t AZ_logo[] PROGMEM={
    0x00, 0xC0, 0xE0, 0xF8, 0xF8, 0xFC, 0x7E, 0x3E, 0xBE, 0xDF, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,   // 0x0010 (16) pixels
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xDF, 0xDF, 0x1F, 0x3F, 0x3E, 0x7E,   // 0x0020 (32) pixels
    0x7C, 0xFC, 0xF8, 0xF8, 0xF0, 0xF0, 0xE0, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0030 (48) pixels
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF9, 0xFE, 0xFF, 0xFF, 0xFF, 0x0F, 0x07, 0x03, 0x03, 0x03, 0x03,   // 0x0040 (64) pixels
    0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,   // 0x0050 (80) pixels
    0x00, 0x00, 0x00, 0x01, 0x03, 0x07, 0x2F, 0x1F, 0xFF, 0xFF, 0xFE, 0xFC, 0xE0, 0x80, 0x00, 0x00,   // 0x0060 (96) pixels
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3E, 0x7E, 0x7E, 0x7E, 0x7E, 0x7E,   // 0x0070 (112) pixels
    0x7E, 0x7E, 0x7E, 0x7E, 0x7E, 0x3E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x7E, 0x7E, 0x7E,   // 0x0080 (128) pixels
    0xBE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0x7E, 0x3E, 0x0B, 0x1F, 0xFF, 0xFF, 0xFF, 0xFE, 0xE0,   // 0x0090 (144) pixels
    0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0x8F, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x00A0 (160) pixels
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x0F, 0x8F, 0xEF, 0xFF, 0xF8, 0xFC, 0xFE, 0xFF,   // 0x00B0 (176) pixels
    0x7F, 0x3F, 0x1F, 0x0F, 0x03, 0x01, 0x00, 0x00, 0x00, 0xC0, 0xFC, 0xFF, 0xFF, 0xFF, 0x3F, 0x07,   // 0x00C0 (192) pixels
    0x00, 0x00, 0x03, 0x0F, 0x3F, 0x7F, 0xFF, 0xFE, 0xF8, 0xF0, 0xE0, 0xC0, 0x80, 0x00, 0x00, 0x00,   // 0x00D0 (208) pixels
    0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xFC, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFC,   // 0x00E0 (224) pixels
    0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xFF, 0x7F, 0x3F, 0x1F, 0x07, 0x01, 0x00, 0x00,   // 0x00F0 (240) pixels
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x07, 0x0F, 0x0F, 0x0F, 0x1F, 0x3F, 0x3F,   // 0x0100 (256) pixels
    0x3E, 0x7E, 0x7C, 0x7C, 0xFC, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0xF8, 0x7C, 0x7C, 0x7C, 0x7E, 0x3E,   // 0x0110 (272) pixels
    0x3F, 0x1F, 0x1F, 0x0F, 0x0F, 0x07, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,   // 0x0120 (288) pixels
};

void setup() {
    lcd.InitLCD();
    lcd.setFont(SmallFont);
}

void loop() {
    lcd.clrScr();
    lcd.drawBitmap(18, 0, AZ_logo, 48, 48);
    lcd.update();
    delay(5000);
  
    lcd.clrScr();
    lcd.print("AZ-Delivery", CENTER, 0);
    lcd.print("TEST", CENTER, 20);
    lcd.drawRect(28, 18, 56, 28);
    lcd.update();
    delay(5000);
  
    lcd.clrScr();
    lcd.drawLine(0, 0, 83, 47);
    lcd.drawLine(0, 47, 83, 0);
    lcd.update();
    delay(2000);
  
    lcd.clrScr();
    lcd.drawRect(14, 14, 70, 34);
    lcd.update();
    delay(2000);
  
    lcd.clrScr();
    lcd.drawRoundRect(14, 14, 70, 34);
    lcd.update();
    delay(2000);
  
    lcd.clrScr();
    lcd.drawCircle(41, 23, 22);
    lcd.update();
    delay(2000);
  
    lcd.invert(true);
    delay(2000);
    lcd.invert(false);
    delay(2000);
}

To create bitmap array, you have to first scale image to 84x48 or less. We already used Gimp, so we’ll continue with it. Open your image in Gimp. Go to Image > Scale Image, and change width to 84, and hit enter. If height is more than 48, then cancel this, and go to Image > Scale Image, again but this time first chane height to 48 and hit enter. Width should be less than 84. Save your image any way you like. Than go on this site and follow steps to get name_of_file.c file. Open name_of_file.c, and copy everything between “{“ and “};”. Then go to our sketch and delete lines of similar numbers (in our array const uint8_t AZ_logo[] PROGMEM), and paste there numbers you copied. And that is how you can upload custom bitmap array.

Everythong else in this sketch is self explanatory. If you read any of our previous Instructable posts (Kinda small displays - part 1 and part 3) you will be able to understand this sketch.

Step 7: Scrolling Text on Nokia LCD

Copy-paste this sketch to your Arduino IDE. Be sure to uncomment first include. This sketch is modified from sketch: File > Examples > LCD5110 > Arduino (AVR) > LCD5110_Scrolling_Text. Just uncomment first include.

/*  LCD pin  - Arduino pin (via logic level converter)
 *      CLK  - Pin 8
 *      DIN  - Pin 9
 *      DC   - Pin 10
 *      RST  - Pin 11
 *      CE   - Pin 12
 *      
 *      VCC  - 3.3V
 *      GND  - GND
 **********************************************
 *      LIGHT - GND > turn ON backlight
 *      LIGHT - 3.3V > turn OFF backlight
*/

#include // <LCD5110_Graph.h>
LCD5110 lcd(8,9,10,11,12);

extern uint8_t SmallFont[];
int y;
void setup() {
    lcd.InitLCD();
    lcd.setFont(SmallFont);
}

void loop() {
    y = random(0, 40);
    for (int i=84; i>=-(34*6); i--) {
         lcd.clrScr();
         lcd.print("AZ-Delivery scrolling text", i, y);
         lcd.update();
         delay(50);
    }
}

Nothing new here. We already used everything in this sketch.

This is all for this Instructable.

If you have any questions, feel free to ask.

You can reach us under info@az-delivery.com at any time and we will be glad to help you, or visit us under www.az-delivery.com

Share

    Recommendations

    • Trash to Treasure

      Trash to Treasure
    • Arduino Contest 2019

      Arduino Contest 2019
    • Tape Contest

      Tape Contest

    Discussions