First attempt at printing to an LCD display from a dsPIC30F4011

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!

Photo of dsPIC driving LCD display

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.)

Circuit diagram of dsPIC30F4011 connected to Hitachi LCD displlay module

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.

Front and back views of the LCD display module

This is the complete breadboard circuit including the dsPIC and LCD module (two views of the same circuit):

Two views of the complete dsPIC / LCD display system

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
	delay_ms(5); // must be more than 4.1ms
	delay_ms(1); // must be more than 100us
	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
		_LATD0 = 1 - _LATD0;

// 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;
	E_PIN = 0;
	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:

  1. Create a new folder.
  2. Save the C program in that folder as “main.c”.
  3. Save the build script in the same folder as “build.bat”.
  4. Open a console window and navigate to that folder.
  5. Type “build.bat” to compile the program.
  6. Use the PICkit 2 application (or another program) to download the compiled program (file “a.hex”) onto the dsPIC.


  1. Wikipedia article on Hitachi HD44780 LCD controller (contains useful summary of connections and commands)
  2. Book: PIC Microcontrollers, Appendix B: Examples (scroll down to the example titled “LCD DISPLAY”)
  3. HD44780 LCD controller datasheet, containing all the gory details (courtesy of Sparkfun who sell lots of LCD modules)
This entry was posted in Uncategorized and tagged , , , , . Bookmark the permalink.

37 Responses to First attempt at printing to an LCD display from a dsPIC30F4011

  1. David Egan says:

    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.

    • batchloaf says:

      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.


  2. David Egan says:

    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

  3. David Egan says:

    That got very messed up in translation, sorry. I might try meet you at some point this week.

  4. Akuma Leon says:

    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?

    • batchloaf says:

      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.


  5. scott says:

    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.

  6. Olex says:

    Hi Ted. Thank you for your work. Would you please help to change this code for 2×16 LCD? Thanks.

  7. Olex says:

    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.


  8. Brek Martin says:

    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.

    • batchloaf says:

      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

          Fosc = 16 * 7,500,000 = 120,000,000 Hz = 120 MHz

      However, the dsPIC30F4011 performs one machine instruction every 4 clock cycles, so the instruction frequency is

          Fcy = 120,000,000 / 4 = 30,000,000 Hz = 30 MHz

      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

          Tcy = 1 / 30,000,000 = 33.33 ns

      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.


    • batchloaf says:

      Oh, and I guess your millisecond delay function will be…

      // Delay by specified number of milliseconds
      void delay_ms(unsigned int n)
          while(n--) __delay32(16000);

      …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.


  9. Art says:

    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.

  10. NATHAN says:

    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.


    • NATHAN says:

      Hi Ted,

      I’d like to post my LCD type.
      Manufacturer: POWERTIP
      Order Code: 1671498
      Manufacturer Part No PC1602ARU-HWB-G-Q


  11. Johan Hough says:

    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?

    • batchloaf says:

      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:

      • PWM1L1/RP15(1)/CN11/PMCS1/RB15
      • PWM1H1/RTCC/RP14(1)/CN12/PMWR/RB14
      • PWM1L2/RP13(1)/CN13/PMRD/RB13
      • PWM1H2/RP12(1)/CN14/PMD0/RB12

      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?


      • Johan Hough says:

        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

      • batchloaf says:

        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:

        void send_command_byte(unsigned short byte)
            LCD_RS = 0;
            send_nibble(byte >> 4);
            send_nibble(byte & 0x000F);


  12. Johan Hough says:

    Is it possible to send you my code then you can have a look at it?

  13. JH says:

    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()
    void delay_us()

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

    LCD_RS = 0;
    LCD_E = 1;

    delay_ms(16); // must be more than 15ms
    delay_ms(5); // must be more than 4.1ms
    delay_ms(1); // must be more than 100us
    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

  14. batchloaf says:

    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:

    _FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, Fcy=30MHz
    _FWDT(WDT_OFF);                  // Watchdog timer off


  15. siva says:

    Hi ted.can you explain how u interface dspic30f2010 with lcd?

  16. Pingback: The PROJECT(s) | modestasjblog

  17. Glenn Attard says:

    Hi Ted,

    I need to change the from portB to portD on a dspic 30f4011. Can you help me with that please?


  18. Just an Engineer says:

    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!

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s