Available Languages?:

Текстовый VGA-терминал на PIC18

Введение

Вашему вниманию предлагается программа для PIC18, в которой реализована функция простой видеокарты для вывода текстовой информации на VGA-дисплей. Конечно, PIC18 не совсем подходит для решения подобных задач, тем не менее, данный пример показывает, что это не невозможно. Эта программа была написана в 2004 году, и здесь она приводится для пояснения принципов работы проекта "Видеоигра на PIC18".

Исходные тексты программы терминала можно взять здесь: terminal.rar

Итак, в статье описана программа, реализующая функции видеокарты со следующими характеристиками:

Текстовое разрешение 30x30 символов
Графическое разрешение 240x480 пикселей
Размер матрицы знакогенератора 8x16 пикселей
Количество цветов 15
Эмуляция курсора Есть. С возможностью изменения размера.
Наборы символов Латинский, кириллица, псевдографика
Интерфейс управления SPI, до 250 кбит/с

Хоть видеокарта и способна отображать 15 цветов, но есть некоторые ограничения:

  1. фон может быть только черным;
  2. один символ может быть только одного цвета (не считая цвета фона).

Вот тестовая картинка, сгенерированная видеокартой (сама программа была написана еще в 2004 году, но функция демо-экрана была добавлена только что для демонстрации):

Управление VGA

Немного теории

VGA-монитор управляется по 5 линиям: 2 цифровых (синхронизация горизонтальная и вертикальная) и 3 аналоговых (красный, зеленый, синий). Линии синхронизации служат для того, чтобы монитор знал, где начало кадра, какое разрешение и какова частота кадров. Аналоговые входы служат для задания цвета. Чем выше напряжение мы подаем на аналоговый вход, тем более яркий оттенок соответствующего цвета мы получим. Регулируя уровни напряжения на трех аналоговых линиях в различных комбинациях, можно получить различные оттенки цветов. Например, подав одинаковые уровни на входы управления красным и зеленым цветом (при нулевом синем) можно получить желтый цвет.

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

Для того чтобы человеческим глазом не было видно мерцания, частота обновления кадров выбирается не менее 60 Гц. Т.е. за секунду должно быть прорисовано 60 кадров. Каждый кадр в стандартном VGA-режиме состоит из 525 строк, из которых 480 являются информативными (содержащими информацию для вывода на экран), а остальные составляют передний и задний фронты синхронизации, а также нижний и верхний бордюр и не несут в себе информации о цвете. Каждая строка также логически разбита на несколько участков: передний и задний фронты синхронизации, левый и правый бордюры, видеоинформация для вывода на экран и гасящий синхроимпульс.

Нетрудно подсчитать, что время одной строки = 1с/60 кадров / 525 строк = 31.75 мкс

Ниже приведен рисунок, поясняющий порядок передачи информации на VGA-монитор:

Генерация изображения

Генерация синхроимпульсов

Здесь все просто. Все, что нам нужно, - это генерировать импульсы отрицательной полярности длительностью 3.9 мкс с постоянным периодом в 31.75 мкс. Это будут импульсы горизонтальной синхронизации. Во время каждых 524-го и 525-го импульсов будем формировать отрицательный импульс вертикальной синхронизации. Очевидно, что для этой цели удобно воспользоваться прерыванием по таймеру.

Учитывая требования к синхронности самих синхроимпульсов и сигналов формирования цвета, на этапе проектирования было принято решение весь код, формирующий непосредственно изображение, поместить в прерывание. Во-первых, это позволит жестко, с точностью до такта, привязать RGB-сигналы к синхроимпульсам, а во-вторых, мы, таким образом, распараллелим процессы формирования VGA-сигналов с формированием видеопамяти и работе SPI.

Формирование цветного изображения

Как мы будем формировать сигналы RGB? Вариантов несколько, но, учитывая специфику выводимой информации, т.е. цветной текст, состоящий из символов шириной по 8 пикселей, где каждый символ может быть прорисован только одним цветом, было принято решение организовать вывод следующим образом (есть еще одна причина, но о ней чуть ниже): одним выводом генерируем значение пикселя "зажжен"/"погашен", тремя выводами с открытым стоком модулируем пиксель для получения нужного цвета и одним выводом регулируем яркость. Схематически это можно изобразить так:

Когда на выходе Data уровень "0", вне зависимости от состояний остальных выходов все линии RGB будут в "0", что будет соответствовать черному цвету. Но когда на выходе Data уровень "1", то состояние линий RGB будет выбираться сигналами контроллера ~R, ~G и ~B. Если вывод установлен в "0" и соответствующий транзистор закрыт, то "1" с выхода Data будет проходить на управление цветом VGA. Если же вывод установлен в "1" и соответствующий транзистор открыт, то на соответствующей линии управления цветом будет "0". Таким образом, генерируя нули и единицы на выходе Data, мы можем каждой светящейся точке задавать цвет, устанавливая выводы ~R, ~G, ~B в нужное значение. Например, для получения точки голубого цвета нам нужно убрать красную составляющую, оставив синюю и зеленую. Т.е. устанавливаем выход ~R в "1", а ~G и ~B - в "0".

У нас есть еще один выход - ~Y. Это управление яркостью. Когда он установлен в "0", транзистор закрыт, и уровни напряжений на линиях VGA-R, VGA-G и VGA-B будут сформированы резистивным делителем, верхнее плечо которого - резистор в стоке транзисторов управления цветом, а нижнее плечо - входное сопротивление VGA = 75 Ом. Но когда выход ~Y устанавливается в "1", все три линии управления цветом прижимаются к земле через диоды, понижая тем самым напряжение на активных линиях до 0.35В (используются диоды 1N5819).

Таким образом, мы получаем 16 цветов: 8 обычных + 8 пониженной яркости. (На самом деле мы получаем 15 цветов, т.к. при данном подходе получаются два черных цвета.)

На практике транзисторы с открытым стоком мы можем реализовать на самом контроллере, установив защелку выходов в "0" и управляя регистром направления выводов (TRIS). Поэтому в устройстве для задания цвета будут присутствовать только диоды и резисторы.

Формирование последовательности пикселей

Как видно из рисунка, поясняющего порядок синхронизации, видимая часть строки длится 25.17 мкс. Это означает, что, имея контроллер с производительностью 10 MIPS (на момент написания программы это была максимальная производительность для PIC18), т.е. со временем программного цикла = 100 нс, мы можем сформировать видимую область с максимальным разрешением 251 пиксель по горизонтали. Но тут можно возразить, что потребуется как минимум один такт на формирование данных и один такт на вывод, т.е. уже 200 нс. Но мы прибегнем к возможностям периферии, а именно - воспользуемся модулем USART, который сможет сгенерировать нам последовательность из 8-ми нулей и единиц длительностью по 100 нс каждый (для этого нужно будет запустить USART в синхронном режиме на максимальной скорости). Таким образом, нам понадобятся всего два такта для формирования 8 пикселей. И еще 6 тактов остаются на всякие дополнительные нужды: чтение таблицы знакогенератора и формирование цвета.

(Использование USART является второй причиной, по которой мы для формирования последовательности "зажженных"/"погашенных" пикселей используем один вывод контроллера).

Т.к. предполагается использовать шрифт шириной 8 пикселей, то и разрешение у нас будет кратным 8, т.е. максимально возможное - 248 точек (31 символ). Однако для простоты и удобства возьмем 30. Таким образом, разрешение видеоизображения по горизонтали будет 240 точек. Для вывода изображения нам нужно после генерации синхроимпульса выдержать паузу (левый бордюр), затем выполнить прорисовку текущей линии изображения, затем выдержать еще паузу (правый бордюр). Порядок обработки прерывания будет таким:

При такой организации у нас от каждой строки остается 3.9 мкс (39 инструкций, за вычетом 5-6 на вход/выход в прерывание и сброс бита прерывания) на обработку данных от SPI. Паузы здесь отмечены условно, т.е. на самом деле это время используется на сопутствующие вычисления (эмуляцию курсора, подсчет строк, формирование указателей).

В нашей программе применяется шрифт шириной 8 пикселей. Знакогенератор хранится во внутренней ROM контроллера и представляет собой массив, в котором на описание каждого символа выделяется 16 байт (по одному байту на каждую строку растра). Например, буква 'A' записана так:

И для вывода этого символа мы будем в течение прорисовки 16 линий через один и тот же промежуток времени после начала горизонтального синхроимпульса записывать в регистр TXREG все байты, описывающие этот символ: в первой и второй линиях байты 0х00, в третьей - 0x10, в четвертой - 0x38 и т.д. Тогда на экране это будет выглядеть так:

Здесь ts - момент начала строки, tc - момент старта символа.

Для вывода одной строки растра каждого символа у нас есть 8 тактов. За это время мы должны успеть выбрать текущий байт из массива знакогенератора для следующего символа и подготовить значение цвета для него. Ровно через 8 тактов после начала вывода предыдущего символа мы выводим следующий, еще через 8 - очередной и так выводятся все 30 символов.

Принципиальная схема

Два слова о некоторых схемных решениях.

В первую очередь нужно обратить внимание на необязательную, но желательную микросхему 74AC245. Ее основная функция - разгрузить вывод RC7. Т.к. входы линий цвета VGA являются низкоомными (75 Ом), то ток, которым они управляются, получается сравнительно велик. Выводу RC7 приходится работать на низкоомную нагрузку сразу по трем линиям: управление красным, зеленым и синим. В принципе, эту микросхему можно и не ставить, а нагрузить все целиком на RC7, но при этом картинка на экране получится немного тусклой.

Во вторую очередь обратим внимание на RC-цепочку, стоящую на выходе RC7 (R4-C8). При отладке программы было обнаружено несоответствие поведения контроллера PIC18F252 (такое же несоответствие было установлено для PIC18F258, PIC18F452 и PIC18F458) с описанием в документации: там сказано, что импульсы USART, работающего в синхронном режиме на максимальной скорости, синхронизированы с тактом Q4. Однако на практике выяснилось, что они синхронизированы с тактом Q2. Для задания цвета каждому символу регистр цвета (TRISB) обновлялся одновременно с началом передачи по USART очередного байта. Проблема в том, что запись в регистр TRISB в этих контроллерах производится как раз по такту Q4. Т.е. запись в регистр управления цветом рассинхронизирована с началом передачи очередного байта по USART на Q4-Q2 = 50 нс (при тактовой частоте 40 МГц). На экране эта рассинхронизация проявляется в виде "заползания" цвета соседних символов друг на друга. Для того чтобы избавиться от этого эффекта, в схему и была внедрена RC-цепочка, задерживающая сигнал с выхода RC7 на 50 нс.

Примечание. Это несоответствие было обнаружено только на PIC18F252, PIC18F258, PIC18F452 и PIC18F458. Если будет использован контроллер более новой модификации (например, 2520), то RC-цепочку ставить не нужно.

Организация программы

Структура видеопамяти

Видеопамять содержит следующую информацию:

  • массив символов, выводимых на экран (8 бит на символ);
  • массив цветов для всех знакомест (4 бита на символ).

Экранное поле разбито на 900 знакомест (30 по вертикали и 30 по горизонтали). Таким образом, требуется 900 байт для хранения символов и 450 байт для хранения массива цветов. Учитывая требования по минимизации времени обработки данных в этих двух массивах, было решено организовать видеопамять в виде массива 3-х байтовых блоков:

xyXXYY

где:

  • x - 4 бита, задающие цвет символа XX;
  • y - 4 бита, задающие цвет символа YY;
  • XX - символ в четной позиции знакоместа;
  • YY - символ в нечетной позиции знакоместа.

Эти трехбайтовые блоки хранятся в памяти последовательно в виде массива из 450 блоков. Такая организация памяти позволяет адресовать сразу два массива (массив символов и массив цветов) одним и тем же указателем FSR.

Распределение SFR

Для того чтобы не тратить дефицитное время на формирование FSR-регистров для указания на активный символ (для отображения или для записи), а также еще некоторых специальных регистров, программа организована таким образом, что за некоторыми регистрами закреплены определенные функции на протяжении всего времени выполнения программы:

FSR1 всегда указывает на активный блок в массиве видеопамяти, куда производится запись (фактически - текущая позиция курсора).
FSR2 Всегда указывает на текущий выводимый на экран символ
TBLPTR Всегда указывает на массив знакогенератора (на текущую линию; подробнее см. Знакогенератор)
PCLATH Всегда содержит значение 0х4 - старший байт адреса подпрограммы обработки SPI (т.к. там присутствует таблица переходов, обращение к которой реализовано присвоением регистру PCL)

Такое закрепление регистров позволит сэкономить время, которое могло быть затрачено на:

  • формирование указателей (и RAM и ROM);
  • сохранение/восстановление SFR при входе/выходе в/из прерывания.

Знакогенератор

Знакогенератор организован в виде массива байтов для хранения информации о 256-ти символах по 16 байт на символ. Эти данные нам нужно разместить в определенном порядке, чтобы максимально сократить время выборки требуемого для вывода на экран байта с учетом того, что за один проход (за прорисовку одной строки растра) из всех 16-байт, описывающих конкретный символ, для каждого символа будет выбран только один байт, причем с одним и тем же индексом. Т.е., выводя строку номер 5, нам потребуются только 5-е байты из 16-ти, описывающих каждый символ. Поэтому массив знакогенератора организован не в виде массива 16-байтов блоков, каждый их которых описывает один символ, а в виде массива из 16-ти 256-байтовых блоков, каждый из которых содержит на каждый символ по одному байту, который будет выводиться при прорисовке текущей линии.

Такая организация позволит при прорисовке очередной строки растра один раз сформировать значение регистра TBLPTRH, который будет указывать на 256-байтный блок, и для каждого символа просто прописывать его значение в регистр TBLPTRL, получая тем самым адрес байта для вывода на экран. С учетом такого подхода у нас есть одно требование: таблица знакогенератора должна быть выровнена по границе 256 байт. В нашей программе знакогенератор будет храниться по адресу 0x1000.

Таблица символов

(Символы с кодами с 0x80 по 0x98, выстроенные в 5 рядов по 5 символов, образуют графическое изображение логотипа Microchip.)

Курсор

Терминал способен отображать мигающий курсор в текущей позиции. Это может оказаться полезным для более удобной организации ввода данных пользователем. Скорость мигания курсора определяется периодом таймера TMR0 (курсор мигает с частотой 2.5 Гц). Вывод курсора в программе организован следующим образом: при прорисовке той строки символов, которая содержит курсор, 2.5 раза в секунду символ, в позиции которого находится курсор, подменяется символом с кодом 0x00 (он представляет собой закрашенный символ), а после прорисовки строки - восстанавливается.

Размер курсора может изменяться пользователем. Чем больше размер курсора, тем в большем количестве строк растра, соответствующих строке, содержащей курсор, будет произведена такая замена.

Демонстрация

Если при подаче питания удерживать вход RA0 в "0", то программа войдет в режим демонстрации, в котором будет отображаться тестовая картинка (см. выше). Как только на RA0 появляется "1", экран очищается, и программа начинает работать в обычном режиме.

Управление терминалом

Интерфейс SPI

Наш терминал управляется внешним устройством по SPI по четырем линиям: notSS, SDI, SDO, SCK. При передачи на терминал (или при приеме от терминала) каждого байта линия notSS должна быть установлена в 0, а по завершении передачи (приема) байта - возвращена в единичное состояние. Данные (входные и выходные) синхронизируются по отрицательному фронту линии SCK. Ввиду того, что большую часть времени программа занята прорисовкой изображения, и из 31.5 мкс остается только около 2.5 мкс, т.е. 7.5% времени. Это накладывает некоторые ограничения на скорость обмена данными с терминалом. Программу удалось организовать так, что за 2.5 мкс будет успевать полностью обработаться один байт данных, т.е. за время прорисовки одной строки растра терминал может принять и полностью обработать один байт. Таким образом, частота следования байтов не может превышать частоты прорисовки строк растра, т.е. 1/31.5 мкс ~ 31.5 Кбайт/сек.

Т.е. частота SPI не должна превышать 250 кбит/сек.

Вывод данных на экран

Для вывода символа на экран в текущей позиции курсора нужно просто передать терминалу по SPI его код от 0x10 до 0xFF. Байты с 0x00 до 0x0F зарезервированы для команд (см. команды управления). При этом:

  1. в текущей позиции курсора будет выведен полученный символ;
  2. этому символу будет установлен текущий цвет;
  3. текущая позиция увеличивается на 1. Если она была в конце строки, то производится перевод на начало следующей. Если позиция находилась в конце экрана (нижний правый угол), то новая позиция курсора будет установлена в начало экрана (верхний левый угол).

Команды управления

Байты 0x00-0x0F зарезервированы для передачи терминалу управляющих команд (установка позиции курсора, текущего цвета, размера курсора и пр.)

Код Команда Параметры Описание
00 SPI_CMD_NOP - Команда ничего не делает
01 SPI_CMD_SET_X X Установить позицию курсора по горизонтали (0..X_SIZE-1)(пр.1)
02 SPI_CMD_SET_Y Y Установить позицию курсора по вертикали (0..Y_SIZE-1)(пр.1)
03 SPI_CMD_SET_CURSOR S Выбор размера курсора (0..15)
04 SPI_CMD_SET_COLOR C Установка текущего цвета (0..15)
05 SPI_CMD_SET_SYMBOL_COLOR C Установка цвета символа, находящегося в текущей позиции курсора (0..15). Текущий цвет при этом не меняется.
06 SPI_CMD_CLRSCR - Очистить экран, установить текущий цвет = 15, установить курсор в позицию 0,0. Выполнение данной команды требует длительного времени (около 2.5 мс). В течение этого времени терминал не будет обрабатывать никаких других команд, кроме команды SPI_CMD_CHECK. Есть два пути определить, что выполнение команды завершено: выдержка паузы 2.5 мс или запрос состояния выполнения этой команды командой SPI_CMD_CHECK до тех пор, пока не будет получен ответ 0x55
07 SPI_CMD_CHECK state (пр.2) Команда проверки состояния выполнения команды очистки экрана. Возвращает 0x55, если очистка экрана завершена (или не производится в данный момент)
08 SPI_CMD_GET_SYMBOL char (пр.2) Прочитать символ из видеопамяти в текущей позиции курсора
09 SPI_CMD_GET_X X (пр.2) Прочитать текущую позицию курсора по горизонтали
0A SPI_CMD_GET_Y Y (пр.2) Прочитать текущую позицию курсора по вертикали
0B SPI_CMD_GET_CURSOR S (пр.2) Прочитать текущий размер курсора
0C SPI_CMD_GET_COLOR C (пр.2) Прочитать текущий цвет
0D SPI_CMD_GET_SYMBOL_COLOR C (пр.2) Прочитать значение цвета символа в текущей позиции курсора
0E SPI_CMD_GET_X_SIZE X_SIZE (пр.2) Получить размер экрана по горизонтали (пр.3)
0F SPI_CMD_GET_Y_SIZE Y_SIZE (пр.2) Получить размер экрана по вертикали (пр.3)

Примечания:

  1. Значения констант X_SIZE и Y_SIZE можно получить командами 0x0E и 0x0F, соответственно;
  2. Чтение любого параметра из терминала производится в три этапа:
    1. на терминал отправляется команда (см. таблицу);
    2. выдерживается пауза не менее 32 мкс;
    3. производится чтение байта.
  3. Эти команды добавлены для того, чтобы осталась возможность расширения возможностей программы терминала. Например, при переносе программы на более мощный контроллер (PIC24, PIC32, ATMEGA и т.д.) появится возможность увеличить разрешение и, следовательно, количество символов в строке. Или кто-нибудь решит переделать программу так, чтобы размер символа был не 8x16, а 8x8, увеличив тем самым количество текстовых строк.

Исходные тексты

Исходные тексты программы терминала можно взять здесь: terminal.rar

Библиотека

Для удобства разработки собственных приложений, управляющих терминалом, создан файл библиотеки. Он расположен в папке APPNOTE. Файл "vga_terminal.c" добавляется в проект, а файл "vga_terminal.h" включается во все файлы, из которых предполагается делать вызов функций терминала, директивой #include.

Ниже приведен список Си-фукций модуля vga_terminal.

Функция Описание
Вывод данных
void vga_outchar (char c); Вывести символ в текущей позиции курсора
void vga_outcharxy (char x, char y, char c); Вывести символ в указанной позиции курсора.
void vga_outtext (const char *t); Вывести текст (строка, заканчивающаяся нулевым символом, расположенная в ROM) в текущей позиции курсора
void vga_outtextxy (char x, char y, const char *t); Вывести текст (строка, заканчивающаяся нулевым символом, расположенная в ROM) в указанной позиции.
void vga_outstr (char *s); Вывести текст (строка, заканчивающаяся нулевым символом, расположенная в RAM) в текущей позиции курсора
void vga_outstrxy (char x, char y, char *s); Вывести текст (строка, заканчивающаяся нулевым символом, расположенная в RAM) в указанной позиции
void vga_window (char x1, char y1, char x2, char y2, char color); Нарисовать пустое прямоугольное окно в указанных координатах с рамкой указанного цвета.
char vga_getchar (void); Прочитать символ в текущей позиции курсора
Очистка экрана
void vga_clrscr (void); Начать очистку экрана. После этой функции нужно либо выдержать паузу 2.5 мс, либо проверять окончание ее выполнения функцией vga_check() (пока она не вернет "1")
char vga_check (void); Возвращает "1", если терминал готов принимать данные. Иначе возвращает "0". Применяется в основном для проверки окончания выполнения очистки экрана.
Управление цветом
void vga_setcolor (char color); Установить текущий цвет.
char vga_getcolor (void); Прочитать текущий цвет.
char vga_getcharcolor (void); Прочитать цвет символа расположенного в текущей позиции курсора
Управление курсором
void vga_gotoxy (char x, char y); Переместить курсор в указанное место
void vga_setcursorsize (char size); Установить размер курсора (0 - спрятать)
char vga_getx (void); Получить текущую X-позицию курсора
char vga_gety (void); Получить текущую Y-позицию курсора
char vga_getwidth (void); Получить ширину экрана (в символах)
char vga_getheight (void); Получить высоту экрана (в символах)

Тест-приложение

Маленькая программа для PIC16F88, демонстрирующая использование библиотеки vga_terminal для управления терминалом: terminal_test.rar Для связи используется аппаратный SPI.

Программа выводит на экран следующую картинку:

Поделки читателей

Терминал с разрешением 51x30 символов - читатель Ковалев Игорь переделал терминал для работы на 16МГц (PIC18F46K20). За счет увеличения скорости он увеличил горизонтальное разрешение; символы визуально сузились, что сделало изображение более симпатичным

катринка

Ссылки

VGA-Терминал на PIC24 - 60х25 символов, 8 цветов, 2 размера шрифта, PS/2 клавиатура, USART. Схема, исходники, отличное описание (англ.)

http://www.micro-examples.com/public/microex-navig/doc/089-pic-pal-tv.html - PIC PAL Video Library - проект для PIC18F4620, написанный на mikorC

http://www.mikrocontroller.net/topic/53140 - текстовый терминал на ATmega8, 40x25, управление UART 19200 (вывод PAL)

http://neil.franklin.ch/Projects/SoftVGA/ - Еще один проект для atmega32.

http://mondo-technology.com/dcvideo.html - текстовый терминал на PIC16F819 с видео выходом. Разрешение 20x10 символов, управление: UART 9600

 
osa/articles/vga_terminal.txt · Последние изменения: 18.06.2011 21:02 От osa_chief
 
Creative Commons License Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki