Графический ЖКИ WG12864A и микроконтроллер ATmega8
Первый раз графические ЖКИ я увидел, когда мне было лет 12, в каталоге фирмы Bolumin. Мне тогда сильно захотелось научится управлять ими. Спустя лет 7 я написал программу на ассемблере, которая выводила точки и буквы на графическом ЖКИ. Позже, когда начал писать на С, потребовалось заново написать функции работы с графическим ЖКИ, такие как рисование точки (X,Y), линии, прямоугольника, вывод символов. Этому и посвящаю данную заметку.
Рассмотрим пациента повнимательней. Графический ЖКИ WG12864A имеет разрешение 128 на 64 точки. Управляет дисплеем 2 контроллера KS0108 (один контроллер KS0108 может управлять только областью 64х64). То есть если мы хотим вывести точку (20,10), то необходимо включить первый контроллер и вывести точку (20,10), а если мы хотим вывести точку (120,10), то необходимо включить второй контроллер, и вывести точку (120-64=56,10). Т.е. адресация точек у контроллеров независимая, от 0 до 63 в каждом. А для точек с Х-вой координатой больше 63 требуется отнимать 64, и передавать второму контроллеру. Теперь рассмотрим каждый контроллер по отдельности. KS0108 выводит на дисплей все, что записано в области отображения. Договоримся производить нумерацию пикселов с левого верхнего угла дисплея, начиная с 0. Каждый пиксел соответствует некоторому биту в памяти дисплея:
Вся память разбита на байты, которые в свою очередь сгруппированы в страницы (по координате У). Например, чтобы засветить пиксел (6,14) нам потребуется выставить адрес Х=6, выставить адрес У=1= [14/8]) и выставить 14%8=6-й бит в данной ячейке памяти ([] – операция взятия целой части числа, % - остача от деления). Для этого необходимо сначала считать эту ячейку памяти (если этого не делать, мы теряем все данные, которые были там до этого выведены), выполнить выставить нужный бит в считанном байте и записать этот байт обратно. Рисование линий же будем производить с помощью алгоритма Брезенхэма адаптированного под WinAVR. Рисование же прямоугольника будем производить с помощью 4-х функций рисования линий. Т.е. для рисования графики краеугольным камнем является рисование точки. Рисование же текста будем производить побайтно, чтобы сэкономить время. Для этого был вручную «нарисован» шрифт 5х8 пикселов на букву в файле symbol.h. Если будет не хватать символов – их просто самому дорисовать.
Теперь посмотрим на интерфейс WG12864A:
Выводы 1,2 – питание, а выводы 19,20 – питание светодиодной подсветки, вывод 3 – настройка контраста. Чтобы передать данные или команду их следует вывести на шину данных DB0-DB7. И сделать короткий импульс продолжительностью не мене 450нс на выводе Е, по спаду сигнала данные будут записаны. Вывод D/I определяет, передается ли команда или данные, CS1, CS2 определяют с какому контроллеру предназначены команда/данные (можно передавать сразу обоим). R/W – определяет производится чтение или запись даны. Считывать данные можно после 320нс после фронта импульса на вывод Е. Вывод Reset инвертирован (активное состояние 0) и предназначен для перезагрузки дисплея. Схема включения WG12864A очень похожа на схему включения символьных ЖКИ:
Источник тактирования - внутренний RC генератор на 1МГц. Резистор R1 – 10 кОм, С7 – 0,1мкФ. Светодиод D1 сигнализирует, что на схему подается питание, R2 – 1кОм. Резистор R3 ограничительный, для подсветки ЖКИ, 17 Ом, VR1 – переменный резистор на 10кОм, для регулировки контраста.
Теперь посмотрим на доступные нам команды:
Здесь все просто. Включаем дисплей командой 0x3f, устанавливаем начальный адрес отображения с помощью команды 0хс0, устанавливаем курсор в нулевой положения по Х и У координате командами 0х40, 0xb8 соответственно. После чего дисплей готов к работе.
На основе вышесказанного напишем демонстрационную программу, в которой будут рисоваться линии, точки, прямоугольники и выводиться текст:
#include <avr/io.h> #include "symbol.h" #define DI 0 #define RW 1 #define E 2 #define CS1 3 #define CS2 4 #define RST 5 void pause (unsigned int a) { unsigned int i; for (i=a;i>0;i--) ; } void lcd_dat (unsigned char data) { PORTD=data; //Выводим данные PORTB|=_BV(DI); //Говорим дисплею что передаются данные asm("nop"); PORTB|= _BV(E); //Импульс записи asm("nop"); PORTB&=~_BV(E); PORTB&=~_BV(DI); } void lcd_com (unsigned char comm) { PORTD=comm; //Выводим команду asm("nop"); PORTB|= _BV(E); // Импульс записи asm("nop"); PORTB&=~ _BV(E); } void gotoxy (unsigned char x, unsigned char y) { if (x<64) PORTB|=_BV(CS1); //Если x<64 включаем CS1 else { PORTB|=_BV(CS2); //else включаем CS2 x=x-64; } lcd_com(0x40+x); //Выставляем X координату lcd_com(0xb8+y/8); //Выставляем Y координату } void clear_screen (void) { unsigned char i,j; PORTB|=_BV(CS1)|_BV(CS2); //Включаем оба чипа for (j=0;j<8;j++) { lcd_com(0xb8+j); for (i=0;i<64;i++) lcd_dat(0x00); //Очищаем } } void wg12864_init(void) { DDRD=0xff; //PD0-7 как выхода PORTD=0x00; PORTB=_BV(RST)|_BV(CS1)|_BV(CS2); //Включаем оба чипа DDRB=_BV(DI)|_BV(RW)|_BV(E)|_BV(CS1)|_BV(CS2)|_BV(RST); //PB0-5 как выхода asm("nop"); lcd_com(0x3f); //Разрешаем отображение lcd_com(0xc0); //Устанавливаем начало отображения lcd_com(0x40); //Ставим X=0 lcd_com(0xb8); //Ставим Y=0 clear_screen(); PORTB&=~(_BV(CS1)|_BV(CS2)); } void put_pixel (const unsigned char x, const unsigned char y, const unsigned char color) { unsigned char temp=0; PORTB=_BV(RST); if ( (x>128)||(y>64) ) return; gotoxy (x,y); PORTD=0xff; DDRD=0x00; //PD0-7 как входа PORTB|=(_BV(RW)|_BV(DI)); //Считываем данные pause(1); PORTB|= _BV(E); asm("nop"); PORTB&=~_BV(E); asm("nop"); PORTB|= _BV(E); asm("nop"); temp=PIND; PORTB&=~_BV(E); if (color==1) temp|= _BV(y%8); //Выставляем/снимаем нужный нам бит else temp&=~_BV(y%8); PORTB&=~(_BV(RW)|_BV(DI)); DDRD=0xff; //PD0-7 как выхода asm("nop"); gotoxy(x,y); //Переходим к X,Y lcd_dat(temp); //Записываем данные PORTB=_BV(RST); } int sign (int x) { if (x<0) return -1; if (x>0) return 1; return 0; } int abs (int x) { if (x<0) return -x; else return x; } void line (unsigned char x1,unsigned char y1,unsigned char x2, unsigned char y2,unsigned char color) { int dx,dy,i,sx,sy,check,e,x,y; dx=abs(x1-x2); dy=abs(y1-y2); sx=sign(x2-x1); sy=sign(y2-y1); x=x1; y=y1; check=0; if (dy>dx) { dx=dx+dy; dy=dx-dy; dx=dx-dy; check=1; } e=2*dy - dx; for (i=0;i<=dx;i++) { put_pixel(x,y,color); if (e>=0) { if (check==1) x=x+sx; else y=y+sy; e=e-2*dx; } if (check==1) y=y+sy; else x=x+sx; e=e+2*dy; } } void rectangle (unsigned char x1,unsigned char y1,unsigned char x2, unsigned char y2,unsigned char color) { line (x1,y1,x2,y1,color); line (x2,y1,x2,y2,color); line (x2,y2,x1,y2,color); line (x1,y2,x1,y1,color); } void put_char (unsigned char x0, unsigned char y0, unsigned char code, unsigned char mode) { unsigned char i,x,y; x=x0; y=y0; PORTB=_BV(RST)|_BV(CS1); if ( (x>128)||(y>64) ) return; for (i=0;i<6;i++) { if (x>=64) { //Включить CS2 x=x-64; PORTB|=_BV(CS2); PORTB&=~_BV(CS1); } lcd_com(0x40+x); //Перейти X,Y lcd_com(0xb8+y); if (i<=4) { if (mode==1) lcd_dat(~sym[code][i]); //Пишем колонку else lcd_dat (sym[code][i]); //Пишем инвертированную колонку } if (i==5) { if (mode==1) lcd_dat(0xff); //Рисуем разделитель else lcd_dat(0x00); //Рисуем инвертированный разделитель } x=x+1; } } int main(void) { unsigned char data[10]={0x24,0x39,0x35,0x2f,0x24,0x25,0x86,0x26,0x32,0x30}; //"avrlab.com" unsigned char data2[8]={0x20,0x10,0x01,0x02,0x08,0x06,0x04,0x0a}; //"WG12864A" unsigned char i; pause(5000); //Задержка для включения ЖКИ wg12864_init(); //Инициализация ЖКИ rectangle(0,0,127,63,1); //Рисуем контур line (127,0,0,63,1); //Рисуем линии line (127,5,5,63,1); line (127,10,10,63,1); line (127,15,15,63,1); line (127,20,20,63,1); line (127,25,25,63,1); line (127,30,30,63,1); line (127,35,35,63,1); line (127,40,40,63,1); line (127,45,45,63,1); line (127,50,50,63,1); line (127,55,55,63,1); line (127,60,60,63,1); for (i=0;i<10;i++) //Пишем "avrlab.com" put_char (20+6*i,1,data[i],1); for (i=0;i<8;i++) put_char (10+6*i,3,data2[i],0); //Пишем "WG12864A" while (1) ; return 1; }
Программа в принципе проста. Сервисные функции lcd_dat(unsigned char data), lcd_com(unsigned char data) служат для выведения на ЖКИ данных и команды соответственно. Функция gotoxy(unsigned char x, unsigned char y) устанавливает курсор в положение Х,У (У – страница памяти, где находиться наш пиксел). сlear_scrren(void) очищает экран, а wg12864_init(void) – инициализирует дисплей. Точка выводиться как мы и писали выше, сперва считываются байт в который будут вноситься изменения, производиться изменения нужного бита и записывается обратно. В процессе написания этой функции я столкнулся с проблемой считать данные с ЖКИ, проблема решился тем, что для считывания 2 раза вывожу импульс на Е, а во время второго импульса считываю данные (делал разные задержки и разную длительность импульса на Е, но считать данные так и не получилось, так что оставил рабочую версию). Функции sign(x), abs(x) – написаны для реализации алгоритма рисования линии Брезенхэма. Рисование же текста функцией put_char (unsigned char x, unsigned char y, unsigned char code, unsigned char mode) проивзодиться побайтным вбитием в RAM дисплея буквы, которая соответствует параметру code в файле шрифта symbol.h Также в функциях рисования точки, линии, прямоугольника и буквы я сделал параметр color (mode), который отвечает, будет ли рисоваться объект черным на белом фоне или наоборот, белым на черном фоне. На этом думаю можно завершить описание программы.
А вот как выглядит работа нашего макета:
Скачать прошивку в виде проекта под AVR studio 4.