Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Interfacing an Arduino with LCDs

4.97/5 (35 votes)
17 Jul 2009CPOL7 min read 183.1K   1.9K  
Interfacing an Arduino with a character LCD and a graphic LCD

Article 1: Introduction to the Arduino Hardware Platform
Article 3: Arduino-Based MIDI Expression Pedal

Introduction

This is the second article in a three part series I am writing on the Arduino hardware platform. This article focuses on wiring an Arduino to a character LCD and a graphic LCD, and includes static and animated demos to show off the capabilities of each display.

Character and Graphic LCDs

After I initially discovered the Arduino platform, I immediately noticed a wide variety of components that can be connected to an Arduino - everything from inexpensive LEDs, to a moderately priced Ethernet shield, to an outrageous and over-the-top tank shield (which sells for nearly $200!). While shopping for my Arduino, I noticed that LCDs were fairly inexpensive so I purchased a $10 16x2 character LCD, and an $18 128x64 Graphic LCD.

Character LCDs and graphic LCDs are completely different devices and require different libraries and APIs to drive them. Fortunately, both devices are supported by the Arduino community. For my character LCD, I used the LiquidCrystal Library, and for my graphic LCD, I used the KS0108 Graphics LCD library.

Character LCD

Connecting a character LCD and programming it was a breeze and I didn't run into any problems. I simply followed the instructions and wiring diagram in the Arduino Character LCD Tutorial and everything worked as expected. After running the LCD_example sample sketch, I wrote a sketch to take advantage of my character LCD called HelloCodeProject:

C++
/*
    HelloCodeProject.cpp, based off of LCD_example from
    http://www.hacktronics.com/Tutorials/arduino-character-lcd-tutorial.html
*/

#include <LiquidCrystal.h>

const int BACK_LIGHT = 13;          // Pin 13 will control the backlight

// Connections:
// RS (LCD pin 4) to Arduino pin 12
// RW (LCD pin 5) to Arduino pin 11
// Enable (LCD pin 6) to Arduino pin 10
// LCD pin 15 to Arduino pin 13
// LCD pins d4, d5, d6, d7 to Arduino pins 5, 4, 3, 2
LiquidCrystal g_lcd(12, 11, 10, 5, 4, 3, 2);

void setup()
{
    pinMode(BACK_LIGHT, OUTPUT);
    digitalWrite(BACK_LIGHT, HIGH);     // Turn backlight on. 
                // Replace 'HIGH' with 'LOW' to turn it off.
    g_lcd.clear();                      // Start with a blank screen
    g_lcd.setCursor(0, 0);              // Set the cursor to the beginning
    g_lcd.print("Hello,");
    g_lcd.setCursor(0, 1);              // Set the cursor to the next row
    g_lcd.print("CodeProject");
}

void loop()
{
}

Image 1

TickerTape

The second sketch I wrote was TickerTape, which simulates a ticker tape message scrolling across the display. Since TickerTape was the first sketch that I wrote which used an algorithm (the algorithm uses a single buffer for the message, an int to keep track of the starting point of the message to display, and takes into consideration when the message 'wraps' around the end of the buffer), I decided to code the algorithm in a native Win32 C/C++ application first. Since the Arduino has only limited debugging capabilities, I felt that coding the algorithm first in an environment which supports line level debugging and breakpoints would be faster than trying to debug within the Arduino using a bunch of Serial.println() statements. After I verified that the algorithm worked, I coded up the TickerTape sketch:

C++
/*
    TickerTape.cpp, based off of LCD_example from
    http://www.hacktronics.com/Tutorials/arduino-character-lcd-tutorial.html
*/

#include <LiquidCrystal.h>

const int BACK_LIGHT = 13;          // Pin 13 will control the backlight
const char* MESSAGE = "Example 2: Hello, CodeProject. ";
const int MESSAGE_LENGTH = 31;
const int DISPLAY_WIDTH = 16;

// Connections:
// RS (LCD pin 4) to Arduino pin 12
// RW (LCD pin 5) to Arduino pin 11
// Enable (LCD pin 6) to Arduino pin 10
// LCD pin 15 to Arduino pin 13
// LCD pins d4, d5, d6, d7 to Arduino pins 5, 4, 3, 2
LiquidCrystal g_lcd(12, 11, 10, 5, 4, 3, 2);

void setup()
{
    pinMode(BACK_LIGHT, OUTPUT);
    digitalWrite(BACK_LIGHT, HIGH);     // Turn backlight on. 
                        // Replace 'HIGH' with 'LOW' to turn it off.
    g_lcd.clear();                      // Start with a blank screen
    Serial.begin(9600);
}

void loop()
{
    static int s_nPosition = 0;
    int i;
    if(s_nPosition < (MESSAGE_LENGTH - DISPLAY_WIDTH))
    {
        for(i=0; i<DISPLAY_WIDTH; i++)
        {
            g_lcd.setCursor(i, 0);
            g_lcd.print(MESSAGE[s_nPosition + i]);
        }
    }
    else
    {
        int nChars = MESSAGE_LENGTH - s_nPosition;
        for(i=0; i<nChars; i++)
        {
            g_lcd.setCursor(i, 0);
            g_lcd.print(MESSAGE[s_nPosition + i]);
        }

        for(i=0; i<(DISPLAY_WIDTH - nChars); i++)
        {
            g_lcd.setCursor(nChars + i, 0);
            g_lcd.print(MESSAGE[i]);
        }
    }

    s_nPosition++;
    if(s_nPosition >= MESSAGE_LENGTH)
    {
        s_nPosition = 0;
    }

    delay(500);
}

Image 2

Graphic LCD

Getting up and running with my graphic LCD didn't go quite as smooth as the character LCD did. After the wires were connected (all 20 of them!), the sample ExampleGLCD sketch which came with the KS0108 library didn't work. After spinning my wheels a bit, I discovered that the released version of the KS0108 library didn't work with my LCD and I needed a beta version of the library (which has since been promoted to the official Release 2 of the library). Once that was sorted out, everything worked fine.

CodeProjectLogo

Image 3

After getting the ExampleGLCD sample sketch running, I started writing my first graphic LCD sketch, CodeProjectLogo. The first thing I did was write a .NET console application which converts a monochrome 1-bit image into an array to be used within code. The first version created an array of "1"s and "0"s. Unfortunately this didn't work out because the bool array was large and caused the generated sketch to be too large to fit within the Arduino.

My second attempt compressed the image by using run length encoding. Since much of the image's pixels repeat, I thought this might be an efficient way to reduce the data needed for the image. The format of each line was:

[1,0,-42], run1, run2, ..., runN, -1

The first int is a marker. 1 indicates the first run of the line is black, 0 indicates the first run of the line is white, and -42 indicates the array is finished. The second int indicates the run length and toggles back and forth between black and white until -1 is found, which indicates the end of the row. As an example, if there was:

1, 64, 32, 32, -1

...64 black pixels would be drawn, then 32 white pixels, then 32 black pixels.

This worked better as the sketch size was now small enough to upload to the Arduino, however the array was too large for the Arduino's stack. When I included the entire array, the Arduino crashed and it kept restarting itself over and over, and I found that if I included only 1/4 of the total array it worked fine, so the RLE approach didn't work either.

After doing some investigation, I discovered that large amounts of data need to be stored in flash memory using the PROGMEM keyword. Using PROGMEM, the raw array is declared as:

C++
static prog_uchar bits[] PROGMEM = {

Data is then read from flash memory via pgm_read_byte_near():

C++
char val = pgm_read_byte_near(bits + (i + (128 * j)));

...and then drawn one pixel at a time via GLCD.SetDot().

In addition to discovering that I needed to store the data in flash memory, I also learned that the beta version of the KS0108 library also included a new function, DrawBitmap(). The second version of CodeProjectLogo (just comment out the #define USE_SET_DOT statement) declares the array as:

C++
static uint8_t newBitmap[] PROGMEM = {

...and draws the bitmap via GLCD.DrawBitmap():

C++
GLCD.DrawBitmap(newBitmap, 0, 0, BLACK);

I've included the sources to the BMP_Dump project (a C# Visual Studio 2008 solution). It's not very robust (it doesn't do any error checking) but it does get the job done, though it is meant for development use only and should be used with kid gloves. I've left all three formats in (raw bool array, compressed RLE int array, and packed paged int array) in case anyone wants to play around with the different formats that are generated and display a different image within their graphic LCD (simply copy and paste the array once it's been generated).

Ski

Image 4

My next graphic LCD sketch was a game called Ski, which is a game that's reminiscent of the 8-bit games published in computer magazines in the 80s, like Compute!, A.N.A.L.O.G., and Antic. It's a very simple game where the player tries to keep within the bounds of a randomly generated ski course and uses a potentiometer to control the direction of the player. I should point out that I am not a game programmer, Ski is not a very polished game, and is just a proof of concept, but it does show the potential of what can be done. For this sketch, in addition to a 128x64 LCD connected to your Arduino, you will need a push button connected to digital pin 2 and a 10K Ohm potentiometer connected to analog pin 5.

Image 5

Image 6

Image 7

Note: This picture didn't turn out very well (but it's the best I have of this shot). I lightened the image a bit but some of the wires are still a bit hard to make out. The ground wire coming out of the breadboard is going into the ground pin of the Arduino, and the ground wire of the potentiometer is going into the ground of the breadboard. Due to the angle the photograph was taken (and the contrast), within the image it looks like the ground wire of the potentiometer is also going into the ground pin of the Arduino. If you zoom in on the image, the wiring is a bit clearer.

The player is first presented with the start screen. Once she/he presses the button, the game begins and a random track is generated. The track, as well as the player, can have a direction (slope) of -2, -1, 0, 1, or 2:

Image 8

The course is held within a circular memory buffer (g_course) and the current position is tracked with g_nCircularBuferPosition. At the end of every 16 moves, a new slope is calculated:

Image 9

(Source code for Ski is a bit lengthy, so please consult the zipped project file for the entire source code.)

Ski2

With the first version of Ski, I was drawing every pixel of every row. Consequently, the game is too slow, so I revised the code to only draw the pixels that have the potential of changing and focus on drawing the current edge +/- 3 pixels:

C++
nEdge = g_course[i] - 3;
if(nEdge < 0)
    nEdge = 0;
GLCD.DrawLine(
    nEdge,
    nYPos,
    g_course[i],
    nYPos,
    BLACK);
.
.
.

... and I found this made the game much more playable (the speed was increased so much that I needed to add a delay() call).

Conclusion

In this article, I have demonstrated how to interface various input and output hardware with an Arduino, and have demonstrated the potential of what can be accomplished. I consider these components basic building blocks from which one can mix and match and make new and unique devices. The potential is huge and only limited by one's imagination. For inspiration and examples on what can be done with the Arduino, be sure to check out the Blimp kit, virtual magic mirror, electronic drum interface, and personal flight recorder projects.

History

  • 16th July, 2009: Initial post
  • 16th July, 2009: Article updated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)