Druhá HDMI obrazovka k Raspberry Pi3 cez DPI rozhranie a FPGA dosku


Na tomto videu je zobrazená doska Raspberry Pi3, ku ktorej je pripojená FPGA doska Mars Rover 2rpi (Cyclone IV) cez konektor GPIO, a k nej je pripojený HDMI monitor. Druhý monitor je pripojený cez štandardný HDMI konektor Raspberry Pi3. Celé to funguje ako systém s dvoma monitormi.

Ďalej vám poviem, ako je to realizované.

Na populárnej doske Raspberry Pi3 je konektor GPIO, cez ktorý môžete pripojiť rôzne rozšírovacie dosky: senzory, LED diódy, ovládače krokových motorov a mnoho ďalšieho. Konkrétna funkcia každého vývodu na konektore závisí od konfigurácie portov. Konfigurácia GPIO ALT2 umožňuje prepínať konektor do režimu DPI rozhrania, Display Parallel Interface. Existujú rozšírovacie dosky na pripojenie VGA monitorov cez DPI. Avšak, po prvé, VGA monitory už nie sú tak rozšírené ako HDMI, a po druhé, digitálne rozhranie je stále lepšie ako analógové. O to viac, že DAC na takých VGA rozširujúcich doskách je zvyčajne vykonaný vo forme R-2-R reťazcov a často nemá viac ako 6 bitov na farbu.

V režime ALT2 majú piny konektora GPIO nasledujúce hodnoty:

Druhá HDMI obrazovka k Raspberry Pi3 cez DPI rozhranie a FPGA dosku

Tu som poupravil RGB vývody konektora na príslušné červené, zelené a modré farby. Iné dôležité signály sú signály synchronizácie rozkladu V-SYNC a H-SYNC, ako aj CLK. Taktná frekvencia CLK je frekvencia, s ktorou sa hodnoty pixelov vydávajú na konektor a závisí od zvoleného video režimu.

Na pripojenie digitálneho HDMI monitora je potrebné zachytiť signály DPI rozhrania a previesť ich na HDMI signály. Na to je možné napríklad použiť akúkoľvek FPGA dosku. Ako sa ukázalo, doska Mars Rover 2rpi je na tieto účely vhodná. Pravdupovediac, hlavná možnosť pripojenia tejto dosky cez špeciálny adaptér vyzerá takto:

Druhá HDMI obrazovka k Raspberry Pi3 cez DPI rozhranie a FPGA dosku

Táto doska slúži na zvýšenie počtu GPIO portov a na pripojenie väčšieho počtu periférnych zariadení k Raspberry. Pri tom sa 4 signály GPIO pri takomto pripojení používajú na signály JTAG, takže program z Raspberry môže nahrávať FPGA firmware do PLIS. Z tohto dôvodu takéto štandardné pripojenie pre mňa nie je vhodné, chýbajú 4 signály DPI. Našťastie, ďalšie konektory na doske majú rozvrhnutie kompatibilné s Raspberry. Takže môžem dosku otočiť o 90 stupňov a stále ju pripojiť k mojej maline:

Druhá HDMI obrazovka k Raspberry Pi3 cez DPI rozhranie a FPGA dosku

Samozrejme, bude potrebné použiť externý JTAG programátor, ale to nie je problém.

Existuje však malý problém. Nie každý výstup FPGA môže byť použitý ako vstup hodinového signálu. Existuje len niekoľko vyhradených pinov, ktoré je možné použiť na tieto účely. Tak sa stalo, že signál CLK z GPIO_0 sa nedostal na vstup FPGA, ktorý je možné použiť ako vstup hodinového signálu PLIS. Takže nakoniec som musel pripojiť jeden kábel na dosku. Spojil som GPIO_0 a signál KEY[1] dosky:

Druhá HDMI obrazovka k Raspberry Pi3 cez DPI rozhranie a FPGA dosku

Teraz poviem niečo o projekte v PLIS. Hlavnou komplikáciou pri generovaní HDMI signálov sú veľmi vysoké frekvencie. Keď sa pozriete na pinovanie konektora HDMI, je vidieť, že RGB signály sú teraz sériové diferenciálne signály:

Druhá HDMI obrazovka k Raspberry Pi3 cez DPI rozhranie a FPGA dosku

Použitie diferenciálneho signálu umožňuje bojovať proti spoločným rušeniam na prenosovej linke. Pri tom sa pôvodný osembitový kód každého farebného signálu pretransformuje na 10-bitový TMDS (Transition-minimized differential signaling). Ide o špeciálny spôsob kódovania, ktorý odstraňuje konštantnú zložku zo signálu a minimalizuje prechody signálov v diferenciálnej linke. Keďže na jeden bajt farby je potrebné preniesť 10 bitov po sériovej prenosovej linke, hodinová frekvencia serializátora musí byť 10-krát vyššia ako hodinová frekvencia pixelov. Ak vezmeme napríklad video režim 1280x720 60Hz, frekvencia pixelov pre tento režim je 74,25MHz. Na serializátore by teda mala byť 742,5MHz.

Bohužiaľ, bežné FPGA nie sú na to vôbec schopné. Na naše šťastie majú FPGA zabudované výstupy DDIO. Sú to výstupy, ktoré fungujú ako sériové 2-k-1. To znamená, že môžu vydávať sekvenčne dva bity na vzostupe a zostupe hodinového signálu. Takže v projekte FPGA môžeme použiť nie 740MHz, ale 370MHz, avšak musíme aktivovať výstupné elementy DDIO v PLIS. Už 370MHz je dosiahnuteľná frekvencia. Bohužiaľ, režim 1280×720 je maximálny. Vyššie rozlíšenie v našej FPGA Cyclone IV na doske Mars Rover 2RPI nedosiahneme.

Takže v projekte hodinová frekvencia pixelov CLK prichádza na PLL, kde sa násobí 5. Na tejto frekvencii sa bajty R, G, B pretransformujú na páry bitov. To vykonáva TMDS enkóder. Zdrojový kód v jazyku Verilog HDL vyzerá takto:

modul hdmi(
	vstupný drôt pixclk,		// 74MHz
	vstupný drôt clk_TMDS2,	// 370MHz
	vstupný drôt hsync,
	vstupný drôt vsync,
	vstupný drôt active,
	vstupný drôt [7:0]red,
	vstupný drôt [7:0]green,
	vstupný drôt [7:0]blue,
	výstupný drôt TMDS_bh,
	výstupný drôt TMDS_bl,
	výstupný drôt TMDS_gh,
	výstupný drôt TMDS_gl,
	výstupný drôt TMDS_rh,
	výstupný drôt TMDS_rl
);

drôt [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));

reg [2:0] TMDS_mod5=0;  // čítač modulus 5
reg [4:0] TMDS_shift_bh=0, TMDS_shift_bl=0;
reg [4:0] TMDS_shift_gh=0, TMDS_shift_gl=0;
reg [4:0] TMDS_shift_rh=0, TMDS_shift_rl=0;

drôt [4:0] TMDS_blue_l  = {TMDS_blue[9],TMDS_blue[7],TMDS_blue[5],TMDS_blue[3],TMDS_blue[1]};
drôt [4:0] TMDS_blue_h  = {TMDS_blue[8],TMDS_blue[6],TMDS_blue[4],TMDS_blue[2],TMDS_blue[0]};
drôt [4:0] TMDS_green_l = {TMDS_green[9],TMDS_green[7],TMDS_green[5],TMDS_green[3],TMDS_green[1]};
drôt [4:0] TMDS_green_h = {TMDS_green[8],TMDS_green[6],TMDS_green[4],TMDS_green[2],TMDS_green[0]};
drôt [4:0] TMDS_red_l   = {TMDS_red[9],TMDS_red[7],TMDS_red[5],TMDS_red[3],TMDS_red[1]};
drôt [4:0] TMDS_red_h   = {TMDS_red[8],TMDS_red[6],TMDS_red[4],TMDS_red[2],TMDS_red[0]};

vždy @(posedge clk_TMDS2)
begín
	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);
drôt [8:0] q_m = {~XNOR, q_m[6:0] ^ VD[7:1] ^ {7{XNOR}}, VD[0]};

reg [3:0] balance_acc = 0;
drôt [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;
drôt balance_sign_eq = (balance[3] == balance_acc[3]);
drôt invert_q_m = (balance==0 || balance_acc==0) ? ~q_m[8] : balance_sign_eq;
drôt [3:0] balance_acc_inc = balance - ({q_m[8] ^ ~balance_sign_eq} & ~(balance==0 || balance_acc==0));
drôt [3:0] balance_acc_new = invert_q_m ? balance_acc-balance_acc_inc : balance_acc+balance_acc_inc;
drôt [9:0] TMDS_data = {invert_q_m, q_m[8], q_m[7:0] ^ {8{invert_q_m}}};
drôt [9:0] TMDS_code = CD[1] ? (CD[0] ? 10'b1010101011 : 10'b0101010100) : (CD[0] ? 10'b0010101011 : 10'b1101010100);

vždy @(posedge clk) TMDS <= VDE ? TMDS_data : TMDS_code;
vždy @(posedge clk) balance_acc <= VDE ? balance_acc_new : 4'h0;

koniec

Potom sa výstupné páry posielajú na výstup DDIO, ktorý postupne vydáva jednobitový signál na vzostupnom a zostupnom okraji.

Samotný DDIO by sa dal opísať takýmto Verilog kódom:

modul ddio(
	vstupný drôt d0,
	vstupný drôt d1,
	vstupný drôt clk,
	výstupný drôt out
	);

reg r_d0;
reg r_d1;
vždy @(posedge clk)
begín
	r_d0 <= d0;
	r_d1 <= d1;
end
priraď out = clk ? r_d0 : r_d1;
koniec

Pravdepodobne to nebude fungovať takto. Je potrebné použiť alternatívnu megafunkciu ALTDDIO_OUT, aby sa skutočne aktivovali výstupné DDIO elementy. V mojom projekte sa používa práve knižnicový komponent ALTDDIO_OUT.

Možno to všetko vyzerá trochu zložito, ale funguje to.

Celý zdrojový kód napísaný v Verilog HDL si môžete pozrieť tu, na github.

Skompilovaný firmware pre FPGA sa nahráva do čipu EPCS, ktorý je nainštalovaný na doske Mars Rover 2 rpi. Takto, pri privedení napájania na dosku FPGA, sa FPGA inicializuje z flash pamäte a spustí sa.

Teraz je potrebné trochu povedať o konfigurácii samotného Raspberry.

Experimentujem na Raspberry PI OS (32 bit) založenom na Debian Buster, verzia: august 2020,
Dátum vydania: 2020-08-20, verzia jadra: 5.4.

Je potrebné spraviť dve veci:

  • upraviť súbor config.txt;
  • vytvoriť konfiguráciu X servera na prácu s dvoma monitormi.

Pri úprave súboru /boot/config.txt je potrebné:

  1. vypnúť používanie i2c, i2s, spi;
  2. povoliť režim DPI pomocou overlay dtoverlay=dpi24;
  3. nastaviť videorežim 1280×720 60Hz, 24 bity na pixel na DPI;
  4. určiť potrebný počet frame bufferov 2 (max_framebuffers=2, iba vtedy sa objaví druhé zariadenie /dev/fb1)

Celý text súboru config.txt vyzerá takto.

# 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

Potom je potrebné vytvoriť konfiguračný súbor pre X server na použitie dvoch monitora na dvoch frame bufferov /dev/fb0 a /dev/fb1:

Môj konfiguračný súbor /usr/share/x11/xorg.conf.d/60-dualscreen.conf je takýto

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

A ak ešte nie je nainštalovaný, je potrebné nainštalovať Xinerama. Potom bude priestor pracovnej plochy plne rozšírený na dva monitory, ako je zobrazené v demonštračnom videu.

A to je asi všetko. Teraz môžu vlastníci Raspberry Pi3 používať dve obrazovky.

Popis a schému dosky Mars Rover 2rpi si môžete pozrieť tu.

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster