EE109 – Spring 2017 Introduction to Embedded Systems

EE109 – Spring 2017: Introduction to Embedded Systems

Lab 6

Writing Strings to the LCD Display


This lab exercise is an extension of Lab 5 where you developed some of the software to interface your Arduinio with an LCD display. In this lab you will write additional software to allow you to display short messages on the LCD, and also use the ADC module to read inputs from the five buttons on the LCD. We'll put all this together to create a very simple video game for the LCD.

Getting Started

  1. Create a "lab6" folder in your "ee109" folder.
  2. From the "lab5" folder, copy the "Makefile" and "lab5.c" file to the "lab6" folder.
  3. Rename the "lab5.c" file to be "lab6.c".
  4. Edit the Makefile to change the OBJECT line to say "lab6.o".

Displaying characters

The LCD display uses the ASCII character codes to determine which characters to display. Any byte written into the data register will result in the character with that ASCII code being displayed on the LCD and the cursor that indicates where the next character will go is advanced one position to the right. For example if the bytes with values 0x55, 0x53, 0x43 are written to the data register one after the other, the character ``USC'' will appear on the screen.

Each character position on the display has a unique address in the same way that bytes in computer's memory have unique addresses. The figure below shows the address of all 32 character positions. The positions on the first line start at address 0x00 at the left side and go up to 0x0F at the right side. Similarly, the positions on the second line start at 0x40 and go up to 0x4F.

The cursor can be moved to any of these positions by sending a command byte to the LCD. The format of the cursor position command is the eight bit value 0x80+p where "p" is the address of the position on the screen where the cursor is to be moved. For example, if you want to move the cursor to the 4th position on the 1st row (address = 0x03), send the command byte 0x83 (0x80 + 0x03). To go to the 11th position on the second row send a 0xCA (0x80 + 0x4A).

Once the cursor has been moved to a position, any subsequent data bytes sent will cause characters to appear starting at this position and extending to the right. Note: if you write more characters than will fit on a line, it doesn't automatically wrap to the next line.

Task 1: Write the top level LCD functions

In Lab 5 we discussed how the LCD software was divided into "layers". The lower level handles the details of manipulating the PORT bits to transfer a 4-bit nibble to the LCD. The mid level contains two functions for transferring an 8-bit byte to either the command or data register. The top level contains functions that make it easy for a programmer to make use of the LCD without having to know the details of how it is interfaced to the microcontroller. In Lab 5 you wrote the low and mid level functions and one top level function (init_lcd). Now we want to add two additional top level functions to lab6.c that will allow us to write strings of characters at any position on the screen. These routines should make use of the functions defined in the mid level of the program. The following functions will need to be written.

The moveto function is used to set the position on the LCD screen where the next character will be written. This position is shown on the screen with a blinking underline cursor.

void moveto(unsigned char row, unsigned char col)
    /* Move the cursor to the row (0 or 1) and column (0 to 15) specified */

The moveto function is designed to hide from the user the ugly details about how the LCD actually addresses the characters in the display (first row : 0x00 to 0x0F, second row: 0x40 to 0x4F) but instead just uses a row number (0 or 1) and a column number (0 to 15). The code in moveto translates the row and column numbers into a single address value, and then calls the writecommand function with an argument of 0x80+address.

The stringout routine writes a string of multiple characters to the LCD starting at the current cursor position. The argument to stringout is pointer to a standard C string, an array of "char" variables terminated by a zero byte.

void stringout(char *str)
    /* Write the string pointed to by "str" at the current position */

The stringout function is very simple. It should just be a loop that does the following:

Keep in mind that the stringout function has no idea where it is writing characters on the screen. It just writes characters at whatever place on the screen that you moved the cursor to with the moveto function. It Don't complicate it by trying to make it wrap text onto another line if it goes past the right side of the LCD. Just write all the characters in the string, one after another, even if some go past the right side and can't be seen.

Creating character strings

Now that you have the top level LCD routines written, it's time to write some strings to the LCD screen. Since the LCD is used to display strings of characters, it's necessary to have a efficient way to create the strings to be displayed. Constant strings are no problem and can be given as an argument to the "stringout" function.

stringout("Hello, world"};

It gets more complicated when you need to create a string containing variable values, such as numbers. For example, in C++ when you write cout << "Answer is " << x << endl;, cout is converting the numeric value of x into the ASCII representation of that number. In C, the best tool for doing that is the "snprintf" function that is a standard part of most C languages. Important: In order use snprintf, or any other routine from the C standard I/O library, you must have the following line at the top of the program with the other #include statements.

#include <stdio.h>;

The snprintf function is called in the following manner

snprintf(buffer, size, format, arg1, arg2, arg3, ...);

where the arguments are


A char array large enough to hold the resulting string.


The size of the buffer array. This tells the snprintf program the maximum number of character it can put in buffer regardless of how many you try to make it do.


The heart of the snprintf function is a character string containing formatting codes that tell the function exactly how you want the following arguments to be formatted in the output string. More on this below.


After the format argument comes zero or more variables containing the values to be placed in the buffer according to the formatting codes that appear in the format argument. For every formatting code that appears in the format argument, there must be a corresponding argument containing the value to be formatted.

The format argument tells snprintf how to format the output string and has a vast number of different formatting codes that can be used. The codes all start with a percent sign and for now we will only be working with two of them (and a simple variation of one of them):

Used to format decimal integer numbers. When this appears in the format string, the corresponding argument will be formatted as an decimal integer number and placed in the output string. It will only place as many characters in the buffer as needed to display the number. A number from 10 to 99 will take up two places, a number from 100 to 999 will take up three places, etc. A useful variation of this is to specify a minimum field width by using the form "%nd" where the 'n' is the minimum number of spaces the converted number should occupy. If it takes less space than 'n' characters it will be right justified in the 'n' character field with spaces to the left. For example, a format of "%4d" will print an argument of 65 as "   65" with the number right justified and two leading spaces.
Used to format string variables. When this appears in the format string, the corresponding argument will be assumed to be a string variable and the whole string variable will be copied into the output string.

The format string must have the same number of formatting codes as there are arguments that follow the format argument in the function call. Each formatting code tells how to format its corresponding argument. The first code tells how to format "arg1", the second code is for "arg2", etc. Anything in the format argument that is not a formatting code will be copied verbatim from the format string to the output string.

Example: Assume you have three unsigned char variables containing the month, day and year values, and a string variable containing the day of the week, and you want to create a string of the form "Date is month/day/year = dayofweek".

char date[30];
unsigned char month, day, year;
char *dayofweek = "Wednesday";
month = 10
day = 5;
year = 16;

snprintf(date, 30, "Date is %d/%d/%d = %s", month, day, year, dayofweek);

After the function has executed, the array "date" will contain "Date is 10/5/16 = Wednesday". Since you told snprintf that the size of the array was 30, the maximum number of characters it will put in the array is 29 since as with all C strings, the string has to be terminated with a null byte (0x00).

Task 2: Write some strings to the screen

We now have all the tools necessary to write something to the LCD screen.

  1. In lab6.c (a copy of the lab5.c from last week) remove all the code in the main "while" loop. None of the counting code from Lab 5 is needed for this lab.
  2. Add code to write two lines of text to the screen. At least one of the strings written to the LCD must have been created using the snprintf routine as described above. These operations should be done before the while(1) loop which should have no code it at this time.

For example, try writing your name (as much as will fit) to the first row, and then using snprintf to create a string with your birthday, similar to the way the string above was formatted with today's date, to write to someplace on the second row. Your program should demonstrate that you can write characters at whatever position you need them to be, not just a string of characters starting in the upper right corner.

Task 3: Determining the button values

The LCD shield has a cluster of six pushbuttons. Five of these are interfaced through a multistage voltage divider to ADC channel 0 of the Arduino. Depending on which of the five buttons is pressed, or none, one of six analog voltages appears on the ADC Channel 0 input. By using the ADC to convert the voltage to a number it's possible to easily determine if one of the five buttons was pressed. The sixth button, marked "RST" is connected to the RESET line of the Arduino and can be used to restart the program.

In order for your program to determine which button was pressed you have to know what the ADC output is for each button, but how do you know what those values are? One way is to write code to loop continuously reading the ADC result and writing the value to the LCD.

Note: Leave the code that displays the two lines of text in the program. You'll use it later to serve a "splash screen" that gets displayed for a few seconds before the rest of the program starts to execute.

Use what you learned in Lab 4 to give the program the ability to use the ADC to read the button's analog signal. To keep the program from getting too messy, create a new function called "init_adc" and add all the ADC initialization code to that function. Make sure to change the initialization steps to use channel 0 as the ADC input. Somewhere near the start of your program add a call to the init_adc function so the ADC is initialized before you try to do any conversions.

In your main program declare a "char" array to hold the ADC value string, something like

char buf[10];

In the while(1) loop of the main routine add code to do the following.

  1. Start a conversion, wait for it to complete and read the value of the ADC.
  2. Use the snprintf routine to format the ADC result into a string of numerical characters that can be displayed. (e.g. something like
    snprintf(buf, 10, "%3d", adcresult);
  3. Use your moveto function to move the cursor to the first character position on the first row.
  4. Use your stringout function to print the string on the display

Once the program is running, try pressing each button and see what value is displayed. Be advised that ADC conversion results may not always be exactly the same each time a button is pressed. Due to electrical noise and thermal effects it may return, for example, 124 one time, 125 another and 126 yet another time.

Record the values shown for each of the five buttons. These will be used in the next task.

Task 4: Write a simple game for the LCD

We want to write a simple (very simple!) video game for the LCD display. The display will show a pattern of empty and filled spaces on both lines. By using the up, down, left and right buttons, move a "player", indicated by an asterisk character, from the starting position (upper-left) to the finish point (marked by an 'F') while avoiding hitting any of the obstacles along the way. If you press the wrong button and hit an obstacle, or move off the top or bottom of the screen, it's "Game Over!".

The playing field will be defined by two "char" arrays, one for the first row and one for second row.

char row0[] = "  ##      ##    ";
char row1[] = "     ## #     #F";

These can be written to the LCD screen using stringout routine whenever you want to display the playing field.

One additional aspect is we want you to track how many moves the user has made during the game. Keep a counter for this value and display it at the end when the game is over (i.e. the user has won or lost).

The program need only perform one game and then wait to be reset. Thus in main:

The game playing loop should do the following:

After leaving the game playing loop, write the results to the LCD screen:

When your program is running, it should only write data to the LCD when needed. Unnecessary writing to the LCD, like when the position of the player hasn't changed, can lead to the display flickering. Your code should check to see if the displayed position has to be changed and only then write to the LCD.


Once you have the assignments working demonstrate them to one of the instructors. Turn in a copy of your source code (see website for details.)