# LCD with PIC16F1847

#### Sp1cyChef

##### New Member
Hello

I'm trying to control an LCD in 4-bit with a PIC microcontroller using the example code provided in the datasheet of the LCD
but I'm having difficulty understanding what to put instead P1 in their example code.
P1 in my code is in both the header and main files

I'm using
1- PIC16F1847 microcontroller
2- NHD-0216BZ-FL-YBW LCD (datasheet attached)

Here is my code for main.c

C:
#include <stdio.h>
#include <stdlib.h>
#include <xc.h>
#include "functions.h"
#define _XTAL_FREQ 8000000

#define RS RB2

void main(void) {

// initialize all ports to outputs (RB0 through RB7)
TRISB = 0b00000000;

P1 = 0;
__delay_ms(100); //Wait >40 msec after power is applied
P1 = (0x30); //put 0x30 on the output port
__delay_ms(5); //must wait 5ms, busy flag not available
Nybble(); //command 0x30 = Wake up
__delay_us(160); //must wait 160us, busy flag not available
Nybble(); //command 0x30 = Wake up #2
__delay_us(160); //must wait 160us, busy flag not available
Nybble(); //command 0x30 = Wake up #3
__delay_us(160); //can check busy flag now instead of delay
P1 = (0x20); //put 0x20 on the output port
Nybble(); //Function set: 4-bit interface

command(0x28); //Function set: 4-bit/2-line
command(0x10); //Set cursor
command(0x06); //Entry Mode set

//write (0x41);
return;
}

and here is my code for the header file
C:
#ifndef XC_HEADER_TEMPLATE_H

#include <xc.h>                 // include processor files - each processor file is guarded.
#define _XTAL_FREQ 8000000      //For __delay_ms()

#define RS RB2
#define E  RB3

//function to read a byte, 4bits at a time
void Nybble()
{
E = 1;
__delay_ms(0.0003); //enable pulse width >= 300ns
E = 0; //Clock enable: falling edge
}

//Function to send commands
void command(char i)
{

P1 = i; //put data on output Port
RS = 0; //D/I=LOW : send instruction
//R/W=LOW : Write  ----> not needed since R/W pin is connected to ground
Nybble(); //Send lower 4 bits
i = i<<4; //Shift over by 4 bits
LATB = i; //put data on output Port
Nybble(); //Send upper 4 bits
}

//Function to send data
void write(char i)
{

P1 = i; //i is 0 for command or 1 for data (RB1 is connected to RS)
RS = 1; //D/I=HIGH : send data
//R/W=LOW : Write  ----> not needed since R/W pin is connected to ground
Nybble(); //Clock lower 4 bits
i = i<<4; //Shift over by 4 bits
LATB = i; //put data on output Port
Nybble(); //Clock upper 4 bits
}

#### Attachments

• 467.3 KB Views: 4

#### rjenkinsgb

##### Well-Known Member
P1 is whatever output port you are using - Port B / LATB, from another part or your code.
You appear for be using B4 - B7 to link to the display D4 - D7

As you are using other bits on the same port for the control lines, you need to AND the data with 0xF0 if there is any possibility that the low bits are non zero - then set the control pins as appropriate.

Does delay work with fractions? I've never seen that. If your compiler has __delay_us() you are better off with that, or just do a couple of dummy operations, int a,b; a=b or similar, to allow a short delay during the E pulse.

Don't forget the R/W signal !

Note that by convention, C header files should normally only contain such as function declarations and not anything that produces program code or uses memory.

All the actual function bodies go in C source files.

eg. the header declaration for "command" would be

void command(char i);

Then in the C file you can use
command(i);

and don't need to re-specify the data types.

That also means that as long as you include the headers first, you do not have to worry about what order functions appear in your source file.

I can't confirm the overall function as the compiler you are using appears to do some things very differently to the one I use.

#### Pommie

##### Well-Known Member
Looks like you're using MPLABX but you've tried to use code intended for an 8051.

Here's my LCD code for MPLABX. I'm assuming that only RS and E are connected and R/W is grounded.
Code:
#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#include "functions.h"

#define _XTAL_FREQ 8000000

void main(void) {
InitLCD();
PutMessage("Hello World!");
while(1);
}

void PutMessage(const char *Message){
while(*Message!=0)
write(*Message++);
}

void InitLCD(void){
TRISB=0;
LATB=0;
RS=0;
__delay_ms(30);             //delay for LCD to initialise.
nibble(0x30);               //Required for initialisation
__delay_ms(5);              //required delay
nibble(0x30);               //Required for initialisation
__delay_ms(1);              //required delay
command(0x20);              //set to 4 bit interface
command(0x2c);              //set to 4 bit interface, 2 line and 5*10 font
command(0x01);              //clear display
command(0x06);              //move cursor right after write
command(0x0C);              //turn on display
}

void nibble(unsigned char dat){
dat<<=4;
dat|=LATB & 0x0f;
LATB = dat;
E = 1;
NOP();
NOP();
E = 0; //Clock enable: falling edge
}

//Function to send commands
void command(unsigned char i){
RS=0;
nibble(i>>4);
nibble(i);
__delay_ms(2);
}

void write(unsigned char i){
RS=1;
nibble(i>>4);
nibble(i);
__delay_ms(2);
}
And the header file (called function.h)
Code:
#include <xc.h>                 // include processor files - each processor file is guarded.
#define _XTAL_FREQ 8000000      //For __delay_ms()

#define RS RB2
#define E  RB3

void nibble(unsigned char);
void write(unsigned char);
void command(unsigned char);
void InitLCD(void);
void PutMessage(const char *);
Give it a try and let me know if you have problems.

Mike.

Last edited:

#### Sp1cyChef

##### New Member
P1 is whatever output port you are using - Port B / LATB, from another part or your code.
You appear for be using B4 - B7 to link to the display D4 - D7

As you are using other bits on the same port for the control lines, you need to AND the data with 0xF0 if there is any possibility that the low bits are non zero - then set the control pins as appropriate.

Does delay work with fractions? I've never seen that. If your compiler has __delay_us() you are better off with that, or just do a couple of dummy operations, int a,b; a=b or similar, to allow a short delay during the E pulse.

Don't forget the R/W signal !

Note that by convention, C header files should normally only contain such as function declarations and not anything that produces program code or uses memory.

All the actual function bodies go in C source files.

eg. the header declaration for "command" would be

void command(char i);

Then in the C file you can use
command(i);

and don't need to re-specify the data types.

That also means that as long as you include the headers first, you do not have to worry about what order functions appear in your source file.

I can't confirm the overall function as the compiler you are using appears to do some things very differently to the one I use.
I'm using MPLAB X IDE v5.25 with XC8 as compiler
How would your code be for P1?

I also changed the delay to be a whole number. R/W is grounded I didn't forget about it
All the functions have been moved to the main.c file like you suggested and only declarations are left in the header file.

#### Sp1cyChef

##### New Member
Looks like you're using MPLABX but you've tried to use code intended for an 8051.

Here's my LCD code for MPLABX. I'm assuming that only RS and E are connected and R/W is grounded.
Code:
#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#include "functions.h"

#define _XTAL_FREQ 8000000

void main(void) {
InitLCD();
PutMessage("Hello World!");
while(1);
}

void PutMessage(const char *Message){
while(*Message!=0)
write(*Message++);
}

void InitLCD(void){
TRISB=0;
RS=0;
__delay_ms(30);             //delay for LCD to initialise.
nibble(0x30);               //Required for initialisation
__delay_ms(5);              //required delay
nibble(0x30);               //Required for initialisation
__delay_ms(1);              //required delay
command(0x20);              //set to 4 bit interface
command(0x2c);              //set to 4 bit interface, 2 line and 5*10 font
command(0x01);              //clear display
command(0x06);              //move cursor right after write
command(0x0C);              //turn on display
}

void nibble(unsigned char dat){
dat<<=4;
dat|=LATB & 0x0f;
LATB = dat;
E = 1;
NOP();
NOP();
E = 0; //Clock enable: falling edge
}

//Function to send commands
void command(unsigned char i){
RS=0;
nibble(i>>4);
nibble(i);
__delay_ms(2);
}

void write(unsigned char i){
RS=1;
nibble(i>>4);
nibble(i);
__delay_ms(2);
}
And the header file (called function.h)
Code:
#include <xc.h>                 // include processor files - each processor file is guarded.
#define _XTAL_FREQ 8000000      //For __delay_ms()

#define RS RB2
#define E  RB3

void nibble(unsigned char);
void write(unsigned char);
void command(unsigned char);
void InitLCD(void);
void PutMessage(const char *);
Give it a try and let me know if you have problems.

Mike.
I have not tried using such code because I'm new to LCDs and I'm trying to develop an understanding to the basic code provided in the datasheet so I can modify it when needed.

Your assumption is correct R/W is grounded

I tried your code it's a slight improvement. A question mark and a blinking cursor came up but it's stuck there.
With my code I could only make a blinking cursor show up.

Here is my new code after adjustments.
main.c
C:
#include <stdio.h>
#include <stdlib.h>
#include <xc.h>
#include "functions.h"
#define _XTAL_FREQ 8000000

void main(void) {

TRISB=0; // initialize all ports to outputs (RB0 through RB7)
__delay_ms(100); //Wait >40 msec after power is applied
LATB = (0x30); //put 0x30 on the output port
__delay_ms(5); //must wait 5ms, busy flag not available
Nybble(); //command 0x30 = Wake up
__delay_us(160); //must wait 160us, busy flag not available
Nybble(); //command 0x30 = Wake up #2
__delay_us(160); //must wait 160us, busy flag not available
Nybble(); //command 0x30 = Wake up #3
__delay_us(160); //can check busy flag now instead of delay
LATB = (0x20); //put 0x20 on the output port
Nybble(); //Function set: 4-bit interface

command(0x28); //Function set: 4-bit/2-line
command(0x10); //Set cursor
command(0x06); //Entry Mode set

write (0x41);
return;
}

//function to read a byte, 4bits at a time
void Nybble()
{
E = 1;
__delay_us(1); //enable pulse width >= 300ns
E = 0; //Clock enable: falling edge
}

void command(char i)
{

LATB = i; //put data on output Port
RS = 0; //D/I=LOW : send instruction
//R/W=LOW : Write  ----> not needed since R/W pin is connected to ground
Nybble(); //Send lower 4 bits
i = i<<4; //Shift over by 4 bits
LATB = i; //put data on output Port
Nybble(); //Send upper 4 bits
}

//Function to send data
void write(char i)
{

LATB = i; //i is 0 for command or 1 for data (RB1 is connected to RS)
RS = 1; //D/I=HIGH : send data
//R/W=LOW : Write  ----> not needed since R/W pin is connected to ground
Nybble(); //Clock lower 4 bits
i = i<<4; //Shift over by 4 bits
LATB = i; //put data on output Port
Nybble(); //Clock upper 4 bits
}
functions.h
C:
#include <xc.h>                 // include processor files - each processor file is guarded.
#define _XTAL_FREQ 8000000      //For __delay_ms()

#define RS RB2
#define E  RB3

//function to read a byte, 4bits at a time
void Nybble();

//Function to send commands
void command(char i);

//Function to send data
void write(char i);

Last edited:

#### rjenkinsgb

##### Well-Known Member
Remember that each time you do this:

LATB = i; //put data on output Port

you are also changing the low bits that you are using for register select an E
Some values of data in i are likely causing the control pins to change when they should not an mess up the write sequence.

Use

LATB = i & 0xf0;

That ensures the control pins are kept low; you also have to set RS appropriately before toggling E

You can either set the RS after each output line, or as Pommie did, OR the low bits back in to the variable before writing to the port, so they are unchanged.

Or, move the LCD control pins to a different port, if you want it to work nearer the example program.

#### Sp1cyChef

##### New Member
Remember that each time you do this:

LATB = i; //put data on output Port

you are also changing the low bits that you are using for register select an E
Some values of data in i are likely causing the control pins to change when they should not an mess up the write sequence.

Use

LATB = i & 0xf0;

That ensures the control pins are kept low; you also have to set RS appropriately before toggling E

You can either set the RS after each output line, or as Pommie did, OR the low bits back in to the variable before writing to the port, so they are unchanged.

Or, move the LCD control pins to a different port, if you want it to work nearer the example program.
After making LATB = I & 0xf0 I'm having the same output from Pommie's code
I have RS just like Pommie has it.

Do you know of any textbook that explains PICs and LCDs or any other way of understanding what I'm doing wrong?

#### rjenkinsgb

##### Well-Known Member
The LCD datasheet is the key part.

Look at the timing diagram on page 7; that shows you what needs to happen and what delays are needed at each stage of a write to the LCD.

You need to post your code again so I can try to follow it..

#### Sp1cyChef

##### New Member
The LCD datasheet is the key part.

Look at the timing diagram on page 7; that shows you what needs to happen and what delays are needed at each stage of a write to the LCD.

You need to post your code again so I can try to follow it..
This is my code now. I'm getting questions marks only. I think it's a timing issue, I have an LED attached and the LCD is printing ? when LED is off which should be the case.

main.c
C:
#include <stdio.h>
#include <stdlib.h>
#include <xc.h>
#include "functions.h"
#define _XTAL_FREQ 4000000

void main(void) {

__delay_ms(100);
TRISB=0; // initialize all ports to outputs (RB0 through RB7)
__delay_ms(100);
RS = 0;

LATB = (0x30); //put 0x30 on the output port
__delay_ms(5); //must wait 5ms, busy flag not available
Nybble(); //command 0x30 = Wake up
__delay_us(160); //must wait 160us, busy flag not available
Nybble(); //command 0x30 = Wake up #2
__delay_us(160); //must wait 160us, busy flag not available
Nybble(); //command 0x30 = Wake up #3
__delay_us(160); //can check busy flag now instead of delay
LATB = (0x20); //put 0x20 on the output port
Nybble(); //Function set: 4-bit interface

command(0x28); //Function set: 4-bit/2-line
command(0x10); //Set cursor
command(0x06); //Entry Mode set

//RS = 1;
//write (0x41);
return;
}

//function to read a byte, 4bits at a time
void Nybble()
{
E = 1;
__delay_us(1); //enable pulse width >= 300ns
E = 0; //Clock enable: falling edge
}

void command(char i)
{

RS = 0;
LATB = i & 0xf0; //put data on output Port
//D/I=LOW : send instruction
//R/W=LOW : Write  ----> not needed since R/W pin is connected to ground
Nybble(); //Send lower 4 bits
i = i<<4; //Shift over by 4 bits
LATB = i & 0xf0; //put data on output Port
Nybble(); //Send upper 4 bits
}

//Function to send data
void write(char i)
{
RS = 1;
LATB = i & 0xf0; //i is 0 for command or 1 for data (RB1 is connected to RS)
//D/I=HIGH : send data
//R/W=LOW : Write  ----> not needed since R/W pin is connected to ground
Nybble(); //Clock lower 4 bits
i = i<<4; //Shift over by 4 bits
LATB = i & 0xf0; //put data on output Port
Nybble(); //Clock upper 4 bits
}

C:
#include <xc.h>                 // include processor files - each processor file is guarded.
#define _XTAL_FREQ 4000000      //For __delay_ms()

#define RS RB2
#define E  RB3

//function to read a byte, 4bits at a time
void Nybble();

//Function to send commands
void command(char i);

//Function to send data
void write(char i);

#### Pommie

##### Well-Known Member
If you do LATB = I & 0xf0 then you're clearing RS hence why I put dat|=LATB & 0x0f; and then LATB = dat;.

The code I posted above has been used by me for many (20+) years and works well. If it's not working then check all your connections as something is wrong.

If you're only getting a single question mark then you can't be using the code above - you should (at least) get a line of question marks.

It's possible (but not likely) that it is a timing problem - if so, replace the 2 NOP instructions with a delay of 1mS.

Mike.
Edit, note also, if you return from main - which you do - then it will do random things as it's got nowhere to return to.
Edit2, your comment says (RB1 is connected to RS) but the code assumes RB2!!! Which is it?

#### Sp1cyChef

##### New Member
If you do LATB = I & 0xf0 then you're clearing RS hence why I put dat|=LATB & 0x0f; and then LATB = dat;.

The code I posted above has been used by me for many (20+) years and works well. If it's not working then check all your connections as something is wrong.

If you're only getting a single question mark then you can't be using the code above - you should (at least) get a line of question marks.

It's possible (but not likely) that it is a timing problem - if so, replace the 2 NOP instructions with a delay of 1mS.

Mike.
Edit, note also, if you return from main - which you do - then it will do random things as it's got nowhere to return to.
Edit2, your comment says (RB1 is connected to RS) but the code assumes RB2!!! Which is it?
Here is the schematic and I double checked the connections. Everything is connected according to the schematic. Your code gives one question mark.

With the last code I uploaded I'm getting a line of question marks, I don't know if that helps.
(RB1 is connected to RS) is wrong, it hasn't been updated. Follow the code

#### Pommie

##### Well-Known Member
A line of question marks means it's getting data but is somehow corrupt. Could you possibly have a bad joint on one or more data lines?

Mike.
Edit, In InitLCD, if you change the line command(0x2c); to command(0x20); do you only see 1 line of characters?

Last edited:

#### rjenkinsgb

##### Well-Known Member
Remember what I said about writing the whole port clearing all the control bits?

The RS line was being set to 0 / low each time you wrote to the port; that's OK for the command as RS should be low, but it messes up the data write.

Change this:

C:
//Function to send data
void write(char i)
{
RS = 1;
LATB = i & 0xf0; //i is 0 for command or 1 for data (RB1 is connected to RS)
//D/I=HIGH : send data
//R/W=LOW : Write  ----> not needed since R/W pin is connected to ground
Nybble(); //Clock lower 4 bits
i = i<<4; //Shift over by 4 bits
LATB = i & 0xf0; //put data on output Port
Nybble(); //Clock upper 4 bits
}
to this:

C:
//Function to send data
void write(char i)
{
LATB = i & 0xf0; //i is 0 for command or 1 for data (RB1 is connected to RS)
RS = 1;
Nybble(); //Clock lower 4 bits
i = i<<4; //Shift over by 4 bits // Could be i <<= 4;
LATB = i & 0xf0; //put data on output Port
RS = 1;
Nybble(); //Clock upper 4 bits
}