Например TDA7294

РадиоКот >Лаборатория >Цифровые устройства >

Теги статьи: LCDДобавить тег

Использование графического LCD WG12864A.

Автор - molchec.

Наряду с символьными ЖК, современные производители выпускают разнообразные графические индикаторы. Если у символьных, как правило, использован одинаковый интерфейс управления и система команд, то в графических индикаторах существует широкий ряд управляющих контроллеров со своими командами. Также следует обратить внимание на встроенный генератор отрицательного напряжения - при его отсутствии нужно будет использовать дополнительный источник питания. В данной статье мы рассмотрим ЖК дисплей 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

Все вопросы - сюда.
Удачи.




Как вам эта статья?

Заработало ли это устройство у вас?

43 1 1
3 1 2

Эти статьи вам тоже могут пригодиться: