PIC32MX: Parallel LCD

From Mech
Jump to navigationJump to search

Parallel LCD

The popular HD44780 (and compatible chipsets) can be interfaced to the NU32 PIC32 board using 6 pins 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.

This implementation of an HD44780 LCD uses only 6 I/O on the PIC, but is not optimized for speed. If you need more efficient output, more pins can be used for communication, and this code would need to be altered slightly, but for debugging purposes start with this implementation.

LCD Hookup

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 - VEE~1V
4 - RSD4
5 - R/WGND
6 - ED10
7 - DB0NC
8 - DB1NC
9 - DB2NC
10 - DB3NC
11 - DB4D0
12 - DB5D1
13 - DB6D2
14 - DB7D3
15 - LED++3.3V
16 - LED-GND

The VEE pin on the LCD sets the contrast of the characters on the LCD. Create with a voltage around 1V using a 10k pot to get a nice contrast, or hook it to 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 set your contrast and check that your LCD works.

The NU32 board is based on 3.3V logical outputs, which works fine for most LCDs.

LCD Commands

In the main.c file, before the main while loop, you must initialize the LCD

lcd_init();  // Always call this first

The basic command used to write to the LCD is

// write Hello World
putsLCD("Hello World");

You can directly set the cursor position by using

// move the cursor to the second line, 3rd character position
lcd_gotoxy(3, 2)

Remember the LCD can display 16 x 2 characters.

Special characters can also be used to set the cursor position

// clear the screen and put the cursor in the top left
putsLCD("\f");

// put the cursor in the bottom right
putsLCD("\n");

Write the contents of a variable using

char buffer[5]; // this will store the string
int n; // this is the number of characters in the string after it has been converted
short a = 10; // the number we will convert to a string
n=sprintf(buffer, "%d", a); // make the string and see how many characters there are
putsLCD("a = "); // write some text
putsLCD(buffer);  // write the contents of the variable

Files and Example Code

Include the following h and c files to your project: h and c files

LCD.h


/*
LCD.h
For the NU32 PIC32 Board
Write to an HD44780 LCD in 4bit mode with no reading back data
*/

#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 Delaytus(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


/*
LCD.c
For the NU32 PIC32 Board
Write to an HD44780 LCD in 4bit mode with no reading back data
*/

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

#include "LCD.h"

// Define constants for PINS
#define LCD_DB4		LATDbits.LATD0
#define LCD_DB5		LATDbits.LATD1
#define LCD_DB6		LATDbits.LATD2
#define LCD_DB7		LATDbits.LATD3

#define LCD_RS    	LATDbits.LATD4
//#define LCD_RW    	LATDbits.LATD9
#define LCD_E     	LATDbits.LATD10

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 D0-4,D9-10 as a digital output, change if you change the pins
	
	LATD |= 0x061F; TRISD &= 0xF9E0;
	// works fine off 3.3V, in fact the following line kills the timing
	//mPORTDOpenDrainOpen(BIT_0 | BIT_1 | BIT_2 | BIT_3 | BIT_4 | BIT_9 | BIT_10);

	Delayms(30);

	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(4);
	   }
	
	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
	   }
	Delayms(5);
	putsLCD("\f");
	Delayms(5);
}

//-------------------------------------
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);   

 Delaytus(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
	Delayms(1); 
	//#endif

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

	//#ifdef USE_LCD_RW
	//LCD_RW = LOW;
	//Delaytus(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':				// Clear, go to First Line
		    {
				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);
		    	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);
}
   
//==========================

void Delayms( unsigned t)
// 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();
}// Delayus

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

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

main.c


/*
main.c
For the NU32 PIC32 Board
Example code for how to write to an HD44780 LCD in 4bit mode with no reading back data
*/

#include "Compiler.h"			// tells MPLAB which compiler to use
#include "HardwareProfile.h"    // info on how the PIC is used, based on the constant you defined above
#include "LCD.h"
#define SYS_FREQ (80000000L)    // system clock is 80 MHz

void main(void)
{
  SYSTEMConfigPerformance(SYS_FREQ);  // tell PIC its operating frequency
  mInitAllLEDs();                    // initialize LED pins, defined in HardwareProfile_NU32.h

  lcd_init();  // initialize the HD44780 LCD in 4 bit mode, do this immediately after power up

  putsLCD("Hello World!");
  Delayms(1000);
  putsLCD("\fHi"); // "\f" clears the screen and goes to the first position on the first line
  lcd_gotoxy(3,2); // move cursor to x,y
  putsLCD("there");
  Delayms(1000);
  putsLCD("\f\nTest this"); // "\n" goes to the next line
  Delayms(500);
  putsLCD("\bA"); // "\b" moves the cursor back one position
  Delayms(1000);
  putsLCD("\f");
  
  // this is how you write the contents of a variable
  char buffer[5]; // this will store the string
  int n; // this is the number of characters in the string after it has been converted
  short a = 10; // the number we will convert to a string
  n=sprintf(buffer, "%d", a); // make the string and see how many characters there are
  putsLCD("a = "); // write some text
  putsLCD(buffer);  // write the contents of the variable

  while(1) {                         // loop forever
    if(swUser) {                     // swUser is high (true) until pressed
      mLED_0_Off();
      mLED_1_On();
      mLED_2_Off();
      mLED_3_On();
    }
    else {                           // swUser is low (false) if pressed
      mLED_0_On();
      mLED_1_Off();
      mLED_2_On();
      mLED_3_Off(); 
    }

    a--; // change the variable and write it to the LCD
    n=sprintf(buffer, "%d", a);
    putsLCD("\fa = ");
    putsLCD(buffer);
    Delayms(500);

  }                                  // end of while loop
}                                    // end of main.c