Графический VGA-контроллер на SoC без знаний HDL
Всем привет!
В одной из предыдущих статей мой коллега Des333 реализовал фреймбуфер для LCD, работающего на графическом контроллере ILI9341. Однако, его написание потребовало существенного опыта в разработке RTL-кода.
К тому же, не у каждого под рукой есть embedded LCD-дисплей, зато наверняка есть монитор с VGA-входом.Что же делать, если опыта разработки под FPGA мало, но есть SoC, а сделать что-то интересное хочется?
В этой статье мы расскажем, как разработать графический контроллер, имея на руках плату с SoC (Altera Cyclone V), дисплей с VGA и минимальные знания языков HDL (в нашем случае — Verilog).
Для примера будем использовать наши платки, но всё описанное заработает и на других.
Кому интересно, прошу под кат.
Я планирую рассказывать в следующем порядке:
- Сначала немного про архитектуру взаимодействия
- Кратко про подключение к VGA
- Как получить прошивку, используя только Quartus Qsys
- Как объяснить ядру, что есть графический контроллер. Расскажу, что нужно добавить в dtb и собрать драйвера
- Как получить терминал и иксы на дисплее
Я буду использовать отладочную плату CB-CV-SOM, работающую вместе с SoDIMM-модулем CV-SE-SOM:
К этой отладочной плате у нас есть шилд, на котором помимо VGA есть много интересного (см. metrotek.spb.ru/cbcvsom.html)
Для вывода изображения на дисплей нам нужны фреймбуффер, драйвер и модуль развёртки, который обеспечит связку между процессором и дисплеем, а также обеспечит непрерывное обновление кадров.
В SoC’е к ARM (также называется HPS — Hard Processing System) подключенна DDR3 память (1 GB в нашем случае), в ней и будет находится наш фреймбуффер. А в FPGA будет модуль, который нам нужно будет сделать с помощью Qsys.
Вот схема:
Работать все будет так:
- Драйвер настраивает модуль в FPGA: после этого модуль готов получать данные из SDRAM-контроллера и «разворачивать» их на дисплей
- В /dev/fb0 записываем картинку. Данные попадают во фреймбуфер в DDR
- Модуль в FPGA непрерывано читает фреймбуффер и обновляет экран
VGA (Video Graphics Array) — это видео интерфейс, использующий аналоговый сигнал для передачи цветовой информации. Формат сигналов и их поведение похожи на тевелизионный сигнал.
Список сигналов:
vga_vs_o — вертикальная синхронизация
vga_hs_o — горизонтальная синхронизация
vga_r_o — данные красной составляющей пикселя
vga_g_o — данные зеленой составляющей пикселя
vga_b_o — данные синий составляющей пикселя
Shield поддерживает 16 бит на цвет, а это значит, что на синий и красный выделяется по 5 бит, а на зеленый 6. ЦАП сделан по схеме R2R.
У мониторов различные разрешения и частота обновления изображения. Эти параметры регулируются в VGA с помощью вертикальной и горизонтальной сихронизаций. У которых есть следующие параметры:
- Тактовая частота появления новых пикселей.
- Front porch — время гашения синхроимпульса.
- Back porch — время нарастания синхроимпульса.
- Sync — длительность синхронизации.
- Display Area — это момент времени, когда передаётся информация.
Времянки выглядят так:
Мы будем использовать режим VGA 800×600 на 60 Hz, cледовательно параметры следующие (параметры для других режимов):
Тактовая частота 40 MHz
Горизонтальная синхронизация:
- Front porch 40 pixels
- Back porch 88 pixels
- Sync 128 pixels
- Display Area 800 pixels
Вертикальная синхронизация:
- Front porch 1 lines
- Back porch 23 lines
- Sync 4 lines
- Display Area 600 lines
Чтобы получить прошивку, нам в Qsys потребуются следующие модули:
- HPS — это наш процессор.
- Frame Reader — это IP-ядро читает кадры, сохраненные во внешней памяти, и выводит их в виде видеопотока.
- Clocked Video Output — это IP-ядро, из Avalon-ST делает вывод в VGA подобном формате.
- Altera PLL — PLL для изменения тактовой частоты: нам нужно получить 40 МГц из 25 МГц, которые есть на плате.
Из процессора выходят AXI интерфейсы H2F и F2H, у IP-ядер Альтеры интерфейсы Avalon-ST и Avalon-MM, поэтому нужен еще модуль межсоединения (Interconnect), который должен конвертировать из одного интерфейса в другой и мультиплексировать потоки данных. Он появится автоматически при генерации файлов.
Подробнее про Frame Reader и Clocked Video Output можно посмотреть тут.
Как собрать прошивку и какие настройки нужны для HPS можно прочитать в этой статье.
Altera PLL
Настройки PLL.
Frame Reader
Здесь настраиваются:
- Параметры FIFO на входе модуля
- Насройки передачи данных
- Количество активных пикселей
Параметры Bits per pixel per color plane и Number of color planes in parallel связаны с драйвером и объясняются ниже. Обратите внимание, что размерность интерфейса с HPS cовпадает с размерностью Master port width.
Clocked Video Output
Здесь:
- Настройки синхронизаций (vga_vs_o, vga_hs_o), которые описывались выше
- Способ каким приходят данные (он такой же как и у Frame Reader)
Qsys Connections
И теперь всё соединяем. Настройки для модуля Frame Reader «цепляем» к h2f master, интерфейс для передачи данных f2h slave. Соединяем Clocked Video Output с Frame Reader avalon_streaming_source → din. Все тактируется outclk0.
И генерируем файлы, нажав Generate HDL ….
Как выше сказано, на плате 16 бит, а из модуля выходит 32 бита, поэтому нужно внимательно назначить пины в qsf-файле, либо отредактировать выход для себя удобным образом в top файле проекта. Нам нужны старшие биты каждого цвета, они более информативны, чем младшие.
Обратите внимание, что это первое и единственное место, где мы редактируем код. Больше это не потребуется.
logic vga_v_sync;
logic vga_h_sync;
logic [31:0] vga_data;
logic [7:0] vid_r;
logic [7:0] vid_g;
logic [7:0] vid_b;
assign vga_r_o = vid_r[7:3];
assign vga_g_o = vid_g[7:2];
assign vga_b_o = vid_b[7:3];
assign vga_hs_o = vga_h_sync;
assign vga_vs_o = vga_v_sync;
assign { vid_r, vid_g, vid_b } = vga_data;
Нам потребуется драйвер altvipfb.
Вернемся к параметрам Bits per pixel per color plane и Number of color planes in parallel в Frame Reader. В драйвере написано:
if (bits_per_color != 8) {
dev_err(&fbdev->pdev->dev,
"bits-per-color is set to %i. Curently only 8 is supported.",
bits_per_color);
return -ENODEV;
}
if (!(fbdev->mem_word_width >= 32 && fbdev->mem_word_width % 32 == 0)) {
dev_err(&fbdev->pdev->dev,
"mem-word-width is set to %i. must be >= 32 and multiple of 32.",
fbdev->mem_word_width);
return -ENODEV;
}
Число бит на один цвет только 8 и ширина слова должна быть больше или кратна 32. С чем же связано такое ограничение? Смотрим дальше и видим:
/* settings for 32bit pixels */
info->var.red.offset = 16;
info->var.red.length = 8;
info->var.red.msb_right = 0;
info->var.green.offset = 8;
info->var.green.length = 8;
info->var.green.msb_right = 0;
info->var.blue.offset = 0;
info->var.blue.length = 8;
info->var.blue.msb_right = 0;
Становится ясно, что драйвер работает в режиме True color, записывая цвет в 32 битное слово (более удобно выравнивать, чем 24), и работает он только в таком режиме.
Чтобы собрать этот драйвер, в конфиге ядра надо внести следующие изменения.
CONFIG_FB_CFB_FILLRECT=m
CONFIG_FB_CFB_COPYAREA=m
CONFIG_FB_CFB_IMAGEBLIT=m
CONFIG_FB_ALTERA_VIP=m
Для того что бы linux узнал, что у нас в FPGA есть фреймбуфер от Альтеры, в dtb надо прописать следующие магические слова:
hps_0_h2f: bridge@0xc0000000 {
compatible = "altr,bridge-1.0", "simple-bus";
reg = < 0xc0000000 0x20000000 >;
#address-cells = < 1 >;
#size-cells = < 1 >;
ranges = <0x00000000 0xc0000000 0x4080 >;
alt_vip_vfr_1: vip2@0x0 {
compatible = "ALTR,vip-frame-reader-13.0", "ALTR,vip-frame-reader-9.1";
reg = < 0x4000 0x00000080 >;
max-width = < 800 >; /* MAX_IMAGE_WIDTH type NUMBER */
max-height = < 600 >; /* MAX_IMAGE_HEIGHT type NUMBER */
mem-word-width = < 0x80 >;
bits-per-color = < 0x8 >;
};
};
В параметре range — диапазон валидных адресов, с которых драйвер будет читать, а в reg = < 0x4000 0x00000080 > — стартовый адрес и сколько адресов занято alt_vip. mem-word-width это параметр Master port width в Frame Reader.
Подробнее про dtb.
Заходим на прибор и загружаем драйвера:
modprobe altvipfb
modprobe fbcon
Затем проверяем, все ли хорошо с помощью dmesg, и смотрим, есть ли похожая строка:
[ 66.424283] altvipfb c0000000.vip2: fb0: altvipfb frame buffer device at 0x2c000000+0x12c000
Ура! Появился fb0:
ls -l /dev/fb0
crw-rw---T 1 root fb 29, 0 Nov 26 10:13 /dev/fb0
Затем выводим консоль на экран, подключенный к плате:
/sbin/getty 38400 tty1
Ставим icewm и запускаем с помощью startx:
apt-get install icewm icewm-themes
startx
Итого: мы получили графический контроллер, с минимальными знаниями HDL языков.