The following example is my first attempt at printing to an LCD display using the dsPIC30F4011 microcontroller. The display I’m using is a 16×1 character screen with what I think is a standard Hitachi interface. It’s my first time using one of these, so the code is a bit long-winded at the moment, but the good news is that it’s working!
This LCD module uses the Hitachi interface, which has 4-bit and 8-bit operating modes. Here, the 4-bit mode is used, which means that each byte transmitted from the dsPIC to the LCD module is split into two 4-bit nibbles which are transmitted one after the other. The advantage of 4-bit mode is that less microcontroller pins are required – 7 digital outputs in total: 3 control lines and 4 data lines. (Actually, strictly speaking, you could get away with only 6 lines.)
The circuit diagram is shown below. Note that the 5 connections to the PICkit 2 USB programmer are not shown. (Link to editable SVG version of this image, created using Inkscape.)
The 7 connections between the dsPIC’s digital outputs and the LCD module are:
LCD pin number | LCD pin name | LCD pin description | dsPIC pin number | dsPIC pin name |
---|---|---|---|---|
4 | RS | 0: command transfer 1: data transfer |
16 | RC14 |
5 | R/W | 0: write data 1: read data (always 0 here) |
15 | RC13 |
6 | E | D4-D7 latched on falling edge of E |
14 | RC15 |
11 | D4 | Bit 0 (lsb) | 2 | RB0 |
12 | D5 | Bit 1 | 3 | RB1 |
13 | D6 | Bit 2 | 4 | RB2 |
14 | D7 | Bit 3 (msb) | 5 | RB3 |
The LCD module I’m using is shown below (front and back views of the same device). The 6-pin header at one end of the breadboard is the connector for the PICkit 2 USB programmer.
This is the complete breadboard circuit including the dsPIC and LCD module (two views of the same circuit):
I was surprised to discover that 16×1 LCD modules of this type (1 line with 16 characters of text) are typically structured as if they had 2 lines of 8 characters. The first 8 characters are “line 1” and the second 8 characters are “line 2”. As a result, in the program below, half of the single line of text to be displayed is written to “line 1” and half is written to “line 2”.
This is the C code for the dsPIC program:
// // LCD display program for dsPIC30F4011 // Written by Ted Burke - Last updated 16-4-2013 // #include <xc.h> #include <libpic30.h> // Configuration settings _FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, Fcy=30MHz _FWDT(WDT_OFF); // Watchdog timer off _FBORPOR(MCLR_DIS); // Disable reset pin #define RS_PIN _LATC14 #define RW_PIN _LATC13 #define E_PIN _LATC15 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() { _TRISD0 = 0; // Make RD0 an output TRISC = 0; // RC13-15 as digital outputs TRISB = 0xFFF0; // RB0-3 as digital outputs _PCFG0 = 1; // AN0 is digital _PCFG1 = 1; // AN1 is digital _PCFG2 = 1; // AN2 is digital _PCFG3 = 1; // AN3 is digital // 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. RW_PIN = 0; RS_PIN = 0; E_PIN = 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 : 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 d"; const char line2[] = "sPIC30F "; // 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) { _LATD0 = 1 - _LATD0; delay_ms(500); } } // Delay by specified number of milliseconds void delay_ms(unsigned int n) { while(n--) __delay32(30000); } void send_nibble(unsigned char nibble) { // Note: data is latched on falling edge of pin E LATB = nibble; delay_ms(1); E_PIN = 0; delay_ms(1); E_PIN = 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) { RS_PIN = 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) { RS_PIN = 1; send_nibble(byte >> 4); send_nibble(byte & 0xF); }
I compiled this with Microchip’s XC16 compiler, using the following simple build script.
xc16-gcc main.c -mcpu=30F4011 -Wl,--script=p30F4011.gld if errorlevel 0 xc16-bin2hex a.out
To use the build script:
- Create a new folder.
- Save the C program in that folder as “main.c”.
- Save the build script in the same folder as “build.bat”.
- Open a console window and navigate to that folder.
- Type “build.bat” to compile the program.
- Use the PICkit 2 application (or another program) to download the compiled program (file “a.hex”) onto the dsPIC.
References
- Wikipedia article on Hitachi HD44780 LCD controller (contains useful summary of connections and commands)
- Book: PIC Microcontrollers, Appendix B: Examples (scroll down to the example titled “LCD DISPLAY”)
- HD44780 LCD controller datasheet, containing all the gory details (courtesy of Sparkfun who sell lots of LCD modules)
That looks good Ted. I am trying to something similar with a PIC18f-4620 but to no avail yet. I am assuming the code will be similar to yours with the output pins changed.
H David,
Yeah, it should be reasonably straightforward to adapt this for the PIC18F4620. You’ll need to update the delay_ms function to adapt to whatever clock speed you’re using. Also, the notation for register names and bit fields is slightly different in the C18 / XC8 compiler, so those will need to be adjusted. Of course, you’ll probably be using slightly different pins to interface to the LCD screen too, so the send_nibble function will need to be modified to reflect that. Anyway, all in all it should be fine to adapt, but will require a lot of little changes.
If you let me know which pins you’re planning to use to interface with the LCD screen, I can try to sketch up a modified example program. Alternatively, if you’re modifying it yourself and you get stuck, just let me know and we can meet up in one of the labs during the week to go through it. It shouldn’t take more than about 20-30 mins.
Ted
This is the plan anyway.
LCD pin desc. LCD pin no. PIC 18F pin name/ number
Bit0 (lsb) 11 D0 19
Bit1 12 D1 20
Bit2 13 D2 21
Bit3 (msb) 14 D3 22
D4-D7 latched 6 C4 23
on falling
edge of E
0: write data 5 C5 24
0: command transfer 4 C6 25
1: data transfer
That got very messed up in translation, sorry. I might try meet you at some point this week.
Hi David,
Ok, I think I get the jist of your pin plan from your previous post: transmit nibble via D0-3, signaling lines C4-6. It should be straightdforward to set it up this way. I’ll try to take a look at the code later on today and we can chat later in the week.
Ted
Transmit nibble D0-D3
Signaling lines are D5-7.
Thanks David. I’ve sketched up some code using those pins, but I’m still ironing out the bugs. I’ll post it here on the blog once it’s working.
Ted
Ok, I got the PIC18F4620 version working. You can see the full code here:
https://batchloaf.wordpress.com/2013/10/08/basic-lcd-display-example-for-the-pic18f4620/
Hi TED! look very good what you did!!
I would like to do similar on a PIC24hjxxx!!
can I use your code for the same project?
Thanks
Hi Akuma,
It should certainly be possible to adapt this for a PIC24, but it will probably require some modifications. Which chip exactly are you planning to use? If you let me know, I’ll look up the datasheet and see what changes are required.
Ted
Hi Ted. I really found this post helpful with the last project I was part of. I found the design very easy to follow and also thanks for the support in making modifications to out put text to the LCD from an array.
Thanks Scott, I’m glad you found it useful!
Hi Ted. Thank you for your work. Would you please help to change this code for 2×16 LCD? Thanks.
Hi Olex,
Can you please tell me the exact model number of your LCD display?
Ted
Hi Ted. Thank you for your fast response. Here is some info:
LCD JHD162A.
Hi Ted. Thank you for your fast response. Here is some info:
LCD JHD162A.
dsPIC33FJ128MC802 (internal fast RC: 7.37MHz)
PORTB 0, 1, 2, 3 for data via D4-D7
RS_PIN _LATB11
E_PIN _LATB10
R/W connected to VSS (-)
Because I have to use the rest of pins of PORTB, I have to mask LCD data before sending to port or set each bit individually.
Thank you for your help.
Olex
May I ask what speed your oscilator is so that I might adjust the pause “milliseconds” properly?
I’m using an 8MHz crystal which I think is PLLed to 64MHz.
Hi Brek,
The oscillator frequency is configured on line 10 of the program above. The specified configuration is to use the internal fast RC oscillator (7.5MHz approx) with the 16x PLL multiplier. Therefore, the oscillator frequency is
However, the dsPIC30F4011 performs one machine instruction every 4 clock cycles, so the instruction frequency is
So the chip is running at approximately 30 MIPS (million instructions per second).
That means that the instruction cycle (time taken to perform one instruction) is
Because the internal oscillator is an RC oscillator, it can vary a bit from chip to chip and over time, and I suppose especially with temperature changes. It should be fine for this example, but if you need the frequency to be really stable, I would recommend switching from the internal oscillator to an external crystal.
Anyway, hopefully that clarifies it.
Ted
Oh, and I guess your millisecond delay function will be…
…although I should point out that it’s quite a crude delay function. It doesn’t take into account the overhead of the function call or the loop at all, so if your chip is really running at 16 MIPS (8MHz * PLL8 / 4_CYCLES_PER INSTRUCTION), the delay will be ever so slightly longer than the specified number of milliseconds.
Ted
Thanks for the replies guys 🙂 I guess I didn’t check back because the LCD is working.
I got the software delay values sorted out by making a signal and checking a scope.
I noticed the last delay in the send nibble function is not needed. If you leave E high,
the delay at the start of the function serves the same purpose next time round.
If you’re not shifting the display in a program, it’s also quicker to use 0x80 command to
return home rather than 0x02 which resets display shifting as well.
Other than that, this is pretty much the same except I had to operate not he port to use the
other four pins for another purpose.
Impressive video!
Hi Ted,
Really appreciated for your excellent code. I have a query that does it make any difference if i applied your code for 16×1 LCD to my 16×2 LCD. Will it work as normal? Thanks for your kind help.
Regards
Nathan
Hi Ted,
I’d like to post my LCD type.
Manufacturer: POWERTIP
Order Code: 1671498
Manufacturer Part No PC1602ARU-HWB-G-Q
Regards
NATHAN
Hi Ted, Im using a dsPIC33FJ128MC802 with XC 16. Im trying to use the lcd in 4 bit mode with DB7-DB4 on pins RB15-RB12. This doesnt seem to work and I dont know why? The same code works with DB7-4 on RB3-0 though. How should i change the rest of the code to fit my application?
Hi Johan,
It’s difficult to say why it’s not working on RB12-RB15. Have you checked that those pins are definitely functioning correctly as digital outputs? For example, you could connect an LED (with current limiting resistor!) to each pin and then run the code. You should see something happening on the LEDs if the pins are working as digital outputs.
One thing that could prevent a pin from operating as a normal digital output is if one of the other functions available on that pin is activated. In the datasheet, the pin diagram for the chip lists several possible uses of each pin. The different options are listed in order of precedence. i.e. if you activate more than one function on a pin, the one which takes effect will be the one farthest to the left in the list. The functions listed for RB12-RB15 are:
As you can see, the normal digital i/o functionality (RBxx) is the lowest priority on each pin, so if one of the other functions is activated on one or more of the pins that might stop things working. Placing LEDs on the four pins will let you check that each one is definitely operating as a digital output.
Could you try that?
Ted
Hi ted, I have done as suggested and the code runs through and the LED’s light up with each of the commands sent through. I have made send nibble and send data/command byte to short to accommodate the 16 pin port. But while doing this:
void send_command_byte(unsigned short byte)
{
LCD_RS = 0;
send_nibble(byte >> 4);
send_nibble(byte & 0xF000);
}
the LED’s dont show anything. Should this happen? How should I mask the bits
Hi Johann,
Shouldn’t the bit mask for sending the second nibble be 0x000F rather than 0xF000? I think the complete function should be as follows:
Ted
Is it possible to send you my code then you can have a look at it?
Hi Johan,
I think you should be able to post it here. Just wrap it in “code” tags as explained here:
https://en.support.wordpress.com/code/posting-source-code/
For C/C++ you should set language option to “cpp”.
Ted
Hi Ted, here is the code….
[/#define LCD_RS LATAbits.LATA2
#define LCD_E LATAbits.LATA4
#define D7 LATBbits.LATB15
#define D6 LATBbits.LATB14
#define D5 LATBbits.LATB13
#define D4 LATBbits.LATB12
void delay_ms();
void delay_us();
void ConfigureOscillator(void);
int p=0;
void delay_ms()
{
__delay32(40000);
}
void delay_us()
{
__delay32(40);
}
void ConfigureOscillator(void)
{
// Configure PLL prescaler, PLL postscaler, and PLL divisor
PLLFBD = 41; // M = 43
CLKDIVbits.PLLPRE=0; // N1 = 2
CLKDIVbits.PLLPOST=0; // N2 = 2
//Initiate clock switch to internal FRC with PLL (NOSC = 0b001)
__builtin_write_OSCCONH(0x01);
__builtin_write_OSCCONL(0x01);
//Wait for clock switch to occur
while (OSCCONbits.COSC != 0b001);
// Wait for PLL to lock
while(OSCCONbits.LOCK!=1) {};
}
void LCD_Init();
void send_nibble(unsigned short nibble);
void send_command_byte(unsigned short byte);
void send_data_byte(unsigned short byte);
int x=0;
int main()
{
TRISA = 0; // RC13-15 as digital outputs
TRISB = 0;
delay_ms(30);
ConfigureOscillator();
LCD_RS = 0;
LCD_E = 1;
send_command_byte(0x0100);
delay_ms(16); // must be more than 15ms
send_nibble(0b0011000000000000);
delay_ms(5); // must be more than 4.1ms
send_nibble(0b0011000000000000);
delay_ms(1); // must be more than 100us
send_nibble(0b0011000000000000);
delay_ms(5); // must be more than 4.1ms
send_nibble(0b0010000000000000); // select 4-bit mode
// Display settings
send_command_byte(0b0010100000000000); // N=0 : 2 lines (half lines!), F=0 : 5×7 font
send_command_byte(0b0000100000000000); // Display: display off, cursor off, blink off
send_command_byte(0b0000000100000000); // Clear display
send_command_byte(0b0000011000000000); // Set entry mode: ID=1, S=0
send_command_byte(0b0000111100000000); // Display: display on, cursor on, blink on
// Define two 8 character strings
const char line1[] = ” JB HOUGH “;
const char line2[] = ” 10165003 “;
// Write the two strings to lines 1 and 2
int n;
send_command_byte(0x0200); // Go to start of line 1
for (n=0 ; n<15 ; ++n) send_data_byte(line1[n]);
send_command_byte(0xC000); // Go to start of line 2
for (n=0 ; n<15 ; ++n) send_data_byte(line2[n]);
// Now just blink LED indefinitely
while(p> 4);
send_nibble(byte & 0x000F);
}
// Send a data byte (i.e. with pin RS high)
void send_data_byte(unsigned short byte)
{
LCD_RS = 1;
send_nibble(byte >> 4);
send_nibble(byte & 0x000F);
}
]
I’m still not having any luck. The LCD doesnt initialise with the suggested changes
There seem to be some missing parts. Where’s the send_nibble function? Also, shouldn’t there be some additional configuration settings at the start? You know… all this stuff:
Ted
Hi ted.can you explain how u interface dspic30f2010 with lcd?
Sorry Siva, I haven’t used that chip before so I’m not sure how to do it.
Ted
Pingback: The PROJECT(s) | modestasjblog
Hi Ted,
I need to change the from portB to portD on a dspic 30f4011. Can you help me with that please?
thanks
I can’t thank you enough for this. Really took my project to a new level. The comments were invaluable. Keep up the good work!
You’re welcome Engineer. Glad it helped with your project!
Ted
Thanks so much for your code!, I translate this with minors changes to dsPIC33EP 70 MIPS. In two files, .c and .h.
Glad you found it useful Silvestre. Good to hear it ported to dsPIC33EP without too much work.
Ted
For fast character to LDC, are: send_nibble_fast(); send_data_byte_fast();
Brilliant stuff – thanks Silvestre! Hopefully, others will find your code very useful.
Ted
Work Excellent in a dsPIC33EP512GM706 at 70 MIPS.
I have a question about code for dspic30f4011 for sensor ts230 , load cell , servo motor , keypad