11 июля 2014 г.

Работа с LCD дисплеем на HD44780 AVR

Если нужно срочно вывести символы и не интересно как он работает, то я расписал как работать с готовой для него библиотекой здесь

Datasheet


Заказал с ebay дисплей 2 линии на 16 символов каждая.




Вообщем HD44780 это контроллер управления одноцветным LCD дисплеем, такие дисплеи были очень популярны в 90-е   [ wiki ]

связку контроллер, дисплей и внешние выводы я буду называть просто дисплей.

У таких дисплеев 16 выводов

1) VSS -  GND, земля или общий провод.
2) VDD - питание контроллера 2.7 - 5.5 В
3) V0 - настройка контрастности, нужно подбирать вручную, у меня всё ясно видно при 2,16 В если использовать 1 линию, 1,19 В если 2. В моём случае яркость отображения символов, меняется в зависимости от количества используемых линий.
4) RS - выбираем что записывать (читать)  0 - команды, 1 - символы.
5) RW - 0 - записывать, 1 - считывать, обычно этот вывод заземляют если предполагается что нужно будет только записывать символы.
6) E - подав сюда импульс происходит передача команды дисплею.
7 - 10) D0 - D3 - low биты передачи данных, не используются при 4 битной работе.
11 - 14) D4 - D7 - high биты передачи данных
15) A - Анод подсветки дисплея (+), советуют ставить 100 ом резистор, но у меня и без него всё в порядке.
16) K - катод подсветки.

Для того чтобы общаться с дисплеем необходимо ему говорить что происходит передача данных, для этого подается импульс на вывод E не меньше 230 нс.

Для того чтобы узнать что команда выполнена можно либо подождать некоторое время, (для каждой команды указано сколько необходимо ждать). Либо проверять "busy flag".

Когда происходит выполнение команды дисплей выставляет "busy flag", когда команда выполнилась он его сбрасывает. Его можно считать  подав на RS 0, RW 1, и тогда он будет на выводе D7, но всё равно следует подождать некоторое время даже если он равен 0, не меньше 6 мкс.

Дисплей будет игнорировать все попытки с ним связаться пока не закончиться выполнение текущей команды.

Чтобы вывести определённый символ нужно передать его код дисплею при RS 1, RW 0,
таблицу кодов символов приведу позже.

Команды для работы с дисплеем.

 

1) Clear display - очистить дисплей и вернуть курсор в левый верхний угол (начало).
2) Return home - вернуть курсор в начало.
3) Entry mode set - выбор как будут выводиться символы на экране при их записи. I/D = 1 курсор движется вправо, 0 влево. S = 1 - вместо курсора движется дисплей, 0 - нет.

I/D =0

S = 0
I/D =0

S = 1

I/D =1

S = 0

I/D =1

S = 1
Анимация взята здесь

4) Display on/off control  - D дисплей вкл (1) / выкл (0), C курсор вкл (1) / выкл (0), B курсор мигает (1) / нет (0).
5) Cursor or display shift - сдвигает S/C 0 = курсор, S/C 1 = дисплей, влево R/L = 0 или вправо R/L = 1/
6) Function set - настраиваем режим работы дисплея, DL = 1 8 бит ( 0 = 4 бита ), N = 1  2  линии ( 0 = 1 линия ), F = 1 шрифт 5x10, = 0 5x8
7) Set CGRAM adress - используется для записи своих символов
8) Set DDRAM adress - используется для перемещения курсора.
9) Read busy flag and adress - чтение "busy" флага/

Рассмотрим работу с дисплеем на примере, используется ATmega8, дисплей 2x16 и передавать данные буду по 8 битам.

Сразу обозначаю ножки которые буду использовать, работаю с ATmega8 у которой нет 8 выводов на 1 порт, поэтому использую несколько портов.

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

Здесь я проверяю "busy" флаг, чтобы узнать что команда выполнена, для передачи нужно подать импульс который измеряется в нс, так что 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;
}

Весь код

#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

Результат


У дисплея есть 2 набора символов, как видно у меня тот в котором есть японский.


Но всё таки нужно написать функции которые бы облегчили бы работу с дисплеем.

Можно заметить что символы в памяти дисплея совпадают по порядку с таблицей ASCII.

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);
}

нужно написать функцию инициализации или выбора режима работы
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);
}

и включения дисплея с курсором.
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);
}

Выбираем кто сдвигается (дисплей или курсор) и куда (влево вправо)
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 clear_display()
{
 //---return cursor to beginning
 LOW_PORT |= (1 << PIN_DB0 );
 //send data
 send();
 //wait
 _delay_us(50);
}

Нужно научиться передвигать курсор по дисплею.

Т.к контроллер дисплея универсальный и его ставят на разные виды дисплеев, то он может работать с различными размерами экранов. В памяти хранится текущее положение курсора и его можно менять с помощью 8 команды, нужно указать адрес куда мы хотим переместить курсор.

Здесь коды указаны в 16 коде, 2 строка начинается с 40 и не важно сколько в нашем дисплее символов на строку, если мы запишем символ в 0х39, хотя у нас дисплей меньше чем на 40 символов, то он просто не отобразиться, хотя дисплей будет считать что всё в порядке.

Возвращаем курсор на место (1 команда).
void return_cursor()
{
 //---return cursor to beginning
 LOW_PORT |= (1 << PIN_DB1 );
 //send data
 send();
 //wait
 _delay_us(1550);
}

Передвигаем курсор.
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);
}

Весь код
#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
Результат



С дисплеем можно работать в 4 битном режиме вместо 8.

Теперь для передачи данных используются выводы DB4 - DB7, но данные нужно передавать 2 раза, начиная с старших битов, то есть 01001000 передается как -> 0100 передача, 1000 передача.

По умолчанию (при включении) дисплей работает в 8-ми битном режиме, поэтому если мы хотим работать с 4 битами то при выборе режима команда отсылается 1 раз, после чего дисплею нужно посылать по 2 команды.

Код для работы с помощью 4 битов здесь

К тому же можно создавать свои символы.

Нельзя записать символ в EEPROM (энергонезависимая память), а только в CGRAM (Character Generator RAM), так что если отключить питание символ исчезнет. Там отведена память под 16 символов которые можно менять (первые 128 байт) На самом деле нам доступно только 8 символов, остальные 8 это копия предыдущих, они обозначены в таблице символов номерами в скобках. (1 и 9 символ один и тот же, если изменить 1 то во втором сразу появится его копия), если попытаться записать в другое место то ничего не запишется и появится мусор. 


То же самое можно увидеть если попытаться вывести первые 16 символов предварительно ничего туда не записав.


Функция для записи нового символа,
1) выставляем режим для работы с CGRAM и ставим адрес с какого начинаем записывать,
2) когда записываем строку адрес сам увеличивается на 1 и начинаем записывать следующую, так записываем сколько нам необходимо.
3) возвращаемся в режим работы с DDRAM и возвращаем курсор на начало.

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();
}

Теперь записываем данные, where = 0 - это 1 строка 1 символа в CGRAM, 8 - это 1 строка 2 символа и т.д. Записываются только биты с 0 по 4 (т.к ширина символа 5 пикселей).

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

Результат


GitHub



Комментариев нет :

Отправить комментарий