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