PIC32MX: Parallel LCD

From Mech
Revision as of 21:54, 3 January 2010 by NickMarchuk (talk | contribs)
Jump to navigationJump to search

Using 7 pins on the PIC32 it is possible to interface with the popular HD44780 (and compatible chipsets) for easy character output to an LCD. This can be highly useful in either debugging or generating a more complex user interface for a PIC (or other microcontroller)-based device.

First you will need to locate the appropriate datasheet for the actual LCD you are using. What you will want to look up in there is the arrangement of connectors on the top-left of the display, and their names so that you can appropriately match the right connectors with the correct pins on the microcontroller, and in turn with the correct software identifiers for those connections. An example of one such pin arrangement is below.

Lcd pins.jpg
PinDescription
1VSS
2VCC
3VEE
4RS
5R/W
6E
7DB0
8DB1
9DB2
10DB3
11DB4
12DB5
13DB6
14DB7
15LED+
16LED-

Your controller may differ from this one. Occasionally even if you've got the right datasheet these pin numbers can be backwards. If it has a backlight, try powering that up first and ensure that works.

Next, you'll need to wire up the LCD for power, and the connections with the pic.

LCD PinPIC Pin
1 - VSSGND
2 - VCC+5V
3 - VEE1.5kOhm to GND
4 - RSB4
5 - R/WB5
6 - EB6
7 - DB0NC
8 - DB1NC
9 - DB2NC
10 - DB3NC
11 - DB4B0
12 - DB5B1
13 - DB6B2
14 - DB7B3
15 - LED++3.3V
16 - LED-GND

The VEE pin on the LCD sets the contrast of the characters on the LCD. Play with a voltage around 1V to get a nice contrast, or hook it too GND through a 1.5kOhm resistor, which also seems to work well.

If you power the LCD but do not send it any commands, the top row should fill with boxes. Use this to check that your LCD works.

We will use the following h and c files to standardize the communication:

LCD.h

// NOTE THAT BECAUSE WE USE THE BOOTLOADER, NO CONFIGURATION IS NECESSARY
// THE BOOTLOADER PROJECT ACTUALLY CONTROLS ALL OF OUR CONFIG BITS

// Let compile time pre-processor calculate the CORE_TICK_PERIOD

#define FPB			SYS_FREQ 		// Result of bootloader
#define HIGH		1
#define LOW			0

// If you only want a 6-pin interface to your LCD, then
// connect the R/W pin on the LCD to ground, and comment
// out the following line.

#define USE_LCD_RW   1     

//========================================

#define lcd_type 2        // 0=5x7, 1=5x10, 2=2 lines
#define lcd_line_two 0x40 // LCD RAM address for the 2nd line

void Delayms(unsigned t);
void Delayus(unsigned t);
void lcd_init(void);
void lcd_send_nibble(short nibble);
void lcd_send_byte(short address, short n);
void lcd_gotoxy(short x, short y);
void putsLCD(char *s);

LCD.c

// flex_lcd.c

#include "Compiler.h"
#include "HardwareProfile.h"
#include <plib.h>

// NOTE THAT BECAUSE WE USE THE BOOTLOADER, NO CONFIGURATION IS NECESSARY
// THE BOOTLOADER PROJECT ACTUALLY CONTROLS ALL OF OUR CONFIG BITS

// Let compile time pre-processor calculate the CORE_TICK_PERIOD
#define SYS_FREQ				80000000L
#define FPB						SYS_FREQ 		// Result of bootloader
#define HIGH					1
#define LOW						0

// Define constants for PINS B0 - B6
#define LCD_DB4			LATBbits.LATB0
#define LCD_DB5			LATBbits.LATB1
#define LCD_DB6			LATBbits.LATB2
#define LCD_DB7			LATBbits.LATB3

#define LCD_RS    		LATBbits.LATB4
#define LCD_RW    		LATBbits.LATB5
#define LCD_E     		LATBbits.LATB6

// If you only want a 6-pin interface to your LCD, then
// connect the R/W pin on the LCD to ground, and comment
// out the following line.

#define USE_LCD_RW   1     

//========================================

#define lcd_type 2        // 0=5x7, 1=5x10, 2=2 lines
#define lcd_line_two 0x40 // LCD RAM address for the 2nd line

void Delayms(unsigned t);
void Delayus(unsigned t);
void lcd_init(void);
void lcd_send_nibble(short nibble);
void lcd_send_byte(short address, short n);
void lcd_gotoxy(short x, short y);
void putsLCD(char *s);


short const LCD_INIT_STRING[4] =
{
 0x20 | (lcd_type << 2), // Func set: 4-bit, 2 lines, 5x8 dots
 0xc,                    // Display on
 1,                      // Clear display
 6                       // Increment cursor
 };
                             

//----------------------------
void lcd_init(void)
{
	short i;

	// Set all analog pins to be digital I/O
	// Comment out if you want to use the analog pins
    AD1PCFG = 0xFFFF;
	
	//Set B0-B7 as a digital output, change if you change the pins
	LATB |= 0x00FF; TRISB &= 0xFF00;

	LCD_RS = LOW; // Register Select Command Register

	#ifdef USE_LCD_RW
	LCD_RW = LOW; 
	#endif

	LCD_E = LOW; // Disable

	Delayms(15);

	for(i=0; i < 3; i++)
	   {
	    lcd_send_nibble(0x03);
	    Delayms(5);
	   }
	
	lcd_send_nibble(0x02);
	
	for(i=0; i < sizeof(LCD_INIT_STRING); i++)
	   {
	    lcd_send_byte(0, LCD_INIT_STRING[i]);
	   
	    // If the R/W signal is not used, then
	    // the busy bit can't be polled.  One of
	    // the init commands takes longer than
	    // the hard-coded delay of 60 us, so in
	    // that case, lets just do a 5 ms delay
	    // after all four of them.
	    #ifndef USE_LCD_RW
	    Delayms(5);
	    #endif
	   }

}

//-------------------------------------
void lcd_send_nibble(short nibble)
{
// Note:  !! converts an integer expression
// to a boolean (1 or 0).
 LCD_DB4 = !!(nibble & 1);
 LCD_DB5 = !!(nibble & 2); 
 LCD_DB6 = !!(nibble & 4);   
 LCD_DB7 = !!(nibble & 8);   

 Delayus(1);
 LCD_E = HIGH; // Enable
 Delayus(2);
 LCD_E = LOW; // Disable
}

//----------------------------------------
// Send a byte to the LCD.
void lcd_send_byte(short address, short n)
{
	LCD_RS = LOW;

	/* Need to investiage bit_test
	#ifdef USE_LCD_RW
	while(bit_test(lcd_read_byte(),7)) ;
	#else
	Delayus(60); 
	#endif*/

	if(address)
	   LCD_RS = HIGH;
	else
	   LCD_RS = LOW;
	     
	 Delayus(1);

	#ifdef USE_LCD_RW
	LCD_RW = LOW;
	Delayus(1);
	#endif

	LCD_E = LOW;
	
	lcd_send_nibble(n >> 4);
	lcd_send_nibble(n & 0xf);
}

//-----------------------------------

//-----------------------------
void putsLCD(char *s)
{ 	
	while( *s != '\0')
	{	
		switch(*s)
		{
			case '\f':				// First Line
		    {
				
				mLED_1_On();
				lcd_send_byte(0,1);
		      	Delayms(2);
		      	break;
		   	}
		    case '\n':				// Next Line
		    {
				lcd_gotoxy(1,2);
		    	break;
			}
		    case '\b':
		    {
				lcd_send_byte(0,0x10);
		    	break;
		   	}
		    default:
		    {
				lcd_send_byte(1,*s);
				if (*s == 'H')
				{
					mLED_2_On();
				}
				if (*s == 'e')
					mLED_3_On();
				
		    	break;
		    }
		}
		s++;
		Delayus(10);
	}
}
//----------------------------

void lcd_gotoxy(short x, short y)
{
	short address;
	
	if(y != 1)
	   address = lcd_line_two;
	else
	   address=0;
	
	address += x-1;
	lcd_send_byte(0, 0x80 | address);
}
//------------------------------

/*
// This sub-routine is only called by lcd_read_byte().
// It's not a stand-alone routine.  For example, the
// R/W signal is set high by lcd_read_byte() before
// this routine is called.     

#ifdef USE_LCD_RW
int8 lcd_read_nibble(void)
{
int8 retval;
// Create bit variables so that we can easily set
// individual bits in the retval variable.
#bit retval_0 = retval.0
#bit retval_1 = retval.1
#bit retval_2 = retval.2
#bit retval_3 = retval.3

retval = 0;
   
output_high(LCD_E);
delay_cycles(1);

retval_0 = input(LCD_DB4);
retval_1 = input(LCD_DB5);
retval_2 = input(LCD_DB6);
retval_3 = input(LCD_DB7);
 
output_low(LCD_E);
   
return(retval);   
}   
#endif

//---------------------------------------
// Read a byte from the LCD and return it.

#ifdef USE_LCD_RW
int8 lcd_read_byte(void)
{
int8 low;
int8 high;

output_high(LCD_RW);
delay_cycles(1);

high = lcd_read_nibble();

low = lcd_read_nibble();

return( (high<<4) | low);
}
#endif


//------------------------------
#ifdef USE_LCD_RW
char lcd_getc(int8 x, int8 y)
{
char value;

lcd_gotoxy(x,y);

// Wait until busy flag is low.
while(bit_test(lcd_read_byte(),7)); 

output_high(LCD_RS);
value = lcd_read_byte();
output_low(lcd_RS);

return(value);
}
#endif
*/
   
//==========================
int main()
{
	// Configure the proper PB frequency and the number of wait states
	SYSTEMConfigPerformance(SYS_FREQ);
	
	mInitAllLEDs();	

	lcd_init();  // Always call this first.
	
	putsLCD("\nH");
	//putsLCD("B");
while(1);
} 


void Delayms( unsigned t)
// Notes:
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ
{
    OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);
    while (t--)
    {  // t x 1ms loop
        WriteTimer1(0);
        while (ReadTimer1() < FPB/256/1000);
	}
	CloseTimer1();

} // Delayms

void Delayus( unsigned t)
{
	OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);
    while (t--)
    {  // t x 1ms loop
        WriteTimer1(0);
        while (ReadTimer1() < FPB/256/1000000);
	}
	CloseTimer1();
	CloseTimer1();
}// Delayus

To test these commands, here is a basic main file that writes some text: main.c

#include "Compiler.h"
#include "HardwareProfile.h"
#include <plib.h>

#define SYS_FREQ	80000000L

int main()
{
	// Configure the proper PB frequency and the number of wait states
	SYSTEMConfigPerformance(SYS_FREQ);
	
	mInitAllLEDs();	

	lcd_init();  // Always call this first.
	
	putsLCD("\nH");
	//putsLCD("B");
while(1);
}