0
\$\begingroup\$

I am trying to interface a PIC16F690 with a HD44780 LCD module, in 4-bit mode. My connections are as follows:

RA5 <-> RS
RA4 <-> EN
RB4 <-> D4
RB5 <-> D5
RB6 <-> D6
RB7 <-> D7
GND <-> R/W

In the code, I have implemented the initialization for 4-bit operation procedure as per page 46 of the HD44780 LCD's datasheet, and the Alfred state webpage: https://web.alfredstate.edu/faculty/weimandn/lcd/lcd_initialization/lcd_initialization_index.html

I am facing a couple of issues here:

  1. When I try to write a message to the LCD module, it is displayed as a series of seemingly random characters. Commonly, a character consisting of several horizontal lines will appear.
  2. The clear function does not work at all. In the main program loop, it does pause, but new characters keep being written without the previous ones being cleared as expected. I have tried brute-forcing this by directly setting the data lines to get a particular character as per the datasheet, but the characters appearing are incorrect.
  3. There is a blinking cursor, despite the Alfred State initialization procedure specifying this would not occur.

What am I missing here in order to fix these issues?

Here is my full code, I am using the XC8 compiler:

#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define _XTAL_FREQ 4000000
#pragma config FOSC=INTRCIO, WDTE=OFF, PWRTE=OFF, MCLRE=ON, CP=OFF, \
                CPD=OFF, BOREN=OFF, IESO=OFF, FCMEN=OFF
// LCD connections
#define LCD_RS PORTAbits.RA5
#define LCD_EN PORTAbits.RA4
#define LCD_D4 PORTBbits.RB4
#define LCD_D5 PORTBbits.RB5
#define LCD_D6 PORTBbits.RB6
#define LCD_D7 PORTBbits.RB7
// LCD commands
#define LCD_CLEAR           0x01
#define LCD_HOME            0x02
#define LCD_MODE_4BIT       0x00
#define LCD_DISPLAY_ON      0x0C
#define LCD_CURSOR_OFF      0x0C

/*
 * Pulse the LCD's enable pin. Falling-edge triggered.
 * Instructions executed upon falling edge of this signal.
 */
void pulse_enable()
{
    LCD_EN = 0;
    __delay_us(1);
    LCD_EN = 1;
    __delay_us(1);
    LCD_EN = 0;
    __delay_us(100);
}

/*
 * Sends a nibble (half a byte) to the LCD.
 */
void lcd_send_nibble(unsigned char nibble)
{
    LCD_D4 = (nibble >> 0) & 0x01;
    LCD_D5 = (nibble >> 1) & 0x01;
    LCD_D6 = (nibble >> 2) & 0x01;
    LCD_D7 = (nibble >> 3) & 0x01;

    pulse_enable();
}

/*
 * Sends a command to the LCD.
 * Using 4-bit mode, sends the upper 4 bits first followed by the lower 4 bits.
 */
void lcd_send_command(unsigned char command)
{
    LCD_RS = 0; // command mode

    lcd_send_nibble(command >> 4);  // Send the upper 4 bits
    lcd_send_nibble(command);       // Send the lower 4 bits
}

/*
 * Sends data to the LCD.
 */
void lcd_send_data(unsigned char data)
{
    LCD_RS = 1; // data mode
    
    lcd_send_nibble(data >> 4);  // Send the upper 4 bits
    lcd_send_nibble(data);       // Send the lower 4 bits
}

/*
 * Initialises the LCD in 4-bit mode.
 * https://web.alfredstate.edu/faculty/weimandn/lcd/lcd_initialization/lcd_initialization_index.html
 */
void initialise_lcd()
{
    LCD_RS = 0; // command mode
    
    // step 1
    __delay_ms(105); // Wait for the LCD to power up
    
    // step 2
    lcd_send_nibble(0x03);
    __delay_us(4500);
    
    // step 3
    lcd_send_nibble(0x03);
    __delay_us(105);
    
    // step 4
    lcd_send_nibble(0x03);
    __delay_us(105);
    
    // step 5
    lcd_send_nibble(0x02);
    __delay_us(105);
    
    // step 6
    lcd_send_nibble(0x02);
    lcd_send_nibble(0x08);
    __delay_us(55);
    
    // step 7
    lcd_send_nibble(0x00);
    lcd_send_nibble(0x08);
    __delay_us(55);
    
    // step 8
    lcd_send_nibble(0x00);
    lcd_send_nibble(0x01);
    __delay_ms(4);
    
    // step 9
    lcd_send_nibble(0x00);
    lcd_send_nibble(0x06);
    __delay_us(55);

    // step 10
    // end of initialisation
    
    // step 11
    // D=1 (display on)
    // C=0 (cursor off)
    // B=0 (cursor blink off)
    lcd_send_nibble(0x00);
    lcd_send_nibble(0x0C);
    __delay_us(60);
}

/*
 * Clears the LCD screen.
 */
void lcd_clear()
{
    lcd_send_command(LCD_CLEAR);
    __delay_ms(2);
}

/*
 * Writes a string to the LCD.
 */
void lcd_write_string(const char* string)
{
    while (*string)
    {
        lcd_send_data(*string++);
    }
}

/*
 * Program entry point.
 */
void main(void)
{
    TRISA = 0x00;   // Port A output
    TRISB = 0x00;   // Port B output
    ANSEL = 0x00;   // analog off
    
    initialise_lcd();
    lcd_clear();
    
    LCD_RS = 1; // data mode
    while(1)
    {   
        lcd_write_string("Hello world!");
        __delay_ms(1000);
        lcd_clear();
        __delay_ms(1000);
    }
}
```
\$\endgroup\$
3
  • \$\begingroup\$ Have you checked the wiring of RS and data pins for problems? The MCU might send correct bits but they might not end up on LCD correctly. How is the LCD connected to MCU? \$\endgroup\$
    – Justme
    Commented Jun 5, 2023 at 14:22
  • \$\begingroup\$ @Justme I double-checked the connections on those pins. They are connected directly between the MCU and LCD. R/W is connected directly to ground. \$\endgroup\$
    – Al2110
    Commented Jun 6, 2023 at 2:07
  • \$\begingroup\$ Never use PORTxto output a value, always use LATx \$\endgroup\$
    – Mike
    Commented Jun 22, 2023 at 13:15

1 Answer 1

1
\$\begingroup\$

lcd_send_nibble is problematic because you send everything in sequence, not at once. You keep writing to the same register over and over, so it will work like this:

  • Set pin D4.
  • MCU will chew on for a while.
  • Set pin D5.
  • MCU will chew on for a while.
    ...

The rule of thumb in general is to avoid bit-fields like the plague - simply never use them for any purpose.

It would be better and faster if you just do PORTB = (nibble << 4);. Done.

\$\endgroup\$
5
  • 1
    \$\begingroup\$ That can't be the issue and source of the problem. The display doesn't care how slowly the bits on data bus settle, as long as they all have settled for long enough before rising E edge and stable for long enough after falling edge. Many Arduino libraries work exactly like this to allow to use of any random IO pins in any order for the data bus. Sure, it would be better and faster, but that's not the problem. \$\endgroup\$
    – Justme
    Commented Jun 7, 2023 at 10:26
  • \$\begingroup\$ @Justme It is a bug, but not necessarily the bug. There may be several bugs. \$\endgroup\$
    – Lundin
    Commented Jun 7, 2023 at 11:13
  • \$\begingroup\$ It's not a bug at all. Perfectly allowed to do it like that, nothing wrong with it. The difference is that a real engineer with years of experience might never do it like that, but things are different for an Arduino hobbyist as long as it gets the job done easily no matter how the hardware is connected. \$\endgroup\$
    – Justme
    Commented Jun 7, 2023 at 12:05
  • \$\begingroup\$ @Justme Well, this is an engineering site, not a quick & dirty DIY site... There's best engineering practices and there's wrong practices. \$\endgroup\$
    – Lundin
    Commented Jun 7, 2023 at 12:29
  • \$\begingroup\$ This worked! Now I have "Hello world!" displayed on the LCD every second or so. Now, let's say I was using this on a port with more free GPIO pins, and I wanted to use that to interface with other components - how would I go about doing that? \$\endgroup\$
    – Al2110
    Commented Jun 7, 2023 at 13:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.