Простой текстовый терминал.

Текстовый дисплей на ПЛИС

На выходных завершил проект простого текстового дисплея. Идея проекта действительно простая. Внутри ПЛИС Cyclone III на плате Марсоход2 реализуем текстовый видеоадаптер. Приходящие из последовательного порта символы печатаем в экран. И это почти все.

Проект платы Марсоход2 для среды Altera Quartus II можно взять здесь: 

Топ модуль проекта выполнен в графическом виде и вот так он выглядит (можно кликнуть, чтобы увеличить):

Пот модуль проекта Altera Quartus II в графическом виде

Модуль serial принимает байты из последовательного порта на скорости 230400 бит в секунду и записывает их в ФИФО serial_fifo. Модуль sterm читает принятые символы из ФИФО и записывает их в текстовый видеоадаптер txtd. Модуль txtd выдает видеосигнал на выходы ПЛИС, на разъем VGA платы Марсоход2.

ФИФО в этом проекту нужно для двух целей. Во-первых, прием данных тактируется от одной частоты 100Мгц, а обработка данных ведется от другой тактирующей частоты 106,5Мгц – это стандартная частота для видеорежима 1440x900 60Гц. Модули ФИФО очень удобны для разделения клоковых доменов, частей схемы, работающих на разных частотах. Во-вторых, ФИФО нужно, чтобы сгладить неравномерность обработки данных. В моем случае неравномерность чтения данных обусловлена тем, что некоторые символы имеют специальное значение. Еще точнее будет сказать, что один символ – особенный. Это символ «перевода каретки», 0x0D. Все приходящие символы я записываю в самую нижнюю строку экрана. И только символ 0x0D должен произвести прокрутку экрана. Нужно переписать все все строки экрана: первую строку в нулевую, вторую в первую, третью во вторую и так далее. Это довольно долго, в смысле не так быстро, как если просто записать новый символ в экран. Поэтому на время прокрутки экрана чтение из ФИФО приостанавливается. Вот ФИФО позволяет не приостанавливать источник данных.

Модуль txtd – это и есть текстовый видеоадаптер, написан на языке Verilog. Пожалуй это самый сложный модуль в проекте. Он генерирует синхросигналы для монитора. Содержит в себе модуль экранной памяти screen2 и модуль памяти шрифта rom_font. Читает символы из экранной памяти, далее, с помощью встроенного VGA шрифта транслирует символы в точки на экране формирая растровое изображение.

В топ модуле можно еще заметить странный модуль screen2. Тут должен признаться я немного поленился. В модуле текстового дисплея уже есть экранная память. Она выполнена в виде такого же модуля screen2 и представляет из себя двухпортовое ОЗУ. Два порта нужны, чтобы легко и независимо можно было бы писать в экранную память и читать из нее. Причем пишут и читают независимые друг от друга модули: sterm и txtd соответственно. Проблема в том, что sterm то же иногда хочет читать из экранной памяти, когда выполняет прокрутку. А еще одного независимого порта на чтение у ОЗУ нет. Именно поэтому я запараллелил два модуля двухпортового озу. В оба модуля пишется одновременно одно и то же, содержимое обоих блоков памяти одинаково. Теперь сам sterm может читать из своей копии экрана, а txtd читает из своей копии экрана. Получилось типа трехпортовое ОЗУ. На самом деле, конечно, решение несколько странное и не экономное. А с другой стороны зачем мне память экономить в этом проекте если ее и так много? А ведь когда-то о таких решениях можно было только мечтать. Для доступа к памяти использовались свободные «окошки», когда дисплейный адаптер не читает из экрана. Специальные сигналы типа WAIT на шине ISA приостанавливали компьютер, если он обращался к экранной памяти не вовремя... Сейчас все можно сделать гораздо проще.

Рассмотрим как происходит запись символов в экран. Я встроил модуль SignalTap в проект, чтобы посмотреть как ведут себя разные сигналы модуля sterm. Я запустил на компьютере программу терминала Teraterm, открыл последовательный порт принадлежащий плате Марсоход2 и все, что набираю в терминале появляется на экране монитора, подключенного к плате.

Altera SignalTap

Здесь видно, что изначально, когда в ФИФО нет принятых байт и сигнал rdempty в единице, модуль sterm находится в состоянии STATE_WAIT_CHAR. Когда появляется готовый символ в ФИФО, то символ забирается сигналом ACK, машина состояний переходит в STATE_WRITE_CHAR. Однако запись не происходит – нет сигнала wr. Машина состояний определила, что символ «непечатный», 0x0D, и перешла в состояние STATE_SCROLL. Адрес для чтения radr последовательно растет 0x0080, 0x0081, 0x0082. Адрес для записи так же последовательно растет 0x0000, 0x0001, 0x0002 и так далее. Сигнал записи wren видно – производится прокрутка содержимого экрана.

Теперь думаю нужно немного рассказать, что такое текстовый экран.

Текстовый экран – это кажется что-то от первобытных компьютеров. В былые «досовские» времена на экране компьютера отображалось 80x25 символов. Один символ занимал на экране 16 строк, ширина символа была 8 точек.

Сейчас мониторы обычно имеют пропорцию 16 к 9. Например, я реализовал стандартный видеорежим 1440x900. Если пытаться отобразить текстовый режим в этом формате, то по вертикали получим 900 / 16 = 56 целых символьных строк. По горизонтали получается 1440 / 8 = 180 символов в строке.. Это как-то много. Я использовал символы шириной 16 точек (растягиваю каждый символ вширь в два раза). Тогда по горизонтали в строке умещается 90 символов. Ну вот, мой текстовый видеорежим будет отображать  90 символов в строке и всего будет 56 строк. Для хранения такого экрана нужно всего 90*56=5040 байт (если символ – это байт). Так как 5040 – это  не очень круглое число, то выбираем ближайшее круглое – 8Кбайт. Обычно к каждому символу в текстовом экране еще добавляется байт - его цвет-атрибут. Значит цветной текстовый экран поместится в 16Кбайт. Такой экран можно реализовать прямо внутри ПЛИС во встроенной статической памяти.

Но вот еще... Нужен знакогенератор, проще говоря, шрифт.  Я использую стандартный кирилический VGA шрифт.

vgafont

Шрифт для текстового дисплея - это битовый образ всех символов. Если символов 256, то для их хранения в памяти в формате 8x16 нужно 256*16=4096 байт. Эта память так же легко поместится прямо в ПЛИС Cyclone III.

Параметры стандартного видеорежима беру из спецификации VESA. Для реализации нужного видеорежима нужно обратить внимание на частоту PixelClock, длину строки, положение и длину строчного синхроимпульса, количество строк и положение кадрового синхроимпульса. Параметров много и нужно точно следовать им, иначе монитор не сможет правильно засинхронизироваться от нашего видеосигнала.

Вот самые главные параметры режима 1440x900, 60Гц:

Pixel Clock = 106.500;

Hor Total Time = 14.157; // (usec) = 242 chars = 1936 Pixels
Hor Addr Time = 10.530; // (usec) = 180 chars = 1440 Pixels
H Front Porch = 0.702; // (usec) = 12 chars= 96 Pixels
Hor Sync Time = 1.112; // (usec) = 19 chars= 152 Pixels
/H Back Porch = 1.814; // (usec) = 31 chars= 248Pixels

Ver Total Time = 13.336; // (msec) = 942 lines
Ver Addr Time = 12.741; // (msec) = 900 lines
V Front Porch = 0.042; // (msec) = 3 lines
Ver Sync Time = 0.085; // (msec) = 6 lines
V Back Porch = 0.467; // (msec) = 33 lines

Алгоритм работы текстового видеоадаптера довольно прост.
Во время развертки строки экрана нужно по очереди считывать символы из экранной памяти. Каждый символ занимает на экране 16 линий в высоту. Значит каждая символьная строка будет считываться 16 раз за один кадр.

Считанный из экранной памяти символ – это индекс в знакогенераторе, в памяти шрифта. Умножаем индекс на высоту одного знака, на 16. Получаем адрес байта одной строки знака. Байт содержит 8 бит – это битовая маска для точек на экране.

Посмотрим, как формируется изображение на экране с помощью инструмента SignalTap.

video signal in Altera SignalTap

На этих диаграммах видно, что сейчас на экране отображается линия line_count с номером 18Ah. Это значит, что отображается текстовая строка номер 18h, и сейчас отображается 10-я (0Ah) линия знака. Из этих чисел сперва формируется адрес для чтения из экранной памяти screen2.rdaddress = 18h*80h=C00h (в строке 128 символов из которых видны только 90). По адресу экранной памяти C00h считывается символ 31h. Это число используется для формирования адреса чтения из памяти шрифта rom_font.address = 31Ah. Из памяти шрифта считывается байт, формирующий строку символа scr_char_line. Из этого байта уже формируется видеосигнал, например, rr – красный цвет.

Думаю и так уже утомил техническими подробностями.
Поэтому дальше просто покажу короткое видео. Здесь паказано, как из программы терминала Teraterm передаю в плату Марсоход2 текстовый файл, как он быстро прокручивается на экране, как набираю текст в консоли терминала и он появляется на экране монитора, подключенного к плате Марсоход.

В принципе, дальше я надеюсь развить эту идею. Можно попытаться прямо в плате Марсоход2 реализовать протокол стандартного терминала типа VT100. Тогда получится вполне полезное устройство – вторая консоль для линукс компьютера.


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