РадиоКот >Лаборатория >Цифровые устройства >
Использование графического LCD WG12864A.
Наряду с символьными ЖК, современные производители выпускают разнообразные графические индикаторы. Если у символьных, как правило, использован одинаковый интерфейс управления и система команд, то в графических индикаторах существует широкий ряд управляющих контроллеров со своими командами. Также следует обратить внимание на встроенный генератор отрицательного напряжения - при его отсутствии нужно будет использовать дополнительный источник питания. В данной статье мы рассмотрим ЖК дисплей WG12864A с управляющим контроллером фирмы Samsung ks0108.
Непосредственно ЖК дисплеем управляет два контроллера ks0108 - каждый своей половиной дисплея 64*64 точек, выбор соответствующего чипа осуществляется выводами CS1 и CS2. При высоких уровнях на обоих выводах запись осуществляется в оба чипа.
Назначение выводов индикатора:
1. Vss - общий 0В.
2. Vdd - +5В питание логики.
3. Vo - контрастность.
4. D/I - выбор данные/инструкции 1-данные 0-инструкции.
5. R/W - чтение/запись 1- чтение, 0-запись.
6. E - стробирующий сигнал при записи/чтении.
7-14. DB0-DB7 - шина данных/инструкций.
15. CS1 - выбор кристалла.
16. CS2 - выбор кристалла.
17. RST - сброс.
18. Vee - выход отрицательного напряжения.
19. A - анод подсветки
20. K - катод подсветки.
Управление контрастностью осуществляется отрицательным напряжением с вывода Vee.
Система команд ЖК:
Перед работой непосредственно с железякой рекомендую сначала отработать прошивку в Proteus, что я сам и сделал.
Питание, контрастность и пр. на индикатор подключать не надо - программа по умолчанию всё делает. Тип индикатора в среде выбран LGM12641BS1R - тут главное, чтоб управляющий контроллер был тот же и выводы CS не инвертирующие, есть и такие модели ЖК. Просто в поиске пишем ks0108, и прога отобразит все доступные в вашей версии ЖК. Контроллер ATMEGA8. Чтоб отключить подключаемый по умолчанию кварц выводам счётчика Т2 в поле TOCP Frequency надо стереть значение оставив поле пустым, в дальнейшем там будет отображаться (Default). Подробнее о работе в среде Proteus написано в обучалке.
Настало время приступить непосредственно к написанию прошивки.
Я пишу на Си в компиляторе ICC for AVR, в функциях использованы преимущественно функции сброса/установки пинов так, что думаю адаптировать код можно под любой компилятор, при желании и под ассемблер.
Для удобства адаптации под другие порты и контроллеры я задаю регистры ввода/вывода следующим образом (согласно схеме) в файле основной программы.
#define LCD_RST 0b00000001
#define LCD_E 0b00000010
#define LCD_RW 0b00000100
#define LCD_RS 0b00001000
#define LCD_CS2 0b00010000
#define LCD_CS1 0b00100000
#define LCD_DB PORTD //Шина данных
#define LCD_COM PORTC //порт управляющих выводов
при отличном от примера подключении требуется только поменять порты и при необходимости значения управляющих пинов, не меняя тела функций.
Функции работы с ЖК располагаю в файле ks0108.h, который обязательно надо включать после объявления регистров и пинов см. выше.
...
#define LCD_COM PORTC //порт управляющих выводов
#include "ks0108.h"
Здесь хочу отменить для тех, кто пишет на Си: расположение функций в отдельном файле значительно облегчает работу с основным кодом и позволяет включать необходимые функции в другие проекты без громоздких копирований.
Описание функций, находящихся в файле ks0108.h.
void WriteCom(unsigned char Com, unsigned char CS)
Запись команды: Com команда, CS выбор кристалла.
void WriteData(unsigned char data,unsigned char CS)
Запись данных: data данные,СS выбор кристалла.
Обращаю внимание на комментированные строки:
//SetBit(LCD_COM,LCD_E);
При их включении в текст программы, в симуляторе индикатор ничего не показывает, на практике же без них помимо нужных битов в RAM пишется всякий мусор, который, естественно отображается на дисплее.
Также возможно потребуется подобрать под конкретный индикатор следующие задержки
...
ClrBit(LCD_COM,LCD_RW);
NOP();
NOP();
LCD_DB=data;
...
SetBit(LCD_COM,LCD_E);
NOP();
NOP();
ClrBit(LCD_COM,LCD_E);
...
в обеих функциях, ориентировочное время задержки 250-300 нс.
void WriteXY(unsigned char x,unsigned char y,const unsigned char CS)
Установка X Y RAM тут думаю всё понятно.
void init_lcd(void)
Инициализация ЖК для этого устанавливаем указатели X Y в 0, задаём начальную область в данном случае 0 и разрешаем отображение RAM.
void clear(void)
Очистка экрана.
В качестве примера нарисуем мордочку кота, будем надеяться что духу сайта картинка понравится. Обратите внимание что точки рисуются тут не снизу вверх, а слева направо.
Листинг основной программы (ks.c):
#include <iom8v.h>
#include <macros.h>
#include <stdlib.h>
#define SetBit(x,y) (x|=y)
#define ClrBit(x,y) (x&=~y)
#define TestBit(x,y) (x&y)
#define LCD_RST 0b00000001
#define LCD_E 0b00000010
#define LCD_RW 0b00000100
#define LCD_RS 0b00001000
#define LCD_CS2 0b00010000
#define LCD_CS1 0b00100000
#define LCD_DB PORTD
#define LCD_COM PORTC
#include "delay.h"
#include "ks0108.h"
void main()
{
DDRC=0xFF;
DDRD=0xFF;
init_lcd();
Delay_ms(10);
clear();
Delay_ms(1);
WriteXY(6,2,LCD_CS1);
Delay_ms(1);
WriteData(0b01000100,LCD_CS1);
WriteData(0b01001000,LCD_CS1);
WriteData(0b11110000,LCD_CS1);
WriteData(0b00001000,LCD_CS1);
WriteData(0b00000100,LCD_CS1);
WriteData(0b01100010,LCD_CS1);
WriteData(0b01100001,LCD_CS1);
WriteData(0b00000010,LCD_CS1);
WriteData(0b00000100,LCD_CS1);
WriteData(0b00001000,LCD_CS1);
WriteData(0b10010000,LCD_CS1);
WriteData(0b01010000,LCD_CS1);
WriteData(0b10010000,LCD_CS1);
WriteData(0b00001000,LCD_CS1);
WriteData(0b00000100,LCD_CS1);
WriteData(0b01100010,LCD_CS1);
WriteData(0b01100001,LCD_CS1);
WriteData(0b00000010,LCD_CS1);
WriteData(0b00000100,LCD_CS1);
WriteData(0b00001000,LCD_CS1);
WriteData(0b11110000,LCD_CS1);
WriteData(0b01001000,LCD_CS1);
WriteData(0b01000100,LCD_CS1);
WriteXY(6,3,LCD_CS1);
WriteData(0b00001000,LCD_CS1);
WriteData(0b00000100,LCD_CS1);
WriteData(0b00000011,LCD_CS1);
WriteData(0b00000100,LCD_CS1);
WriteData(0b00001000,LCD_CS1);
WriteData(0b00010000,LCD_CS1);
WriteData(0b00100000,LCD_CS1);
WriteData(0b01000100,LCD_CS1);
WriteData(0b10001000,LCD_CS1);
WriteData(0b10010000,LCD_CS1);
WriteData(0b10010101,LCD_CS1);
WriteData(0b10010010,LCD_CS1);
WriteData(0b10010101,LCD_CS1);
WriteData(0b10010000,LCD_CS1);
WriteData(0b10001000,LCD_CS1);
WriteData(0b01000100,LCD_CS1);
WriteData(0b00100000,LCD_CS1);
WriteData(0b00010000,LCD_CS1);
WriteData(0b00001000,LCD_CS1);
WriteData(0b00000100,LCD_CS1);
WriteData(0b00000011,LCD_CS1);
WriteData(0b00000100,LCD_CS1);
WriteData(0b00001000,LCD_CS1);
while(1);
}
Вот что у меня получилось:
Усы не очень вышли, но без них кот не кот.
(Если ЭТО - кот, то я - китайский паровоз. Прим. Кота.)
В симуляторе всё работает и радостные и довольные идём подключать индикатор.
Теперь нужно быть особенно бдительным и не запутаться в куче проводков. Особенно не попутайте + и -, я вот на радостях попутал и у меня теперь в столе труп лежит аж 240*128 точек.
ВНИМАНИЕ!!!!!! ТЩАТЕЛЬНО ПРОВЕРЬТЕ ПОДКЛЮЧЕНИЕ ПИТАНИЯ И ОСТАЛЬНЫХ ПРОВОДОВ ПЕРЕД ВКЛЮЧЕНИЕМ.
Подсветку я подключаю через резистор на 15 Ом, можно конечно на прямую 5В подать, но не советую, так на символьном сжёг и он теперь не очень жизнерадостно выглядит.
Контрастность подключаем следующим образом (остальные выводы не показаны для наглядности)
Фото ЖК с включенной подсветкой.
Графика это конечно хорошо, однако текст тоже нужен. Мазохисты могут понабивать шрифт вручную, для остальных я уже набил массив с русским и английским языком плюс различные символы. Лежит это все в массиве sym файла symvol.h, который следует добавить в листинг файла ks0108.h. Также для работы наших функций потребуется здесь же объявить несколько глобальных переменных. Таким образом начало ks0108.h (Для наглядности, вся работа с текстом происходит в файле ks0108_1.h и ks_1.c - будьте внимательны. Прим. Кота.) теперь выглядит так:
#include "symvol.h"
unsigned char
textx,
texty,
curx,
cury,
ch[6];
void WriteCom(unsigned char Com,unsigned char CS)
...
Для установки текущего адреса RAM в соответствующем кристалле напишем следующую функцию:
unsigned char gotoxy(unsigned char x, unsigned y)
{
unsigned char CS, textCS;
if(x < 10)
{
CS=LCD_CS1;
textCS=0;
}
else
{
CS=LCD_CS2;
textCS=64;
}
WriteXY(x*6-textCS+4,y,CS);
return CS;
}
Поскольку в области 64*64 может уместиться только 10 целых символов шириной 5 точек с расстоянием в одну точку, текст начинаем выводить с адреса 4,0, иначе символ будет приходиться на границу полуэкрана, функция возвращает значение определённого полуэкрана.
Теперь приступим к функции вывода символов, координаты задаются глобальными переменными textx, texty.
void putc (unsigned char data, unsigned char inv)
{
unsigned char textL, CS=gotoxy(textx,texty);
if(data < 0x90)
{
textL=0x20;
}
else
{
textL=0x60;
}
WriteData((sym[data-textL][0])^inv,CS);
WriteData((sym[data-textL][1])^inv,CS);
WriteData((sym[data-textL][2])^inv,CS);
WriteData((sym[data-textL][3])^inv,CS);
WriteData((sym[data-textL][4])^inv,CS);
WriteData(0^inv,CS);
if(textx < 19)
{
textx++;
}
else
{
textx=0;
if(texty < 8)texty++;
}
}
Аргумент inv используется для инверсии, если оная не требуется, то устанавливается в 0. Если требуется инверсия, то 0xff. При желании можно инвертировать верхнюю или нижнюю часть символа 0x0f и 0xf0 соответственно. Функция снабжена инкрементом переменной textx, и texty при достижении конца строки. И ещё небольшой нюанс: дело в том что английский алфавит кончается адресом 0x80, таким образом если разобраться
if(data < 0x90)
{
textL=0x20;
}
else
{
textL=0x60;
}
здесь мы резервируем адреса 0x80-0x8f т.е. если потребуется сделать дополнительные символы достаточно будет внести их коды в файл symvol.h после буквы z.
Глядим что у нас получилось
Наблюдательный читатель заметил, что букв ё и Ё нет - дело в том что у них коды не лежат в соответствующих диапазонах, если они таки понадобятся, представляю написать их самим, доработав функцию putc.
Для вывода срок напишем функцию puts:
void puts (unsigned char str[],unsigned char n,unsigned char inv)
{
unsigned char a;
for(a=0;(a
{
putc(str[a],inv);
}
}
Аргумент n задаёт количество выводимых символов из массива str[]. Дело в том, что функции strlen() не всегда удаётся корректно определить конец строки. Так, например, функции преобразования utoa и др. не добавляют символа конца строки. Аргумент inv рассматривали выше.
Напишем небольшую программку и посмотрим как работают наши функции.
Объявления остаются прежними изменяем только текст основной программы
void main()
{
DDRC=0xFF;
DDRD=0xFF;
Delay_ms(100);
SetBit(LCD_COM,LCD_RST);
Delay_ms(10);
init_lcd();
Delay_ms(10);
clear();
Delay_ms(10);
textx=5;
texty=3;
puts("РадиоКОТ",8,0);
textx=3;
texty=4;
puts("www.radiokot.ru",15,0xff);
while(1);
}
У меня получилось так:
Текст и графику выводить научились пора браться за курсор, здесь всё не так просто. Для установки курсора необходимо считать текущий символ и записать его обратно с установленными битами 0х80.
Начнём с функции чтения.
В основном файле добавим макросы:
#define LCD_DBI PIND
#define LCD_IO DDRD
К порту D, кто забыл, подключены выводы DB0-DB8 индикатора.
Функцию поместим естественно в файл ks0108.h
unsigned char ReadData(unsigned char CS)
{
unsigned char data=0;
LCD_IO=0;
SetBit(LCD_COM,CS);
SetBit(LCD_COM,LCD_RS);
SetBit(LCD_COM,LCD_RW);
NOP();
NOP();
SetBit(LCD_COM,LCD_E);
NOP();
NOP();
data=LCD_DBI;
ClrBit(LCD_COM,LCD_E);
Delay_mks(4);
ClrBit(LCD_COM,(LCD_CS1+LCD_CS2));
SetBit(LCD_COM,LCD_E);
LCD_IO=0xff;
Delay_mks(4);
return data;
}
Аналогично функциям записи на других индикаторах возможно придётся подобрать задержки. Напомню, что приводимый код для кварца 8МГц.
Функция чтения символа:
void getc (unsigned char data[],unsigned char readx, unsigned ready)
{
unsigned char CS=gotoxy(readx,ready);
ReadData(CS);
data[0]=ReadData(CS);
data[1]=ReadData(CS);
data[2]=ReadData(CS);
data[3]=ReadData(CS);
data[4]=ReadData(CS);
data[5]=ReadData(CS);
}
Адрес задаётся аргументами readx и ready, обращаю внимание на функцию пустого чтения - здесь поплюхался. Proteus при чтении сначала выдаётся в шину 0xff, и так каждый байт, т.е полезная информация содержится в n*2 байтах. Это я не сразу догнал и долго не мог понять, где треклятая собака порылась. На этом проблемы не кончились - на железяке оказалось 0xff выдаёт только первый байт, а остальные как надо, так что если кто будет юзать индикатор, другой фирмы учите это. Почему это так честно не знаю - кто разберется, надеюсь, расскажет.
Двигаемся дальше - впереди функция удаления курсора:
void DelCur(void)
{
unsigned char CS=gotoxy(curx,cury);
WriteData(ch[0],CS);
WriteData(ch[1],CS);
WriteData(ch[2],CS);
WriteData(ch[3],CS);
WriteData(ch[4],CS);
}
Здесь всё просто при установке курсора мы записываем значение текущего символа в глобальный массив ch[], в переменных curx и cury хранится текущее положение курсора.
Функция установки курсора:
void SetCur(unsigned char x, unsigned char y)
{
unsigned char CS;
DelCur();
curx=x;
cury=y;
CS=gotoxy(curx,cury);
getc(ch,x,y);
gotoxy(curx,cury);
WriteData(ch[0]|0x80,CS);
WriteData(ch[1]|0x80,CS);
WriteData(ch[2]|0x80,CS);
WriteData(ch[3]|0x80,CS);
WriteData(ch[4]|0x80,CS);
}
Переменные x и y задают координаты курсора.
Здесь надо помнить, что при первом вызове функции curx, cury и ch[] имеют случайные значения, кто желает может задать глобальный флаг и обрабатывать его значение при первом вызове и обновлении информации с момента последнего вызова. При обновлении экрана в принципе можно просто обновить ch[].
В цикл основной программы добавим код для демонстрации функции курсора:
...
while(1)
{
SetCur(5,3);
Delay_ms(500);
SetCur(6,3);
Delay_ms(500);
SetCur(7,3);
Delay_ms(500);
SetCur(8,3);
Delay_ms(500);
SetCur(9,3);
Delay_ms(500);
SetCur(10,3);
Delay_ms(500);
SetCur(11,3);
Delay_ms(500);
SetCur(12,3);
Delay_ms(500);
}
}
Код всей проги прилагается.
Если возникнет необходимость в мерцающем курсоре, рекомендую вывесить его в прерывание от таймера, тогда в функции записи/ чтения RAM надо будет запрещать прерывания от этого счётчика на время чтения/записи, иначе не исключены ошибки.
Приведённые в статье функции немного "сыроваты" основное их назначение показать функции контроллера ks0108, однако в инете и таковых найти не удалось, только даташит на ks0108.
PS Если намереваетесь много работать с текстом на индикаторе то рекомендую поискать ЖК на контроллере LC7981 или T6963(Т6963С), последний по моему лучше. У них знакогенератор встроен, правда, только для англицких букав, стоят они конечно дороже.
Файлы:
Файл проекта Proteus-a - тут.
Заголовочный файл первой части статьи (работа с графикой) - ks0108.h
Файл основного кода первой части статьи (работа с графикой) - ks.c
Заголовочный файл второй части (работа с текстом) - ks0108_1.h
Файл основного кода второй части (работа с текстом) - ks_1.c
Файл шрифта - symvol.h
Все вопросы - сюда.
Удачи.
Как вам эта статья?
|
Заработало ли это устройство у вас?
|
|
|
Эти статьи вам тоже могут пригодиться: