I previously posted an LCD display example program for the dsPIC30F4011 microcontroller. This is the same program adapted for the PIC18F4620 microcontroller. I’ll have to update this later with a circuit diagram, but here’s a quick photo of the program running on a breadboard circuit. Hopefully you can work out which pins are which from the code and/or photo, as well as by referring to the more complete documentation for the dsPIC30F4011 example.
// // lcd.c - LCD display examplefor dsPIC18F4620 // Written by Ted Burke - Last updated 8-10-2013 // #include <xc.h> // Select clock oscillator (default frequency Fosc=1MHz -> Tcy = 4us). // Disable reset pin, watchdog timer, low voltage programming and // brown-out reset. #pragma config OSC=INTIO67,MCLRE=OFF,WDT=OFF,LVP=OFF,BOREN=OFF // Select which pins the program will use for the LCD screen // control signals, RS, RW and E. // NB I had to change these from the names used in my previous // dsPIC30F4011 example to avoid a clash with the equivalent // definitions in the XC8 compiler's peripheral library. #define PIN_RS LATDbits.LATD7 #define PIN_RW LATDbits.LATD6 #define PIN_E LATDbits.LATD5 // Select a pin to use for the flashing LED #define PIN_LED LATDbits.LATD4 // Function prototypes for transmitting to LCD void delay_ms(unsigned int n); void send_nibble(unsigned char nibble); void send_command_byte(unsigned char byte); void send_data_byte(unsigned char byte); int main() { TRISD = 0b00000000; // Set RD0-7 as digital outputs // Let's just write to the LCD and never read! // We'll wait 2ms after every command since we can't // check the busy flag. PIN_RW = 0; PIN_RS = 0; PIN_E = 1; // Some LCD modules require a delay after power-up // before you can begin communicating with them, so // I'm putting a 1 second delay here just in case. delay_ms(1000); // Initialisation delay_ms(16); // must be more than 15ms send_nibble(0b0011); delay_ms(5); // must be more than 4.1ms send_nibble(0b0011); delay_ms(1); // must be more than 100us send_nibble(0b0011); delay_ms(5); // must be more than 4.1ms send_nibble(0b0010); // select 4-bit mode // Display settings send_command_byte(0b00101000); // N=0 : 2 lines (half lines!), F=0 : 5x7 font send_command_byte(0b00001000); // Display: display off, cursor off, blink off send_command_byte(0b00000001); // Clear display send_command_byte(0b00000110); // Set entry mode: ID=1, S=0 send_command_byte(0b00001111); // Display: display on, cursor on, blink on // Define two 8 character strings const char line1[] = " Ted's "; const char line2[] = "PIC18F "; // Write the two strings to lines 1 and 2 int n; send_command_byte(0x02); // Go to start of line 1 for (n=0 ; n<8 ; ++n) send_data_byte(line1[n]); send_command_byte(0xC0); // Go to start of line 2 for (n=0 ; n<8 ; ++n) send_data_byte(line2[n]); // Now just blink LED indefinitely while(1) { PIN_LED = 1; delay_ms(500); PIN_LED = 0; delay_ms(500); } } // Delay by specified number of milliseconds void delay_ms(unsigned int n) { // At Fosc=1Mhz, Tcy is 4us. That's the time // taken to perform one machine code instruction. // Therefore a delay of 250 x Tcy = 1ms. while(n--) _delay(250); } void send_nibble(unsigned char nibble) { // Set RD0-3 without affecting RD4-7 LATD = (LATD & 0xF0) + nibble; delay_ms(1); // Note: data is latched on falling edge of pin E PIN_E = 0; delay_ms(1); PIN_E = 1; delay_ms(2); // Enough time even for slowest command } // Send a command byte (i.e. with pin RS low) void send_command_byte(unsigned char byte) { PIN_RS = 0; send_nibble(byte >> 4); send_nibble(byte & 0xF); } // Send a data byte (i.e. with pin RS high) void send_data_byte(unsigned char byte) { PIN_RS = 1; send_nibble(byte >> 4); send_nibble(byte & 0xF); }
I compiled the program using Microchip’s XC8 compiler. This was the command I used:
xc8 --chip=18F4620 lcd.c
I then used the PICkit 2 application to transfer the compiled hex file onto the microcontroller.
EDIT 18-4-2015: Thanks to Brek Martin who left a comment below suggesting the 1-second delay at power-up, which apparently is required for some LCD modules.
Thanks for writing this Ted. Much appreciated.
No problem David – I’m sure it will come in useful again! Did you get yours working yet?
Yea, all working. The analog input is also working. I should have the whole system working tomorrow. I will keep you posted.
Thanks again for the help.
Hi Ted,
First off great article, I cannot get it to work though.
I am using C18 compiler inside MPLAB v8.73, using a PIC18F2525 running @ 4MHz.
When I run the simulator in MPLAB with suitable break points the PIN_E doesn’t seem to toggle within the send_nibble function.
If you wouldn’t mind taking a quick look I would be grateful.
Here is the amended code that I am using.
Thank you.
#include
#pragma config OSC = XT
#pragma config PWRT = OFF
#pragma config BOREN = OFF
#pragma config WDT = OFF
#pragma config LVP = OFF
#pragma config PBADEN = OFF
#define _XTAL_FREQ 4000000
#define PIN_RS LATAbits.LATA4
//#define PIN_RW LATDbits.LATD6
#define PIN_E LATAbits.LATA5
// Select a pin to use for the flashing LED
#define PIN_LED LATBbits.LATB1
const char line1[] = “Ted’s “; //These cause buid failure
const char line2[] = “PIC18F “; //when in main body
int n; //…as does this!
// Function prototypes for transmitting to LCD
void delay_ms(unsigned int n);
void send_nibble(unsigned char nibble);
void send_command_byte(unsigned char byte);
void send_data_byte(unsigned char byte);
void main(void)
{
TRISC = 0b00000000; // Set RD0-7 as digital outputs
// Let’s just write to the LCD and never read!
// We’ll wait 2ms after every command since we can’t
// check the busy flag.
// PIN_RW = 0;
PIN_RS = 0;
PIN_E = 1;
// Initialisation
delay_ms(16); // must be more than 15ms
send_nibble(0b0011);
delay_ms(5); // must be more than 4.1ms
send_nibble(0b0011);
delay_ms(1); // must be more than 100us
send_nibble(0b0011);
delay_ms(5); // must be more than 4.1ms
send_nibble(0b0010); // select 4-bit mode
// Display settings
send_command_byte(0b00101000); // N=0 : 2 lines (half lines!), F=0 : 5×7 font
send_command_byte(0b00001000); // Display: display off, cursor off, blink off
send_command_byte(0b00000001); // Clear display
send_command_byte(0b00000110); // Set entry mode: ID=1, S=0
send_command_byte(0b00001111); // Display: display on, cursor on, blink on
// Define two 8 character strings
// line1[] = “Ted’s “;
// line2[] = “PIC18F “;
// Write the two strings to lines 1 and 2
send_command_byte(0x02); // Go to start of line 1
for (n=0 ; n<8 ; ++n) send_data_byte(line1[n]);
send_command_byte(0xC0); // Go to start of line 2
for (n=0 ; n<8 ; ++n) send_data_byte(line2[n]);
// Now just blink LED indefinitely
while(1)
{
PIN_LED = 1;
delay_ms(500);
PIN_LED = 0;
delay_ms(500);
}
}
// Delay by specified number of milliseconds
void delay_ms(unsigned int n)
{
// At Fosc=1Mhz, Tcy is 4us. That's the time
// taken to perform one machine code instruction.
// Therefore a delay of 250 x Tcy = 1ms.
while(n–){ Delay10TCYx(25);}
}
void send_nibble(unsigned char nibble)
{
// Set RD0-3 without affecting RD4-7
LATC = (LATC & 0xF0) + nibble;
delay_ms(1);
// Note: data is latched on falling edge of pin E
PIN_E = 0;
delay_ms(1);
PIN_E = 1; //When simulated this pin remains unchanged > 4);
send_nibble(byte & 0xF);
}
// Send a data byte (i.e. with pin RS high)
void send_data_byte(unsigned char byte)
{
PIN_RS = 1;
send_nibble(byte >> 4);
send_nibble(byte & 0xF);
}
Hi Darren,
In your modified delay_ms function, it looks like you have while(n-) rather than while(n–). If so, n won’t be decrementing and the program will get stuck in in the delay_ms function forever, rather than for the specified number of milliseconds. Is it possible that the problem is as simple as that?
There could be lots of other things, but I’ll wait to hear whether the n– thing makes any difference. If the problem persists, I’ll have another more careful look, although unfortunately I don’t have a PIC18F2525 or MPLAB/C18 to test it with.
Ted
Oh yeah, also, because your clock speed is different (4MHz crystal), your delay_ms function will need to include a different number of instruction cycles per millisecond. I think it should be working on the basis of 1000 * Tcy = 1ms.
So, I think your delay_ms function should probably look a bit like this:
Give that a try and let me know how you get on.
Ted
Hi Ted,
Thanks for the speedy response.
First of all, you were correct with regard to the crystal freq / timing issue. Lack of concentration on my part. The issue with the ENable pin was due to the ADCON1 register not being set for digital output (i.e. ADCON1 = 0b00001111;). The simulation in MPLAB appears to work, now to try it with the hardware..
I will let you know if it works.
Regards,Darren.
Good stuff. Best of luck getting it working!
Ted
Hi Ted,
The program doesn’t work.
I know the hardware I’m using is fine because it has been used programmed with different software using another compiler/ ide.
I have tried inserting the PIN_LED call at different points in the main function, however the led never lights, therefore the program isn’t running in the PIC at all. All in all baffling.
I’ll keep trying, if you have any thoughts?
Hi Ted,
Ok, bit of a red herring with the led. I didn’t set LATB as digital outputs, so it now works. The lcd doesn’t.
Hi Darren,
Perhaps you could email me the exact C code you have at this point? If you include a high res photo showing your complete circuit, I’ll see if I can spot the problem. It’s probably something that will be easy to fix – it usually is, even when it’s hard to spot.
My email is: ted.burke “at” dit.ie
Ted
Hi Ted,
How i can configure for 4*20 lcd?
I know that i need chance de binary code from the first send_command_byte(), but i don’t know the new code for the lcd.
knightd23
Hi knightd23,
I don’t know the answer to that off the top of my head, but you should be able to find the information you need in one of the following:
Ted
Hi Ted,
Can you help me out with interfacing 2×16 LCD with PIC18F46K20.? I am a new user to microcontrollers and I was trying with all background readings and documets but nothing displays on the LCD.
I am using a PIC18F4XK20 starter kit, MPLAB and C18 compiler.
Here’s the code I am using to display a name or a word, when i debug i can see the leds glowing on the kit but nothing displays on the LCD. First the voltage was too low to enable so i used level shifter between the pic and lcd even then i am not able to get any display on LCD. I guess my connections to the LCD are wrong. could you suggest me a harware connection diagram for the LCD and PIC18F46K20.
Can you also check if my code is correct aswell!!?
LCD.c FILE:
// Hitachi HD44780 LCD library (4-bit mode)
#include “p18f46k20.h”
#include “delays.h”
#include “LCD.h”
// pin defines
#define lcdRS PORTCbits.RC3 // RS
#define lcdEN PORTCbits.RC2 // EN
#define lcdDB PORTD // DB4 – RD0 , DB5 – RD1 , DB6 – RD2 , DB7 – RD3
// pulse EN line
void lcdENpulse (void) {
lcdEN = 1;
Delay10TCYx(20); // delay 200us
lcdEN = 0;
}
// initialise routine
void lcdInit (void) {
TRISCbits.TRISC2 = 0;
TRISCbits.TRISC3 = 0;
TRISD=0;
lcdDB=0; // initialise port
Delay1KTCYx(20); // wait at least 15ms after Vcc on
lcdRS = 0; // R/S = 0 for command
lcdEN = 0;
lcdDB = 0x3; // send 0011 (7 6 5 4)
lcdENpulse(); // pulse EN line to latch command
Delay1KTCYx(5); // delay 5ms
lcdDB = 0x3; // send 0011 again
lcdENpulse();
Delay10TCYx(20); // delay 200us
lcdDB = 0x3; // send 0011 again
lcdENpulse();
Delay10TCYx(20); // delay 200us
lcdDB = 0x2; // Four bit mode
lcdENpulse();
// function set
lcdWriteCommand(0b00101000); // 001DNF00: 4-bit,2 lines,5×8 font
// enable display/cursor
lcdWriteCommand(0b00001111); // 00001DCB: display on,cursor on,blink on
lcdClearScreen(); // Clear and home screen
// set entry mode
lcdWriteCommand(0b00000110); // 000001IS: increment cursor,no display shift
}
// send command
void lcdWriteCommand(unsigned char c) {
lcdRS = 0; // for command
Delay10TCYx(4); // small delay
lcdDB = (( c >> 4) & 0b00001111 ); // send high nibble
lcdENpulse();
Delay1KTCYx(5); // delay 5ms
lcdDB = ( c & 0b00001111 ); // send low nibble
lcdENpulse();
Delay1KTCYx(5); // delay 5ms
}
// send data
void lcdWriteData(char c) {
lcdRS = 1; // for data
Delay10TCYx(4); // small delay
lcdDB = (( c >> 4) & 0b00001111 ); // send high nibble
lcdENpulse();
Delay10TCYx(20); // delay 200us for data
lcdDB = ( c & 0b00001111 ); // send low nibble
lcdENpulse();
Delay10TCYx(20); // delay 200us for data
}
// write string of characters
void lcdWriteDataString(const rom char *string) {
while( *string ) {
lcdWriteData(*string);
string++;
}
}
// clear and home screen
void lcdClearScreen(void) {
lcdWriteCommand(0x1);
}
void lcdGoto(unsigned char address) {
lcdWriteCommand(0x80+address);
}
LCD.h FILE:
extern void lcdENpulse(void);
extern void lcdInit(void);
extern void lcdClearScreen(void);
extern void lcdGoto(unsigned char address);
extern void lcdWriteData(char c);
extern void lcdWriteCommand(unsigned char c);
extern void lcdWriteDataString(const rom char *string);
main.c FILE:
// config bits
#pragma config FOSC = INTIO7, FCMEN = OFF, IESO = OFF // CONFIG1H
#pragma config PWRT = OFF, BOREN = OFF, BORV = 27 // CONFIG2L
#pragma config WDTEN = OFF, WDTPS = 32768 // CONFIG2H
#pragma config MCLRE = ON, LPT1OSC = OFF, PBADEN = OFF, CCP2MX = PORTC // CONFIG3H
#pragma config STVREN = ON, LVP = OFF, XINST = OFF // CONFIG4L
#pragma config CP0 = OFF, CP1 = OFF, CP2 = OFF, CP3 = OFF // CONFIG5L
#pragma config CPB = OFF, CPD = OFF // CONFIG5H
#pragma config WRT0 = OFF, WRT1 = OFF, WRT2 = OFF, WRT3 = OFF // CONFIG6L
#pragma config WRTB = OFF, WRTC = OFF, WRTD = OFF // CONFIG6H
#pragma config EBTR0 = OFF, EBTR1 = OFF, EBTR2 = OFF, EBTR3 = OFF // CONFIG7L
#pragma config EBTRB = OFF // CONFIG7H
// includes
#include “p18f46k20.h”
#include “delays.h”
#include “LCD.h”
//#include “lcd.h”
void initOsc(void);
// static allocated uninitialised variables
#pragma romdata
const rom char *name = “HELLO”;
#pragma udata
// executable code
#pragma code
// functions
void main(void)
{
initOsc();
Delay10KTCYx(100);
lcdInit();
/*
lcdGoto(0x05);
lcdWriteData(‘H’);
lcdWriteData(‘E’);
lcdWriteData(‘L’);
lcdWriteData(‘L’);
lcdWriteData(‘O’);
lcdGoto(0x45);
lcdWriteData(‘W’);
lcdWriteData(‘O’);
lcdWriteData(‘R’);
lcdWriteData(‘L’);
lcdWriteData(‘D’);
*/
Delay10KTCYx(100);
lcdClearScreen();
lcdWriteDataString(name);
while(1);
}
void initOsc(void){
OSCCON = 0b01010000; // bit 6-4 = 101 : 4 MHz internal oscillator
// i.e. instruction cycle = 1 MHz (1 us)
OSCTUNEbits.PLLEN = 0; // not 8 or 16 MHz so don’t need PLL
}
Thank you.
SINDHU
Thanks 🙂 It worked almost straight up on dsPIC33FJ64GP802. I’m using inverters for level conversion so I XOR the nibble before it goes out the port, and invert all control pin values.
Also some LCD modules need a delay prior to initialisation. Mine is one of those.
Some modules and programming languages recommend a second delay before talking to
the LCD module at all, especially in the case where the micro is fast and can start initialisation before the LCD module is finished it’s own reset and looking for it.
The code for two lines worked straight away. Cheers 🙂
Hi Brek,
Glad to hear you got it working and thanks for that useful tip about leaving a delay at the start – I’ll have to keep that in mind! I’ll add a note to the above article to point that out in case anyone runs into that problem.
Ted
Great job Ted, it works perfectly ( PIC18F2550 on PicStart USB from Mikroe ).
I especialy appreciate the simplicity of your code.
( don’t forget to connect the contrast voltage pin, it’s mandatory ! )
Austin ( France )
Thanks Austin!
I can’t remember now what I was up to there with the contrast pin. I suppose I probably didn’t have a potentiometer to hand so I just tried a single pull-down resistor instead to set the contrast to maximum. I guess it must have worked, judging by the picture!
Anyway, thanks again.
Ted