Учебный курс. Подключение lcd к микроконтроллеру. Получение первых результатов.

29/10/2009 - 21:00
   За все время увлечения электроникой мне довелось пользоваться ЖКД  от нескольких производителей - DataVision, WINSTAR, Uniworld Technology Corp. Они отличались типом контроллера, количеством выводов и длинною строк, но при этом все имели одинаковую схему подключения, систему команд и обслуживались одной и той же программой со стороны микроконтроллера. Поэтому, хотя речь сейчас пойдет о дисплее WH0802A фирмы WINSTAR,  все ниже сказанное применимо к символьным ЖК-дисплеям и других фирм.

Итак, подключаем дисплей WH0802A-YGH-CT к микроконтроллеру

   WH0802A – двухстрочный символьный дисплей на 8 знакомест со встроенным управляющим контроллером KS0066.
   Разбираем назначение выводов дисплея.

назначение выводов lcd

   У некоторых дисплеев есть два дополнительных вывода – выводы подсветки +LED и –LED. Причем если выводы есть – это еще не означает что есть и подсветка. Как и наоборот. У моего дисплея подсветка есть, а выводов управления нет.
  По умолчанию подсветка у дисплея WH0802A-YGH-CT отключена. Чтобы ее включить, нужно проделать парочку нехитрых манипуляций, а именно – установить две перемычки и впаять токоограничительный резистор (смотри на фотке RK, JF и RA соответственно).

WH0802A с обратной стороны

Схема подключения дисплея

схема подключения lcd к микроконтроллеру
    Это типовая схема включения символьных LCD. Схему управления подсветкой дисплея мы задействовать не будем, но я ее на всякий случай нарисовал. 

Начальный код

   Подав питание на схему, нужно покрутить регулятор контраста (резистор R1). Если на экранчике появилась верхняя строка, значит, он живой и самое время приступать к написанию кода. На начальном этапе мы будем использовать 8-ми разрядную шину. Чтобы получить первые результаты, нам понадобится написать две функции – функцию записи данных и функцию записи команд. Отличаются они всего одной строчкой – когда записываются данные, сигнал RS должен быть 1, когда записывается команда, RS должен быть 0. Функции чтения мы пока использовать не будем, поэтому сигнал R/W будет всегда 0.  

   Цикл записи для 8-ми разрядной шины выглядит следующим образом:
1.    Установить RS (0 - команда, 1 – данные)
2.    Вывести значение байта данных на шину DB7…DB0
3.    Установить E=1
4.    Программная задержка 1
5.    Установить E=0
6.    Программная задержка 2

   Контроллер символьного ЖК-дисплея, не обладает бесконечным быстродействием, поэтому между некоторыми операциями используются программные задержки. Первая нужна для удержания на некоторое время строб сигнала, вторая, для того чтобы контроллер успел записать данные или выполнить команду. Величины задержек всегда приводятся в описании на контроллер дисплея и нужно всегда выдерживать хотя бы их минимальное значение, в противном случае неизбежны сбои в работе контроллера.
  Вообще у контроллера дисплея есть так называемый флаг занятости – BF. Если флаг в 1 – контроллер занят, если в 0 – свободен. Вместо второй программной задержки можно читать флаг занятости и проверять, когда контроллер дисплея освободится. Но поскольку мы хотим быстро получить первые результаты, с флагом занятости будем  разбираться потом.

//подключаем символьный ЖК-дисплей к AVR
#include
<ioavr.h>
#include <intrinsics.h>

//порт к которому подключена шина данных ЖКД
#define PORT_DATA PORTD
#define PIN_DATA  PIND
#define DDRX_DATA DDRD

//порт к которому подключены управляющие выводы
#define PORT_SIG PORTB
#define PIN_SIG  PINB
#define DDRX_SIG DDRB

//номера выводов микроконтроллера
//к которым подключены управляющие выводы ЖКД

#define RS 5
#define RW 6
#define EN 7

//макросы для работы с битами
#define ClearBit(reg, bit)       reg &= (~(1<<(bit)))
#define SetBit(reg, bit)          reg |= (1<<(bit))   

#define F_CPU 8000000
#define _delay_us(us)     __delay_cycles((F_CPU / 1000000) * (us));
#define _delay_ms(ms)     __delay_cycles((F_CPU / 1000) * (ms));

//функция записи команды
void LcdWriteCom(unsigned char data)
{
  ClearBit(PORT_SIG, RS);    // устанавливаем RS в 0
  PORT_DATA = data;        // выводим данные на шину   
  SetBit(PORT_SIG, EN);    // устанавливаем Е в 1
  _delay_us(2);
  ClearBit(PORT_SIG, EN);    // устанавливаем Е в 0
  _delay_us(40);
}
 
//функция записи данных
void LcdWriteData(unsigned char data)
{
  SetBit(PORT_SIG, RS);     //устанавливаем RS в 1
  PORT_DATA = data;        //выводим данные на шину     
  SetBit(PORT_SIG, EN);     //устанавливаем Е в 1
  _delay_us(2);
  ClearBit(PORT_SIG, EN);    // устанавливаем Е в 0
  _delay_us(40);
}

int main( void )
{
  while(1);
  return 0;
}


Здесь нет сложных мест, все должно быть понятно. Идем дальше.

   Любой ЖК-дисплей перед использованием нужно инициализировать. Процесс инициализации обычно описан в datasheet`е на контроллер дисплея. Но даже если там и нет информации, последовательность, скорее всего, будет такая.
 
1. Подаем питание

2. Ждем >40 мс

3. Подаем команду Function set



DL – бит установки разрядности шины
0 – 4 разрядная шина, 1 – 8 разрядная шина

N – бит установки количества строк дисплея
0 – однострочный режим, 1 – двухстрочный режим

F – бит установки шрифта
0 – формат 5*8, 1 – формат 5*11

* - не важно что будет в этих битах

4. Подаем команду Display ON/OFF



D – бит включения/выключения дисплея
0 – дисплей выключен, 1 – дисплей включен

C – бит включения/выключения курсора
0 – курсор выключен, 1 – курсор включен

B – бит включения мерцания
0 – мерцающий курсор включен, 1 – мерцающий курсор выключен

5. Подаем команду Clear Display



6. Ждем > 1,5 ms


7. Подаем команду Entry Mode Set



I/D – порядок увеличения/уменьшения адреса DDRAM(ОЗУ данных дисплея)
0 – курсор движется влево, адрес уменьшается на 1, 1 – курсор движется вправо, адрес увеличивается на 1

SH – порядок сдвига всего дисплея
0 – сдвига нет, 1 – сдвиг происходит согласно сигналу I/D – если он 0 – дисплей сдвигается вправо, 1 – дисплей сдвигается влево

Для нашего примера функция инициализации будет выглядеть так
//функция инициализации
void InitLcd(void)
{
  //настраиваем порты ввода/вывода
  DDRX_DATA = 0xff;
  PORT_DATA = 0xff;   
  DDRX_SIG = 0xff;
  PORT_SIG |= (1<<RW)|(1<<RS)|(1<<EN);
  ClearBit(PORT_SIG, RW);

  _delay_ms(40);
  LcdWriteCom(0x38); //0b00111000 - 8 разрядная шина, 2 строки
  LcdWriteCom(0x0f);  //0b00001111 - дисплей, курсор, мерцание включены
  LcdWriteCom(0x01);  //0b00000001 - очистка дисплея
  _delay_ms(2);
  LcdWriteCom(0x06);  //0b00000110 - курсор движется вправо, сдвига нет
}


   Теперь у нас есть необходимый минимум, чтобы начать работу с дисплеем. Выведем на него слово "Test."
//собственно наша программа
int main( void )
{
  InitLcd();
  LcdWriteData('T');
  LcdWriteData('e');
  LcdWriteData('s');
  LcdWriteData('t');
  LcdWriteData('.');
  while(1);
  return 0;
}
   Первые результаты мы получили.  Доводить программу до ума будем в следующих статьях.

Файлы

Comments   

# alexandershahbazov 2009-12-08 20:57
Точно не помню похож ли контроллер
KS0066 на HD44780 .
Т.е. применимо ли выше сказанное для
HD44780 .
# Pashgan 2009-12-08 22:28
У HD44780 немного отличается инициализация.. она выполняется без чтения флага занятости. Если его не использовать, то все тоже самое.
# Vetal-Soft 2010-01-27 09:32
У дисплея на фото выводы для подключения подсветки обозначены А и К. Анод и катод соответственно :-)
# Guest 2010-04-16 18:16
Спасибо тебе огромное за статьи, всё понятно и доходчиво. Я как начинающий электронщик не знаю что бы без них делал, и с нетерпением жду новых статей.
Если по делу, то в файле исходника для IAR для соответствия прогаммы и схемы неибходимо заменить соответствующую часть на:
#define RS 4
#define RW 5
#define EN 6
Проверенно во Proteus
# Aleks 2010-10-21 00:14
По-моему источник этой ошибки - комментарий
"//номера выводов микроконтроллер а
//к которым подключены управляющие выводы ЖКД"
Как в комментарии указано "номера выводов", так и определено: RS прицеплен к выводу 5 (корпус DIP), вот и определено
#define RS 5
был бы использован корпус TQFP, было бы
#define RS 44
___
По-моему если бы комментарий был:
"//номера битов порта микроконтроллер а
//к которым подключены управляющие выводы ЖКД", то и было бы
#define RS 4
или
#define RS PB4
___
Это кстати не только для IAR, а во всех примерах эта ошибка, и в html (2010-10-20)
___
С уважением Александр.
За статьи спасибо.
# Pashgan 2010-04-17 06:12
Спасибо, исправлю.
# Guest 2010-06-04 14:09
спасибо большое.
Вс' так красиво и доступно расписано.

Только вот здеся ошибочка есть:
"3. Установить E=0
4. Программная задержка 1
5. Установить E=1
6. Программная задержка 2"

наоборот же надо ведь, во всяком случае у вас в коде наоборот написано, сначала установить, потом обнулить
# Pashgan 2010-06-04 17:48
Ужас.. косяки так и всплывают. Давно писал этот материал, руки не доходят покопаться в нем снова.
# Guest 2010-07-05 18:48
Замечательная серия статеи. У мня ЖКД -
MTC-16201x (mega16, кварц 11.0592). Были проблемы с отображением,
терялись символы, нарушалась последовательно сть, но после установки флага BF, все пришло в норму (подобрать задержку не получилось).
Подскажите, как русифицировать текст?
Спасибо за статьи.
# Guest 2010-08-26 10:08
Спасибо за статьи продолжай в том же духе!
Исправь здесь пожалуйста
1.Установить RS (0 - данные, 1 – команда)
# Pashgan 2010-08-26 16:16
Исправил все перечисленные ошибки ;-)
# Ali 2010-11-19 07:08
А чтонить поменяется в программе, железе если использовать wh1602a ?
# Pashgan 2010-11-21 22:59
Нет.
# Not_found 2010-11-21 05:55
А статья для 4-х битного подключения будет? уже год прошел с момента написания, но эта тема все равно всегда будет актуальна... ;-)
# Not_found 2010-11-21 06:06
упс, простите за флуд кое-что нашел)) :oops:
# Volonter 2010-11-21 20:47
Почему в коде:
#define PORT_DATA PORTC
#define PIN_DATA PINC
#define DDRX_DATA DDRC

"С" если на схеме четко показано что двунаправленная шина подключена к порту "D"?
# Pashgan 2010-11-21 22:59
В тексте очепятка. Исправил
# Volonter 2010-11-21 23:36
Не могу заставить работать lcd точно такой-же как у тебя на картинке с atmega128 в proteus... хоть убей но не пашет, на макетке то-же...
# Pashgan 2011-01-29 22:06
Выложи код на форуме, там разберемся.
# АлексейЛ81 2011-01-28 21:26
Добрый день. Попробовал, не работает. Точнее рисует "0__\_" Я скачал ваш пример (для CodeVision) точно также. Подскажите, почему так. Контроллер ATmega8535 в TQFP корпусе.
# АлексейЛ81 2011-01-28 21:27
Я новичёк в контроллерах, не ругайтесь.
# Oposition Spb 2011-03-10 13:24
У меня тоже что-то все печально...
пробывал юзать HDM08216L - он вроде на HD44780 ... результат дальше появления второй строчки не ушел. Уже даже сам написал весь код используя даташит. И не получилось ничего с WH1602L-DYK-CTT , получил на экране появление кирилицы в место S - З, t-г, f-Л... если слать более трех символов курсор начинает бегать по полю... Наверно мне дохлые стекляшки попались :(
# Pashgan 2011-03-15 19:45
Попробуй вот эту библиотеку http://chipenable.ru/index.php/programming-c/74-avr-biblioteka-lcd-ks0066-hd44780.html
# Pigsel 2011-03-24 12:10
В схеме внешний кварц обязателен, или со внутреннего генератора будет работать?
# AndreySkv 2011-07-17 18:02
Вот еще ошибку нашел:

//номера выводов микроконтроллер а
//к которым подключены управляющие выводы ЖКД
#define RS 5
#define RW 6
#define EN 7

Не номера выводов микроконтроллер а , а номера порта!!!

Правильно так:

#define RS 4
#define RW 5
#define EN 6

Иначе не работает. Проверено!
# Cudi 2011-09-13 15:17
Обяхательно все контакты LCD (Data) подключать?! НА бейсике я подключал только 4 порта Дата : data7, 6, 5, 4.
Нельзя код сделать для 4х портов дата?!
# Stranger 2011-12-16 03:54
Что надо сделать, чтобы подключить индикатор к ATmega8?
Можно ли использовать тактовую частоту контроллера от внутреннего генератора 1МГц?
Пробовал подключить индикатор к тем же портам контоллера ATmega8 - не работает. В тексте кода (из примера для CodeVision) поменял первую строку на #include . Нужно ли еще что нибудь менять?
# Шурик 2012-01-02 13:05
Скажи, пожалуйста, где ты нашел переменный резистор в Eagle, когда схему рисовал.
# _Alex_ 2012-03-23 18:22
Зачётная статья =)
# usta 2012-09-05 08:20
огромное спасибо автору
совместно с этим статя будет понятна всем
http://www.geocities.com/dinceraydin/djlcdsim/djlcdsim.html
# MS24 2012-10-14 07:46
А как сделать буегущую строку? я пытаюсь сдвигать экран, но у меня ничего не получается, просто уходит в невидимую область.
# Константин1982 2012-10-17 20:36
Дисплей как у вас, как сделать что бы писал и на нижней строке?
# Константин1982 2012-10-18 08:22
Сам отвечу на свой вопрос. Информацию подчерпнул из даташита. Что бы начать писать во вторую строку надо перевести внутренний счетчик контроллера дисплея на адрес 40H, следовательно ввести "команду" 11000000 bin или C0 Hex, а после можно снова вводить "данные"
# Pashgan 2012-10-18 20:06
Почитай следующие статьи посвященные lcd. Там добавлен макрос LCD_Goto(x, y) для позиционировани я курсора.
Code:
/*позиционирование курсора*/
#define LCD_Goto(x,y) LCD_WriteCom(((((y)& 1)*0x40)+((x)& 7))|128)
# Константин1982 2012-11-04 18:30
Спасибо!
# Игорь7 2013-01-17 14:46
Здравствуйте, я попробовал по описанной выше методике сделать программу на ассемблере в Algoritthm Builder, но почему-то самостоятельно МК отказывается работать. Не подскажите почему? При самом программировани и все выводится на экран как нужно, а после отключения программатора и перезапуска МК только экран очищается.
# Pashgan 2013-01-18 06:37
А дисплей такой же?
# Игорь7 2013-01-18 10:41
Дисплей WH0802A1.
# Игорь7 2013-01-18 10:42
Может я что-то в программе не так делаю ...
# Игорь7 2013-01-18 11:35
http://www.fayloobmennik.net/2532296
# Валян 2016-08-04 12:45
Спасибо большое! Как раз то, что искал. Везде с 4 выводами только, а мне вот этот как раз нужно было. Чтобы понять все что к чему.

У вас недостаточно прав для комментирования.