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
:
#include <LiquidCrystal.h>
const int BACK_LIGHT = 13;
LiquidCrystal g_lcd(12, 11, 10, 5, 4, 3, 2);
void setup()
{
pinMode(BACK_LIGHT, OUTPUT);
digitalWrite(BACK_LIGHT, HIGH); g_lcd.clear(); g_lcd.setCursor(0, 0); g_lcd.print("Hello,");
g_lcd.setCursor(0, 1); g_lcd.print("CodeProject");
}
void loop()
{
}
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:
#include <LiquidCrystal.h>
const int BACK_LIGHT = 13; const char* MESSAGE = "Example 2: Hello, CodeProject. ";
const int MESSAGE_LENGTH = 31;
const int DISPLAY_WIDTH = 16;
LiquidCrystal g_lcd(12, 11, 10, 5, 4, 3, 2);
void setup()
{
pinMode(BACK_LIGHT, OUTPUT);
digitalWrite(BACK_LIGHT, HIGH); g_lcd.clear(); 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);
}
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
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:
static prog_uchar bits[] PROGMEM = {
Data is then read from flash memory via pgm_read_byte_near()
:
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:
static uint8_t newBitmap[] PROGMEM = {
...and draws the bitmap via GLCD.DrawBitmap()
:
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
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.
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:
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:
(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:
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