В това видео е показано: платката Raspberry Pi3, към която чрез GPIO интерфейса е свързана FPGA платка Mars Rover2rpi (Cyclone IV), към която е свързан HDMI монитор. Вторият монитор е свързан през стандартния HDMI порт на Raspberry Pi3. Всичко работи заедно като система с два монитора.
След това ще обясня как това е реализирано.
На популярната платка Raspberry Pi3 има GPIO порт, чрез който могат да се свързват различни разширителни платки: сензори, светодиоди, драйвери за стъпкови мотори и много други. Конкретната функция на всеки изход на порта зависи от конфигурацията на портовете. Конфигурацията GPIO ALT2 позволява превключване на порта в режим на DPI интерфейс, Display Parallel Interface. Съществуват разширителни платки за свързване на VGA монитори през DPI. Въпреки това, първо, VGA мониторите вече не са толкова разпространени, колкото HDMI, а на второ място, цифровият интерфейс е все по-добър от аналоговия. Освен това, ЦАП-а на подобни VGA разширителни платки обикновено е реализиран във вид на R-2-R вериги и често не е повече от 6 бита на цвят.
В режим ALT2, щифтите на GPIO интерфейса имат следното значение:

Тук оцветих RGB изходите на порта в червено, зелено и синьо. Други важни сигнали са сигналите за синхронизация на вертикалната и хоризонталната сканираща (V-SYNC и H-SYNC), а също и CLK. Тактовата честота на CLK е честотата, с която стойностите на пикселите се предават на порта, и зависи от избрания видеорежим.
За свързване на цифров HDMI монитор, трябва да се захватят сигналите на DPI интерфейса и да се преобразуват в HDMI сигнали. Това може да се направи например с помощта на някоя FPGA платка. Оказа се, че платката Mars Rover2rpi е подходяща за тези цели. Всъщност основният вариант за свързване на тази платка през специален адаптер изглежда така:

Тази платка служи за увеличаване на броя на GPIO портовете и за свързване на повече периферни устройства към Raspberry. В този случай, 4 GPIO сигнала по време на такава връзка се използват за JTAG сигнали, така че програмата от Raspberry може да зарежда FPGA фърмуер в ПЛИС. Поради това стандартното свързване не ми подхожда, тъй като 4 DPI сигнала отпадат. За щастие, допълнителните редове на платката имат съвместима с Raspberry распиновка. Така че мога да завъртя платката на 90 градуса и все пак да я свържа към моята Raspberry:

Разбира се, ще трябва да използвам външен JTAG програмист, но това не е проблем.
Има малък проблем все пак. Не всеки изходен FPGA може да бъде използван като вход на тактовата честота. Има само няколко специализирани контакта, които могат да се използват за тези цели. Така се получи, че сигналът CLK от GPIO_0 не попада на входа на FPGA, който може да се използва като вход на тактовата честота на ПЛИС. Така че все пак се наложи да свържа един проводник към платката. Свързвам GPIO_0 и сигнала KEY[1] на платката:

Сега ще разкажа малко за проекта в ПЛИС. Основната трудност при формирането на HDMI сигналите е много високата честота. Ако погледнете закрепването на HDMI конектора, ще видите, че сигналите RGB сега са последователни диференциални сигнали:

Използването на диференциален сигнал позволява да се борим с общовълновите смущения по линията за предаване. При това, оригиналният осембитов код на всеки цветен сигнал се преобразува в 10-битов TMDS (Transition-minimized differential signaling). Това е специален начин на кодиране, за да се премахне постоянната съставка от сигнала и да се минимизират превключванията на сигналите в диференциалната линия. Тъй като за един байт цвят сега по последователната линия за предаване трябва да се предадат 10 бита, то излиза, че тактовата честота на сериализатора трябва да бъде 10 пъти по-висока от тактовата честота на пикселите. Ако вземем например видео режим 1280x720 60Hz, честотата на пикселите за този режим е 74,25MHz. На сериализатора трябва да бъде 742,5MHz.
Обикновените FPGA всъщност не са способни на това, за съжаление. Въпреки това, за наше щастие, в FPGA има вградени изходи DDIO. Това са такива изходи, които вече всъщност действат като сериализатори 2-к-1. Тоест, те могат да издават последователно два бита на фронта и спада на тактовата честота. Значи в проекта FPGA може да се използва не 740MHz, а 370MHz, но трябва да активираме изходните елементи DDIO в ПЛИС. Ето 370MHz вече е постижима честота. За съжаление, режим 1280×720 е пределът. По-висока резолюция в нашия FPGA Cyclone IV, инсталиран на платката Mars Rover 2rpi, не може да бъде достигната.
И така, в проекта входната честота на пикселите CLK постъпва на PLL, където се умножава по 5. На тази честота байтовете R, G, B се преобразуват в двойки битове. Това прави TMDS енкодерът. Изходният код на Verilog HDL изглежда така:
модул hdmi(
входный провод pixclk, // 74MHz
входный провод clk_TMDS2, // 370MHz
входный провод hsync,
входный провод vsync,
входный провод active,
входный провод [7:0]red,
входный провод [7:0]green,
входный провод [7:0]blue,
выходной провод TMDS_bh,
выходной провод TMDS_bl,
выходной провод TMDS_gh,
выходной провод TMDS_gl,
выходной провод TMDS_rh,
выходной провод TMDS_rl
);
провод [9:0] TMDS_red, TMDS_green, TMDS_blue;
TMDS_encoder encode_R(.clk(pixclk), .VD(red ), .CD({vsync,hsync}), .VDE(active), .TMDS(TMDS_red));
TMDS_encoder encode_G(.clk(pixclk), .VD(green), .CD({vsync,hsync}), .VDE(active), .TMDS(TMDS_green));
TMDS_encoder encode_B(.clk(pixclk), .VD(blue ), .CD({vsync,hsync}), .VDE(active), .TMDS(TMDS_blue));
рег [2:0] TMDS_mod5=0; // счетчик по модулю 5
рег [4:0] TMDS_shift_bh=0, TMDS_shift_bl=0;
рег [4:0] TMDS_shift_gh=0, TMDS_shift_gl=0;
рег [4:0] TMDS_shift_rh=0, TMDS_shift_rl=0;
провод [4:0] TMDS_blue_l = {TMDS_blue[9],TMDS_blue[7],TMDS_blue[5],TMDS_blue[3],TMDS_blue[1]};
провод [4:0] TMDS_blue_h = {TMDS_blue[8],TMDS_blue[6],TMDS_blue[4],TMDS_blue[2],TMDS_blue[0]};
провод [4:0] TMDS_green_l = {TMDS_green[9],TMDS_green[7],TMDS_green[5],TMDS_green[3],TMDS_green[1]};
провод [4:0] TMDS_green_h = {TMDS_green[8],TMDS_green[6],TMDS_green[4],TMDS_green[2],TMDS_green[0]};
провод [4:0] TMDS_red_l = {TMDS_red[9],TMDS_red[7],TMDS_red[5],TMDS_red[3],TMDS_red[1]};
провод [4:0] TMDS_red_h = {TMDS_red[8],TMDS_red[6],TMDS_red[4],TMDS_red[2],TMDS_red[0]};
всегда @(posedge clk_TMDS2)
начало
TMDS_shift_bh <= TMDS_mod5[2] ? TMDS_blue_h : TMDS_shift_bh [4:1];
TMDS_shift_bl <= TMDS_mod5[2] ? TMDS_blue_l : TMDS_shift_bl [4:1];
TMDS_shift_gh <= TMDS_mod5[2] ? TMDS_green_h : TMDS_shift_gh [4:1];
TMDS_shift_gl <= TMDS_mod5[2] ? TMDS_green_l : TMDS_shift_gl [4:1];
TMDS_shift_rh <= TMDS_mod5[2] ? TMDS_red_h : TMDS_shift_rh [4:1];
TMDS_shift_rl <= TMDS_mod5[2] ? TMDS_red_l : TMDS_shift_rl [4:1];
TMDS_mod5 4'd4) || (Nb1s == 4'd4 && VD[0] == 1'b0);
провод [8:0] q_m = {~XNOR, q_m[6:0] ^ VD[7:1] ^ {7{XNOR}}, VD[0]};
рег [3:0] balance_acc = 0;
провод [3:0] balance = q_m[0] + q_m[1] + q_m[2] + q_m[3] + q_m[4] + q_m[5] + q_m[6] + q_m[7] - 4'd4;
провод balance_sign_eq = (balance[3] == balance_acc[3]);
провод invert_q_m = (balance == 0 || balance_acc == 0) ? ~q_m[8] : balance_sign_eq;
провод [3:0] balance_acc_inc = balance - ({q_m[8] ^ ~balance_sign_eq} & ~(balance == 0 || balance_acc == 0));
провод [3:0] balance_acc_new = invert_q_m ? balance_acc - balance_acc_inc : balance_acc + balance_acc_inc;
провод [9:0] TMDS_data = {invert_q_m, q_m[8], q_m[7:0] ^ {8{invert_q_m}}};
провод [9:0] TMDS_code = CD[1] ? (CD[0] ? 10'b1010101011 : 10'b0101010100) : (CD[0] ? 10'b0010101011 : 10'b1101010100);
всегда @(posedge clk) TMDS <= VDE ? TMDS_data : TMDS_code;
всегда @(posedge clk) balance_acc <= VDE ? balance_acc_new : 4'h0;
конецмодуль
След това изходните двойки се подават на изхода DDIO, който последователно издава еднобитов сигнал на възходящия и спадащия фронт.
Сама DDIO може да бъде описана с такъв Verilog код:
модул ddio(
входен провод d0,
входен провод d1,
входен провод clk,
изходен провод out
);
рег r_d0;
рег r_d1;
винаги @(posedge clk)
начало
r_d0 <= d0;
r_d1 <= d1;
край
назначете out = clk ? r_d0 : r_d1;
краймодулНо така работата вероятно няма да се получи. Трябва да се използва алтеровата мегафункция ALTDDIO_OUT, за да се активират изходните DDIO елементи. В моя проект е използван точно библиотечният компонент ALTDDIO_OUT.
Може би всичко това изглежда малко сложно, но работи.
Целият изходен код, написан на Verilog HDL, може да се види .
Скомпилираната фърмуер за FPGA се записва в EPCS чипа, монтиран на платката MarsRover2rpi. По този начин, при подаване на захранване на платката FPGA, ПЛИС ще се инициализира от флаш паметта и ще стартира.
Сега трябва да кажа малко за конфигурацията на самия Raspberry.
Правя експерименти на Raspberry PI OS (32 бита), основана на Debian Buster, Версия: Август 2020,
Дата на пускане: 2020-08-20, Ядро версия: 5.4.
Трябва да направя две неща:
- да редактирам файла config.txt;
- да създам конфигурация на X сървъра за работа с два монитора.
При редактирането на файла /boot/config.txt трябва:
- да деактивирам използването на i2c, i2s, spi;
- да активирам режима DPI с помощта на оверлея dtoverlay=dpi24;
- да настроя видео режима 1280×720 60Hz, 24 бита на точка на DPI;
- да посоча необходимия брой рамки 2 (max_framebuffers=2, само тогава ще се появи второто устройство /dev/fb1)
Пълният текст на файла config.txt изглежда така.
# For more options and information see
# http://rpf.io/configtxt
# Some settings may impact device functionality. See link above for details
# uncomment if you get no picture on HDMI for a default "safe" mode
#hdmi_safe=1
# uncomment this if your display has a black border of unused pixels visible
# and your display can output without overscan
disable_overscan=1
# uncomment the following to adjust overscan. Use positive numbers if console
# goes off screen, and negative if there is too much border
#overscan_left=16
#overscan_right=16
#overscan_top=16
#overscan_bottom=16
# uncomment to force a console size. By default it will be display's size minus
# overscan.
#framebuffer_width=1280
#framebuffer_height=720
# uncomment if hdmi display is not detected and composite is being output
hdmi_force_hotplug=1
# uncomment to force a specific HDMI mode (this will force VGA)
#hdmi_group=1
#hdmi_mode=1
# uncomment to force a HDMI mode rather than DVI. This can make audio work in
# DMT (computer monitor) modes
#hdmi_drive=2
# uncomment to increase signal to HDMI, if you have interference, blanking, or
# no display
#config_hdmi_boost=4
# uncomment for composite PAL
#sdtv_mode=2
#uncomment to overclock the arm. 700 MHz is the default.
#arm_freq=800
# Uncomment some or all of these to enable the optional hardware interfaces
#dtparam=i2c_arm=on
#dtparam=i2s=on
#dtparam=spi=on
dtparam=i2c_arm=off
dtparam=spi=off
dtparam=i2s=off
dtoverlay=dpi24
overscan_left=0
overscan_right=0
overscan_top=0
overscan_bottom=0
framebuffer_width=1280
framebuffer_height=720
display_default_lcd=0
enable_dpi_lcd=1
dpi_group=2
dpi_mode=87
#dpi_group=1
#dpi_mode=4
dpi_output_format=0x6f027
dpi_timings=1280 1 110 40 220 720 1 5 5 20 0 0 0 60 0 74000000 3
# Uncomment this to enable infrared communication.
#dtoverlay=gpio-ir,gpio_pin=17
#dtoverlay=gpio-ir-tx,gpio_pin=18
# Additional overlays and parameters are documented /boot/overlays/README
# Enable audio (loads snd_bcm2835)
dtparam=audio=on
[pi4]
# Enable DRM VC4 V3D driver on top of the dispmanx display stack
#dtoverlay=vc4-fkms-v3d
max_framebuffers=2
[all]
#dtoverlay=vc4-fkms-v3d
max_framebuffers=2
След това трябва да създам конфигурационен файл за X сървъра за използване на два монитора на два рамкопомощника /dev/fb0 и /dev/fb1:
Моят конфигурационен файл /usr/share/x11/xorg.conf.d/60-dualscreen.conf е такъв
Section "Device"
Identifier "LCD"
Driver "fbturbo"
Option "fbdev" "dev/fb0"
Option "ShadowFB" "off"
Option "SwapbuffersWait" "true"
EndSection
Section "Device"
Identifier "HDMI"
Driver "fbturbo"
Option "fbdev" "dev/fb1"
Option "ShadowFB" "off"
Option "SwapbuffersWait" "true"
EndSection
Section "Monitor"
Identifier "LCD-monitor"
Option "Primary" "true"
EndSection
Section "Monitor"
Identifier "HDMI-monitor"
Option "RightOf" "LCD-monitor"
EndSection
Section "Screen"
Identifier "screen0"
Device "LCD"
Monitor "LCD-monitor"
EndSection
Section "Screen"
Identifier "screen1"
Device "HDMI"
Monitor "HDMI-monitor"
EndSection
Section "ServerLayout"
Identifier "default"
Option "Xinerama" "on"
Option "Clone" "off"
Screen 0 "screen0"
Screen 1 "screen1" RightOf "screen0"
EndSection
И ако все още не е инсталирано, трябва да инсталирам Xinerama. Тогава работната площ ще се разшири напълно на два монитора, както е показано по-горе в демонстрационния видеоклип.
И това е всичко. Сега и собствениците на Raspberry Pi3 могат да използват два монитора.
Описание и схемата на платката Mars Rover2rpi може .
Източник: habr.com
