Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

HD44780 Timing problem

Status
Not open for further replies.

Gobbledok

Active Member
Hi guys

I am learning C18 and trying to get a HD44780 LCD display working, like I have done millions of times in assembly (well, maybe not millions).

First up, here's the code:

Code:
	LCD_E = 1;			// Sets the enable line (the default idle state)
	Delay10KTCYx(160);	// Waits 100 milliseconds to make sure the LCD is powered on properly
	LCD_Port = LCD_Port & 0b11110000;	//Clears the bits of the LCD data port
	LCD_Port = LCD_Port + 3;			// Puts the first initialisation data onto the port
	Nop();
	Nop();
	Nop();
	Nop();
	Pulse_E();
	LCD_Delay();

	Pulse_E();
	LCD_Delay();

	Pulse_E();
	LCD_Delay();
	
	LCD_Port = LCD_Port - 1;
	Nop();
	Nop();
	Nop();
	Nop();
	Nop();
	Pulse_E();
	LCD_Delay();	

	Nop();
	Nop();
	Nop();
	Nop();
	Pulse_E();
	LCD_Delay();	

	LCD_Port = 8;
	Nop();
	Nop();
	Nop();
	Nop();
	Pulse_E();	
	LCD_Delay();

	LCD_Port = 0;
	Nop();
	Nop();
	Nop();
	Nop();
	Pulse_E();
	LCD_Delay();	

	LCD_Port = 8;
	Nop();
	Nop();
	Nop();
	Nop();
	Pulse_E();
	LCD_Delay();	

	LCD_Port = 0;
	Nop();
	Nop();
	Nop();
	Nop();
	Pulse_E();
	LCD_Delay();	

	LCD_Port = 1;
	Nop();
	Nop();
	Nop();
	Nop();
	Pulse_E();
	LCD_Delay();	

	LCD_Port = 0;
	Nop();
	Nop();
	Nop();
	Nop();
	Pulse_E();
	LCD_Delay();	

	LCD_Port = 4;
	Nop();
	Nop();
	Nop();
	Nop();
	Pulse_E();
	LCD_Delay();

The Pulse_E and LCD_Delay routines:

Code:
void 	Pulse_E(){   		// Pulses the LCD Enable line
	LCD_E = 0;
	Nop();
	Nop();
	Nop();
	Nop();
	Nop();
	Nop();
	Nop();
	Nop();
	Nop();
	LCD_E = 1;
	Nop();
	Nop();
	Nop();
	Nop();
	Nop();
	Nop();
	Nop();
	Nop();
	Nop();
}

void LCD_Delay(){
	Delay10KTCYx(8);	// Equal to about 5 milliseconds with a 64MHz clock
}

I am running the PIC at 64MHz (overclocked). However even when I use the internal oscillator @ 1MHz, it still doesn't work.

Although if I put breakpoints on 'LCD_E = 0' and 'LCD_E = 1' in the Pulse_E routine and step between breakpoints by hand, it works.

I have read the data sheet and tried following all of the timing requirements (including data setup time, E cycle and pulse time, and delay between commands) but have no idea why this one is playing up :confused:

I have tried more than 1 screen and the result is the same (unfortunately).

If anybody can see my (possibly glaring) mistake, can you point it out and put me out of my misery please ;)
 
LOL good point, but it doesn't explain why it still doesn't work when running off the 1MHz internal oscillator. Or running within spec on the external oscillator.

The overclock was only for a bit of fun to see if it would work really. It is now running within spec with the same results.
 
Hi,

I always have LCD_E set to '0' as default.

I have succesfully run a HD44780 on a pic 18F2420 running at 64MHz with only one code modification (a nop immediately before the 'wait for busy flag' line).

**broken link removed**
 
Last edited:
Thanks for the reply Angry Badger.

I finally got mine working. I just needed to insert more NOP's during the Pulse_E routine, funnily enough. I've never had to before but oh well.

I did use some of the code from your links though!
 
Working code

For anybody interested, here is the full code. I have it working on a PIC 18F4685 @ 48MHz.

You will have to set your LCD Port pins first using the #defines at the start.

If you are running faster than 48MHz, then your delays may need to be increased.

If you are running considerably slower than 48MHz, you screen may be really slow to update, and you will need to decrease the delays.

Code:
#include <p18f4685.h>
#include <timers.h>

//Defines for the LCD screen
#define LCD_RS		LATEbits.LATE0		// Defines which pin the LCD RS signal is connected to
#define LCD_RW		LATEbits.LATE1		// Defines which pin the LCD RW signal is connected to
#define LCD_E 		LATEbits.LATE2   	// Defines which pin the LCD Enable signal is connected to
#define LCD_Port 	LATD   			// Defines which port the LCD data lines are connected to (The high bits of the LCD must be connected to the low bits of the port (e.g. D4=Port?.0, D7=Port?.3)
#define LCD_Tris	TRISD			// Defines the tristate latch byte for the data prot
#define Busy_Flag	PORTDbits.RD3

unsigned char Data;

// Function prototypes
void InterruptHandlerLow(void); /* Interupt Service Routine */
void Delay10KTCYx( unsigned char unit );
void Delay1KTCYx( unsigned char unit );
void Delay100TCYx( unsigned char unit );
void Delay10TCYx( unsigned char unit );
void Pulse_E( void );
void LCD_Delay(void);
void LCD_init(void);
void write2LCD (void);
void PrintLCD (unsigned char Data);
void AddressLCD (unsigned int Data);


// Main code section. Execution starts here.
void main(void){
 
  // Set all of PortA and PortD tristate latches
  TRISA=0b00000000; // 0 = output, 1 = input
  TRISB=0b00000000; // 0 = output, 1 = input
  TRISC=0b00000000; // 0 = output, 1 = input
  TRISD=0x00000000; // 0 = output, 1 = input
  TRISE=0x00000000; // 0 = output, 1 = input

  // Clear the PortA and PortD data latches
  LATA=0b00000000;
  LATB=0b00000000;
  LATC=0b00000000;
  LATD=0b00000000;
  LATE=0b00000000;
  
  ADCON1=0b00001111;    // Set all analog pins as digital I/O

LCD_init();		// Initialises the LCD display

AddressLCD(0x00);
PrintLCD('L');
PrintLCD('i');
PrintLCD('n');
PrintLCD('e');
PrintLCD(' ');
PrintLCD('1');
PrintLCD(' ');
PrintLCD('w');
PrintLCD('o');
PrintLCD('r');
PrintLCD('k');
PrintLCD('i');
PrintLCD('n');
PrintLCD('g');
PrintLCD(' ');
PrintLCD(' ');

AddressLCD(0x40);
PrintLCD('L');
PrintLCD('i');
PrintLCD('n');
PrintLCD('e');
PrintLCD(' ');
PrintLCD('2');
PrintLCD(' ');
PrintLCD('w');
PrintLCD('o');
PrintLCD('r');
PrintLCD('k');
PrintLCD('i');
PrintLCD('n');
PrintLCD('g');
PrintLCD(' ');
PrintLCD(' ');

  // loop that never ends since '1' never changes
  while(1){
		
  }


}
  // end of main

 
// Start of our functions
// These functions are for the HD44780 LCD Display ////////////////////////////////////////////
void 	Pulse_E(){   		// Pulses the LCD Enable line
	LCD_E = 1;
	Delay10TCYx(3);
	LCD_E = 0;
	Delay10TCYx(3);
}

void LCD_Delay(){
	Delay10KTCYx(6);	// Equal to about 5 milliseconds with a 48MHz clock
}

void PrintLCD(){
	Data = WREG;
	LCD_RS = 1;
	write2LCD();
	Delay10TCYx(50);
}

void AddressLCD(){
	Data = WREG;
	Data = Data + 128;
	LCD_RS = 0;
	write2LCD();
	Delay10TCYx(50);
}

void write2LCD (){
	LCD_Port = ((Data >> 4) & 0xf);    // swap nibbles. After swap, 'AND' upper 4 port bits (mask them)
	Pulse_E();
	LCD_Port = (Data & 0xf);
	Pulse_E();   
}

void LCD_init(){
// This next part of the code initialises the LCD Display
	Delay10KTCYx(160);					// Waits 100 milliseconds to make sure the LCD is powered on properly
	LCD_Port = LCD_Port & 0b11110000;	//Clears the bits of the LCD data port
	LCD_Port = LCD_Port + 3;			// Puts the first initialisation data onto the port
	Pulse_E();
	LCD_Delay();
	Pulse_E();
	LCD_Delay();
	Pulse_E();
	LCD_Delay();
	
	LCD_Port = LCD_Port - 1;			// Sets LCD to 4 bit mode
	Pulse_E();
	LCD_Delay();
// end of software reset

	Data = 0x28;						// 2 Rows; 4-bit; 5x7.
	write2LCD();
	LCD_Delay();

	Data = 0x0C;						// Display on; Cursor off; Blink off
	write2LCD();
	LCD_Delay();

	Data = 0x01;						// Clear screen
	write2LCD();
	LCD_Delay();

	Data = 0x06;						// Increment mode
	write2LCD();
	LCD_Delay();
}
 
Last edited:
Your Enable line seems to be inverted. On all the LCDs I've ever worked with the enable line should be pulsed high. You are pulsing it low and leaving it enabled. I suspect this is why you are have timing problems. Also, as you have R/W connected, why don't you use the busy signal instead of delays?

Mike.
 
Last edited:
Yes I converted my ASM LCD routine and for whatever reason I used it inverted in that too. Just looking at the data sheet it most certainly is wrong.

Now that I've changed it to what it should be, I only need half of the delay in the Pulse_E routine.

I've never actually gotten the busy signal to work properly, but that's probably because my Enable was inverted. I'll give it a go again this arvo.
 
Angry Badger, from your link all I have to do to check the busy flag is this:

Code:
void LCD_busy (void) { 
    TRISCbits.TRISC3 = 1; // PORTC bit 7 (LCD pin14 [DB7]) 
    lcd_rw = 1;   
    lcd_en = 1; 
    while (lcd_bf); 
    lcd_en = 0; 
    lcd_rw = 0; 
    TRISCbits.TRISC3 = 0; // PORTC bit 7 (LCD pin14 [DB7]) }

It doesn't seem to work though. I was under the impression that I had to clock the busy flag out of the display.

My code to check the busy flag is:
Code:
void LCD_Busy (void) { 
    LCD_Tris = LCD_Tris + 15; // Sets the LCD port to an input
    LCD_RS = 0;   			  // Makes sure the register select is 0
    LCD_RW = 1;   			  
    LCD_E = 1;
	Delay10TCYx(1); 
    while (Busy_Flag); 
    LCD_E = 0; 
    LCD_RW = 0; 
    LCD_Tris = LCD_Tris - 15; // Sets the LCD Port back to an output 
}
 
That should work for checking the busy flag but a better way is,
Code:
void LCD_Busy (void) { 
    LCD_Tris |= 0x0f; 	// Sets the LCD port to an input
    LCD_RS = 0;   			  // Makes sure the register select is 0
    LCD_RW = 1;   			  
    LCD_E = 1;
	Delay10TCYx(1); 
    while (Busy_Flag); 
    LCD_E = 0; 
    LCD_RW = 0; 
    LCD_Tris &= 0xf0; 	// Sets the LCD Port back to an output 
}
This method doesn't assume that the port is a certain value before you change it. In case you don't know, |= and &= are OR with and AND with. You can also do LCD_Tris += 15 instead of LCD_Tris = LCD_Tris + 15.

Mike.
 
Last edited:
Pommie,

Thanks for the tips. I'm still learning C and they're all handy :)

The @#%$ing busy flag still doesn't work though.

If I check for it at the start of the write2LCD routine, the LCD won't initialise (even when leaving my delays in there).

If I check for it straight after the Data = WREG command in the Address_LCD and Print_LCD routines, it works fine until I take out my delays.

I officially hate the busy flag :p
 
You can't use the busy flag during initialization. See the attached for the process.

Mike.
 

Attachments

  • init.png
    init.png
    27.7 KB · Views: 180
Ah that explains that one then.

However even if I check it before sending a command (as below), it still won't work once I take my delays out.

Code:
void PrintLCD(){
	Data = WREG;
	LCD_Busy();
	LCD_RS = 1;
	write2LCD();
//	Delay10TCYx(50);
}

void AddressLCD(){
	Data = WREG;
	LCD_Busy();
	Data = Data + 128;
	LCD_RS = 0;
	write2LCD();
//	Delay10TCYx(50);
}
 
HI Gobbledok,

Post your entire code.

You're using 4 bit mode? You only check the busy flag after the second 4 bit write i.e. after the whole byte has been sent to the lcd.

AB

BTW, what brand of LCD are you using? I found that Powertip LCD's (the one's I have anyway would only work as above, as per the HD44780 data sheet, but other brands worked ok when checking the busy flag after each four bit write.)
 
Last edited:
Here is my version of the routines so you can check against yours.
Code:
//Initialises the LCD
void InitLCD(void){
    LCDTRIS &= 0xf0;                //ensure data bits are output
    LCD_E=0;                        //clear enable
    LCD_RS = 0;                     //going to write command
    LCD_TRIS_E=0;                   //Set enable to output
    LCD_TRIS_RS=0;                  //set RS to output
    LCD_TRIS_RW=0;
    LCD_RW=0;
    delay_ms(30);                   //delay for LCD to initialise.
    [COLOR="red"]WriteNibble(0x03);              [/COLOR]//Required for initialisation
    delay_ms(5);                    //required delay
    [COLOR="red"]WriteNibble(0x03);              [/COLOR]//Required for initialisation
    delay_ms(1);                    //required delay
    WriteCommand(0x20);             //set to 4 bit interface
    WriteCommand(0x2c);             //set to 4 bit interface, 2 line and 5*10 font
    WriteCommand(0x01);             //clear display
    WriteCommand(0x06);             //move cursor right after write
    WriteCommand(0x0C);             //turn on display
    PutMessage("Hello World!")
}

//wait for the LCD to finish last command.
void WaitLCDBusy(void){
    LCD_RS=0;
    LCD_RW=1;
    LCDTRIS|=0x3c;
    LCD_E=1;
    while(LCD_BUSY==1);
    LCD_E=0;
    LCDTRIS&=0xc3;
    LCD_RW=0;
}

//Send a command to the LCD
void WriteCommand(unsigned char command){
    WaitLCDBusy();                  //wait until not busy
    LCD_RS = 0;                     //setup to send command
    WriteNibble(command>>4);        //write the high nibble
    WriteNibble(command);           //then the low nibble
}

//Send a character to the LCD
void WriteChar(unsigned char chr){
    WaitLCDBusy();                  //wait until not busy
    LCD_RS=1;                       //Setup to send character
    WriteNibble(chr>>4);            //write the high nibble
    WriteNibble(chr);               //then the low nibble
}

//Send any 4 bits to the LCD
void WriteNibble(unsigned char command){
    LCDPORT &= 0xf0;                //clear the data bits
    LCDPORT|=(command & 0x0f);      //or in the new data
    LCD_E = 1;                      //enable the LCD interface
    _asm{
    nop
    nop
    nop
    nop
    }
    LCD_E = 0;                      //disable it

//Write a string to the LCD
void PutMessage(rom char *Message){
char i=0;
    while(Message[i]!=0)
        PutChar(Message[i++]);
} 

}

Edit, Wow, I just found a bug in my code. The lines marked in red were WriteNibble(0x30). Shows how flexible the newer displays are.
Edit2, added the print hello world bit.

Mike.
 
Last edited:
Hi Angry Badger,

Yep in 4-bit mode. I'm checking for the busy flag before I send the first nibble, but not before the second.

Is it supposed to be checked after the last nibble or it doesn't matter?


Code:
// Include the necessary device header file
#include <p18f4685.h>
#include <timers.h>

//Defines for the LCD screen
#define LCD_RS		LATEbits.LATE0		// Defines which pin the LCD RS signal is connected to
#define LCD_RW		LATEbits.LATE1		// Defines which pin the LCD RW signal is connected to
#define LCD_E 		LATEbits.LATE2   	// Defines which pin the LCD Enable signal is connected to
#define LCD_Port 	LATD   				// Defines which port the LCD data lines are connected to (The high bits of the LCD must be connected to the low bits of the port (e.g. D4=Port?.0, D7=Port?.3)
#define LCD_Tris	TRISD				// Defines the tristate latch byte for the data prot
#define Busy_Flag	PORTDbits.RD3

unsigned char Data;

// Function prototypes
void InterruptHandlerLow(void); /* Interupt Service Routine */
void Delay10KTCYx( unsigned char unit );
void Delay1KTCYx( unsigned char unit );
void Delay100TCYx( unsigned char unit );
void Delay10TCYx( unsigned char unit );
void Pulse_E( void );
void LCD_Delay(void);
void LCD_init(void);
void write2LCD (void);
void PrintLCD (unsigned char Data);
void AddressLCD (unsigned int Data);
void LCD_Busy (void);

#pragma code
#pragma interrupt InterruptHandlerLow
void InterruptHandlerLow(){						
}


#pragma code lowvector=0x18			// Low priority interrupts will end up here
void lowvector(void){
		_asm goto InterruptHandlerLow _endasm;
	}
#pragma code

#pragma code highvector=0x08			// Low priority interrupts will end up here
void highvector(void){
		_asm goto InterruptHandlerLow _endasm;
	}
#pragma code




// Main code section. Execution starts here.
void main(void){
 
  // Set all of PortA and PortD tristate latches
  TRISA=0b00000000; // 0 = output, 1 = input
  TRISB=0b00000000; // 0 = output, 1 = input
  TRISC=0b00000000; // 0 = output, 1 = input
  TRISD=0x00000000; // 0 = output, 1 = input
  TRISE=0x00000000; // 0 = output, 1 = input

  // Clear the PortA and PortD data latches
  LATA=0b00000000;
  LATB=0b00000000;
  LATC=0b00000000;
  LATD=0b00000000;
  LATE=0b00000000;
  
  ADCON1=0b00001111;    // Set all analog pins as digital I/O

LCD_init();		// Initialises the LCD display

AddressLCD(0x00);
PrintLCD(' ');
PrintLCD('I');
PrintLCD(' ');
PrintLCD('c');
PrintLCD('e');
PrintLCD('r');
PrintLCD('t');
PrintLCD('i');
PrintLCD('f');
PrintLCD('i');
PrintLCD('a');
PrintLCD('b');
PrintLCD('l');
PrintLCD('y');
PrintLCD(' ');
PrintLCD(' ');

AddressLCD(0x40);
PrintLCD('h');
PrintLCD('a');
PrintLCD('t');
PrintLCD('e');
PrintLCD(' ');
PrintLCD('b');
PrintLCD('u');
PrintLCD('s');
PrintLCD('y');
PrintLCD(' ');
PrintLCD('f');
PrintLCD('l');
PrintLCD('a');
PrintLCD('g');
PrintLCD('s');
PrintLCD('.');

  // loop that never ends since '1' never changes
  while(1){
		
  }


}
  // end of main

 
// Start of our functions
// These functions are for the HD44780 LCD Display ////////////////////////////////////////////
void LCD_Busy (void) { 
    LCD_Tris |= 0x0f; 	// Sets the LCD port to an input
    LCD_RS = 0;   			  // Makes sure the register select is 0
    LCD_RW = 1;   			  
    LCD_E = 1;
	Delay10TCYx(1); 
    while (Busy_Flag); 
    LCD_E = 0; 
    LCD_RW = 0; 
    LCD_Tris &= 0xf0; 	// Sets the LCD Port back to an output 
	Delay10TCYx(1);
}



void 	Pulse_E(){   		// Pulses the LCD Enable line
	LCD_E = 1;
	Delay10TCYx(3);
	LCD_E = 0;
	Delay10TCYx(3);
}

void LCD_Delay(){
	Delay10KTCYx(6);	// Equal to about 5 milliseconds with a 48MHz clock
}

void PrintLCD(){
	Data = WREG;
//	LCD_Busy();
	LCD_RS = 1;
	write2LCD();
	Delay10TCYx(50);
}

void AddressLCD(){
	Data = WREG;
//	LCD_Busy();
	Data = Data + 128;
	LCD_RS = 0;
	write2LCD();
	Delay10TCYx(50);
}

void write2LCD (){
	LCD_Port = ((Data >> 4) & 0xf);    // swap nibbles. After swap, 'AND' upper 4 port bits (mask them)
	Pulse_E();
	LCD_Port = (Data & 0xf);
	Pulse_E();   
}

void LCD_init(){
// This next part of the code initialises the LCD Display
	Delay10KTCYx(160);					// Waits 100 milliseconds to make sure the LCD is powered on properly
	LCD_Port = LCD_Port & 0b11110000;	//Clears the bits of the LCD data port
	LCD_Port = LCD_Port + 3;			// Puts the first initialisation data onto the port
	Pulse_E();
	LCD_Delay();
	Pulse_E();
	LCD_Delay();
	Pulse_E();
	LCD_Delay();
	
	LCD_Port = LCD_Port - 1;			// Sets LCD to 4 bit mode
	Pulse_E();
	LCD_Delay();
// end of software reset

	Data = 0x28;						// 2 Rows; 4-bit; 5x7.
	write2LCD();
	LCD_Delay();

	Data = 0x0C;						// Display on; Cursor off; Blink off
	write2LCD();
	LCD_Delay();

	Data = 0x01;						// Clear screen
	write2LCD();
	LCD_Delay();

	Data = 0x06;						// Increment mode
	write2LCD();
	LCD_Delay();
}
 
Oh almost forgot.

The display has 'SDEC' written on the board which as far as I can tell is the brand.

The part number is BSC2A16ULGY.
 
Thanks Pommie was wondering how to write a whole string instead of letter by letter.

I'm glad I helped you find a bug in your code ;)
 
I don't suppose it really matters if you check before or after a write so long as you don't check in between nybbles. I only asked about the display brand because as well as my own experiences I have seen on other threads elsewhere that Powertip timings can be a bit flaky compared to some other types. So long as you stick to the data sheet timings you'll be fine.
 
Ah ok no worries.

Yeh I don't have the data sheet for my exact display, I'm just using a generic HD44780 datasheet.

I might just stick to timed delays I think, the busy flag seems too much trouble, for this display anyway.
 
Status
Not open for further replies.

Latest threads

Back
Top