Дисплей WG12864. Часть 2.

(Конечно, мастистые радиопрофессионалы улыбнуться, прочитав эту статью, т.к. понятие видео ОЗУ для них давно знакомо, и автор статьи ниже «изобрёл велосипед». Однако для начинающих эмбеддеров статья будет весьма полезна — ведь представлен ход мысли в этом направлении, и, в конечном итоге — результат.
s_black )

Приветствую. Это снова я. В прошлой статье я говорил, что такое WG12864A и с чем его едят, но это ещё не всё, что я хотел бы рассказать. Вот, например, я столкнулся с такими случаями, когда самодельный осциллограф имеет подобный дисплей и внешний АЦП с частотой дискретизации до 20МГц. Но автор жалуется, что никак не может достичь частоту замера чаще, чем 400 кГц — почему так? Я тщательно рассмотрел программу и выяснил, что осциллограмма формируется следующим образом: подаём сигнал на АЦП, считываем показание, обрабатываем показание, добавляем сетку в осциллограмму, выводим уровень на дисплей и повторяем N-е количество раз. Казалось бы, вроде  алгоритм правильный, но частота маловата. А проблема в том, что время  вывода информации на дисплей достаточно велико — и тут меня осенило! Надо выделить промежуток в ROM памяти, куда мы будем сохранять изображение, а выводить всё сразу, когда все замеры готовы.
Для тех, кто не понял, поясню: я предлагаю выделить участок памяти, в котором будет храниться изображение для вывода на дисплей. Это надо для того, чтобы можно было всегда проводить операции логического ИЛИ изображения, которое уже выведено на дисплее, с изображением что мы готовим для вывода. Согласно инструкции дисплея количество памяти, которое необходимо выделить должно, быть не меньше 1КБ ROM памяти. Давайте рассчитаем, почему же так? Я уже говорил, что дисплей разбит на 8 строк по 128 столбцов, в одной строке имеется по 8 пикселей на дисплее. Каждый пиксель может принимать только два множества значений – 0 или 1, правильно? Поэтому необходимо умножить 8 строк на 128 столбцов и на 8 бит.

8 * 128 *8 = 8192 бита, что равно 1024 байт

Наши расчёты совпадают с инструкцией на дисплей, далее, в программе нам необходимо прописать такую переменную которая могла бы содержать такое количество памяти. Назавём её VideoROM:

unsigned char VideoROM[8][128];

означающее, что переменная VideoROM имеет матрицу значений в 8 строк по 128 столбцов, при этом каждая из этих значений может занимать unsigned char  по 1 байту данных. Память есть, теперь как занести данные под нужным адресом, можно сделать так:

VideoROM[0][0] = 0xAA;
VideoROM[0][1] = 0x55;
VideoROM[0][2] = 0xAA;

и т.д.

Но становится невероятно нудно переключать столбец, с целью предотвращения этого можно сделать так:

i = 0
j = 0
VideoROM[j][i] = 0xAA; i++;
VideoROM[j][i] = 0x55; i++;
VideoROM[j][i] = 0xAA; i++;

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

void VideoROM_cim (unsigned char p, unsigned char i, unsigned char j){
switch (p)
{
case 0: VideoROM[j][i] = 0x3E; i++;
        VideoROM[j][i] = 0x51; i++; 
        VideoROM[j][i] = 0x49; i++;
        VideoROM[j][i] = 0x45; i++; 
        VideoROM[j][i] = 0x3E; i++;
break;
 
case 1: VideoROM[j][i] = 0x00; i++;
        VideoROM[j][i] = 0x42; i++;
        VideoROM[j][i] = 0x7F; i++;
        VideoROM[j][i] = 0x40; i++; 
        VideoROM[j][i] = 0x00; i++;
break;
 
case 2: VideoROM[j][i] = 0x42; i++;
        VideoROM[j][i] = 0x61; i++;
        VideoROM[j][i] = 0x51; i++;
        VideoROM[j][i] = 0x49; i++;
        VideoROM[j][i] = 0x46; i++; 
break;
 
case 3: VideoROM[j][i] = 0x21; i++;
        VideoROM[j][i] = 0x41; i++; 
        VideoROM[j][i] = 0x45; i++;
        VideoROM[j][i] = 0x4B; i++; 
        VideoROM[j][i] = 0x31; i++;
break;
 
case 4: VideoROM[j][i] = 0x18; i++;
        VideoROM[j][i] = 0x14; i++;
        VideoROM[j][i] = 0x12; i++;
        VideoROM[j][i] = 0x7F; i++;
        VideoROM[j][i] = 0x10; i++;
break;
 
case 5: VideoROM[j][i] = 0x27; i++;
        VideoROM[j][i] = 0x45; i++;
        VideoROM[j][i] = 0x45; i++;
        VideoROM[j][i] = 0x45; i++; 
        VideoROM[j][i] = 0x39; i++;
break;
 
case 6: VideoROM[j][i] = 0x3C; i++; 
        VideoROM[j][i] = 0x4A; i++;
        VideoROM[j][i] = 0x49; i++;
        VideoROM[j][i] = 0x49; i++; 
        VideoROM[j][i] = 0x30; i++;
break;
 
case 7: VideoROM[j][i] = 0x01; i++; 
        VideoROM[j][i] = 0x71; i++;
        VideoROM[j][i] = 0x09; i++;
        VideoROM[j][i] = 0x05; i++;
        VideoROM[j][i] = 0x03; i++; 
break;
 
case 8: VideoROM[j][i] = 0x36; i++;
        VideoROM[j][i] = 0x49; i++;
        VideoROM[j][i] = 0x49; i++; 
        VideoROM[j][i] = 0x49; i++; 
        VideoROM[j][i] = 0x36; i++;
break;
 
case 9: VideoROM[j][i] = 0x06; i++; 
        VideoROM[j][i] = 0x49; i++; 
        VideoROM[j][i] = 0x49; i++; 
        VideoROM[j][i] = 0x29; i++; 
        VideoROM[j][i] = 0x1E; i++; 
break;
}}

Вот где то так. И так чтобы занести в VideoROM цифру 1 под адресом 0 строка и 28 столбец нам необходимо вызвать подпрограмму с такими данными:

VideoROM_cim (1; 28; 0);

Теперь нам нужна такая подпрограмма, которая могла бы наши переменные записывать код каждый свой адрес. Назовём эту подпрограмму Play_VideoROM, и пропишем её алгоритм:

Итак, наша подпрограмма имеет циклическую форму со встроенным циклом. В начале подпрограммы мы устанавливаем оператор цикла со счётчиком, этот оператор будет отсчитывать наше количество строй в памяти и переключать VideoROM . Затем включаем обе половины дисплея и подаём команды перехода дисплея в нужную строку (lcd_com(0xB8+j) ) и в нулевой столбец (lcd_com(0x40)). Есть, теперь устанавливаем второй оператор цикла со счетчиком, который будет отсчитывать наше количество столбцов в памяти. Теперь нам необходимо узнать на какой половине дисплея будем выводить нашу информацию, для этого устанавливаем оператор ветвления с условием, если счётчик i насчитал меньше 63 значений то в работе будет первая половина, если же больше чем 63 значения то вторая.

void Play_VideoROM(void){
 
for(j=0; j<8;  j++){PORTD &= ~_BV(E2);PORTD &= ~_BV(E1);lcd_com(0xB8+j);lcd_com(0x40);
 
for(i=0; i<128;i++){if(i>63){PORTD |= _BV(E2);PORTD &= ~_BV(E1);}else {PORTD |= _BV(E1);PORTD &= ~_BV(E2);}
 
lcd_dat(VideoROM[j][i]);}}}

Не сомневайтесь, этот алгоритм работает чётко, проверено мною.

Знакогенератор есть, но зачем же нам нужен графический дисплей, если мы не будем выводить графические изображения. Для вывода подобного изображения мы можем воспользоваться специальной компьютерной программой KS0108 v 3.5 For PIC. Данная программа способна преобразовывать простой формат JPG, BMP, PNG в массив данных для вывода на дисплей или составить своё изображение простым нажатием. Работать с этой программой легче, чем играть в видео игру, поэтому разберется даже ребёнок. Как истинный любитель фирмы ATMEL и его продукта изобразим в нашем тесте такое изображение:

Данное изображение имеет оттенки серого и великовато для нашего дисплея, поэтому преобразуем данное изображение в монохромное с разрешением 128 Х 64.

Вот и занесём его в нашу программу.

Преобразовываем и получаем наш готовый массив, который заносим в нашу программу (в архиве). Итак, пользуясь старым добрым Proteus 7 Professional, моделируем наш случай, однако следует заметить, что контроллер, который будет выполнять эту операцию должен содержать ROM память больше чем 1 Кбайт. Мой выбор пал на микроконтроллер серии ATmega32, поэтому вперёд.

После не хитрых операций я получил такой проект:
Замечательно! Изображение имеется, но всё же основным преимуществом Video ROM памяти является возможность вывести на дисплей изображение при одном клике Play_VideoROM(), а также возможность делать операции логический ИЛИ двух и более изображений без изменения источника. Я же в своих устройствах использую обе эти прелести.

Выводить изображение можно по клику Play_VideoROM() но в таких устройствах как, например игровая приставка или что то более космическое, я предлагаю выводить от временных промежутков, прям как в компьютере. Берём внутренний таймер-счётчик настраиваем его прерывание скажем на 10 Гц то есть на 1/10 секунды и в подпрограмме обработки прерываний пишем просто Play_VideoROM(). В итоге получаем постоянно обновляемое изображение такого либо события, хотя опять же почему такая низкая частота обновления кадров, а вы посчитайте сколько машинных циклов занимает один полный вывод изображения. Не буду нагружать вас бессмысленными цифрами скажу прямо, что где то приблизительно 52милисекунды, то есть 0.052секунды, что для контроллера такого класса очень серьёзный и длительный процесс.

И так в итоге получили программный текст который занимает приблизительно 4556Байт памяти программы и 1024+2Байт оперативной памяти, если рассмотреть наш итоговый текст программы то мы увидим что основную память программы занимает операции занесения изображения в оперативную память, и исходя из этого мы узнаём что одно полное изображение в памяти программы занимает приблизительно 4170Байт.

И на последок хотелось бы сказать, не бойтесь экспериментировать и модернизировать мой вариант, также отмечайте свои идеи в комментариях мне будет интересно вас послушать и подискутировать с вами.

Привет из Донбасса.

Об авторе Konoplj2010

Учение - свет, а неучение - тьма. Вот почему необходимо заниматся электроникой, чтобы быть неучем но при этом освещать себе путь ;-)
Запись опубликована в рубрике Микроконтроллерный конструктор с метками . Добавьте в закладки постоянную ссылку.

6 комментариев: Дисплей WG12864. Часть 2.

  1. aahz говорит:

    в синклерах применялось множество алгоритмов сжатия данных для вывода на экран. можно и для ДАННОГО экрана применить для экономии…
    а изображения на сайте ДИКО разноцветные получились, почему-то…

  2. s_black говорит:

    Это вина, или архиватора, или электронной почты. Извините, коллеги((( Постараемся исправить !

  3. TwoS говорит:

    Не понял, почему автор не захотел собирать выборки с АЦП в простой массив. А отображать уже потом, масштабируя и извращаясь над изображением как угодно.

  4. TwoS говорит:

    ведь для осцилоскопа важно быстродействие сбора ОДНОЙ ПОРЦИИ данных с АЦП, а промежутки между этими выборками могут быть очень длительными. Важно лишь правильно понимать когда настал необходимый МОМЕНТ СТАРТА выборки т.е. то, что в осцилоскопах называется синхронизацией: вручную менять частоту ручкой\кнопками, по подьему\спаду, по внешнему сигналу, однократно, есть еще много вариантов…
    Размер порции может быть разным: к примеру в распространенном ATTEN ADS1062C (работающем до 62МГц) хватает 4КБ, в более новых моделях 1МБ… а для самодельного и 1КБ imho хватит.

  5. Ильгам говорит:

    Уважаемый! А можно ли к Протеусу прикрутить WB160128?

  6. s_black говорит:

    Я с Протеусом не работаю, поэтому сказать не могу.

Добавить комментарий

Ваш e-mail не будет опубликован.