Over on AVRfreaks there are a lot of code snippets describing how to connect an AVR to a character LCD display, such as a standard HD44780-compatible display. But not a lot talking about how to do the same using an Xmega.
For a small project I’d been working on, I had an LCD display connected to an Xmega16 using the 8-bit parallel mode. As anybody who’s tried to migrate from an older AVR to an Xmega knows, the I/O port operations are slightly different, meaning that code written for an AVR won’t even compile using WinAVR (AVR GCC) for the Xmega. After a couple of aborted attempts, I decided to modify some existing AVR code LCD routines to support the Xmega.
4 vs 8 Bit Mode
The code given below is specifically for the 8-bit mode of the HD44780-compatible display. It does not support 4-bit mode. Not that there’s a lot of difference between the two modes, but of course in 4-bit mode every byte being sent to the display requires writing two 4-bit nibbles. So if you need to support 4-bit mode, you’ll need to edit this code slightly to add that in.
Delays vs Busy Flag
Ah, the eternal debate. This code does simple delays. It does not read the LCD busy flag. If you care to know more, read on.
When you write to the LCD display, it’ll be busy for some period of time before you can write again. So how to know when it’s safe to write again (for example to write the next character you want to be displayed)? You have two basic options.
Option 1 is to poll the busy flag of the LCD. However, this requires the ability to read the LCD. After all, you want to read its busy flag. Which means hardware. At the very least the R/W pin of the LCD must be connected to the Xmega so the Xmega can read from the device. Also, voltage levels may be a consideration. The Xmega is a 3.3V device that is not 5V tolerant. Which means that if you’re using a 5V powered LCD, which many of them are, you’re going to need some hardware to perform voltage-level translation on the LCD data bus. For example a 74xx245 IC. Otherwise the LCD will drive the data bus to 5V levels, which can damage the Xmega.
If being able to update the LCD display at the absolute fastest possible rate is important to you, this is the way to do it. By polling that busy flag, the moment the LCD is ready for its next write, you’ll know about it and you can write. The vast majority of applications don’t require this very fast LCD update rate, and so the extra difficulties and costs involved with supporting reading from the LCD are simply not worth it.
Option 2 is a simple delay. You write, then you delay for a while before you write again. The LCD module datasheets generally provide estimates on how long the delays need to be, but most people make their delays much much longer simply to be safe, and to help ensure compatibility with a variety of different LCD module manufacturers.
What you do while you’re delaying is up to you. This code simply waits, twiddling its thumbs. More complex code can set timers, return to an ongoing process and then return back here to write again when the timer expires, etc. But this code implementation just waits. It’s extremely simple. In the case of using a slow processor it’s a moot point anyway, because the delay may only work out to a relatively few CPU cycles. For hardware, simply tie the LCD module’s R/W pin low (to GND) so that the LCD module is told it’s always being written to. That’s it.
And Now, The Code…
Given below are three code sections. The first is the LCD routines. The second is a little .h file which you can include in your application to easily access the LCD routines. And the third is some example code on how to use the LCD routines.
First up are the LCD handling routines; lcd.c
// -------------------------------------------------------------------------------------
// lcd.c file for a 16*2 char LCD interfaced with the ATMEL ATMega32 MCU
// By - Nandan Banerjee
// Updated by - Carlos E. Marciales
// Date - 08/02/2010
//
// Updated by - Frank Van Hooft, Sept 2010 to support Xmega,
// and to add a new function and a .h file
//
// This code only supports 8-bit mode, and it only writes to the LCD.
// It uses simple delays for timing (no reading of the busy flag).
//
// Compiled using AVR-GCC (WinAVR)
// IDE - AVRStudio 4.18
// 8-bit communication mode for the HD44780
// Be sure to set the Frequency field in project properties
// To modify this file, edit the #defines below, and also make any
// necessary changes to lcd_init at the bottom of this file.
#include
#include
#include
// This implementation uses Port A for the Data port, all 8 bits of it
#define DATA_PORT PORTA
// This implementation uses Port B for the Control port
#define LCD_RS PIN3_bm /* RS on pin PB3 */
#define LCD_E PIN1_bm /* E on pin PB1 */
#define COMM_PORT PORTB
// This function clears the RS line to write a command
void lcd_set_write_instruction() {
COMM_PORT.OUTCLR = LCD_RS; // set RS line low
_delay_us(50);
}
// This function sets the RS line to write data
void lcd_set_write_data() {
COMM_PORT.OUTSET = LCD_RS; // set RS line high
_delay_us(50);
}
// This function writes a byte to the LCD
void lcd_write_byte (char c) {
DATA_PORT.OUT = c; // Place data on Data Port
COMM_PORT.OUTSET = LCD_E; // set E line high
_delay_us(1);
COMM_PORT.OUTCLR = LCD_E; // set E line low to latch data into LCD
_delay_ms(10);
}
// This function clears LCD and sets address to beginning of first line
void lcd_clear_and_home() {
lcd_set_write_instruction();
lcd_write_byte(0x01);
_delay_ms(50);
lcd_write_byte(0x02);
_delay_ms(50);
}
// This function sets address to beginning of first line
void lcd_home() {
lcd_set_write_instruction();
lcd_write_byte(0x02);
_delay_ms(50);
}
// This function moves cursor to a given line and position
// line is either 0 (first line) or 1 (second line)
// pos is the character position from 0 to 15.
void lcd_goto(uint8_t line, uint8_t pos)
{
uint8_t position = 0;
lcd_set_write_instruction();
switch(line)
{
case 0: position = 0;
break;
case 1: position = 0x40;
break;
}
lcd_write_byte(0x80 | (position + pos));
}
// This function moves the cursor to 1st character of 1st line
void lcd_line_one() { lcd_goto(0, 0); }
// This function moves the cursor to 1st character if 2nd line
void lcd_line_two() { lcd_goto(1, 0); }
// This function writes a character to the LCD
void lcd_write_data(char c) {
lcd_set_write_data();
lcd_write_byte(c);
}
// This function writes a string (in SRAM) of given length to the LCD
void lcd_write_string(char *x, uint8_t len ) {
while (--len > 0)
lcd_write_data(*x++);
}
// This function writes a null-terminated string (in SRAM) to the LCD
void lcd_write_string_0(char *x) {
while (*x)
lcd_write_data(*x++);
}
// Same as above, but the string is located in program memory,
// so "lpm" instructions are needed to fetch it, and a \0
// must be defined at the end of the string to terminate it.
void lcd_write_string_p(const char *s)
{
char c;
for (c = pgm_read_byte(s); c; ++s, c = pgm_read_byte(s))
lcd_write_data(c);
}
// This function initializes the LCD plus any AVR ports etc
void lcd_init() {
// Initialise the AVR ports and any other hardware bits
DATA_PORT.OUT = 0x00; // initial data lines all low
DATA_PORT.DIRSET = 0xff; // set the 8-bit data port to all outputs
COMM_PORT.OUTCLR = LCD_E | LCD_RS; // all LCD control lines low
COMM_PORT.DIRSET = LCD_E | LCD_RS; // set the LCD control line pins to outputs
// Now do the actual LCD initialisations
// startup delay - make it long to allow time for power to settle
// (you want wish to remove this line)
_delay_ms(500);
// LCD display initialisation by instruction magic sequence
lcd_write_byte(0x38); // function set
_delay_us(50);
lcd_write_byte(0x0c); // display on/off control
_delay_us(50);
lcd_write_byte(0x01); // display clear
_delay_ms(5);
lcd_write_byte(0x06); // entry mode set
_delay_ms(5);
lcd_write_byte(0x14); // Cursor shift
lcd_clear_and_home(); // LCD cleared and cursor is brought to
// the beginning of 1st line
}
Make sure you change the #define’s at the top of the code to reflect the AVR ports your LCD is connected to.
Then this is a simple lcd.h file you can create and include into your main application file.
// lcd.h
// Created by Frank Van Hooft Sept 2010
// to support the lcd.c file
void lcd_set_write_instruction();
void lcd_set_write_data();
void lcd_write_byte (char c);
void lcd_clear_and_home();
void lcd_home();
void lcd_goto(uint8_t line, uint8_t pos);
void lcd_line_one();
void lcd_line_two();
void lcd_write_data(char c);
void lcd_write_string(char *x, uint8_t len );
void lcd_write_string_0(char *x);
void lcd_write_string_p(const char *s);
void lcd_init();
Here are some code snippets on how to use the functions.
// Snippet 1
// Initialise the LCD Display
lcd_init();
// Snippet 2
// clear display
lcd_clear_and_home();
// Snippet 3
// display "Hello World" on LCD first line
lcd_line_one();
lcd_write_string_p(PSTR("Hello World\0")); // message from flash space
// Snippet 4
// display "Currently: xxx" with the variable i on LCD second line
uint32_t i;
char MsgTemp[25];
i = 14;
lcd_line_two();
sprintf_P(MsgTemp, PSTR("Currently: %d"), i);
lcd_write_string_0(MsgTemp);
For the code snippets, the first couple should be pretty self-evident, where the initialisation of the LCD takes place, plus an example of how to clear the whole display in a single function call. The third snippet shows how to display a fixed string on the first line of the display. Note it’s using the lcd_write_string_p() function, with the trailing _p indicating it will read from program memory (flash). So the string is located in program memory by use of the PSTR() macro.
The fourth snippet takes this a small step further. It displays a string on the second line of the LCD display (assuming the LCD physically has two lines, of course). It uses the sprintf_P() function, which is a variant of the normal sprintf() function, to read from a fixed format string in program memory and write into a string in SRAM. Then the function lcd_write_string_0() is used to write that SRAM string’s contents to the LCD. The _0 at the end of the function name indicates it expects a SRAM string will a normal null-termination at the end. This helps distinguish it from the lcd_write_string() function, whose name is slightly misleading because it actually expects a parameter telling it how many characters to write. This code snippet shows just one possible way of how to display a variable’s value on the LCD display.
Hopefully this code proves useful. I’m using it on a small project and it works just fine.
Your best bet is to post your question over at http://www.avrfreaks.net/. Good luck!
Hi Frank
I am trying to interface an HD44780 LCD to an XMega16 MCU. I Have followed all your instructions on this page but nothing is being displayed on the LCD. I am using an STK600 development board with DC power and USB for programming. I gave connected the LCD to the port pins on the development board and i am using a separate 5V regulated power supply for the LCD power. I have grounded the RW pin as per your instructions. Port A is being used for DATA and PortB as the control. I compiled the program using AVR-GCC on the following IDE’s: AVR Studio 5.0 and AVR Studio 4.18. The program builds and compiled with o errors. I used all of your code snippets to test the LCD but nothing is being written.
Please help me with this problem. I do not understand what I am doing wrong. Your help will be greatly appreciated.
Thank you for a great webpage and all your time and effort.
Sudhir owthar
Hello Frank,
I find this code very useful in a project of my own that I am working on. The one thing I am having troubles with is the correct code for displaying a graph like scene on the HD44780 LCD. If you follow this link (http://www.quinapalus.com/hd44780udg.html) I am looking for something like what is displayed in Row 6 Columns 1-5. I have some other pre-made code a libraries that I am trying to manipulate. I am using the HSC12-USB Dragonboard with C Code and using Codewarrior. If you have any ideas or other code samples you thing could help me out I would greatly appreciate it.
Thanks for your time and efforts,
Stuart Goranson