LCD HD44780u
There are many libraries out there that you can find and use them easily without any worry about what happens on the pins of the LCD (Hitachi HD44780). For example, in Arduino you just need to set up some pieces of line code and print string of characters there, really easy and much time save. But for you who want to now a bit more about what data/address is actually accessed to the LCD and how to do it with a microcontroller directly (in this case atemega8535 is used), and (may be) how. this post may help.

Schematic Circuit
So bellow is our schematic circuit diagram

Figure 1 Configuration of atmega8535 and lm016 (proteus)

Note that all of our configuration that we are going to discuss bellow is based on the above schematic. In other words changing any pin or PORT (e.g., from PORTC like above to PORTB) will make these code useless (unless you know what should be changed on PORT configuration). The discussion only emphasizes about memory accessing to the LCD with atmega8535 MCU without ‘dynamic’ configuration prepared – like on Arduino.

Files Needed to prepare
After the schematic we have, next creating our file hierarchy where we type our scripts certainly. Bellow they are:
--build
----main.c
----lcd_reg.h
----lcd_reg.c
----Makefile
There are four files we need to create. For the compiler, I use GNU gcc compiler of course (and may be always…) with help of Makefile that I create to compile. 

As seen on the above files, there are four of them with a ‘build’ folder where all compiled files are put. I deliberately put the compiled files (such as .hex, .asm and etc.) separately, so that our original destination files (the four files) keeps clean. Now just take a look on the main.c file:

Content of main.c File
#include "lcd_reg.h"
  
int main()
{
   lcd_init();

   lcd_print("hello lcd");
   while(1);

   return 0;
}
From the above lines of code, the purpose may easily be guessed that the string of “hello lcd” will be sent to the LCD to be displayed on the its screen. To do that, the first thing we need to do is ‘include’-ing our header file (that we are going to discussed later). All of parameters and functions prototypes are there in the header file: lcd_reg.h, that are then to be included in main file, where our main(){...} definition exists.

In main() function, lcd_init() is called. This is where all LCD addresses configuration takes place on which our discussion focuses. The next line is lcd_print() that simply transferring string of characters to the LCD.

Now lets take a glance on the header file, i.e., lcd_reg.h.

Content of lcd_reg.h File
#ifndef LCD_REG_HEADER_
#define LCD_REG_HEADER_
#include <avr/io.h>

//macros defined
#define DATA_PINS PORTC //port aliase
#define RS 0x04   //pin lcd to mcu
#define EN 0x08

//parameters taken from hd44780u datasheet
//table 6 on page 24
#define FUNCTION_SET    0x20
#define DISP_2LINES     0x08
#define CLEAR_DISPLAY   0x01
#define DISP_CONTROL    0x08
#define DISP_ON         0x04

#define ENTRY_MODESET   0x04
#define ENTRYLEFT       0x02

void send(uint8_t val);
void command(uint8_t val);
void write(uint8_t val);
void enablePin_pulse(void);
void send_4line(uint8_t ch);
void lcd_init(void);
void clear_disp(void);
void lcd_print(const char *s);

#endif
The contents of this file are some macro variables followed by function prototypes needed by lcd_reg.c file and any other files that may call it. For the three lines:
#define DATA_PINS PORTC //port aliase
#define RS 0x04 //pin lcd to mcu
#define EN 0x08
is where our pins configuration for the atmega8535 MCU pin/port register is set. As seen on the schematic figure 1 above that PORTC is used to interface the LCD. The higher nibble (i.e., PC4, PC5, PC6, and PC7) of PORTC is used to transfer data-bit into D4, D5, D6, and D7 respectively. Therefore to ease the semantic of the code, “DATA_PINS” is used as an alias for PORTC. The next two lines are RS and EN aliases for PORTC’s pins that we use for the transfer control. You can see on the figure 1 that PC2 and PC3 are connected to RS and E of LCD pins respectively. For the hexadecimal values of 0x04 and 0x08, about how to define those values according to the PORTx of the MCU, you can see it on the related posts bellow.

Posts related:
Note:
The ‘nibble’ term in this case is parts of a byte in data-memory. Figure 2 bellow depicts what I mean:
Figure 2. higher nibble and lower nibble (source: Atmega8535 datasheet)

Next lines are macro definitions of:
#define FUNCTION_SET 0x20
#define DISP_2LINES 0x08
…
All hexadecimal values of each line of the macros above refer to the data-sheet of Hitachi HD44780u LCD. These values can be found on page 24 in table 6. These aliases are then passed as argument functions that call them later.

The lines:
void send(uint8_t val);
void command(uint8_t val);
…
are function prototypes that are also included along with the macros above. Without these prototypes, main() function cannot call lcd_init() and lcd_print() functions.

For lcd_reg.c is rather long to attach at once. Therefore each of function definitions will be explained step-by-step begun from lcd_init() function.

Content / Definitions of lcd_reg.c File

lcd_init definition
void lcd_init(void)
{
   configure_pins();
   //since on the schematic, the 4-line connection is used
   //we set the lcd mode into 4-bit
   //the steps are according to hitachi HD44780 flow-chart, see page 45 - 46
   //starting with 8-bit mode
   send_4line(0x03);
   _delay_us(4100);  //wait for 4.1ms

   //next step
   send_4line(0x03);
   _delay_us(4100);  //wait for 4100us

   //finally
   send_4line(0x03);
   _delay_us(150);

   send_4line(0x02); //for 4-bit mode

   //set # lines, font size, etc.   
   command(FUNCTION_SET | DISP_2LINES);
   _delay_us(4200);  //waitmore than 4.1ms

   //turn the display on
   command(DISP_CONTROL | DISP_ON);

   //clear the display
   clear_disp();

   //set the entry mode
   command(ENTRY_MODESET | ENTRYLEFT);
}
to ease to explain, we refer to the flow of the function above. Note that the documentations of the code above may ‘describe’ each line of the code ‘aim’. And the explanation for next section will also refer to the documentation.
So the first line of the code calls the configure_pins() function in which the definition is:

configure_pins Definition
void configure_pins(void)
{
   DDRC |= 0xfc;  //activate pins according to our schematic

   //according to datasheet, we need to wait more than 4.5ms after Vcc rises to 2.7v
   _delay_us(50000);

   //to begin the command, RW, EN, and RS pins need to be low
   //based on our schematic, RW is directly connected to ground
   //that automatically has the low state
   DATA_PINS &= ~(RS | EN);
}
The definition above aims to activate PORTC on PC2 to PC7, and pull PC2 and PC3 low for RS and EN pins respectively to start instruction of the LCD.

send_4line Definition
void send_4line(uint8_t ch)
{
   DATA_PINS &= 0x0C;   //mask for RS and EN bits
   DATA_PINS |= (ch << 4); //shift value according to our schematic
   enablePin_pulse();
}
the first two lines of function definition above simply send 4-bit data via high-nibble of PORTC with 2-bit (for RS an EN pin) data-sate is retained by masking it with 0x0C. Then the next line enablePin_pulse() is for pulse generator that is needed by the EN pin of LCD in each data transferred.

enablePin_pulse Definition
void enablePin_pulse(void)
{
   DATA_PINS &= ~EN; //mask by its own value
   _delay_us(20);
   DATA_PINS |= EN;
   _delay_us(20);    //enable pulse must be more than 450ns
   DATA_PINS &= ~EN;
   _delay_us(100);   //commands need to be more than 37us
   //see table 6 of page 24 for details
}
by referring to its data-sheet that each step in this process needs a delay at least 450ns. When this process is finished, we need to delay the execution once more for at least 37us before going to the next execution / process or before leaving this function.

command Definition
void command(uint8_t val)  //command mode
{
   DATA_PINS &= ~RS; //rs on lcd pin needs to be low
   send(val);
}
by pulling RS line low while transferring any data (by calling send(val) above), this means that we are transferring in command mode, otherwise we are transferring in data mode.

send Definition
void send(uint8_t val)
{
   send_4line(val >> 4);
   send_4line(val);
}
This is where 8-bit data is transferred by way of transferring 4-bit (or nibble) data twice. On the first send_4line() function, we need to shift four-bit to the left to get the high nibble of data-bit to transfer first, and followed by the low nibble of data-bit on the second call of send_4line(). Note that the HD44780u LCD in 4-bit mode, the higher nibble of byte needed to be received first, and followed by the lower nibble.

clear_disp Definition
void clear_disp(void)
{
   //clear entire display and set DDRAM to 0 on address counter
   command(CLEAR_DISPLAY);
   _delay_ms(2);
}
By passing alias value of CLEAR_DISPLAY (or simply 0x02) in command mode, the display of the LCD and the DDRAM address counter are cleared and set to 0 respectively. Note that before continuing the next step, the LCD needs to wait about 2ms for the process to be done.

lcd_print Definition
void lcd_print(const char *s)
{
   while(*s)
      write(*s++);
}
This function definition simply prints each character of the string with write() function. The process is done in while() loop by incrementing pointer-value / memory-address-value to point to each character of char array (or simply string).

write Definition
void write(uint8_t val) //write mode
{
   DATA_PINS |= RS;  //rs on lcd pin needs to be high
   send(val);
}
by pulling RS line high while transferring any data (by calling send(val) above), this means that we are transferring in data mode, otherwise we are transferring in command mode.

Compiling and Run
Before going to the compiling process, for this project to be able to run, you need to save all the definition above in a file called lcd_reg.c. Then just make the file hierarchy like we have already discussed above with the ‘build’ folder created (the name must be the same, i.e., ‘build’, because it refers to our Makefile configuration).
Finally after the hierarchy file you have, like the top of this post, open your terminal and navigate to this four files destination, and type
make
then, you would see the message like bellow:
avr-gcc -c -Wall -Os -std=c11 -g -mmcu=atmega8535 -DF_CPU=2000000UL lcd_reg.c -o build/lcd_reg.c.o
avr-gcc -c -Wall -Os -std=c11 -g -mmcu=atmega8535 -DF_CPU=2000000UL main.c -o build/main.c.o
avr-gcc -g -mmcu=atmega8535 -Wl,-Map,main.map -o build/main.c.out build/main.c.o build/lcd_reg.c.o
avr-objdump -h -S build/main.c.out > build/main.lst
avr-size build/main.c.out
   text    data     bss     dec     hex filename
    314      10       0     324     144 build/main.c.out
avr-objcopy -O ihex -R .flash build/main.c.out build/main.c.hex
for the .hex file, you can find it in the build folder.

So here is our Makefile

Makefile Content
MCU=atmega8535
CPU=2000000UL
MCU_FLAG=-g -mmcu=$(MCU)
CPU_FLAG=-DF_CPU=$(CPU)
CC=avr-gcc

main.c.hex : main.c.out
   avr-objdump -h -S build/main.c.out > build/main.lst
   avr-size build/main.c.out
   avr-objcopy -O ihex -R .flash build/main.c.out build/main.c.hex
   
main.c.out : main.c.o
   $(CC) $(MCU_FLAG) -Wl,-Map,main.map -o build/main.c.out build/main.c.o build/lcd_reg.c.o

main.c.o : main.c
   $(CC) -c -Wall -Os -std=c11 $(MCU_FLAG) $(CPU_FLAG) lcd_reg.c -o build/lcd_reg.c.o
   $(CC) -c -Wall -Os -std=c11 $(MCU_FLAG) $(CPU_FLAG) main.c -o build/main.c.o
For this Makefile, we will discuss on the next post.

That’s it! How we can use the LCD based on address configuration.
Thanks for reading, and if you have any question, just ask bellow on the comment.