Large numbers on small displays…

Widely available and cheap, small 2 and 4-line LCD panels are convenient displays to add capability to  Arduino projects that require the display of information or data. With the “LiquidCrystal.h” library built into the Arduino IDE, the availability of both parallel and I2C interfaces, and a wide variety of bright and subtle backlight colours, they are simple to add to your Arduino projects and look great.

4 x 20 Character Liquid Crystal Display Module

4×20 Character Liquid Crystal Display Module

However, the popular 2 (line) x16 (column) and 4×20 LCDs appear to use the same HD44780 or equivalent LCD controller that has an inbuilt character set based on the 5×8 pixel ‘cell’ to display all alphanumeric text. This means that there is just one single-size font to produce characters of up to 4.5mm high. While this suits many applications, it is a limitation for projects such as clocks designed to be viewed from a distance.

So, for my latest “gpSKY CLOCK“, a GPS-based clock equipped with a 4×20 display, I have created a simple way to display numbers at 3x scale…  Yep, whopping great big 13mm high numbers!

gpSkyClock in clock mode

gpSkyClock in large time display mode

According to the datasheet, the HD44780 driver of the 4×20 (and 2×16) LCD display can store and display only 8 custom designed characters (gylphs), each of which fits into a regular 5×8 pixel (“cell”). The design of each glyph is specified by an array of 8 bytes; one byte for each row. The 5 least significant bits of each byte determine which pixels are turned on (or off) in that row.

I have used this capability before to create special characters for various different projects. As an example, for a temperature display I created a glyph for the degree symbol (the 8 bytes are {0x06,0x09,0x09,0x06,0x00,0x00,0x00,0x00} ) to show the character  º  .

While simple single characters are easy to create and use, the challenge was how to create no more than 8 glyphs and use them in different combinations to make each of the numbers 0 through 9. For the gpSkyClock, I wanted to display the time – in hr:mn:se format – using the lower three of the 4 lines of the display. As the display is 20 cells wide, this meant that each of the large numbers had to fit into a grid of 2 cells wide by 3 cells high; in other words, constructed from 6 glyphs. In addition, each LCD cell is surrounded by a one-pixel wide border of white space which means that the design of the large numbers do not merge together but will appear “blocky”. Old-school displays have that same look which works well for displaying the time.

Using EXCEL to create a 2×3 grid of 5×8 pixel cells, I played around with designs until I came up with the character set, shown below, available in two options. The first option has more ‘white space” in the first row of each number to allow plenty of space between text written on the first line of the display and the large numbers written on lines 2, 3 and 4. The second option has more space on the last line each number for greater horizontal white space to text written below the numbers. In both options, only 7 unique characters needed to be created to display all of the numbers. This allowed me to create a further character for the “:” separator in the time format.

Large characters to display on the 4x20 LCD panel

Large characters to display on the 4×20 LCD panel

The 8-bytes for each of the 7 number-piece glyphs and the colon separator glyph were created using a simple EXCEL tool, and using Arduino code, loaded as special characters into the display. A subroutine was written to reconstruct any number by writing the appropriate set of 6 glyphs into the 2×3 pattern. Calling the subroutine with the value and position of each digit of the time results in the display of 3x high numbers.

Et voila! The resulting character set (option 1) is easy to read at 15′ and looks pretty cool. Simple solution to a challenging problem.

Large numbers on the gpSkyClock

Large numbers displaying time on the gpSkyClock

Neat huh?

Here is some test code that displays “12:34:56” using the option 1 large character set. Feel free to improve upon this if you wish to embed this into your project.


p style=”text-align: justify;”>[codesyntax lang=”php” title=”4×20 LCD Large Numbers” blockstate=”collapsed”]

 * /                              Large Numerals on a 4x20 LCD Module
 * /                                 Adrian Jones, Jan. 2015
 * /

// Build 1
//   r1 150114 - test

#define build 1
#define revision 1

// LCD
#include <LiquidCrystal.h>
LiquidCrystal lcd(A5, A4, A3, A2, A1, A0); // initialize LCD pins lcd(rs, en, d4, d5, d6, d7)

// OPTION 1 - for text on line 1
byte x10[8] = {0x00,0x00,0x00,0x00,0x00,0x07,0x07,0x07};   
byte x11[8] = {0x00,0x00,0x00,0x00,0x00,0x1C,0x1C,0x1C};
byte x12[8] = {0x00,0x00,0x00,0x00,0x00,0x1F,0x1F,0x1F};
byte x13[8] = {0x07,0x07,0x07,0x07,0x07,0x1F,0x1F,0x1F};
byte x14[8] = {0x1C,0x1C,0x1C,0x1C,0x1C,0x1F,0x1F,0x1F};
byte x15[8] = {0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C};
byte x16[8] = {0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07};
byte x17[8] = {0x00,0x00,0x0E,0x0E,0x0E,0x00,0x00,0x00};

byte row = 0,col = 0;

//                                      Initial Setup
void setup() {

    lcd.begin(20, 4);                            // setup LCD  for 16 columns and 2 rows
    lcd.createChar(0, x10);                      // digit piece
    lcd.createChar(1, x11);                      // digit piece
    lcd.createChar(2, x12);                      // digit piece
    lcd.createChar(3, x13);                      // digit piece
    lcd.createChar(4, x14);                      // digit piece
    lcd.createChar(5, x15);                      // digit piece
    lcd.createChar(6, x16);                      // digit piece
    lcd.createChar(7, x17);                      // digit piece (colon)

    row = 1;
    doNumber( 1,row,2);                          // static
    doNumber( 2,row,5);
    doNumber(11,row,7);                          // colon
    doNumber( 3,row,8);
    doNumber( 4,row,11);
    doNumber(11,row,13);                          // colon
    doNumber( 5,row,14);
    doNumber( 6,row,17); 
    lcd.print(F("Big Numbers (AJ)"));

//                                      MAIN LOOP
void loop() {                   // run over and over again


// doNumber: routine to position number 'num' starting top left at row 'r', column 'c'
void doNumber(byte num, byte r, byte c) {
    switch(num) {
      case 0: lcd.write(byte(2)); lcd.write(byte(2)); 
              lcd.setCursor(c,r+1); lcd.write(byte(5)); lcd.write(byte(6)); 
              lcd.setCursor(c,r+2); lcd.write(byte(4)); lcd.write(byte(3)); break;
      case 1: lcd.write(byte(0)); lcd.write(byte(1)); 
              lcd.setCursor(c,r+1); lcd.print(" "); lcd.write(byte(5));
              lcd.setCursor(c,r+2); lcd.write(byte(0)); lcd.write(byte(4)); break;
      case 2: lcd.write(byte(2)); lcd.write(byte(2)); 
              lcd.setCursor(c,r+1); lcd.write(byte(2)); lcd.write(byte(3)); 
              lcd.setCursor(c,r+2); lcd.write(byte(4)); lcd.write(byte(2)); break;  
      case 3: lcd.write(byte(2)); lcd.write(byte(2)); 
              lcd.setCursor(c,r+1); lcd.write(byte(0)); lcd.write(byte(3)); 
              lcd.setCursor(c,r+2); lcd.write(byte(2)); lcd.write(byte(3)); break;  
      case 4: lcd.write(byte(1)); lcd.write(byte(0)); 
              lcd.setCursor(c,r+1); lcd.write(byte(4)); lcd.write(byte(3)); 
              lcd.setCursor(c,r+2); lcd.print(" "); lcd.write(byte(6)); break;  
      case 5: lcd.write(byte(2)); lcd.write(byte(2)); 
              lcd.setCursor(c,r+1); lcd.write(byte(4)); lcd.write(byte(2)); 
              lcd.setCursor(c,r+2); lcd.write(byte(2)); lcd.write(byte(3)); break;  
      case 6: lcd.write(byte(1)); lcd.print(" ");     
              lcd.setCursor(c,r+1); lcd.write(byte(4)); lcd.write(byte(2)); 
              lcd.setCursor(c,r+2); lcd.write(byte(4)); lcd.write(byte(3)); break;  

      case 7: lcd.write(byte(2)); lcd.write(byte(2));
              lcd.setCursor(c,r+1); lcd.print(" "); lcd.write(byte(6)); 
              lcd.setCursor(c,r+2); lcd.print(" "); lcd.write(byte(6)); break;  

      case 8: lcd.write(byte(2)); lcd.write(byte(2)); 
              lcd.setCursor(c,r+1); lcd.write(byte(4)); lcd.write(byte(3)); 
              lcd.setCursor(c,r+2); lcd.write(byte(4)); lcd.write(byte(3)); break;  
      case 9: lcd.write(byte(2)); lcd.write(byte(2)); 
              lcd.setCursor(c,r+1); lcd.write(byte(4)); lcd.write(byte(3)); 
              lcd.setCursor(c,r+2); lcd.print(" "); lcd.write(byte(6)); break;  

       case 11: lcd.setCursor(c,r+1); lcd.write(byte(7)); lcd.setCursor(c,r+2); lcd.write(byte(7)); break; 


3 thoughts on “Large numbers on small displays…

    1. Adrian Post author

      Very nice project, David…
      Yes, the GPS module has some great features and I use it for both stationary and moving positioning.
      For my clocks I primarily use an ESP8266 and an NTP request to the NIST atomic clock. I have recently added additional information into the WiFi config so that the user can enter time zone and lattitude and longitude.
      Keep making!


Come on... leave a comment...