New LCD routines for Orangutan

The LCD still seems to be a sticking point for many. I spent some time studying the documentation for the display actually used on the Orangutan – available at
uoxray.uoregon.edu/orangutan/Oran_LCD.pdf
and began to wonder why any of the posted routines ever worked! I haven’t seen an example yet where the initialization is done according to instructions in the data sheet.

I’ve posted a revised set of routines that have been tested on the older Orangutan using the ATMega8 (clock speeds 1 MHz and 8MHz) as well as the newer one using the ATMega168.

Download version with tabs from
uoxray.uoregon.edu/orangutan/lcd_or.c

also posted here for convenience.

/*

lcd_or.c

LCD Routines for Orangutan/WinAVR. Runs on either the ATMega8 or ATmega168 versions.

Substantially modified from a post on the Pololu forum.
The required delays included here are generously in accord with the LCD data sheet, and should be
independent of the clock speed as long as F_CPU is set correctly (>= 1 MHz)

Tested with compiler optimization -O0 and -O3,

Added a routine – LCDGotoXY (X,Y) – which allows printing to any position on the display.
X = character position (0-39), Y=line (0 or 1).

Needed includes: <util/delay.h>, <avr/io.h>

Jim Remington, sjames_remington at yahoo dot com

*/

//Function prototypes

void LCDInit(void);
void LCDSendData(unsigned char data);
void LCDSendCommand(unsigned char command);
void LCDPrintString(const unsigned char *string);
void LCDGotoXY(unsigned char x,unsigned char y);

#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>

// useful defines (LCD commands)

#define LCD_Clear 0x01
#define LCD_Line0 0x80
#define LCD_Line1 0xC0

/*
Send lower 4 bits of data byte to display
*/
void LCDSendNibble( unsigned char data )
{
data &= 0x0F; //lower 4 bits of data
data <<= 3;
PORTB &= ~0x38; //clear LCD bus bits 456
PORTB |= (data & 0x38); //or in data
data <<= 1;
PORTD &= ~0x80; //same for top bit on PORTD.7
PORTD |= (data & 0x80);
_delay_us(1);
PORTD |= (1<<4); //E = 1
_delay_us(2); //required minimum 1 us delay
PORTD &= ~(1<<4); //E = 0
_delay_us(1);
}

/*
Send a character to the LCD display
*/

void LCDSendData( unsigned char data )

{
PORTD &= ~(1<<3); //R/W =0
PORTD |= (1<<2); //RS = 1;
LCDSendNibble( data >> 4 );
LCDSendNibble( data );
_delay_us(100); //38 us typically needed to complete this action
}

/*
Send a command to display. Required delay time depends on the command and the
LCD controller clock frequency – here assumed to be the minimum 190 kHz
*/
void LCDSendCommand( unsigned char command )

{
PORTD &= ~(3<<2); // R/W=0, RS = 0;
LCDSendNibble(command>> 4 );
LCDSendNibble(command);
_delay_ms(3); //maximum required is 2.1 ms for “clear display”
}
// print a string constant

void LCDPrintString( const unsigned char *str )
{
while (*str != 0) LCDSendData( *str++ );
}

// set print position to (x,y) where y=line number (0 or 1), x = character position 0, 1, etc.

void LCDGotoXY(unsigned char x, unsigned char y)
{
volatile unsigned char ddram_addr;
ddram_addr=0x80; //initialize data ram address to 0 (default)
if (y==1) ddram_addr=0xC0; //start print at 2nd line, DDRAM address 0x40
LCDSendCommand(ddram_addr+ (x&0x7F) );
}
/*
Initialize the LCD Display, timing requirements taken from datasheet
Set PORTB.3,4,5 to DB 4,5,6
Set PORTD.2,3,4,7 to RS, R/W, E and DB7
Send required start-up sequence to set 4 bit interface, 2 lines, 5x8 characters and clear display
*/

void LCDInit( void )
{
DDRB |= (7<<3);
DDRD |= (1<<7) | (7<<2);
PORTD &= ~(7<<2); // E=0,R/W=0, RS = 0;
_delay_ms(20); //required startup sequence from power-on (see datasheet)
LCDSendNibble(0x03); //set interface=8 bits
_delay_ms(10); //wait at least 5 ms
LCDSendNibble(0x03); //set interface=8 bits
_delay_ms(1); //wait at least 100 us
LCDSendNibble(0x03); //set interface=8 bits
_delay_ms(1); //wait at least 100 us
LCDSendNibble(0x02); //set interface=4 bits
_delay_ms(1); //delays after this are built into SendCommand
LCDSendCommand(0x28); //set interface=4 bits, 2 lines, 5x8 characters
LCDSendCommand(0x08); //display off, cursor off, blink off
LCDSendCommand(0x01); //clear display
LCDSendCommand(0x06); //entry mode set, cursor shifts right after character rcvd.
LCDSendCommand(0x0D); //0b01DCB D=1:Display on, C=1:cursor on, B=1:Blink on
}

int main(void)
{
unsigned char i,j;

LCDInit();

LCDPrintString("Hello,");
LCDSendCommand(LCD_Line1);
LCDPrintString("World!");
for(j=1;j<5;j++) _delay_ms(250);  	//wait 1 second
for(j=1;j<5;j++) _delay_ms(250);  	//wait 1 second

//wipe out the message just printed…

for (i=0;i<8;i++) {
LCDGotoXY(i,0);						//target a character for destruction on line 0
for(j=1;j<5;j++) _delay_ms(250);  	//wait 1 second
LCDPrintString(".");				//overwrite with a period
}
for (i=0;i<8;i++) {
LCDGotoXY(7-i,1);						//same on line 1
for(j=1;j<5;j++) _delay_ms(250);
LCDPrintString(".");
}
LCDSendCommand(LCD_Clear);
LCDSendCommand(LCD_Line0);
LCDPrintString("Done!");
return 0;

}

Got a funny for you, and maybe an answer to your question about why any of the posted LCD routines ever worked:

When I was testing out my pin change interrupt code on my Orangutan, I ran into problems with the LCD. Turns out it was the LCD, not the interrupt code.

I finally narrowed it down by sticking cli() sei() around various bits of code. (See? I really do get your point about how fast an encoder can generate interrupts. Truly I do. :wink: ) In the bit where it writes a nibble out to the LCD, I had to have this oddball 25-35us delay that didn’t match anything in the datasheet. No matter what I did, I couldn’t get rid of it and still have the LCD function. After some hemming and hawing, the cli() and sei() wound up bracketing that one meaningless delay.

Turns out it was the LCD init. There were a couple of differences between what I was doing (which DID come off of one of those posted routines that shouldn’t have worked) and the data sheet. I went through and one by one tweaked them up. In the end I was able to get rid of not only the cli() sei() crud, but the delay itself.

The new LCD code also looks at the busy flag on the LCD rather than using fixed delays after a command. Neat, because it speeds things up a lot. But that’s also why I was so confused. I shouldn’t have needed any extra delays!

I think the answer to your quesiton is this: The reason why the posted routines ever worked is because the LCD controller chips have a pretty funky state machine in them that will let them get into something approximating a working condition, even if you didn’t take the right steps to get there.

I can’t say it was a 100% working condition because of that oddball delay I had to throw in. But once I did take the proper steps, everything fell into place and the device behaved exactly as described.

This is also probably why LCDs get the reputation for being incredibly twitchy devices. They’re not. But any number of initialization incantations can get them into a mostly working mode, albeit a twitchy one. Follow the recipe exactly as the data sheet describes, and they really are friendly little things.

Tom

Tom:

I agree completely. The LCD controller is mighty forgiving and it is possible to get something working that probably should not work.

For example, the data sheet clearly states that the first three data packets sent during initialization (three times the command 0x03, set interface to 8 bits) should be nibbles, but most initialization routines that I’ve seen send bytes instead. This means that zero nibbles get interspersed. I can only assume that they are ignored.

It is far better to read the busy flag and avoid all delays. If you would be so kind as to post that code, it would be vastly superior to anything that anyone has done so far, and all of us would be eternally grateful.

Best regards, Jim

Funny you mention the sending bytes rather than nibbles! That was one of the things I changed that suddenly had the whole thing working correctly! Yeah, I’m guessing the zero nibbles are ignored, but there’s no way to be sure. Maybe they were setting something funky that didn’t quite manage to break it. Who knows.

The busy flag version of the LCD code is in the 0.2 rev of Orangutan-lib. Here’s the URL:

orangutan-lib.sourceforge.net

The docs are still in a bit of a shambles, especially the LCD docs. But it’s a start.

I’d be happy to post the LCD stuff here, but it’s kinda long and sprawly.

I saw your bar graph source in your ADC post. That’s cool! One of the functions of the LCD I hadn’t really written anything for was the character generation commands. I was thinking of using them for some animation, but the bar graph is a lot more useful than my plans to do a stick figure bouncing a ball. :wink: A GUI for the Orangutan!

Tom