FPGA: Текстовый видеоконтроллер.
Черно-белый и цветной RGB.
В одном из проектов (FPGA) для отладки требовалось выводить информацию о работе устройства.
На первом этапе вся информация выводилась в консоль (COM порт). Вывод в консоль прост в
реализации, но данное решение имеет недостаток. Необходимо просматривать длинные логи работы
системы, что не удобно. Часть событий происходит часто, другая часть реже. Поиск нужного события
занимает время. В результате лог оказывается перегружен однотипными сообщениями.
С целью более удобного просмотра параметров пришла идея реализовать текстовый видео-
контроллер и выводить параметры на экран монитора. При этом вывод графики не требовался,
только символьная информация.
Так как проект в FPGA уже был сформирован, к видео контроллеру предъявлялись следующие
требования:
- минимальное количество логических элементов,
- минимальное количество используемой памяти.
Решено было остановиться на разрешении VGA 640x480 точек, только текст, цвет черно-белый
(две градации).
В контроллере реализован только текстовый режим, одно знакоместо - один байт.
Для шрифта 8x16 точек и без меж символьного зазора, то получается:
640 / 8 = 80 символа в строке.
480 / 16 = 30 строк.
80 x 30 = 2400 байт (размер видео памяти).
В FPGA память выделяется фиксированными блоками кратными степени два (как правило).
Следовательно размер видео памяти будет не 2400 байт, а 4096 байт, что не рационально.
Т.е. часть памяти не будет использоваться.
Поэтому вводится ограничение в 25 строк текста за место 30.
Итого размер видеопамяти 80 x 25 = 2000 байт, если округлить то получается 2048 байт.
Таблица шрифтов занимает: символ 1 (байт - 8 бит) x 16(строк) x 256(символов) = 4096 байт
Если взять во внимание шрифт 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
в качестве примера.
Дополнительно для удобства просмотра всех символов в одном окне из бинарного файла,
была создана утилита view_font.py .
На что следует обратить внимание при выборе шрифта:
1 - Кодировка(кодовая страница).
Кодовые страницы DOS(866, 437) и WINDOWS(1251) отличаются расположением русского алфавита,
а также набором дополнительных символов.
2 - Размер шрифта.
На просторах интернета можно встретить подборки шрифтов разного типа.
Например для размерности 8x16 можно встретить заглавные символы высотой в 10 строк, а также и в 12.
Шрифт с наклоном и т.п.
3 - Меж символьные зазоры.
В DOS видеоадаптер между символами вставляет пустую колонку, для того чтобы символы не сливались между собой.
В результате получается следующая длинна строки:
80 символов в строке 8 точек на символ (шрифт) + 1 точка зазор получаем: 80 * 9 = 720 точек в строке.
25 строк * 16 = 400 (точек)строк. Итого 720x400 точек.
Т.к. данный видеоадаптер реализует режим VGA: 640x480, и меж символьный зазор не предусматриваются в режиме 80x25,
необходимо выбрать шрифт с зазором, т.е. одна из колонок не используется (первая или восьмая).
Оценить зазоры в шрифте можно при помощи утилиты view_font.py.
Данная утилита выводит шрифты без дополнительного зазора, т.е. показывает символы так как они будут отображаться на экране.
ВНИМАНИЕ: Реализовать режим DOS 720x400 можно, но потребуется генератор на частоту 28.322 MHz.
Получение данной частоты из 50 или 25 МГц затруднено, поэтому режим 720x400 не рассматривается.
Для работы контроллера шрифты предварительно должны быть загружены в ROM
(память шрифтов). В память нужно загрузить готовый дамп предварительно подготовленных
шрифтов в виде файла.
Видео контроллер применялся для FPGA фирмы Intel(Altera), поэтому можно загрузить файлы
в двух форматах HEX и MIF. HEX файл - неудобно редактировать, поэтому загрузка шрифтов
осуществляется из MIF файла. Формат MIF гораздо проще.
Для конвертации бинарного файла шрифтов в MIF файл, была создана утилита конвертера bin2mif
Видеоконтроллер для FPGA фирмы Xilinx.
В Xilinx для загрузки памяти применяются файлы в формате COE (Xilinx ISE - MIF файлы не знает).
В результате в утилиту bin2mif было добавлена генерация файлов в формате COE.
Видеоконтроллер для FPGA фирмы GoWin.
В GoWin для загрузки памяти применяются файлы в формате MI.
В результате в утилиту bin2mif было добавлена генерация файлов в формате MI.
Тест ROM знакогенератора под выбранный шрифт.
Вывод на экран.
Если перенаправить вывод 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 было много в наличии, не пропадать же добру ! :)
2023 год, VGA Контроллер для GoWin GW1NZ-LV1FN32C6/I5.
Занимаемые ресурсы (без SPI):
---------------------------------------------------------- |
Плата:
Модуль: видеоконтроллера на FPGA GoWin GW1NZ-LV1FN32C6/I5.
Данный контроллер был применен в качестве внешнего видео адаптера к устройству калибратора
сигналов.
Калибратор построен на основе микроконтроллера STM32 + измерительная головка + интерфейс
для подключения к тестируемому устройству.
Свое состояние калибратор выводит в консоль. Для отображения состояния работы калибратора
требовался ПК с терминальной программой. После добавления видео адаптера к калибратору,
необходимость в ПК отпала.
Результат работы калибратора сигналов.
Итог.
В интернете полно проектов видеоадаптеров на микроконтроллерах, которые позволяют выводить
не только цветное изображение, но и графику. При этом ресурсы микроконтроллера как правило
задействованы по полной (т.е. 90% ресурсов работают на VGA...). Тем не менее, это вполне
работоспособное решение которое можно применять в своих проектах, не затрагивая тему FPGA.
Стоимость FPGA может оказаться выше чем стоимость микроконтроллера. К тому же основы
программирование FPGA знакомо не каждому, что является еще одним плюсом в сторону
микроконтроллерных решений.
Тем не менее данная реализация может применяться в качестве готового модуля в проекте FPGA,
где микроконтроллеру нет места (по разным причинам).
Также данное решение может быть интересно новичкам в области FPGA, которые могут посмотреть
вариант реализации видеоконтроллера на языка verilog. Или тем у кого есть свободная микросхема
FPGA, и хочется сделать нечто подобное, или еще по какой либо причине.
Даешь многообразия свободных и бесплатных исходников видео контроллеров :) !
Исходники VGA TEXT на GitHub.
Цветной текстовый контроллер RGB.
Июнь 2023
По результатам работы на практике были внесены изменения в VGA контроллер.
1. Преобразованы исходники: один модуль - один файл (не все среды разработки понимают несколько verilog модулей в одном файле).
2. Поддерживаются два режима разрешений 80x25 и 64x30, в результате память видео буфера уменьшена до 2048 байт (за место 4096).
3. Добавлен еще один видео буфер под атрибуты цвета и мигания размером 2048 байт. Задается цвет знакоместа и символа, а также режим мигания символа. Один символ - один атрибут. Глубина цвета 3-бита, палитра 8-цветов на символ и 8-цветов на знакоместо.
Описание битовых полей атрибутов:
7 6 5 4 3 2 1 0 M B G R - B G R | | | | | | | | | | | | | | | +-- CHAR COLOR RED | | | | | | +---- CHAR COLOR GREEN | | | | | +------ CHAR COLOR BLUE | | | | +-------- clear (not used) | | | +---------- background RED | | +------------ background GREEN | +-------------- background BLUE +---------------- CHAR BLINK |
4. Две отдельный шины доступа к памяти видео буфера и атрибутов (для сохранения совместимости с черно-белой версией).
Буфер - запись символа:
input wire [10:0] i_vram_addr_wr, // bus adr video ram input wire [7:0] i_vram_data_wr, // bus data video ram input wire i_vram_wr_h, // strobe write data to mem |
Буфер - атрибутов символа:
input wire [10:0] i_cram_addr_wr, // bus adr color video ram input wire [7:0] i_cram_data_wr, // bus data color video ram input wire i_cram_wr_h, // strobe write color data to mem |
Принцип работы следующий:
Производится запись символа по заданному адресу (буфер символа) и запись атрибута по тому же адресу (буфер атрибута).
5. В проект добавлен отдельный файл конфигурации vga_config.vh, задающий параметры VGA контроллера.
В файле vga_config.vh дефайнами задаются следующие параметры:
Разрешение:
`define VGA80x25
`define VGA64x30
Включение цвета:
`define VGA_COLOR_ENABLE
Прямой доступ к шинам видео буферов (без посредников типа SPI контроллера):
`define VGA_DMA_PORT
6. В планах добавить поддержку обработки цвета в SPI контроллер (пока предварительно на уровне esc кодов чтобы не ломать совместимость в черно-белой версией).
Тест проводился на плате с FPGA Lattice MachXO2 исходники проекта на GitHub VGA Lattice.
Тест 80х25:
Тест 64х30:
STM32 - SPI Test.
Также был создан тест для проверки интерфейса SPI.
Исходники теста на GitHub.
Дополнения и исправления.
May 9, 2022
Разрешение 80x25.
Устранена ошибка синхронизации VS+HS.
Dec 11, 2022
Определены дефайны для разрешений: VGA80x25, VGA64x30, VGA64x30.
May 30, 2023
Разрешение 80x25.
Устранена ошибка приводящая к копированию 6 бита в 7, что приводило к смазыванию шрифта.
Устранена ошибка разрешения: за место 80x25 выводилось 79x25.
Июнь 2023
Добавлен цвет RGB, тестирование на плате с FPGA Lattice MachXO2.
Пока контроллер работает с режиме прямого доступа к памяти, т.е. без SPI контроллера.