FPGA: Текстовый видеоконтроллер.
В одном из проектов (FPGA) для отладки требовалось выводить информацию о работе устройства.
На первом этапе вся информация выводилась в консоль (COM порт). Вывод в консоль прост в
реализации, но данное решение имеет недостаток. Необходимо просматривать длинные логи работы
системы, что не удобно. Часть событий происходит часто, другая часть реже. Поиск нужного события
занимает время. В результате лог оказывается перегружен однотипными сообщениями.
С целью более удобного просмотра параметров пришла идея реализовать текстовый видео-
контроллер и выводить параметры на экран монитора. При этом вывод графики не требовался,
только символьная информация.
Так как проект в FPGA уже был сформирован, к видео контроллеру предъявлялись следующие
требования:
- минимальное количество логических элементов,
- минимальное количество используемой памяти.
Решено было остановиться на разрешении VGA 640x480 точек, только текст, цвет черно-белый
(две градации).
В контроллере реализован только текстовый режим, одно знакоместо - один байт.
Если взять во внимание шрифт 8x16 точек и плюс 2 точки зазор между символами, то получается:
640 / (8+2) = 64 символа в строке.
480 / 16 = 30 строк.
64 x 30 = 1920 байт, если округлить то получается 2048 байт. В FPGA память выделяется
фиксированными блоками кратными степени два (как правило).
Таблица шрифтов занимает: 1 (байт - 8 бит) x 16(строк) x 256(символов) = 4096 байт
Если применить шрифт 8x8 точек, то получается:
640 / (8+2) = 64 символа в строке.
480 / 8 = 60 строк.
64 x 60 = 3840 байт, если округлить 4096 байт.
Таблица шрифтов занимает: 1 (байт - 8 бит) x 8(строк) x 256(символов) = 2048 байт
В контроллере применяется два вида памяти:
- Экранная память (RAM - двух портовая чтение и запись),
- Память шрифтов (ROM - только чтение).
Размер памяти ROM необходимый для размещения шрифтов:
256 (символов - таблица ASCII) * 16 (строк в каждом символе) = 4096 байт.
Шрифт.
Шрифты были взяты готовые для кодовой страницы cp1251 (windows), для удобства вывода
надписей на родном языке прямо из исходников без дополнительных преобразований.
Шрифты по умолчанию идут в виде двоичного файла. Для просмотра шрифтов была написана
утилита font_view_bin на языке Си для работы в консоли ОС Windows (исходники на GitHub).
Утилита font_view_bin выводит в консоль символы, распечатывая построчно битовые образы
каждого символа(лог работы font_view_bin). Там же на GitHub находятся шрифт 8x16 system.fnt
в качестве примера.
Для работы контроллера шрифты предварительно должны быть загружены в ROM
(память шрифтов). В память нужно загрузить готовый дамп предварительно подготовленных
шрифтов в виде файла.
Видео контроллер применялся для FPGA фирмы Intel(Altera), поэтому можно загрузить файлы
в двух форматах HEX и MIF. HEX файл - неудобно редактировать, поэтому загрузка шрифтов
осуществляется из MIF файла. Формат MIF гораздо проще.
Для конвертации бинарного файла шрифтов в MIF файл, была создана утилита конвертера bin2mif
Позже видеоконтроллер был перенесен на FPGA фирмы Xilinx.
В Xilinx для загрузки памяти применяются файлы в формате COE (Xilinx ISE - MIF файлы не знает).
В результате в утилиту bin2mif было добавлена генерация файлов в формате COE.
Вывод на экран.
Если перенаправить вывод printf или fprintf в интерфейс видеоконтроллера, то это упростит вывод
текста стандартными функциями прямо на монитор. Остается создать функцию установки курсора в
заданную позицию и вывод на монитор под полным контролем.
Примеры функций для работы с VGA контроллером:
//------------------------------------------------------------------------------
// SPI CS = 0
//------------------------------------------------------------------------------
void set_cs_l(void)
{
SPI_CS_PORT->BRR = SPI_CS_PIN;
}
//------------------------------------------------------------------------------
// SPI CS = 1
//------------------------------------------------------------------------------
void set_cs_h(void)
{
SPI_CS_PORT->BSRR = SPI_CS_PIN;
}
//------------------------------------------------------------------------------
// SPI RX-TX, 1-byte
//------------------------------------------------------------------------------
uint8_t spi_byte(uint8_t outdat)
{
uint8_t b;
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE)==RESET);
SPI_I2S_SendData(SPI2, outdat);
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE)==RESET);
b = SPI_I2S_ReceiveData(SPI2);
return b;
}
//------------------------------------------------------------------------------
// read vga adres cursor
//------------------------------------------------------------------------------
uint16_t vga_get_cur_adr( void )
{
uint8_t l = 0;
uint8_t h = 0;
set_cs_l();
spi_byte(VGA_CMD_R_CUR_AH);
spi_byte(0);
h = spi_byte(0);
set_cs_h();
set_cs_h();
set_cs_h();
set_cs_h();
set_cs_l();
spi_byte(VGA_CMD_R_CUR_AL);
spi_byte(0);
l = spi_byte(0);
set_cs_h();
return (h << 8) | l;
}
//------------------------------------------------------------------------------
// read vga status - return 0xa1 is OK
//------------------------------------------------------------------------------
uint8_t vga_get_status( void )
{
uint16_t t;
set_cs_l();
spi_byte(VGA_CMD_R_STATUS);
spi_byte(0);
t = spi_byte(0);
set_cs_h();
return t & 0xff;
}
//------------------------------------------------------------------------------
// Write char to pos Cursor
//------------------------------------------------------------------------------
void vga_print_char( char c )
{
uint16_t ca = 0; // cursor adr
if (c == '\r'){
ca = vga_get_cur_adr();
ca &= (~(VGA_RESOLUTION_X-1));
vga_set_cur_pos(ca);
return;
}
if (c == '\n'){
ca = vga_get_cur_adr();
ca = ca & (~(VGA_RESOLUTION_X-1));
ca = ca + VGA_RESOLUTION_X;
vga_set_cur_pos(ca);
return;
}
set_cs_l();
spi_byte(VGA_CMD_W_DATA);
spi_byte(c);
set_cs_h();
}
//------------------------------------------------------------------------------
// Write char to pos Cursor
//------------------------------------------------------------------------------
void vga_p( unsigned char c )
{
vga_print_char( c );
}
//------------------------------------------------------------------------------
// Write Cursor position
//------------------------------------------------------------------------------
void vga_set_cur_pos( uint16_t xy )
{
set_cs_l();
spi_byte(VGA_CMD_W_CUR_ADR);
spi_byte((xy >> 8) & 0xff);
spi_byte((xy >> 0) & 0xff);
set_cs_h();
}
//------------------------------------------------------------------------------
// Write Cursor position
//------------------------------------------------------------------------------
void vga_set_xy( uint8_t x, uint8_t y )
{
uint16_t adr = 0;
if (x > VGA_RESOLUTION_X - 1) x = VGA_RESOLUTION_X - 1;
if (y > VGA_RESOLUTION_Y - 1) y = VGA_RESOLUTION_Y - 1;
adr = y * VGA_RESOLUTION_X + x;
vga_set_cur_pos( adr );
}
//------------------------------------------------------------------------------
// Clear frame
//------------------------------------------------------------------------------
void vga_clr(void)
{
int i;
vga_set_xy( 0,0 );
for (i=0; i<VGA_RESOLUTION_X*VGA_RESOLUTION_Y; i++){
vga_print_char(' ');
}
vga_set_xy( 0,0 );
}
Пример вывода значения на экран при помощи fprintf:
vga_set_xy(cx, cy);
fprintf(vga_p, "| %4d | %3d | %3d |\n", adc_value_max, i, qmaxn);
Проект verilog.
Проект контроллера работает на частоте выдачи видео данных.
Частота выдачи видео данных составляет 25,175 МГц, и может быть установлена в 25 МГц.
Частоту 25 МГц в проекте получить гораздо проще чем 25,175 МГц.
При 25 МГц все применяемые мониторы и телевизоры работали без искажений.
Интерфейс контроллера (vga_640x480_text.v):
i_cmd | Входной порт команд. |
i_cur_adr | Входной порт адреса, обращение к экранной памяти. |
i_port | Входной порт данных, данные на запись в экранную память или установка внутр. регистров. |
o_port | ВЫходной порт данных, чтение статусных регистров. |
i_cs_h | Входной порт выбора контроллера. |
i_rl_wh | Входной порт Чтение=0/Запись=1. |
o_ready_h | ВЫходной порт готовности контроллера к обработке новой команды. |
Команды:
REG_STATUS = 8'h00 | Чтение статуса контроллера, должен возвращать 0xa1. |
REG_DATA = 8'h01 | Данные, только запись для экранной памяти. |
REG_CUR_AL = 8'h02 | Позиция курсора, младший адрес. Чтение и запись. |
REG_CUR_AH = 8'h03 | Позиция курсора, старший адрес. Чтение и запись. |
REG_CONTROL = 8'h04 | Регистр включения-выключения курсора, 0-бит. Чтение и запись. |
Данный VGA контроллер был успешно применен в ряде FPGA проектов в качестве готового модуля.
Добавление SPI интерфейса.
Позже к VGA контроллеру был добавлен SPI интерфейс, что позволило подключать контроллер к
любому микроконтроллеру в качестве внешнего видеоадаптера.
Был собран макет на FPGA Intel(ALtera) Cyclone-2 и проведена отладка.
Исходники VGA TEXT видеоконтроллера на GitHub.
В результате контроллер занял следующие ресурсы ПЛИС.
+----------------------------------------------------------------------------------+ ; Fitter Summary ; +------------------------------------+---------------------------------------------+ ; Fitter Status ; Successful - Tue Jul 30 13:37:29 2019 ; ; Quartus II 64-Bit Version ; 13.0.0 Build 156 04/24/2013 SJ Full Version ; ; Revision Name ; vga_640x480 ; ; Top-level Entity Name ; vga_640x480 ; ; Family ; Cyclone II ; ; Device ; EP2C5T144C8 ; ; Timing Models ; Final ; ; Total logic elements ; 494 / 4,608 ( 11 % ) ; ; Total combinational functions ; 377 / 4,608 ( 8 % ) ; ; Dedicated logic registers ; 285 / 4,608 ( 6 % ) ; ; Total registers ; 285 ; ; Total pins ; 8 / 89 ( 9 % ) ; ; Total virtual pins ; 0 ; ; Total memory bits ; 65,536 / 119,808 ( 55 % ) ; ; Embedded Multiplier 9-bit elements ; 0 / 26 ( 0 % ) ; ; Total PLLs ; 1 / 2 ( 50 % ) ; +------------------------------------+---------------------------------------------+ |
Итого: 494 LE.
Макет: VGA контроллер с SPI, FPGA Intel(Altera) Cyclone-2 EP2C5.
Обмен по интерфейсу SPI.
Контроллер принимает по интерфейсу SPI следующие команды
(формат: старший бит первый. тактовый сигнал: передний фронт. CS активен при = 0):
----------------------------------------------------------------------
CMD_R_STATUS = 0x00 чтение статуса контролера
0-байт 1-байт 2-байт
<CMD_R_STATUS> <не используемый байт> <Status-byte>
Status-byte: Всегда долже возвращать 0xA1 иначе контроллер не доступен.
---------------------------------------------------------------------
CMD_R_CONTROL = 0x04 Чтение состояния курсора (используется только 0-бит).
0-байт 1-байт 2-байт
<0x04> <не используемый байт> <Control-byte>
Control-byte[0]: =0 - курсор погашен, =1 - курсор включен.
------------------------------------------------------------------------
CMD_R_CUR_AL = 0x02; Чтение текущей позиции курсора, (младшей байт)
0-байт 1-байт
<0x02> <мл.байт позиции>
Control-byte[0]: =0 - курсор ВЫКЛючен, =1 - курсор ВКЛючен.
------------------------------------------------------------------------
CMD_R_CUR_AH = 0x03; Чтение текущей позиции курсора, (старший байт)
0-байт 1-байт
<0x03> <ст.байт позиции>
------------------------------------------------------------------------
CMD_W_CONTROL = 0x84; Запись состояния курсора (используется только 0-бит).
0-байт 1-байт
<0x84> <Control-byte>
Control-byte[0]: =0 - курсор погашен, =1 - курсор включен.
-----------------------------------------------------------------
CMD_W_DATA = 0x81; Записать символ в текущюю позицию курсора, после записи розиция курсора инкрементируется (+1)
0-байт 1-байт
<CMD_W_DATA> < символ >
----------------------------------------------------------------
CMD_W_CUR_ADR = 0x82 Установить новую позицию курсора.
0-байт 1-байт 2-байт
<0x82> <ст.байт адреса> <мл.байт адреса>
----------------------------------------------------------------
VGA Контроллер для Xilinx Spartan 3.
Понадобился VGA контроллер. Под рукой оказались ПЛИС Spartan 3 XC3S50 в корпусе pqfp-208,
купленные давным давно и лежащие без дела. Решено их применить, корпус великоват, но это не
критично. Далее была заказана и изготовлена печатная плата для контроллера.
Для переноса VGA контроллера на Xilinx достаточно заменить блоки памяти.
Описание модулей памяти находится в файле (vga_640x480_text.v):
module rom_font, module ram_video_buf.
Файлы шрифтов в формате COE, необходимо создать утилитой bin2mif.
Модуль: видеоконтроллера на FPGA Spartan-3, XC3S50.
XC3S50 в 240-ногом корпусе, пусть это не смущает, такое количество ног в проекте не нужно,
просто таких FPGA было много в наличии, не пропадать же добру ! :)
Данный контроллер был применен в качестве внешнего видео адаптера к устройству калибратора
сигналов.
Калибратор построен на основе микроконтроллера STM32 + измерительная головка + интерфейс
для подключения к тестируемому устройству.
Свое состояние калибратор выводит в консоль. Для отображения состояния работы калибратора
требовался ПК с терминальной программой. После добавления видео адаптера к калибратору,
необходимость в ПК отпала.
Результат работы калибратора сигналов.
Итог.
В интернете полно проектов видеоадаптеров на микроконтроллерах, которые позволяют выводить
не только цветное изображение, но и графику. При этом ресурсы микроконтроллера как правило
задействованы по полной (т.е. 90% ресурсов работают на VGA...). Тем не менее, это вполне
работоспособное решение которое можно применять в своих проектах, не затрагивая тему FPGA.
Стоимость FPGA может оказаться выше чем стоимость микроконтроллера. К тому же основы
программирование FPGA знакомо не каждому, что является еще одним плюсом в сторону
микроконтроллерных решений.
Тем не менее данная реализация может применяться в качестве готового модуля в проекте FPGA,
где микроконтроллеру нет места (по разным причинам).
Также данное решение может быть интересно новичкам в области FPGA, которые могут посмотреть
вариант реализации видеоконтроллера на языка verilog. Или тем у кого есть свободная микросхема
FPGA, и хочется сделать нечто подобное, или еще по какой либо причине.
Даешь многообразия свободных и бесплатных исходников видео контроллеров :) !
Исходники VGA TEXT на GitHub.