Add An LCD To Your AVR Microcontroller: Write-Up
Add An LCD To Your AVR Microcontroller: Write-Up
to your AVR
microcontroller
There are hundreds, if not thousands, of online articles about interfacing HD44780-
compatible LCD displays to microprocessors/ microcontrollers. I have contributed one to the
pile already, with my write-up on adding LCD displays to the Raspberry Pi. Here is what sets
this article apart from the rest:
Breadboard construction
ATmega328 microcontroller
Uses C language (AVR studio, AVR-gcc)
Does not rely on libraries or other code
Simple, no-nonsense functionality
If you have an AVR micro that you’d like to connect to an LCD display, this is for you.
Next, connect the power lines. The +5 and Gnd lines from the boarduino will supply power
the rest of the project.
Start AVR studio, choose ‘Device Programming’ from the Tools menu, or press Ctrl-Shift-P.
Choose AVRISP II as the Tool, ATmega328P as the Device, and ISP as the Interface. Click
Apply. Now click on the Device Signature Read button. A result of ‘0x1E950F’ indicates
successful 2-way communication with your microcontroller.
From this device programming window you can also set the microcontroller’s fuses. Click on
‘Fuses’ in the left-hand pane. All the fuses except ‘SPIEN’ should be unchecked. (You will
need to uncheck the CKDIV8 fuse.) Also, the SUT_CKSEL fuse should be set to
EXTXOSC_8MHZ_16KCK_14CK_65MS. This will run the chip at 16 MHz, using the external
resonator. After checking your values, click the program button. You need to program the
fuses only once.
3) CODING
For this project I chose ‘C’ as my programming language. For me, C is a bit easier to use
than assembly language. The complete source code for my project is given at the end of this
article. Like many microcontroller projects, the outer shell of the program is very simple:
int main (void)
{
init();
DoSomething();
}
First, init() is called for do-once, initialization steps. Next, DoSomething() is called to create
interesting displays on the LCD. This routine is typically set up as an infinite “while(1)” loop,
so that the program never ends.
The first initialization job is to set up microcontroller pins as inputs or outputs. We need only
outputs for this project. To set a pin as an output, we write a ‘1’ to the ports data direction
register. DDRB is the data direction register for port B. Look at this statement:
DDRB = 0x3F; // 0011.1111; set B0-B5 as outputs
In Port B, we use the lower 6 pins (B5-B0) as outputs, so the corresponding bits are set to
logic 1:
B7 B6 B5 B4 B3 B2 B1 B0
0 0 1 1 1 1 1 1
Put on your thinking cap, because it’s time for the interesting part: sending data to the LCD
controller. There are 8 bits to each byte, but we will only send 4 bits at a time. And we have
to time them according to the controller’s specifications. Check out the datasheet for the
specific details. The gist is to send the upper 4 bits of the data, pulse the LCD enable pin, and
then send the lower 4 bits. The half-byte chunks are called nibbles.
The SendNibble() routine takes the upper 4 bits of the data and places them on four I/O lines
(PB2-PB5). The first line takes all 4 pins to logic 0. SetBit() is an inline macro which sets the
value of a port pin to logic 1.
Pulsing the LCD enable line is as simple as taking the line high, waiting, then taking it low
again. The data is clocked into the module on the high-to-low transition. A very short waiting
period, in microseconds, is specified in the datasheet.
Sending a byte to the LCD controller involves two SendNibble operations: send the upper
half of the byte first, then send the lower half. A right-sift operation ‘<< 4’ moves the lower
four bits into the upper 4 bits.
Bytes can be send to the controller as either data byte (characters) or as commands. The
controller uses the input line RS to distinguish the two: anything sent when RS is low is a
command, and anything sent when RS is high is a character. We’ll create two new routines
to account for this requirement.
Now we have routines for sending commands and characters to the display. It would be nice
to test them right away, but we can’t: we have to initialize the display first. All HD44780-
based displays require certain startup commands to specify things like data-length, cursor-
mode, etc. These command bytes will set the data-length to 4 bits, turn the cursor off,
enable sequential addressing, and clear the display.
void InitLCD()
{
SendCmd(0x33); // initialize controller
SendCmd(0x32); // set to 4-bit input mode
SendCmd(0x28); // 2 line, 5x7 matrix
SendCmd(0x0C); // turn cursor off (0x0E to enable)
SendCmd(0x06); // cursor direction = right
SendCmd(0x01); // start with clear display
msDelay(3);
}
It’s time to write something on the LCD display, like ‘Hello, World’. All we need to do is write
each character, one at a time. A simple for-loop would do the trick. But if we want to get
fancy, and use the fact that strings in c are null-terminated character arrays, we can use a
compact while loop instead:
void ShowMessage(const char *text)
{
while (*text) // string ends with 0x00
{
SendChar(*text++); // auto-increment the array pointer
}
}
5) PROTOTYPING
Boarduino
ISP header
4 data lines
2 control lines
Backlight power
Display
power
There are two control lines (yellow) and four data lines (red) between the PortB pins of the
microcontroller and the display. The two resistors are for an I2C bus, not used in this demo.
6) SOURCE CODE:
//-----------------------------------------------------------------------------
// lcd01: Experiments interfacing ATmega328 to an HD44780 LCD display
//
// Author : Bruce E. Hall <[email protected]>
// Website : w8bh.net
// Version : 1.0
// Date : 7 Sep 2013
// Target : ATmega328P microcontroller
// Language : C, using AVR studio 6
// Size : 836 bytes, using -O1 optimization
//
// Fuse settings: 8 MHz osc with 65 ms Delay, SPI enable; *NO* clock/8
// ---------------------------------------------------------------------------
// GLOBAL DEFINES
// ---------------------------------------------------------------------------
// INCLUDES
// ---------------------------------------------------------------------------
// TYPEDEFS
// ---------------------------------------------------------------------------
// MISC ROUTINES
void SetupPorts()
{
DDRB = 0x3F; // 0011.1111; set B0-B5 as outputs
DDRC = 0x00; // 0000.0000; set PORTC as inputs
}
void FlashLED()
{
SetBit(PORTB,LED);
msDelay(250);
ClearBit(PORTB,LED);
msDelay(250);
}
// ---------------------------------------------------------------------------
// HD44780-LCD DRIVER ROUTINES
//
// Routines:
// LCD_Init initializes the LCD controller
// LCD_Cmd sends LCD controller command
// LCD_Char sends single ascii character to display
// LCD_Clear clears the LCD display & homes cursor
// LCD_Home homes the LCD cursor
// LCD_Goto puts cursor at position (x,y)
// LCD_Line puts cursor at start of line (x)
// LCD_Hex displays a hexadecimal value
// LCD_Integer displays an integer value
// LCD_Message displays a string
//
// The LCD module requires 6 I/O pins: 2 control lines & 4 data lines.
// PortB is used for data communications with the HD44780-controlled LCD.
// The following defines specify which port pins connect to the controller:
void PulseEnableLine ()
{
SetBit(PORTB,LCD_E); // take LCD enable line high
_delay_us(40); // wait 40 microseconds
ClearBit(PORTB,LCD_E); // take LCD enable line low
}
// ---------------------------------------------------------------------------
// MAIN PROGRAM
int main(void)
{
SetupPorts(); // use PortB for LCD interface
LCD_Init(); // initialize LCD controller
LCD_Message("Ready."); // welcome message
msDelay(2000); // wait
while(1)
FillScreen(); // fill screen with ASCII characters
}