Custom Large Font for 16x2 LCDs

119K9733

Intro: Custom Large Font for 16x2 LCDs

A couple of years ago i was learning to use the Ardiuno and started playing around with an Hitachi HD44780 based 16x2 LCD screen. I soon learned that the screen has 8 customizable character slots. I had found a project where someone used those slots to create there own custom characters that could then be used to form large character font using both rows of the screen. I didn't like the look of those characters and couldn't make since of the sketch that person wrote. So i decided i needed to create my own set and use my limited programming skills to create a more user friendly sketch to support my large character font.

In this Instructable i'll show you how i designed my large character font and break down the sketch to make it easier to understand. But first we need to set up the Arduino and LCD. 

Materials Needed:

- Arduino (i used a Nove)
- Hitachi HD44780 based 16x2 LCD
- Breadboard
- 5K Potentiometer
- button or switch
- jumper wires


This shows a revised version of the font.

Here is the finished font

STEP 1: Connecting the LCD to the Arduino

First i want to cover how to connect a Hitachi HD44780 based LCD screen to an Arduino. The first thing you need to do is identify Pin 1 on your display and figure out which pins you need. The first image shows a very simple layout for connecting the LCD to your Arduino. Here is the pin Layout for the LCD.
  • Pin 1 - Grd
  • Pin 2 - VCC
  • Pin 3 - Vee (controls screen contrast)
  • Pin 4 - RS (controls where in the LCD's memory your writing too)
  • Pin 5 - RW (controls weather your Reading or Writing to the LCD)
  • Pin 6 - E (enables writing to the register)
  • Pin 7 - D0 (not used)
  • Pin 8 - D1 (not used)
  • Pin 9 - D2 (not used)
  • Pin 10 - D3 (not used)
  • Pin 11 - D4
  • Pin 12 - D5
  • Pin 13 - D6
  • Pin 14 - D7
  • Pin 15 - LED+ (LCD back light)
  • Pin 16 - LED-  (Grd)

My LCD only had 15 pins which is fine since 16 should be tied to ground anyway. As you can see in the picture Vee is tied into a potentiometer. This controls the contrast of the screen. The data pins are the individual bits your writing to or reading from the register.

For the purposes of keeping things simple i wired mine up a bit differently. I like to use ribbon cable whenever possible to keep the clutter of wires down. I makes keeping track of the connections allot easier too. 
  • RS pin to D7
  • E pin to D6
  • D4 pin to D5
  • D5 pin to D4
  • D6 pin to D3
  • D7 pin to D2
  • V0 tied to a pot to control brightness
  • Grd and R/W tied to ground
  • Vcc to +5V
  • pin 15 to push button/switch that is tied to ground for control of back light
With everything connected we can now go over how i created the fonts.

STEP 2: Numerical Font Design

It then came time to think about the font design. What kind of segments would i need to create to make a nice and sharp looking large character font for this screen?

I wanted to start simple and get numbers out of the way first. I figured people would be looking for a good large font to use for an Arduino based digital clock or other project. So logically i started by designing the number 8 since all the segments that comprise the 8 can be mixed to create most of the other numbers. This used up only 6 of the custom blocks. But i still needed one more custom block to be able to display a 0, 1 and  7.

The images show the first generation of numbers i produced. A couple of changes where made in the custom blocks to make the numbers look better. I'll show them in a later step.

STEP 3: Letter Font Design

After i had created the numbers i moved on to designing letters. I got request from people for letters that are commonly used in Temp, RPM, and Speed displays. So i went ahead and created the full alphabet. To get everything looking right i needed to use the final available custom character block.

The images show the first generation of letters i produced. A couple of changes where made in the custom blocks to make the letters look better. I'll show them in the next step.

STEP 4: Refining the Look

My first design for the letters where too rounded for my taste. I then learned how to call up the various predefined characters the LCD is able to display. So i pulled up the Data sheet on the Hitachi HD44780 driver so see the available characters.I noticed the last character is a solid block so i decided to use that in conjunction with the custom characters. I also need the blank character right above the full block. I made a slight adjustment to the custom characters i created and we where ready to build a sketch.

Here is a video of the original characters scrolling across the screen. Some changes have been made since this video was taken.



The code for this will be discussed in the next step.

STEP 5: Arduino Sketch

The attached files below are the Arduino sketch that creates the large font and scrolls it across the screen. I've included the sketch in both ide and text format. It has had a couple of updates since the video. Namely the upper left segment of the number 5 has been changed to the full block to help differentiate it from the S. Also the bottom of the 1, 4 and 9 have been changed to the solid box. What follows is a break down of the sketch in hopes of making it easier to understand so you can adapt it for your own project. A more usable but less understandable sketch was created and is give in the next step.

In the code i first want to lay out the basic info about the sketch and the circuit layout.

To help control the LCD we use the LiquidCrystal.h Library. (#include )

We then set the pins on the Arduino that will be used to control the LCD.

Next the sketch sets up 8 arrays which create and store the 8 customized characters that are used to create the large font. Each array is in Binary. The zeros are blank pixels in the character blocks and the ones represent filled in pixels. The abbreviations i used where to help me keep track of where each block would go in reference to building the large number 0. I have labeled the image to give you a better understanding of the segments and there labels.
  • LT= left top
  • UB= upper bar
  • RT= right top
  • LL= lower left
  • LB= lower bar
  • LR= lower right
  • UMB= upper middle bar(upper middle section of the '8')
  • LMB= lower middle bar(lower middle section of the '8')
Next we get into the void setup() of the sketch. Here we need to assign the arrays we just built to the memory slots on the LCD that are available to store custom characters. We assign them a number between zero and seven.

Next we have a set of void customxx() that define the placement of each custom block to create the large font. In each one we first set the cursor on the screen where we want  to start displaying the blocks needed. Lets look at the first one.

void custom0O()
{ // uses segments to build the number 0
  lcd.setCursor(x, 0);  Sets cursor on the first row of the display at location x (x used as a variable that can change so the characters can scroll across the screen)
  lcd.write(8);   places the LT segment
  lcd.write(1);   places the UB segment
  lcd.write(2);   places the RT segment
  lcd.setCursor(x, 1);    Moves cursor to 2nd row
  lcd.write(3);    places the LL segment
  lcd.write(4);    places the LB segment
  lcd.write(5);    Places the LR segment
}

When we get down to the '5' we start using the solid block which is located at address 255. When we reach the 'A' we use a blank block located at address 254. Its important to note that some Hitachi HD44780 compatible screens will have a blank character at 255 and the solid character will be at 254.

void custom5()
{
  lcd.setCursor(x,0);
  lcd.write(255);
  lcd.write(6);
  lcd.write(6);
  lcd.setCursor(x, 1);
  lcd.write(7);
  lcd.write(7);
  lcd.write(5);
}

After we have defined all the letters we lay out the scrolling of the letters. Since the screens buffer isn't large enough we need to break it down into groups that only use 40 columns. Most of the letters only take up 3 columns plus you use 1 column to keep a space between letters. So we lay it out for the first scrolling group to be A, B, C, D, E, F, G, H, I, and J. We throw a small delay on there so you can see the last couple of letters before it clears the screen to move onto the next group. It takes 4 groups to show all the characters on the screen.

Now we reach the main body of the sketch the void loop(). Here we make a call to the first letter group we defined which lays out the letters into the screen's buffer. We then start a For() statement which starts the letters scrolling across the screen. We give it a slight delay after each column shift so the letters are not blurred. When the For() statement is complete we clear the LCD display and have a short delay before moving onto the next group. The sketch will just keep scrolling through all the letter and number groups until you cut the power.

That pretty much covers the sketch. The pictures do a good job of showing the full font. If there is anything i missed or was unclear on please let me know and i'll update this step accordingly.

STEP 6: More Efficient Sketch

A few days after i had posted my code on the Arduino forums back in early 2010, Mark S adapted it to be table driven. Since it is table driven it uses no RAM. While it complicates the sketch a bit it is a more efficient way of setting up the large font and goes a long way to making the fonts usable. I don't fully understand it myself but i'm including the code here in text form only.

This was made back in 2008 on IDE 0.8. Things have changed a bit since then so the sketch might not compile. I had to make some adjustments to my original sketch before i could post it here since the Liquid CrystalLibrary had changed a bit since i last used the sketch. 

Here is a link to the thread in the Arduino Forum Archives where i first demonstrated this custom font. There you can read about the evolution that took place in refining the font and the sketch.
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1265696343/0

24 Comments

Hi. If I have a 4x20 LCD display, can I use your custom large font on the top two lines and regular size font on the bottom two lines? Thanks.
I originally found Michael's rounded font on the forum he mentioned above and hadn't seen this instructable. I found an LCD which had no gap between the two rows of characters which seemed to be just the thing for a double height font, so implemented my own version with chunky block characters, but took another look at the rounded font and found it worked better. So I created a full 96 character ASCII character set using the rounded font elements. The LCD module I am using has a few extra predefined block characters which helped when creating the lowercase characters, but I also created a usable lowercase set that should be compatible with most standard LCDs. See my one at http://projects.scorchingbay.nz/dokuwiki/arduino/m... The alternate fonts are defined in http://projects.scorchingbay.nz/dokuwiki/_media/ar....

(Animated GIF image doesn't display properly so added a PNG of the character set)
Made a standalone version of my lowercase characters in case anyone wanted a simple way to try them out:

/*
* I have backported this from my custom driver library, into a standalone sketch so the structure is a bit odd
* The library was designed so that output was done through normal lcd.print statements and that looked after
* printing strings and numbers etc ... this version just does simple character at a time output.
* But you can turn on "autoScroll" and just keep writing and scrolling forever if you want.
*/

// Include the library code:
#include "LiquidCrystal.h"

// Initialize the library with the numbers of the interface pins
// rs, enable, d0, d1, d2, d3, d4, d5, d6, d7
//
// You have to change this to reflect your hardware connections (d0 to d3 optional)
LiquidCrystal lcd(12, 13, 4, 5, 6, 7, 8, 9, 10, 11);

#define mx300_bigfont1c // Set to bigfont1b, bigfont1c as desired
// bigfont1c - Based on Michael Pilcher/Dale Gass's original, but altered characters and lowercase & punctuation added
// bigfont1b - Michael Pilcher/Dale Gass's character set with slight alterations and lowercase added

uint8_t rows = 2;
uint8_t cols = 20;
uint8_t currline;
uint8_t currcol;

bool autoScroll; // auto scroll-left if writing beyond column 19
bool shifted; // we have reached column 20
bool writebig; // write the next characters in bigfont

//unsigned long count;
//boolean scroll = false;
//int pos = 0;

// Michael's set of user programmable characters for rounded big fonts
static uint8_t charpixels[8][8] = {
{ 0x07,
0x0F,
0x1F,
0x1F,
0x1F,
0x1F,
0x1F,
0x1F
},

{ 0x1F,
0x1F,
0x1F,
0x00,
0x00,
0x00,
0x00,
0x00
},

{ 0x1C,
0x1E,
0x1F,
0x1F,
0x1F,
0x1F,
0x1F,
0x1F
},

{ 0x1F,
0x1F,
0x1F,
0x1F,
0x1F,
0x1F,
0x0F,
0x07
},

{ 0x00,
0x00,
0x00,
0x00,
0x00,
0x1F,
0x1F,
0x1F
},

{ 0x1F,
0x1F,
0x1F,
0x1F,
0x1F,
0x1F,
0x1E,
0x1C
},

{ 0x1F,
0x1F,
0x1F,
0x00,
0x00,
0x00,
0x1F,
0x1F
},

{ 0x1F,
0x00,
0x00,
0x00,
0x00,
0x1F,
0x1F,
0x1F
},
};

// Full 96 ASCII character set, 4 bytes for upper half, 4 bytes for lower half of each character
// each byte defines which LCD character for use, usually one of the user-defined ones from the array above
// but any character can be used
// 32 = space
// 45 = minus sign
// 255 = solid block
// 'e', 's' and 'z' are a bit ugly

#ifdef mx300_bigfont1c
static const uint8_t bigFont[96][8] = {
{32,32,32,32,32,32,32,32}, // space
{5,32,32,32,4,32,32,32}, // !
{0,0,32,32,32,32,32,32}, // "
{4,0,0,4,4,3,3,4}, // #
{0,255,6,32,7,255,5,32}, // $
{1,4,5,32,0,1,4,32}, // %
{0,255,4,32,3,5,7,32}, // &
{0,32,32,32,32,32,32,32}, // '
{0,32,32,32,3,32,32,32}, // (
{2,32,32,32,5,32,32,32}, // )
{3,255,5,32,0,255,2,32}, // *
{32,4,32,32,1,255,1,32}, // +
{32,32,32,32,5,32,32,32}, // ,
{32,32,32,32,1,1,1,32}, // -
{32,32,32,32,4,32,32,32}, // .
{32,4,5,32,0,1,32,32}, // /
{0,1,2,32,3,4,5,32}, // 0
{1,2,32,32,4,255,4,32}, // 1
{6,6,2,32,3,7,7,32}, // 2
{6,6,2,32,7,7,5,32}, // 3
{3,4,2,32,32,32,5,32}, // 4
{3,6,6,32,4,7,5,32}, // 5
{0,6,6,32,3,7,5,32}, // 6
{1,1,2,32,32,0,32,32}, // 7
{0,6,2,32,3,7,5,32}, // 8
{0,6,2,32,32,32,5,32}, // 9
{1,32,32,32,1,32,32,32}, // :
{1,32,32,32,5,32,32,32}, // ;
{4,45,1,32,1,45,4,32}, // <
{4,4,4,32,1,1,1,32}, // =
{1,45,4,32,4,45,1,32}, // >
{1,6,2,32,32,7,32,32}, // ?
{1,7,2,32,3,4,5,32}, // @
{0,6,2,32,5,32,5,32}, // A
{0,6,5,32,3,7,2,32}, // B
{0,1,1,32,3,4,4,32}, // C
{2,1,2,32,5,4,5,32}, // D
{0,6,6,32,5,7,7,32}, // E
{0,6,6,32,5,32,32,32}, // F
{0,1,1,32,3,4,2,32}, // G
{0,4,0,32,5,32,5,32}, // H
{1,0,1,32,4,5,4,32}, // I
{32,32,2,32,4,4,5,32}, // J
{0,4,1,32,5,32,2,32}, // K
{2,32,32,32,3,4,4,32}, // L
{0,3,5,2,5,32,32,5}, // M
{0,3,32,0,5,32,2,5}, // N
{0,1,1,2,3,4,4,5}, // O
{0,6,2,32,5,32,32,32}, // P
{0,1,2,32,3,4,3,4}, // Q
{0,6,5,32,5,32,2,32}, // R
{0,6,6,32,7,7,5,32}, // S
{1,2,1,32,32,5,32,32}, // T
{0,32,2,32,3,4,5,32}, // U
{2,32,0,32,3,4,5,32}, // V
{0,32,32,2,3,0,2,5}, // W
{3,4,5,32,0,1,2,32}, // X
{3,4,5,32,32,5,32,32}, // Y
{1,6,5,32,0,7,4,32}, // Z
{5,1,32,32,2,4,32,32}, // [
{3,4,32,32,32,1,2,32}, // backslash
{1,3,32,32,4,0,32,32}, // ]
{0,2,32,32,32,32,32,32}, // ^
{32,32,32,32,4,4,4,32}, // _
{2,32,32,32,32,32,32,32}, // `
{32,4,4,32,255,7,2,32}, // a
{2,32,32,32,3,6,5,32}, // b
{32,32,32,32,0,6,6,32}, // c
{32,32,0,32,0,6,5,32}, // d
{4,45,4,32,3,7,7,32}, // e
{0,6,32,32,3,32,32,32}, // f
{255,6,2,32,4,4,5,32}, // g
{2,32,32,32,3,1,2,32}, // h
{6,32,32,32,3,32,32,32}, // i
{32,32,6,32,4,4,5,32}, // j
{5,4,32,32,5,2,32,32}, // k
{2,32,32,32,3,32,32,32}, // l
{32,32,32,32,0,3,5,2}, // m
{32,32,32,32,0,1,2,32}, // n
{32,4,32,32,255,7,255,32}, // o
{0,6,255,32,5,32,32,32}, // p
{255,6,2,32,32,32,3,32}, // q
{32,4,4,32,0,32,32,32}, // r
{4,45,45,32,6,6,255,32}, // s
{4,0,4,32,4,5,32,32}, // t
{32,32,32,32,3,4,0,32}, // u
{4,4,32,32,3,5,32,32}, // v
{32,32,32,32,5,4,3,32}, // w
{4,32,4,32,7,255,7,32}, // x
{0,4,2,32,32,4,5,32}, // y
{45,45,4,32,255,6,6,32}, // z
{4,0,1,32,1,3,4,32}, // {
{32,255,32,32,32,255,32,32}, // |
{1,2,4,32,4,5,1,32}, // }
{4,1,4,1,32,32,32,32}, // ~
{255,255,255,32,255,255,255,32} // solid block
};
#endif

#ifdef mx300_bigfont1b
static const uint8_t bigFont[96][8] = {
{32,32,32,32,32,32,32,32},
{255,32,32,32,7,32,32,32},
{5,5,32,32,32,32,32,32},
{4,255,4,255,1,255,1,255},
{0,255,6,32,7,255,5,32},
{1,32,4,1,4,1,32,4},
{0,6,2,32,3,7,2,4},
{5,32,32,32,32,32,32,32},
{0,1,32,32,3,4,32,32},
{1,2,32,32,4,5,32,32},
{1,4,4,1,4,1,1,4},
{4,255,4,32,1,255,1,32},
{32,32,32,32,5,32,32,32},
{4,4,4,32,32,32,32,32},
{32,32,32,32,4,32,32,32},
{32,32,4,1,4,1,32,32},
{0,1,2,32,3,4,5,32},
{1,2,32,32,32,255,32,32},
{6,6,2,32,3,7,7,32},
{6,6,2,32,7,7,5,32},
{3,4,2,32,32,32,255,32},
{255,6,6,32,7,7,5,32},
{0,6,6,32,3,7,5,32},
{1,1,2,32,32,0,32,32},
{0,6,2,32,3,7,5,32},
{0,6,2,32,32,32,255,32},
{1,32,32,32,1,32,32,32},
{1,32,32,32,5,32,32,32},
{32,4,1,32,1,1,4,32},
{4,4,4,32,1,1,1,32},
{1,4,32,32,4,1,1,32},
{1,6,2,32,32,7,32,32},
{0,6,2,32,3,4,4,32},
{0,6,2,32,255,32,255,32},
{255,6,5,32,255,7,2,32},
{0,1,1,32,3,4,4,32},
{255,1,2,32,255,4,5,32},
{255,6,6,32,255,7,7,32},
{255,6,6,32,255,32,32,32},
{0,1,1,32,3,4,2,32},
{255,4,255,32,255,32,255,32},
{1,255,1,32,4,255,4,32},
{32,32,255,32,4,4,5,32},
{255,4,5,32,255,32,2,32},
{255,32,32,32,255,4,4,32},
{0,3,5,2,255,32,32,255},
{0,2,32,255,255,32,3,5},
{0,1,2,32,3,4,5,32},
{255,6,2,32,255,32,32,32},
{0,1,2,32,3,4,255,4},
{255,6,2,32,255,32,2,32},
{0,6,6,32,7,7,5,32},
{1,255,1,32,32,255,32,32},
{255,32,255,32,3,4,5,32},
{2,32,32,5,32,2,0,32},
{255,32,32,255,3,0,2,5},
{3,4,5,32,0,32,2,32},
{3,4,5,32,32,255,32,32},
{1,6,5,32,0,7,4,32},
{255,1,32,32,255,4,32,32},
{1,4,32,32,32,32,1,4},
{1,255,32,32,4,255,32,32},
{0,2,32,32,32,32,32,32},
{32,32,32,32,4,4,4,4},
{2,32,32,32,32,32,32,32},
{32,4,4,32,255,7,2,32},
{2,32,32,32,3,6,5,32},
{32,32,32,32,0,6,6,32},
{32,32,0,32,0,6,5,32},
{4,45,4,32,3,7,7,32},
{0,6,32,32,3,32,32,32},
{255,6,2,32,4,4,5,32},
{2,32,32,32,3,1,2,32},
{6,32,32,32,3,32,32,32},
{32,32,6,32,4,4,5,32},
{5,4,32,32,5,2,32,32},
{2,32,32,32,3,32,32,32},
{32,32,32,32,0,3,5,2},
{32,32,32,32,0,1,2,32},
{32,4,32,32,255,7,255,32},
{0,6,255,32,5,32,32,32},
{255,6,2,32,32,32,3,32},
{32,4,4,32,0,32,32,32},
{4,45,45,32,6,6,255,32},
{4,0,4,32,4,5,32,32},
{32,32,32,32,3,4,0,32},
{4,4,32,32,3,5,32,32},
{32,32,32,32,5,4,3,32},
{4,32,4,32,7,255,7,32},
{0,4,2,32,32,4,5,32},
{45,45,4,32,255,6,6,32},
{4,0,1,32,1,3,4,32},
{32,255,32,32,32,255,32,32},
{1,2,4,32,4,5,1,32},
{4,1,4,1,32,32,32,32},
{255,255,255,32,255,255,255,32}
};
#endif

void setup() {
autoScroll = false;
shifted = false;
writebig = false;
currcol = 0;
currline = 0;
// Set up the LCD's number of columns and rows:
lcd.begin(cols, rows);
// Setup the character segments used by the bigfont
for (int n = 0; n < 8; n++)
lcd.createChar(n, charpixels[n]);

// Print a message to the LCD.
lcd.print("hello, world!");
delay(1000);
lcd.clear();
shifted = false;
currcol = 0;
currline = 0;

}

void writeBig(uint8_t upper, uint8_t lower) {
// Write two characters in one column to form part of bigfont character
if (shifted & autoScroll ) lcd.scrollDisplayLeft();
lcd.setCursor(currcol % 40, 0);
lcd.write(upper);
lcd.setCursor(currcol % 40, 1);
lcd.write(lower);
currcol++;
if ((currcol >= cols) & autoScroll ) shifted = true;
}

void writeIt(uint8_t value) {
// "shifted" keeps track of whether we've already printed past the end of the visible display area
// - if so we leftshift the display with each new character.
// This allows us to print _numcols visible characters then start scrolling, and continue scrolling forever
// even once the cursor has wrapped around to 0 (only 40 characters in LCD line buffer)

if (!writebig) {
// Just write normal character but keep track of column in case we will be scrolling
if (shifted & autoScroll ) lcd.scrollDisplayLeft();
lcd.write(value);
currcol++;
if ((currcol >= cols) & autoScroll ) shifted = true;
if ((currcol % 40) == 0) {
currcol = 0;
lcd.setCursor(currcol, currline);
}
} else {
// Write two halves of the bigfont character unless it is a blank column
// (allows for variable width characters)
if ((value >= ' ') & (value < 128)) { // have only defined ASCII characters 32 to 127
for (int i = 0; i < 4; i++ ) { // upto 4 columns per character
if ((bigFont[value - 32][i] != 32) | (bigFont[value-32][i+4] != 32)) { //not a blank column in character
writeBig(bigFont[value - 32][i], bigFont[value - 32][i+4]);
}
}

// If the character to be printed was a space we didn't actually print anything, so print one now
if (value==' ') writeBig(' ', ' ');

// Print a blank gap between characters
writeBig(' ', ' ');
}
currcol = currcol % 40;
lcd.setCursor(currcol, currline);
}
}


void loop() {
// If you want the display to autoscroll, set autoScroll to true and set "shifted" to false and
// "currcol" to 0 whenever you lcd.clear or lcd.home the display.
// You can set "currcol" to reposition the cursor, but using it with autoScroll can get confusing.

autoScroll = false;

unsigned long start, count;
start = count = millis()/1000;
writebig = true;
while ((count-start) < 12) {
currcol = 0;
writeIt(char('0'+((count / 1000))% 10));
writeIt(char('0'+((count / 60)) % 10));
writeIt(':');
writeIt(char('0'+((count % 60)/10) % 10));
writeIt(char('0'+((count % 60)) % 10));
delay(955);
count = millis() / 1000;
}
/*
delay(500);
lcd.clear();
shifted = false;
currcol = 0;
currline = 0;
//unsigned long start, count;
start = count = millis()/1000;
writebig = true;
while ((count-start) < 12) {
currcol = 3;
writeIt(char('0' + ((count / 1000) % 10)));
writeIt(char('0' + ((count / 100) % 10)));
writeIt(char('0' + ((count / 10) % 10)));
writeIt(char('0' + ((count / 1) % 10)));
delay(955);
count = millis() / 1000;
}
*/
lcd.clear();
shifted = false;
currcol = 0;
currline = 0;
autoScroll = true;
char str3[43] = "THE QUICK BROWN FOX JUMPS OVER A LAZY DOG.";
for (int i = 0; i < 42; i++) {
writeIt(str3[i]);
delay(300);
}
delay(3000);

lcd.clear();
shifted = false;
currcol = 0;
currline = 0;

char str4[53] = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
for (int i = 0; i < 13; i++) {
for (int j = 0; j < 4; j++)
writeIt(str4[i * 4 + j]);
delay(300);
lcd.clear();
shifted = false;
currcol = 0;
currline = 0;
}
//delay(3000);

lcd.clear();
shifted = false;
currcol = 0;
currline = 0;
autoScroll = true;
char str[43] = "A quick brown fox jumps over the lazy dog.";
for (int i = 0; i < 42; i++) {
writeIt(str[i]);
delay(300);
}
delay(3000);

lcd.clear();
shifted = false;
currcol = 0;
currline = 0;
autoScroll = true;
for (int i = 0; i < 1000; i++) {
writeIt(char(' ' + (i % 96)));
delay(300);
}
delay(3000);
lcd.clear();
shifted = false;
currcol = 0;
currline = 0;
}
A minor point with Dale's code, the ASCII space character is 20hex, so 32 decimal, or octal code "\040" not "\024", but on many LCDs character 20 is undefined so it works fine :-)

Hey, great instructable. When I first saw the 2x16 display had 8 programmable characters, I was hoping large fonts or some graphics would be possible, but given the few characters, and only two lines to work with, I just assumed a big font would be impossible.

But wow, you did an amazing job making it look nice with minimal characters. Kudos. It was inspiring for me to contribute back some improvements. Very creative an minimalistic use of the corners and edge pieces.

While your letters looked great, in all honesty, your novice (but working!) C code truly made my eyeballs bleed. (Not a snob; I used to do mainframe C compliler language compliance testing, and have worked on Unix utilities for years; so I'm a bit fussy about my C. Code efficiency and compactness is key with me, and are right at home in the Arduino world :) Rather than complain, I'll contribute :)

Updated Version

I've reworked your code to add the following:

- Single nested table for programmable char definitions, rather than 8 global vars
- Character design moved into tables, rather than many lines of code. This cuts
the sketch size by about half. and makes editing/adding characters easier.
- Put character table as a ' '-'_' (ascii 20-5F) table, so other ascii characters could be filled in.
- I added Space, period to get character additions rolling. But I got bored, and did a first crack at all the missing characters 0x20-0x5F (mostly punctuation). (A crazy Friday night here, I tell ya, a bit bleary-eyed near the end.) Some of the chars are pretty rough, it'd be great if Michael and/or others could improve upon them.
- Used space (hex 20) instead of FE (254 dec) as a blank character, more portable.
- Overwrites empty spots in a letter with a space, to help clear overwritten chars.
- Added writeString() and writeChar() function. These handle wrapping around the 40-character LCD buffer elegantly for continuous scrolling.
- Redid the demo loop, removing global varibables, lists of letters, etc., much simplified and more browseable, IMO.
- Call lcd.begin() before programming characters. I would get random dropouts on the prog characters before I did this.

This should be easier to use, more compact, faster, easier to maintain.

I'm going to post it to a blog or instructable (with references back here), but wouldn't mind feedback here (specificially from Michael on the glyphs) before finalizing it.

The sketch

{{{

// Based upon the amazing 2-line character set designed by Michael Pilcher

// Cleaned up for space, efficiency:
//
// - Single array for programmable characters
// - Looped initialization from this table, rather than a series of calls
// - Big character composition stored in strings, rather than drawn in functions
// One-line per character definition, instead of 11 or so
// Makes character tweaking a lot easier
// - Characters in an ASCII table, to encourage adding of more characters
// - Added the rest of ascii characters 0x20-0x5F (some could use improvement :)
// - writeString() function
// - Far fewer system calls by writing strings rather than one-byte-per-call
// (Note: I changed back to one-byte-per-write, as writing multiple bytes at once
// seems to fail to wrap in the LCD's circular display buffer).
// - Handles wrapping of LCD circular display buffer :)
// - Writes out blank spots in each letter, for cleaner overwriting of previous text
// - Uses Space instead of hex 254 blank character; possibly more portable to other displays
// - The demo loop is far more elegant (no more x = x + 4, x = x + 4, x = x + 4 :)
// - Initialization (begin) called before programming the characters
// (Without this, I had inconsistent character programming between runs.)

// This should run faster, be more stable, provide more characters, take half the space,
// and be easier to maintain, than the original.
//
// Dale Gass
// May 10, 2014

#include <LiquidCrystal.h>

// Taylor to your pin arrangement
// My wiring keeps the 'interruptable' pins 2/3 free for interrupt sue
// and pins 10/11 free for SPI communictions/PWM. Plus on the mini Pro,
// the six control lines are now sequential along the side of the board,
// making wiring easier (using a pin header).

LiquidCrystal lcd(9, 8, 7, 6, 5, 4);

/* My circuit:
* LCD RS pin to digital pin 9
* LCD Enable pin to digital pin 8
* LCD D4 pin to digital pin 7
* LCD D5 pin to digital pin 6
* LCD D6 pin to digital pin 5
* LCD D7 pin to digital pin 4
* LCD R/W pin to ground
* 10K resistor:
* ends to +5V and ground - I just grounded pin 3 for full contract
* wiper to LCD VO pin (pin 3)
*/

// The standard wiring which prevents int 2/3 and SPI use:
// Most common in Arduino LCD samples
// LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

// Eight programmable character definitions
byte custom[8][8] = {
{ B11111,
B11111,
B11111,
B00000,
B00000,
B00000,
B00000,
B00000 },

{ B11100,
B11110,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111 },

{ B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
B01111,
B00111 },

{ B00000,
B00000,
B00000,
B00000,
B00000,
B11111,
B11111,
B11111 },

{ B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
B11110,
B11100 },

{ B11111,
B11111,
B11111,
B00000,
B00000,
B00000,
B11111,
B11111 },

{ B11111,
B00000,
B00000,
B00000,
B00000,
B11111,
B11111,
B11111 },

{ B00111,
B01111,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111 }
};

// Characters, each with top and bottom half strings
// \nnn string encoding is octal, so:
// \010 = 8 decimal (8th programmable character)
// \024 = 20 decimal (space)
// \377 = 255 decimal (black square)

const char *bigChars[][2] = {
{"\024\024\024", "\024\024\024"}, // Space
{"\377", "\007"}, // !
{"\005\005", "\024\024"}, // "
{"\004\377\004\377\004", "\001\377\001\377\001"}, // #
{"\010\377\006", "\007\377\005"}, // $
{"\001\024\004\001", "\004\001\024\004"}, // %
{"\010\006\002\024", "\003\007\002\004"}, // &
{"\005", "\024"}, // '
{"\010\001", "\003\004"}, // (
{"\001\002", "\004\005"}, // )
{"\001\004\004\001", "\004\001\001\004"}, // *
{"\004\377\004", "\001\377\001"}, // +
{"\024", "\005"}, // ,
{"\004\004\004", "\024\024\024"}, // -
{"\024", "\004"}, // .
{"\024\024\004\001", "\004\001\024\024"}, // /
{"\010\001\002", "\003\004\005"}, // 0
{"\001\002\024", "\024\377\024"}, // 1
{"\006\006\002", "\003\007\007"}, // 2
{"\006\006\002", "\007\007\005"}, // 3
{"\003\004\002", "\024\024\377"}, // 4
{"\377\006\006", "\007\007\005"}, // 5
{"\010\006\006", "\003\007\005"}, // 6
{"\001\001\002", "\024\010\024"}, // 7
{"\010\006\002", "\003\007\005"}, // 8
{"\010\006\002", "\024\024\377"}, // 9
{"\004", "\001"}, // :
{"\004", "\005"}, // ;
{"\024\004\001", "\001\001\004"}, // <
{"\004\004\004", "\001\001\001"}, // =
{"\001\004\024", "\004\001\001"}, // >
{"\001\006\002", "\024\007\024"}, // ?
{"\010\006\002", "\003\004\004"}, // @
{"\010\006\002", "\377\024\377"}, // A
{"\377\006\005", "\377\007\002"}, // B
{"\010\001\001", "\003\004\004"}, // C
{"\377\001\002", "\377\004\005"}, // D
{"\377\006\006", "\377\007\007"}, // E
{"\377\006\006", "\377\024\024"}, // F
{"\010\001\001", "\003\004\002"}, // G
{"\377\004\377", "\377\024\377"}, // H
{"\001\377\001", "\004\377\004"}, // I
{"\024\024\377", "\004\004\005"}, // J
{"\377\004\005", "\377\024\002"}, // K
{"\377\024\024", "\377\004\004"}, // L
{"\010\003\005\002", "\377\024\024\377"}, // M
{"\010\002\024\377", "\377\024\003\005"}, // N
{"\010\001\002", "\003\004\005"}, // 0/0
{"\377\006\002", "\377\024\024"}, // P
{"\010\001\002\024", "\003\004\377\004"}, // Q
{"\377\006\002", "\377\024\002"}, // R
{"\010\006\006", "\007\007\005"}, // S
{"\001\377\001", "\024\377\024"}, // T
{"\377\024\377", "\003\004\005"}, // U
{"\003\024\024\005", "\024\002\010\024"}, // V
{"\377\024\024\377", "\003\010\002\005"}, // W
{"\003\004\005", "\010\024\002"}, // X
{"\003\004\005", "\024\377\024"}, // Y
{"\001\006\005", "\010\007\004"}, // Z
{"\377\001", "\377\004"}, // [
{"\001\004\024\024", "\024\024\001\004"}, // Backslash
{"\001\377", "\004\377"}, // ]
{"\010\002", "\024\024"}, // ^
{"\024\024\024", "\004\004\004"}, // _
};

int writeBigChar(char ch, int x, int y) {
const char *(*blocks)[2] = NULL; // Pointer to an array of two strings (character pointers)

if (ch < ' ' || ch > '_') // If outside our table range, do nothing
return 0;

blocks = &bigChars[ch-' ']; // Look up the definition

for (int half = 0; half <=1; half++) {
int t = x; // Write out top or bottom string, byte at a time
for (const char *cp = (*blocks)[half]; *cp; cp++) {
lcd.setCursor(t, y+half);
lcd.write(*cp);
t = (t+1) % 40; // Circular scroll buffer of 40 characters, loop back at 40
}
lcd.setCursor(t, y+half);
lcd.write(' '); // Make space between letters, in case overwriting
}
return strlen((*blocks)[0]); // Return char width
}

void writeBigString(char *str, int x, int y) {
char c;
while ((c = *str++))
x += writeBigChar(c, x, y) + 1;
}

void setup()
{
lcd.begin(16, 2);
for (int i=0; i<8; i++)
lcd.createChar(i+1, custom[i]);

lcd.clear();
writeBigString("TEST", 0, 0);
delay(2000);

lcd.clear();
}

int x = 16; // Start writing the character just off the display, and scroll it in

void loop()
{
char ch;

for (ch=' '; ch<='_'; ch++) {
int w = writeBigChar(ch, x, 0); // Write big character just off the scren
for (int j=0; j<w+1; j++) { // Scroll it in
lcd.scrollDisplayLeft();
delay(400);
}
x = (x + w + 1) % 40; // Adjust our new X, handling circular buffer wrap
}
}

[/code]

Let me know any changes or suggestions, in particular with the Glyph design, and I'll get this posted in the next few days.

Pointers

The C should be pretty straight forward; I use a few slightly advanced C pointer constructs, like "const char *(*p)[2]" (a variable v, which is a pointer, to an array of two pointers to const chars [an array of two string pointers]. There's an art to understanding and creating C pointers like that; think reading from the inside outworks, working from right to left. Hard to explain, but good to understand to be able to do things efficiently.

const char *(*p)[2] - 'p' is a pointer to...

const chart *(*p)[2] - an array of two...

const char *(*p)[2] = pointers to const chars (i.e. constant strings)

This page has a good rundown of C pointer syntax. Required reading if you're doing any level of Arduino programming beyond the basics.

(If you are tight for space, you could null out {"", ""} the definintions for characters that you aren't using in your app. Even better, moving those arrays to PROGMEM (putting them in FLASH instead of prescious RAM) would save a lot, too. Unfortunately all the type-casting required to make PROGMEM arrays obfuscates the code too much. I erred on the side of readability.)

Thanks again for a creative and inspired font design under tough technical restrictions. Very inspiring.

Open to any questions, feedback, suggestions.

-dale

Very big thanks. I've been looking for a normal code for a long time and I've finally found it.

Thanks Dale, yours is easier to understand. (at least, for my limited knowledge of C/C++;)

x2nie

OMG! Thanks!

Only took 6 years for someone to finally make this useable for most everyone. This deserves to be an instructable. I'd wait for a suitable contest to come along before posting it. Would make for a good entry.

When i find some time i'll have to dive into this a bit and see what else i can do. May prove rather useful in a couple of projects i have in mind.

Hello, thank's for this sketch. I have made a digital clock, based on this. I'm using M302 LCD, from the old fax-printer, which is suprisingly compatible with LiquidCrystal library. It was required to modify a character table a bit, because the blank and filled characters, were located at different addresses than 255 and 254, so it's been glitched, but in the end, i made it works fine :)

https://www.youtube.com/watch?v=v8ifW4CsqwQ&feature=youtu.be

The figure right below the "Step 1" misses the LCD's connection Anode and Katode. If you follow the instructions only looking at that picture your circuit will fail. read the entire text of this project in order to fully understand how its wired.

Wow! Cool!

So without doing any real research do you think this could be done with larger screens ei 20x4 or 40x4? I haven't really done any research on this topic but since you could create any combination I would think it's possible unless the screen has some physical limitations. When I get some time I will definitely try this. Thank you for posting!

You would need to check the specs of the particular screen. For the most part the screens should have some programmable character slots to work with.

Thanks great work

Naksh

www.nrdcentre.com

very interesting and wery useful this article... I adapted your sketch for LCD with i2c interface and is ok... my adapted sketch is at http://nicuflorica.blogspot.ro/2013/06/caractere-mari-pe-afisaj-de-2-randuri.html
I just found this. Thanks great work. I am trying to build a large font display for an antenna rotor for a friend who can't see the small analogue needle. So I got my display reading my pot input as 0-360 , but how do I get it to display the large fonts??
Thanks again.
Take a look at step 5 again and see the sketch posted there. Most of the code sets up each letter and number as custom calls. Then the void letters set up which characters i want on the screen. The in the void loop we bring it all together using for statements to have the full set of characters scroll across the screen.

Any sketch you create will have to call to the correct void custom to bring up the letter or number you want to display. You will need to take the number you want to display and have a translator function that can break 360 down to 3, 6, and 0 each being stored to there own variable. Then make comparisons between the variable and each number. When a match is found call to that custom character and display it. It's also important to have the correct number of cursor spaces between the first column of the first character and the first column of the next character. Characters like the 'M' and 'W' are wider then the rest.

Course all of this makes your code much longer and slower. So if you don't want any kind of delay between moving your pot and getting the readout then this may not be the best option for you.
Ok sounds interesting. ANy ideas on how to do that? (You knew I'd ask...LOL) I looked at converting to a String and you can get character/number positions out of it but I don't know how yet.
Think about a math problem that will be able to get rid of the other digits. May have to be a series of equations that will pull it off. Take the variable and subtract 100. If the variable is greater than 100 add 1 to a tracking variable and loop back to subtract 100 again. If the variable is less than 100 store the tracking variable to the variable for displaying the hundreds and move on to doing a similar equation for the tens and ones. By the end of it all you will have a variable for the hundreds, tens, and ones that match the original number and can make calls to the appropriate character to be displayed.

There is likely a much simpler way of doing it but that is the first thing to come to mind.

Quick code of the hundreds breakdown:
int analog = ??? //value read from the pot input
int track = 0 //tracking variable
int hund = 0 //hundreds place
int ten = 0 //tens place
int one = 0 //ones place
int comp = analog //for storing analog variable after numbers have been subtracted

viod breakhund()
if comp >100
comp = analog-100
track++
breakhun
end

if comp < 100
hund = track
track = 0
breakten

void breakten()


Of course punctuation is missing but that should give you an idea. The tens and ones will follow in the same fashion. You can pretty much copy and past the same code just drop a zero each time. 
Thanks. I finally got some time and I got it to work as you indicated. (Or very close) so I have it displaying 0-360 like I need. I can't get it to clear the numbers properly, such as when you change from 0 to 1 it still displays bits of the 0 along with the 1. I tried using the lcd.write(" "); but it didn't work, I put it into the spot where it prints characters in the function that is called to print a custom character. I also tried the lcd.clear but it is either too fast to see the characters or too slow and they blink.
I thought I'd pick your brain a bit to see if you have an idea.
Thanks for your help so far!!

Here is the code..

// These constants won't change. They're used to give names
// to the pins used:
const int analogInPin = A0; // Analog input pin that the potentiometer is attached to

int sensorValue = 0; // value read from the pot
int outputValue = 0; // value output to the PWM (analog out)
//char outputValueChar[5];
int hundreds;
int tens;
int ones;
#include
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);


// the 8 arrays that form each segment of the custom numbers
byte LT[8] =
{
B00111,
B01111,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111
};
byte UB[8] =
{
B11111,
B11111,
B11111,
B00000,
B00000,
B00000,
B00000,
B00000
};
byte RT[8] =
{
B11100,
B11110,
B11111,
B11111,
B11111,
B11111,
B11111,
B11111
};
byte LL[8] =
{
B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
B01111,
B00111
};
byte LB[8] =
{
B00000,
B00000,
B00000,
B00000,
B00000,
B11111,
B11111,
B11111
};
byte LR[8] =
{
B11111,
B11111,
B11111,
B11111,
B11111,
B11111,
B11110,
B11100
};
byte UMB[8] =
{
B11111,
B11111,
B11111,
B00000,
B00000,
B00000,
B11111,
B11111
};
byte LMB[8] =
{
B11111,
B00000,
B00000,
B00000,
B00000,
B11111,
B11111,
B11111
};

// loop counter
int x = 0;


void setup()
{
Serial.begin(9600);
// assignes each segment a write number
lcd.createChar(0,LT);
lcd.createChar(1,UB);
lcd.createChar(2,RT);
lcd.createChar(3,LL);
lcd.createChar(4,LB);
lcd.createChar(5,LR);
lcd.createChar(6,UMB);
lcd.createChar(7,LMB);


// sets the LCD's rows and colums:
lcd.begin(16, 2);

}

void custom0()
{ // uses segments to build the number 0
lcd.setCursor(x, 0); // set cursor to column 0, line 0 (first row)
lcd.write((byte)0); // call each segment to create
lcd.write(1); // top half of the number
lcd.write(2);
lcd.setCursor(x, 1); // set cursor to colum 0, line 1 (second row)
lcd.write(3); // call each segment to create
lcd.write(4); // bottom half of the number
lcd.write(5);
}

void custom1()
{
lcd.setCursor(x,0);
lcd.write(1);
lcd.write(2);
lcd.setCursor(x+1,1);
lcd.write(5);
}

void custom2()
{
lcd.setCursor(x,0);
lcd.write(6);
lcd.write(6);
lcd.write(2);
lcd.setCursor(x, 1);
lcd.write(3);
lcd.write(7);
lcd.write(7);
}

void custom3()
{
lcd.setCursor(x,0);
lcd.write(6);
lcd.write(6);
lcd.write(2);
lcd.setCursor(x, 1);
lcd.write(7);
lcd.write(7);
lcd.write(5);
}

void custom4()
{
lcd.setCursor(x,0);
lcd.write(3);
lcd.write(4);
lcd.write(2);
lcd.setCursor(x+2, 1);
lcd.write(5);
}

void custom5()
{
lcd.setCursor(x,0);
lcd.write((byte)0);
lcd.write(6);
lcd.write(6);
lcd.setCursor(x, 1);
lcd.write(7);
lcd.write(7);
lcd.write(5);
}

void custom6()
{
lcd.setCursor(x,0);
lcd.write((byte)0);
lcd.write(6);
lcd.write(6);
lcd.setCursor(x, 1);
lcd.write(3);
lcd.write(7);
lcd.write(5);
}

void custom7()
{
lcd.setCursor(x,0);
lcd.write(1);
lcd.write(1);
lcd.write(2);
lcd.setCursor(x+1, 1);
lcd.write((byte)0);
}

void custom8()
{
lcd.setCursor(x,0);
lcd.write((byte)0);
lcd.write(6);
lcd.write(2);
lcd.setCursor(x, 1);
lcd.write(3);
lcd.write(7);
lcd.write(5);
}

void custom9()
{
lcd.setCursor(x,0);
lcd.write((byte)0);
lcd.write(6);
lcd.write(2);
lcd.setCursor(x+2, 1);
lcd.write(5);
}





void loop() {
// read the analog in value:
sensorValue = analogRead(analogInPin);
// map it to the range of the analog out:
outputValue = map(sensorValue, 0, 1023, 0, 360);

// print the results to the serial monitor:
Serial.print("sensor = " );
Serial.print(sensorValue);
Serial.print("\t output = ");
Serial.println(outputValue);

String stringOne = String(outputValue);
// prints the value of outputValue
//Serial.print("Output value string is ");
//Serial.println(stringOne);
x=0;
hundreds = outputValue /100;
//Serial.print(hundreds);
delay(500);

switch (hundreds) {
case 3:
custom3();
break;

case 2:

custom2();
break;

case 1:

custom1();
break;

case 0:

custom0();
break;

}

tens=outputValue /10;
tens=tens%10;
//Serial.println(tens);

switch (tens) {
case 9:
x=x+3;

custom9();
break;

case 8:
x=x+3;

custom8();
break;

case 7:
x=x+2;

custom7();
break;

case 6:
x=x+3;

custom6();
break;

case 5:
x=x+3;

custom5();
break;

case 4:
x=x+3;

custom4();
break;

case 3:
x=x+3;

custom3();
break;

case 2:
x=x+3;

custom2();
break;

case 1:
x=x+3;

custom1();
break;

if (outputValue<10)
{
//case 0:
x=x+3;

custom0();
break;
}

case 0:
x=x+3;

custom0();
break;
}

ones=outputValue%10;
//ones=ones%100;
// Serial.println(ones);

switch (ones) {
case 9:
x=x+3;

custom9();
break;

case 8:
x=x+3;

custom8();
break;

case 7:
x=x+3;

custom7();
break;

case 6:
x=x+3;

custom6();
break;

case 5:
x=x+3;

custom5();
break;

case 4:
x=x+3;

custom4();
break;

case 3:
x=x+3;

custom3();
break;

case 2:
x=x+3;

custom2();
break;

case 1:
x=x+3;

custom1();
break;

case 0:
x=x+3;

custom0();
break;

}
/*
delay(500);
lcd.setCursor(0,1);
lcd.print(outputValue); */
//delay(300);
//lcd.clear();

//if (outputValue < 100)
// {
// x=0;
// lcd.print(" ");
// }

// wait 2 milliseconds before the next loop
// for the analog-to-digital converter to settle
// after the last reading:
//delay(10);


}
Put the lcd.clear before the set cursor and drop the delay before the set cursor. Keep and play around with the delay after the lcd.print. Should smooth things out for you a bit.

There is already a delay with the processing leading up to the print update. So that combined with the delay after the print leaves the display showing strong. Then it get through the processing and clears the screen a millisecond before it prints again causing an imperceptible update.
More Comments