Big Font LCD Characters

As a follow-on from a recent post about creating large numbers on a 4×20 LCD, I have now completed a full big font character set that uses 2, rather than 3, lines of an LCD. This allows you to use these fonts on both the popular 2×16 and 4×20 displays.

Big Font Display

Big Font Display occupying top 2 lines of 2×40 LCD

Big Font Display

Big Font Display occupying lower 2 lines

This work is built upon an old Instructable” article that was posted in 2010. Since then others have made many incremental improvements to the code and look of the big font characters (follow links and threads from the original article). However, I found that even the most recent designs still use SRAM to store the character sets and the custom glyphs used to construct them. As SRAM is a precious resource that should be used sparingly, my code has all of the character generation table driven and located in program space (see also my  PROGMEM article on how to do this). This has freed up at least 700B of precious SRAM and the code overhead is minimal.

The standard font set of the 2×16 and 4×20 LCDs is as follows. While the Japanese character set as it stands is not particularly useful to me, in addition to the custom glyphs, I also use the ASCII 0x20 “space” character and the ASCII 0xFF black rectangle (“block”) character (seen in the top left and bottom right of the character set table respectively).

LCD Normal Character Font Set

LCD Normal Character Font Set

The eight glyphs are custom characters in a 5×8 pixel character “cell”. When combined with the “space” and “block” characters, these glyphs are used to create a full set of big font ASCII characters. They differ slightly from those in the instructable article as I created more rounded characters. Here are the 8 custom glyphs.

Glyphs for Big Font LCD Character Set

Glyphs for Big Font LCD Character Set

Each big font character is two character cells tall (occupying 2 rows) and from 1 to 4 character cells wide. As an example, in the pictures above, notice that all of the numbers and the “B”, “G”, “F” and “T” characters are 3 cells wide, the “N” is 4 cells wide while the ” ” and “:” characters are just 1 cell wide. In the lower picture you can see that there are just enough cells in the 4×20 LCD to allow a time (formatted as “hr:mn:se”) or date (formatted as “yr/mn/dy”) to be displayed.

Here’s the Arduino test code that includes all of the character generation tables. For its display, I am using a 4×20 LCD with an I2C interface.

[codesyntax lang=”php” title=”Big LCD Font Test Code” blockstate=”collapsed”]

//**************************************************************************************************
//                                 BIG FONT (2-line) LCD CHARACTERS 
//                                   Adrian Jones, February 2015
//
//Initial idea by http://www.instructables.com/id/Custom-Large-Font-For-16x2-LCDs/step5/Arduino-Sketch/
//**************************************************************************************************

// Build 1
//   r1 150214 - initial build with glyphs and big font character table in program memory

//********************************************************************************************
#define build 1
#define revision 1
//********************************************************************************************

#include <avr/pgmspace.h>            // for memory storage in program space

#include <Wire.h> 
#include <LCD.h>                     // Standard lcd library
#include <LiquidCrystal_I2C.h>       // library for I@C interface

#define I2C_ADDR  0x27               // address found from I2C scanner
#define RS_pin    0                  // pin configuration for LCM1602 interface module
#define RW_pin    1
#define EN_pin    2
#define BACK_pin  3
#define D4_pin    4
#define D5_pin    5
#define D6_pin    6
#define D7_pin    7

LiquidCrystal_I2C lcd(I2C_ADDR, EN_pin, RW_pin, RS_pin, D4_pin, D5_pin, D6_pin, D7_pin, BACK_pin, POSITIVE);

const char custom[][8] PROGMEM = {                        // Custom character definitions
      { 0x1F, 0x1F, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00 }, // char 1 
      { 0x18, 0x1C, 0x1E, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }, // char 2 
      { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x0F, 0x07, 0x03 }, // char 3 
      { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x1F }, // char 4 
      { 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1E, 0x1C, 0x18 }, // char 5 
      { 0x1F, 0x1F, 0x1F, 0x00, 0x00, 0x00, 0x1F, 0x1F }, // char 6 
      { 0x1F, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x1F }, // char 7 
      { 0x03, 0x07, 0x0F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F }  // char 8 
};

// BIG FONT Character Set
// - Each character coded as 1-4 byte sets {top_row(0), top_row(1)... bot_row(0), bot_row(0)..."}
// - number of bytes terminated with 0x00; Character is made from [number_of_bytes/2] wide, 2 high
// - codes are: 0x01-0x08 => custom characters, 0x20 => space, 0xFF => black square

const char bigChars[][8] PROGMEM = {
      { 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Space
      { 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // !
      { 0x05, 0x05, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00 }, // "
      { 0x04, 0xFF, 0x04, 0xFF, 0x04, 0x01, 0xFF, 0x01 }, // #
      { 0x08, 0xFF, 0x06, 0x07, 0xFF, 0x05, 0x00, 0x00 }, // $
      { 0x01, 0x20, 0x04, 0x01, 0x04, 0x01, 0x20, 0x04 }, // %
      { 0x08, 0x06, 0x02, 0x20, 0x03, 0x07, 0x02, 0x04 }, // &
      { 0x05, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // '
      { 0x08, 0x01, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00 }, // (
      { 0x01, 0x02, 0x04, 0x05, 0x00, 0x00, 0x00, 0x00 }, // )
      { 0x01, 0x04, 0x04, 0x01, 0x04, 0x01, 0x01, 0x04 }, // *
      { 0x04, 0xFF, 0x04, 0x01, 0xFF, 0x01, 0x00, 0x00 }, // +
      { 0x20, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 
      { 0x04, 0x04, 0x04, 0x20, 0x20, 0x20, 0x00, 0x00 }, // -
      { 0x20, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // .
      { 0x20, 0x20, 0x04, 0x01, 0x04, 0x01, 0x20, 0x20 }, // /
      { 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x00 }, // 0
      { 0x01, 0x02, 0x20, 0x04, 0xFF, 0x04, 0x00, 0x00 }, // 1
      { 0x06, 0x06, 0x02, 0xFF, 0x07, 0x07, 0x00, 0x00 }, // 2
      { 0x01, 0x06, 0x02, 0x04, 0x07, 0x05, 0x00, 0x00 }, // 3
      { 0x03, 0x04, 0xFF, 0x20, 0x20, 0xFF, 0x00, 0x00 }, // 4
      { 0xFF, 0x06, 0x06, 0x07, 0x07, 0x05, 0x00, 0x00 }, // 5
      { 0x08, 0x06, 0x06, 0x03, 0x07, 0x05, 0x00, 0x00 }, // 6
      { 0x01, 0x01, 0x02, 0x20, 0x08, 0x20, 0x00, 0x00 }, // 7
      { 0x08, 0x06, 0x02, 0x03, 0x07, 0x05, 0x00, 0x00 }, // 8
      { 0x08, 0x06, 0x02, 0x07, 0x07, 0x05, 0x00, 0x00 }, // 9
      { 0xA5, 0xA5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // :
      { 0x04, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ;
      { 0x20, 0x04, 0x01, 0x01, 0x01, 0x04, 0x00, 0x00 }, // <
      { 0x04, 0x04, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00 }, // =
      { 0x01, 0x04, 0x20, 0x04, 0x01, 0x01, 0x00, 0x00 }, // >
      { 0x01, 0x06, 0x02, 0x20, 0x07, 0x20, 0x00, 0x00 }, // ?
      { 0x08, 0x06, 0x02, 0x03, 0x04, 0x04, 0x00, 0x00 }, // @
      { 0x08, 0x06, 0x02, 0xFF, 0x20, 0xFF, 0x00, 0x00 }, // A
      { 0xFF, 0x06, 0x05, 0xFF, 0x07, 0x02, 0x00, 0x00 }, // B
      { 0x08, 0x01, 0x01, 0x03, 0x04, 0x04, 0x00, 0x00 }, // C
      { 0xFF, 0x01, 0x02, 0xFF, 0x04, 0x05, 0x00, 0x00 }, // D
      { 0xFF, 0x06, 0x06, 0xFF, 0x07, 0x07, 0x00, 0x00 }, // E
      { 0xFF, 0x06, 0x06, 0xFF, 0x20, 0x20, 0x00, 0x00 }, // F
      { 0x08, 0x01, 0x01, 0x03, 0x04, 0x02, 0x00, 0x00 }, // G
      { 0xFF, 0x04, 0xFF, 0xFF, 0x20, 0xFF, 0x00, 0x00 }, // H
      { 0x01, 0xFF, 0x01, 0x04, 0xFF, 0x04, 0x00, 0x00 }, // I
      { 0x20, 0x20, 0xFF, 0x04, 0x04, 0x05, 0x00, 0x00 }, // J
      { 0xFF, 0x04, 0x05, 0xFF, 0x20, 0x02, 0x00, 0x00 }, // K
      { 0xFF, 0x20, 0x20, 0xFF, 0x04, 0x04, 0x00, 0x00 }, // L
      { 0x08, 0x03, 0x05, 0x02, 0xFF, 0x20, 0x20, 0xFF }, // M
      { 0xFF, 0x02, 0x20, 0xFF, 0xFF, 0x20, 0x03, 0xFF }, // N
      { 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x00 }, // 0
      { 0x08, 0x06, 0x02, 0xFF, 0x20, 0x20, 0x00, 0x00 }, // P
      { 0x08, 0x01, 0x02, 0x20, 0x03, 0x04, 0xFF, 0x04 }, // Q
      { 0xFF, 0x06, 0x02, 0xFF, 0x20, 0x02, 0x00, 0x00 }, // R
      { 0x08, 0x06, 0x06, 0x07, 0x07, 0x05, 0x00, 0x00 }, // S
      { 0x01, 0xFF, 0x01, 0x20, 0xFF, 0x20, 0x00, 0x00 }, // T
      { 0xFF, 0x20, 0xFF, 0x03, 0x04, 0x05, 0x00, 0x00 }, // U
      { 0x03, 0x20, 0x20, 0x05, 0x20, 0x02, 0x08, 0x20 }, // V
      { 0xFF, 0x20, 0x20, 0xFF, 0x03, 0x08, 0x02, 0x05 }, // W
      { 0x03, 0x04, 0x05, 0x08, 0x20, 0x02, 0x00, 0x00 }, // X
      { 0x03, 0x04, 0x05, 0x20, 0xFF, 0x20, 0x00, 0x00 }, // Y
      { 0x01, 0x06, 0x05, 0x08, 0x07, 0x04, 0x00, 0x00 }, // Z
      { 0xFF, 0x01, 0xFF, 0x04, 0x00, 0x00, 0x00, 0x00 }, // [
      { 0x01, 0x04, 0x20, 0x20, 0x20, 0x20, 0x01, 0x04 }, // Backslash
      { 0x01, 0xFF, 0x04, 0xFF, 0x00, 0x00, 0x00, 0x00 }, // ]
      { 0x08, 0x02, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00 }, // ^
      { 0x20, 0x20, 0x20, 0x04, 0x04, 0x04, 0x00, 0x00 }  // _
};
byte col,row,nb=0,bc=0;                                   // general
byte bb[8];                                               // byte buffer for reading from PROGMEM


//*****************************************************************************************//
//                                      Initial Setup
//*****************************************************************************************//
void setup() {
  lcd.begin(20, 4);
  for (nb=0; nb<8; nb++ ) {                     // create 8 custom characters
    for (bc=0; bc<8; bc++) bb[bc]= pgm_read_byte( &custom[nb][bc] );
    lcd.createChar ( nb+1, bb );
  }
  lcd.clear();
  writeBigString("B G F N T", 0, 0);
  lcd.setCursor(0, 3); 
  lcd.print(F("  [B")); 
  lcd.print(build); 
  lcd.print(F(".")); 
  lcd.print(revision);
  lcd.print(F(" RAM: "));  
  lcd.print(freeRam()); 
  lcd.print(F("B]")); 
  delay(5000);
  lcd.clear();
}

//*****************************************************************************************//
//                                      MAIN LOOP
//*****************************************************************************************//
void loop() {

  lcd.setCursor(0, 0); 
  lcd.print(F(" BIG CHARACTER FONT ")); 
  writeBigString("10:23:46", 0, 2);
}
  
// ********************************************************************************** //
//                                      SUBROUTINES
// ********************************************************************************** //
// writeBigChar: writes big character 'ch' to column x, row y; returns number of columns used by 'ch'
int writeBigChar(char ch, byte x, byte y) {
  if (ch < ' ' || ch > '_') return 0;               // If outside table range, do nothing
  nb=0;                                             // character byte counter 
  for (bc=0; bc<8; bc++) {                        
    bb[bc] = pgm_read_byte( &bigChars[ch-' '][bc] );  // read 8 bytes from PROGMEM
    if(bb[bc] != 0) nb++;
  }  
 
  bc=0;
  for (row = y; row < y+2; row++) {
    for (col = x; col < x+nb/2; col++ ) {
      lcd.setCursor(col, row);                      // move to position
      lcd.write(bb[bc++]);                          // write byte and increment to next
    }
//    lcd.setCursor(col, row);
//    lcd.write(' ');                                 // Write ' ' between letters
  }
  return nb/2-1;                                      // returns number of columns used by char
}

// writeBigString: writes out each letter of string
void writeBigString(char *str, byte x, byte y) {
  char c;
  while ((c = *str++))
  x += writeBigChar(c, x, y) + 1;
}


// ********************************************************************************** //
//                                      OPERATION ROUTINES
// ********************************************************************************** //
// FREERAM: Returns the number of bytes currently free in RAM  
int freeRam(void) {
  extern int  __bss_end, *__brkval; 
  int free_memory; 
  if((int)__brkval == 0) {
    free_memory = ((int)&free_memory) - ((int)&__bss_end); 
  } 
  else {
    free_memory = ((int)&free_memory) - ((int)__brkval); 
  }
  return free_memory; 
}

[/codesyntax]

If you plan on having many character strings and you want to save more SRAM, locate your character strings in PROGMEM and read them into a buffer as and when you need them. For details as to how to do this, please read my article Accessing Arduino Program Space.

Bigger is better!

 

13 thoughts on “Big Font LCD Characters

  1. Dade Murphy

    I have Numbers[]. The range of numbers is 0-255, wenn i print 255 and decrease the value to 0 I hav 0 and a cutted rest of 55

    int speed = Numbers[1];

    char speedbuf[4];
    const char *speedmask = “%3d”;
    sprintf(speedbuf, speedmask, speed);

    writeBigString(speedobuf,0,2);

    Reply
    1. Anonymous

      Dade,
      In your case you need to write out three characters. This will overwrite the “55” of your example.

      I think that the simplest thing to do would be to edit the writeBigString() routine to add a test to add one (or 2) leading spaces (or 0’s) for values < 10 (100).

      Many thanks,

      … Adrian

      Reply
  2. John

    Thank you SOOO much, ive been searching for hours and finally, this is the only sketch that works!!

    Reply
  3. Bryan

    Thanks for the email Adrian, got this working 🙂

    If anyone else needs (my badly written code):

    char time[9];
    String colon = “:”;
    String stringOne = hours + colon + minutes + colon + seconds;

    stringOne.toCharArray(time, 9);
    writeBigString(time, 0, 0);

    Cheers

    Reply
  4. Bryan

    Hi Adrian, able to offer any help with this ? I seem to be failing at the first hurdle:

    String stringOne = “TEST”;
    writeBigString(stringOne, 0, 0);

    error:

    /home/pi/sketchbook/LCDBigFont/BigFont_Test2/BigFont_Test2.ino: In function ‘void loop()’:
    BigFont_Test2:125: error: cannot convert ‘String’ to ‘char*’ for argument ‘1’ to ‘void writeBigString(char*, byte, byte)’
    writeBigString(stringOne, 0, 0);
    ^
    exit status 1
    cannot convert ‘String’ to ‘char*’ for argument ‘1’ to ‘void writeBigString(char*, byte, byte)’

    I’m ultimately trying to populate stringOne with the time, ie, hours + “:” + minutes + “:” + seconds, but I cant even pass a simple string to your function :/

    Regards,

    Reply
    1. Adrian Post author

      Alexey,
      You have several options… You can create a string from your number (the temperature in degrees Celsius) like “XX.XX” and then pass this to the routine “writebigString”. Alternatively, you can take each digit of your float number (e.g. 12.34… take 1, then 2, then . then 4 etc.) and add 0x10 to each and pass it to “writeBigChar”.
      OK?

      Reply
    2. Adrian Post author

      For the float of the temperature, you can convert it to a string (in buffer[]) as follows:

      void double_to_char(double t,char * buffer){
      gcvt(t,10,buffer);
      }
      This function converts the double precision floating-point number (t) to a NULL-terminated ASCII string (buffer[].

      Reply

Come on... leave a comment...