Saturday, September 27, 2014

Working with LCD display on HD44780 AVR

If you need only ready to use library, you can get it here.

Datasheet


I ordered display from ebay, 2 lines by 16 symbols.



Basicly HD44780 is a controller for working with mono colored LCD displays, they were quite popular in the 90-s.  [ wiki ]

Controller and display and all other discrete elements on the PCB i will just call display.

It has 16 pins.

1) VSS -  GND
2) VDD - power supply 2.7 - 5.5 V
3) V0 - brightness level, i see clearly with 2.16 V for 1 line and 1.19 for 2 lines.
4) RS - choose what to send (read)  0 - commands, 1 - symbols.
5) RW - 0 - write, 1 - read, usually this pin is grounded.
6) E - when we send impulse here, data is being transmitted (received) to the display.
7 - 10) D0 - D3 - low bits for data transfer, not used if we are working in 4 bit mode.
11 - 14) D4 - D7 - high bits
15) A - anode of the display backlight (+), people recommend place 100 ohm resistor there, but it works fine for me.
16) K - backlight cathode.

In order to communicate with display we need to tell him that data transfer is in the progress, so we send impulse in E pin, for not less than 230 ns.

To be sure that our command is completed, we can wait some time (all command execution time is written in datasheet), or check "busy flag".

When some command is executed, display set "busy flag" on, when it finished it resets the flag. If we set RS 0, and RW 1, After that we can read "busy flag" from D7 pin, but after busy flag is dropped we stiil need to wait at least 6 us.

Display will ignore all attempts to contact him until the current command is executed.

Commands for working with display

 

1) Clear display - clears display and moves cursor to the left top corner.
2) Return home - move cursor to the beggining.
3) Entry mode set - choosing how letters will be written in the display. I/D = 1 cursor moves to the right, 0 left. S = 1 - display moves instead of cursor, 0 - cursor moves.

I/D =0

S = 0
I/D =0

S = 1

I/D =1

S = 0

I/D =1

S = 1
Gifs from here geocities.com

4) Display on/off control  - D display on (1) / off(0), C cursor on (1) / off (0), cursor blinks (1) / no (0).
5) Cursor or display shift - shifts S/C 0 = cursor, S/C 1 = display, left R/L = 0 or right R/L = 1
6) Function set - determine display operation mode, DL = 1, 8 bit ( 0 = 4 бита ), N = 1,  2  lines ( 0 = 1 line), F = 1, font 5x10, F = 0, 5x8
7) Set CGRAM adress - used for writing symbols.
8) Set DDRAM adress - used for moving cursor.
9) Read busy flag and adress - Read busy flag.

I will use ATmega8, display 2x16 and 8 bit mode for sending data.

At the beggining i decide which pins will be used:

//PORT B
#define PIN_RS 0
#define PIN_RW 1
#define PIN_E  2

//PORT C
#define PIN_DB0  0
#define PIN_DB1  1
#define PIN_DB2  2
#define PIN_DB3  3

//PORT D
#define PIN_DB4  0
#define PIN_DB5  1
#define PIN_DB6  2
#define PIN_DB7  3

Here i check "busy" flag, to know that command is executed, for sending we need to send impulse with length of ns, so 1 us should be enough.

void wait_busy()
{
 PORTB |= ( 1 << PIN_RW );
 do 
 {
  PORTB &= ~( 1 << PIN_E );
  _delay_us(1);
  PORTB |=  ( 1 << PIN_E );

 } while ( PIND & ( 1 << PIN_DB7 ) );
 PORTB = 0;
 _delay_us(7);
}

Sending data.

void send()
{
 PORTB |= ( 1 << PIN_E );
 //wait
 _delay_us(1);
 //don't send
 PORTB &= ~( 1 << PIN_E );

 //clear command
 PORTD = 0;
 PORTC = 0;
}

All code

#define F_CPU 8000000UL

//PORT B
#define PIN_RS 0
#define PIN_RW 1
#define PIN_E  2

//PORT C
#define PIN_DB0  0
#define PIN_DB1  1
#define PIN_DB2  2
#define PIN_DB3  3

//PORT D
#define PIN_DB4  0
#define PIN_DB5  1
#define PIN_DB6  2
#define PIN_DB7  3

#include <avr/io.h>
#include <util/delay.h>

void wait_busy();
void send();

int main(void)
{

 DDRB = 0xFF;
 DDRC = 0xFF;
 DDRD = 0xFF;

 //---wait for initialization
 wait_busy();

 //---8 bit 2 line mode
 PORTD |= ( 1 << PIN_DB4 )|( 1 << PIN_DB5 );
 PORTC |= ( 1 << PIN_DB3 );
 //send data
 send();
 wait_busy();

 //----turn on display, enable cursor, blniking cursor
 PORTC |= (1 << PIN_DB0 )|(1 << PIN_DB1 )|(1 << PIN_DB2 )|(1 << PIN_DB3 );
 //send data
 send();
 wait_busy();

 //---return cursor to beginning
 PORTC |= ( 1 << PIN_DB1 );
 //send data
 send();
 wait_busy();

 //---write symbol
 PORTB |= ( 1 << PIN_RS );
 PORTD |= ( 1 << PIN_DB4 )|( 1 << PIN_DB5 );
 //send data
 send();
 wait_busy();

 //---write symbol
 PORTB |= ( 1 << PIN_RS );
 PORTD |= ( 1 << PIN_DB6 )|( 1 << PIN_DB7 );
 PORTC |= ( 1 << PIN_DB3 );
 //send data
 send();
 wait_busy();

    while(1)
    {

    }
}

void wait_busy()
{
 PORTB |= ( 1 << PIN_RW );
 do 
 {
  PORTB &= ~( 1 << PIN_E );
  _delay_us(1);
  PORTB |=  ( 1 << PIN_E );

 } while ( PIND & ( 1 << PIN_DB7 ) );
 PORTB = 0;
 _delay_us(7);
}

void send()
{
 PORTB |=  ( 1 << PIN_E );
 //wait
 _delay_us(1);
 //don't send
 PORTB &= ~( 1 << PIN_E );

 //clear command
 PORTD = 0;
 PORTC = 0;
}

GitHub

Result


Display is manufactured with 1 of the 2 font sets, I have the Japanese one.


You can notice that the characters in the display memory are palced in the same order as in ASCII table, so it's easy to write writing function.

void write_symbol( char a )
{
 int small_bit = a % 16;
 int high_bit = a / 16;

 PORTB |= (1 << PIN_RS );

 PORTC = small_bit;
 PORTD = high_bit;

 //send data
 send();
 //wait
 _delay_us(50);
}

initialization and working mode
void initialize( int bit_num, int line_num )
{
 DDRB = 0xFF;
 DDRC = 0xFF;
 DDRD = 0xFF;

 //---wait for initialization
 //check busy flag
 PORTB |= ( 1 << PIN_RW );
 do
 {
  PORTB &= ~( 1 << PIN_E );
  _delay_us(1);
  PORTB |=  ( 1 << PIN_E );

 } while ( PIND & ( 1 << PIN_DB7 ) );
 PORTB = 0;

 //---8 bit 2 line mode
 PORTD |= ( bit_num << PIN_DB4 )|( 1 << PIN_DB5 );
 PORTC |= ( line_num << PIN_DB3 );
 //send data
 send();

 //wait
 _delay_us(50);
}

enable display and cursor.
void enable_display( int display_on, int cursor_on, int blinking_on )
{
 //----turn on display, enable cursor, blniking cursor
 PORTC |= (blinking_on << PIN_DB0 )|(cursor_on << PIN_DB1 )|(display_on << PIN_DB2 )|(1 << PIN_DB3 );
 //send data
 send();
 //wait
 _delay_us(50);
}

what will be shifted (display or cursor) and where (left or right)
void set_mode( int display_shift, int cursor_shift )
{
 //----turn on display, enable cursor, blniking cursor
 LOW_PORT |= (display_shift << PIN_DB0 )|(cursor_shift << PIN_DB1 )|(1 << PIN_DB2 );
 //send data
 send();
 //wait
 _delay_us(50);
}


Clear display
void clear_display()
{
 //---return cursor to beginning
 LOW_PORT |= (1 << PIN_DB0 );
 //send data
 send();
 //wait
 _delay_us(50);
}

How to move the cursor.

Because controller is universal, it is used with many different LCD sizes. It keeps position of the cursor in the DDRAM memory, which we can change with the help of the N 8 command.

Codes are presented in hexadecimal, 2 line starts with 0x40, despite the size of our LCD, if we try to move cursor to the 0x39 place (our display has less than 40 symbols per line), it just will not be displayed. But controller will be thinking that everything is fine (the same goes for symbols).

return cursor (1 command).
void return_cursor()
{
 //---return cursor to beginning
 LOW_PORT |= (1 << PIN_DB1 );
 //send data
 send();
 //wait
 _delay_us(1550);
}

Move cursor.
void move_cursor( int x_pos, int line )
{

 int small_bit = x_pos % 16;
 int high_bit  = x_pos / 16;

 //---moves cursor to the beginning of 2 line
 HIGH_PORT = high_bit;
 HIGH_PORT |= (1 << PIN_DB7 )|((line - 1) << PIN_DB6 );
 LOW_PORT  = small_bit;
 //send data
 send();
 //wait
 _delay_us(50);
}

All code
#define F_CPU 8000000UL

#define PROGRAM_PORT PORTB
//PORT B
#define PIN_RS 0
#define PIN_RW 1
#define PIN_E  2

#define LOW_PORT PORTC
//PORT C
#define PIN_DB0  0
#define PIN_DB1  1
#define PIN_DB2  2
#define PIN_DB3  3

#define HIGH_PORT PORTD
//PORT D
#define PIN_DB4  0
#define PIN_DB5  1
#define PIN_DB6  2
#define PIN_DB7  3

#include <avr io.h>
#include <util delay.h>

void send();
void write_symbol( char a );
void initialize( int bit_num, int line_num );
void set_mode( int display_shift, int cursor_shift );
void enable_display( int display_on, int cursor_on, int blinking_on );
void return_cursor();
void clear_display();
void move_cursor( int x_pos, int line )

int main(void)
{

 initialize( 1, 1 );
 set_mode( 0, 1 );
 enable_display( 1, 0, 0 );
 clear_display();
 return_cursor();


 write_symbol(' ');
 write_symbol(' ');
 write_symbol(' ');
 write_symbol(' ');
 write_symbol(' ');

 write_symbol('4');
 write_symbol('a');
 write_symbol('4');
 write_symbol('i');
 write_symbol('k');


    while(1);
}


void send()
{
 PROGRAM_PORT |=  ( 1 << PIN_E );
 //wait
 _delay_us(1);
 //don't send
 PROGRAM_PORT &= ~( 1 << PIN_E );

 //clear command
 PROGRAM_PORT = 0;
 HIGH_PORT    = 0;
 LOW_PORT     = 0;
}

void write_symbol( char a )
{
 int small_bit = a % 16;
 int high_bit  = a / 16;

 PROGRAM_PORT |= (1 << PIN_RS );

 LOW_PORT  = small_bit;
 HIGH_PORT = high_bit;

 //send data
 send();
 //wait
 _delay_us(50);
}

void initialize( int bit_num, int line_num )
{
 DDRB = 0xFF;
 DDRC = 0xFF;
 DDRD = 0xFF;

 //---wait for initialization
 //check busy flag
 PROGRAM_PORT |= ( 1 << PIN_RW );
 do
 {
  PROGRAM_PORT &= ~( 1 << PIN_E );
  _delay_us(1);
  PROGRAM_PORT |=  ( 1 << PIN_E );

 } while ( PIND & ( 1 << PIN_DB7 ) );
 PROGRAM_PORT = 0;

 //---8 bit 2 line mode
 HIGH_PORT |= ( bit_num << PIN_DB4 )|( 1 << PIN_DB5 );
 LOW_PORT  |= ( line_num << PIN_DB3 );
 //send data
 send();

 //wait
 _delay_us(50);
}

void enable_display( int display_on, int cursor_on, int blinking_on )
{
 //----turn on display, enable cursor, blniking cursor
 LOW_PORT |= (blinking_on << PIN_DB0 )|(cursor_on << PIN_DB1 )|(display_on << PIN_DB2 )|(1 << PIN_DB3 );
 //send data
 send();
 //wait
 _delay_us(50);
}

void set_mode( int display_shift, int cursor_shift )
{
 //----turn on display, enable cursor, blniking cursor
 LOW_PORT |= (display_shift << PIN_DB0 )|(cursor_shift << PIN_DB1 )|(1 << PIN_DB2 );
 //send data
 send();
 //wait
 _delay_us(50);
}

void return_cursor()
{
 //---return cursor to beginning
 LOW_PORT |= (1 << PIN_DB1 );
 //send data
 send();
 //wait
 _delay_us(1550);
}

void clear_display()
{
 //---return cursor to beginning
 LOW_PORT |= (1 << PIN_DB0 );
 //send data
 send();
 //wait
 _delay_us(50);
}

void move_cursor( int x_pos, int line )
{

 int small_bit = x_pos % 16;
 int high_bit  = x_pos / 16;

 //---moves cursor to the beginning of 2 line
 HIGH_PORT = high_bit;
 HIGH_PORT |= (1 << PIN_DB7 )|((line - 1) << PIN_DB6 );
 LOW_PORT  = small_bit;
 //send data
 send();
 //wait
 _delay_us(50);
}

GitHub
Result



Now lets work with display in 4 bit mode.

Now for sending data we will be using pins DB4 - DB7, but now we need to send data 2 times, starting from higher bits. e.g. if we want to send 0b01001000 we will send it 2 times -> 0100 pause 1000 end.

By default display is working in 8 bit mode, so when we first choose working mode, we need to send only 1 command to tell the display that we work in 4 bit mode, after that we need to send any command 2 times.

4 bit mode source code here

In addition we can create custom symbols.

We can't write directly in EEPROM (where symbols are stored), only in CGRAM (Character Generator RAM), so if we disable power supply our symbol will be erased. There is extra memory for 16 symbols which we can use for our needs (first 128 bytes). If we try to write in the other place it just won't work and we will get garbage as an output.


The same junk we can see if we display first 16 symbols without writing there anything.


Function for writing new symbol.
1) change mode for working with CGRAM and set starting address for writing.
2) when we write 1 line of the symbol (starting from top), address is auto incremented, so we can start writing second one.
3) Change mode for working with DDRAM and return cursor to the beginning

void add_character( int where, unsigned char line1, unsigned char line2,
unsigned char line3, unsigned char line4, unsigned char line5, unsigned char line6, unsigned char line7, unsigned char line8 )
{
 // starts sending char lines
 int small_bit = where % 16;
 int high_bit  = where / 16;

 HIGH_PORT  = high_bit;
 HIGH_PORT |= (1 << PIN_DB6 );
 LOW_PORT   = small_bit;

 send();

 // 1 line
 small_bit = line1 % 16;
 high_bit  = line1 / 16;

 HIGH_PORT = high_bit;;
 LOW_PORT  = small_bit;
 PROGRAM_PORT |= (1 << PIN_RS );

 send();

 // 2 line
 small_bit = line2 % 16;
 high_bit  = line2 / 16;

 HIGH_PORT = high_bit;;
 LOW_PORT  = small_bit;
 PROGRAM_PORT |= (1 << PIN_RS );

 send();

 // 3 line
 small_bit = line3 % 16;
 high_bit  = line3 / 16;

 HIGH_PORT = high_bit;;
 LOW_PORT  = small_bit;
 PROGRAM_PORT |= (1 << PIN_RS );

 send();

 // 4 line
 small_bit = line4 % 16;
 high_bit  = line4 / 16;

 HIGH_PORT = high_bit;;
 LOW_PORT  = small_bit;
 PROGRAM_PORT |= (1 << PIN_RS );

 send();


 // 5 line
 small_bit = line5 % 16;
 high_bit  = line5 / 16;

 HIGH_PORT = high_bit;;
 LOW_PORT  = small_bit;
 PROGRAM_PORT |= (1 << PIN_RS );

 send();

 // 6 line
 small_bit = line6 % 16;
 high_bit  = line6 / 16;

 HIGH_PORT = high_bit;;
 LOW_PORT  = small_bit;
 PROGRAM_PORT |= (1 << PIN_RS );

 send();

 // 7 line
 small_bit = line7 % 16;
 high_bit  = line7 / 16;

 HIGH_PORT = high_bit;;
 LOW_PORT  = small_bit;
 PROGRAM_PORT |= (1 << PIN_RS );

 send();

 // 8 line
 small_bit = line8 % 16;
 high_bit  = line8 / 16;

 HIGH_PORT = high_bit;;
 LOW_PORT  = small_bit;
 PROGRAM_PORT |= (1 << PIN_RS );

 send();

 // Back to normal regime
 HIGH_PORT |= (1 << PIN_DB7 );

 send();
}

Now write data, where = 0 - this is 1 line of 1 symbol in CGRAM, 8 - it is 1 line of 2 symbol etc. Written bits are only from 0 to 4 (because symbol width is 5 pixels).

 add_character( 0, 
 0b00000000, 
 0b00001010, 
 0b00001010, 
 0b00000000, 
 0b00010001, 
 0b00001110, 
 0b00000000, 
 0b00000000  );

Result


GitHub

Info:



No comments :

Post a Comment