catmegalcd

Problem driving lcd with Atmega: no characters on second line


I have a project where a 8x2 character lcd is controlled by an atmega8 microcontroller.

The lcd is a dips082-hnled: https://eu.mouser.com/datasheet/2/127/dips082e-274871.pdf

As it is quite easy to control I wrote my own functions to initialize it and write some text to it. Writing some text is really all I need, for the project...

The problem is the following:
While the first line of the lcd works perfectly fine with my program, I can't get anything to show up on the second line at all.

I checked with the lcds datasheet and with the datasheet of the driver chip the lcd uses and I can't figure out what the problem is.
I also checked with a second, known good lcd of the same type and I get the same problem, so it can't be a faulty lcd either.

Here is all my code for driving the lcd:

#include <xc.h>

#include "pinmapping.h"
#include "main.h"

#include <util/delay.h>
//LCD:
//RS: HIGH=Screen Data, LOW=Command
//RW: HIGH=Read, LOW=Write
//E: FALLING EDGE=Execute Command/Write Data
//D0...D7: PORTD, 8-bit data lines

void toggleEnable(){
    
    //set pin low
    PORTB = PORTB & (~LCD_E);
    //wait for 3 ms so the lcd has time to execute the instruction
    _delay_ms(3);
    //set pin high again
    PORTB = PORTB | LCD_E;
    //allow for a minimal high time
    _delay_ms(3);
    
}

void initLCD(){
    
    //Wait until internal LCD init complete
    _delay_ms(25);
    
    //Set RS and RW low
    PORTB = PORTB & (~(LCD_RS | LCD_RW));
    
    //Function Set (8-bit data, 2 lines, 5x8 font)
    PORTD = 0b00110000;
    toggleEnable();
    
    //Display ON/OFF (Display on, Cursor visible, Cursor blink)
    PORTD = 0b00001100;
    toggleEnable();
    
    //Clear Display, Cursor Home
    PORTD = 0b00000001;
    toggleEnable();
    
    //Entry Mode set (Cursor auto-increment)
    PORTD = 0b00000110;
    toggleEnable();
    
    //Set RS and RW high again
    PORTB = PORTB | (LCD_RS | LCD_RW);
    
}

void testLCD(){
    
    char testchars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvxyz0123456789!?-+,. :;<=>*/()%&#$";
    
    //Set RS low and RW low
    PORTB = PORTB & (~LCD_RS);
    PORTB = PORTB & (~LCD_RW);
    
    //Clear Display, Cursor Home
    PORTD = 0b00000001;
    toggleEnable();
    
    //Set RS high and RW low
    PORTB = PORTB | LCD_RS;
    PORTB = PORTB & (~LCD_RW);
    
    //Write the array contents to the lcd - this should completely fill the DDRAM
    for(int i = 0; i < 80; i++){
        
        PORTD = testchars[i];
        toggleEnable();
        
    }
    
}

void writeLCD(char text[16]){
    
    //Set RS low and RW low
    PORTB = PORTB & (~LCD_RS);
    PORTB = PORTB & (~LCD_RW);
    
    //Clear Display, Cursor Home
    PORTD = 0b00000001;
    toggleEnable();
    
    //Set RS high and RW low
    PORTB = PORTB | LCD_RS;
    PORTB = PORTB & (~LCD_RW);
    
    //loop through string send it to lcd
    for(int i = 0; i < 16; i++){
        
        //We have a 16 char lcd so we need to jump to line 2 after 8 chars
        if(i == 8){
            
            //Set RS low and RW low
            PORTB = PORTB & (~LCD_RS);
            PORTB = PORTB & (~LCD_RW);

            //Set DD-RAM Address to 0x40 - the beginning of line 2
            PORTD = 0xC0;
            //and execute command
            toggleEnable();

            //Set RS high and RW low
            PORTB = PORTB | LCD_RS;
            PORTB = PORTB & (~LCD_RW);
            
        }
        
        //put the current char to data bus
        PORTD = text[i];
        
        //then toggle the enable pin to actually write it to the lcd
        toggleEnable();
        
    }
    
    //Set RS and RW low
    PORTB = PORTB & (~(LCD_RS | LCD_RW));
    
    //reset cursor to start
    PORTD = 0b00000010;
    toggleEnable();
    
    //Set RS and RW high again
    PORTB = PORTB | (LCD_RS | LCD_RW);
    
}

void commandLCD(char command){
    
    //Set RS and RW low
    PORTB = PORTB & (~(LCD_RS | LCD_RW));
    
    PORTD = command;
    toggleEnable();
    
    //Set RS and RW high again
    PORTB = PORTB | (LCD_RS | LCD_RW);
    
}

As you can see I wrote a test function that should fill the entire 80 bytes of display memory, the lcd has.

I am using the xc8 compiler.

The initLCD function gets called at the beginning of the main right after io-port initialization.

The writeLCD funtion is then later called from different parts of the program...

The pinmapping.h file defines bitmasks for the different pins that are used. (eg: LCD_RS)

The 8 data lines of the lcd are connected to port D of the atmega, matching their bits (eg: lcd D0 to avr D0, lcd D1 to avr D1 etc.).

Maybe I overlooked something or I am doing something wrong, but I can't figure out whats causing the problem...


Solution

  • So I finally figured it out!

    I basically had everything correct, but apparently you need to do the function set command twice in order to set the display to both 8-bit data mode and 2-line display.

    This is mentioned nowhere in either the datasheet for the display or the one for the controller used.

    So this is the modified initLCD function that makes it work. (Yes, I flipped the bit for the font while testing, but according to the datasheet, that gets ignored on a 2-line display)

    void initLCD(){
        
        //Wait until internal LCD init complete
        _delay_ms(25);
        
        //Set RS and RW low
        PORTB = PORTB & (~(LCD_RS | LCD_RW));
        
        //Function Set (8-bit data, 2 lines, 5x8 font)
        PORTD = 0b00111100;
        toggleEnable();
        
        //Apparently this has to be done twice, to get 2 lines to work
        PORTD = 0b00111100;
        toggleEnable();
        
        //Display ON/OFF (Display on, Cursor visible, Cursor blink)
        PORTD = 0b00001100;
        toggleEnable();
        
        //Clear Display, Cursor Home
        PORTD = 0b00000001;
        toggleEnable();
        
        //Entry Mode set (Cursor auto-increment)
        PORTD = 0b00000110;
        toggleEnable();
        
        //Set RS and RW high again
        PORTB = PORTB | (LCD_RS | LCD_RW);
        
    }
    

    Line 2 starts at address 40 as the datasheet suggests.
    There is no address 81. Writing more than 80 characters just overrides the first ones. It wraps around after 80, so if you write 81 characters to it, the last character overrides the first one.