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

(исходники на GutHub).

 

    Позже видеоконтроллер был перенесен на 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.