Druhý monitor HDMI k Raspberry Pi3 cez rozhranie DPI a dosku FPGA


Toto video ukazuje: doska Raspberry Pi3, ktorá je k nej pripojená cez konektor GPIO, je doska FPGA Mars Rover2rpi (Cyclone IV), ku ktorej je pripojený monitor HDMI. Druhý monitor je pripojený cez štandardný HDMI konektor Raspberry Pi3. Všetko spolu funguje ako systém dvoch monitorov.

Ďalej vám poviem, ako sa to implementuje.

Populárna doska Raspberry Pi3 má konektor GPIO, cez ktorý môžete pripojiť rôzne rozširujúce karty: senzory, LED diódy, ovládače krokových motorov a mnoho ďalšieho. Presná funkcia každého kolíka na konektore závisí od konfigurácie portu. Konfigurácia GPIO ALT2 umožňuje prepnúť konektor do režimu rozhrania DPI, Display Parallel Interface. Pre pripojenie VGA monitorov cez DPI existujú rozširujúce karty. Po prvé, monitory VGA už nie sú také bežné ako HDMI a po druhé, digitálne rozhranie je čoraz lepšie ako analógové. Navyše DAC na takýchto rozširujúcich doskách VGA je zvyčajne vyrobený vo forme reťazcov R-2-R a často nie viac ako 6 bitov na farbu.

V režime ALT2 majú kolíky konektora GPIO nasledujúci význam:

Druhý monitor HDMI k Raspberry Pi3 cez rozhranie DPI a dosku FPGA

Tu som zafarbil kolíky RGB konektora červenou, zelenou a modrou. Ďalšími dôležitými signálmi sú signály V-SYNC a H-SYNC, ako aj CLK. Hodinová frekvencia CLK je frekvencia, pri ktorej sú hodnoty pixelov na výstupe konektora; závisí od zvoleného režimu videa.

Ak chcete pripojiť digitálny monitor HDMI, musíte zachytiť signály DPI rozhrania a previesť ich na signály HDMI. Dá sa to urobiť napríklad pomocou nejakej dosky FPGA. Ako sa ukazuje, doska Mars Rover2rpi je na tieto účely vhodná. V skutočnosti hlavná možnosť pripojenia tejto dosky cez špeciálny adaptér vyzerá takto:

Druhý monitor HDMI k Raspberry Pi3 cez rozhranie DPI a dosku FPGA

Táto doska slúži na zvýšenie počtu GPIO portov a na pripojenie viacerých periférnych zariadení k malinovke. Zároveň sú pre JTAG signály použité 4 GPIO signály s týmto zapojením, aby program od Raspberry vedel nahrať firmvér FPGA do FPGA. Z tohto dôvodu mi toto štandardné pripojenie nevyhovuje, vypadávajú 4 DPI signály. Našťastie ďalšie hrebene na doske majú pinout kompatibilný s Raspberry. Takže môžem otočiť dosku o 90 stupňov a stále ju pripojiť k mojej maline:

Druhý monitor HDMI k Raspberry Pi3 cez rozhranie DPI a dosku FPGA

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

Stále je tu malý problém. Nie každý pin FPGA môže byť použitý ako hodinový vstup. Na tieto účely je možné použiť iba niekoľko vyhradených kolíkov. Tu sa teda ukázalo, že signál GPIO_0 CLK sa nedostane na vstup FPGA, ktorý možno použiť ako hodinový vstup FPGA. Tak som ešte musela dať jeden drôtik na šatku. Pripojím GPIO_0 a signál KEY[1] dosky:

Druhý monitor HDMI k Raspberry Pi3 cez rozhranie DPI a dosku FPGA

Teraz vám poviem niečo o projekte FPGA. Hlavným problémom pri generovaní signálov HDMI sú veľmi vysoké frekvencie. Ak sa pozriete na vývod konektora HDMI, môžete vidieť, že signály RGB sú teraz sériové diferenciálne signály:

Druhý monitor HDMI k Raspberry Pi3 cez rozhranie DPI a dosku FPGA

Použitie diferenciálneho signálu vám umožňuje bojovať proti rušeniu bežného režimu na prenosovej linke. V tomto prípade sa pôvodný osembitový kód každého farebného signálu prevedie na 10-bitový TMDS (Transition-minimized Differential Signing). Ide o špeciálnu metódu kódovania na odstránenie jednosmernej zložky zo signálu a minimalizáciu prepínania signálu v diferenciálnom vedení. Keďže teraz je potrebné preniesť 10 bitov cez sériovú linku pre jeden bajt farby, ukazuje sa, že rýchlosť hodín serializátora musí byť 10-krát vyššia ako rýchlosť hodín pixelov. Ak si vezmeme napríklad video režim 1280x720 60Hz, tak pixelová frekvencia tohto režimu je 74,25 MHz. Serializátor by mal byť 742,5 MHz.

Bežné FPGA to, žiaľ, nedokážu. Avšak našťastie pre nás má FPGA zabudované DDIO piny. Toto sú závery, ktoré sú už takpovediac serializátormi 2:1. To znamená, že môžu vydávať dva bity postupne na vzostupnej a zostupnej hrane hodinovej frekvencie. To znamená, že v projekte FPGA môžete použiť nie 740 MHz, ale 370 MHz, ale musíte použiť výstupné prvky DDIO v FPGA. Teraz je 370 MHz už úplne dosiahnuteľná frekvencia. Bohužiaľ, limitom je režim 1280 x 720. Vyššie rozlíšenie nie je možné dosiahnuť v našom Cyclone IV FPGA nainštalovanom na doske Mars Rover2rpi.

Takže v návrhu ide vstupná frekvencia pixelov CLK do PLL, kde sa vynásobí 5. Pri tejto frekvencii sa bajty R, G, B konvertujú na páry bitov. Toto robí kodér TMDS. Zdrojový kód vo Verilog HDL vyzerá takto:

module hdmi(
	input wire pixclk,		// 74MHz
	input wire clk_TMDS2,	// 370MHz
	input wire hsync,
	input wire vsync,
	input wire active,
	input wire [7:0]red,
	input wire [7:0]green,
	input wire [7:0]blue,
	output wire TMDS_bh,
	output wire TMDS_bl,
	output wire TMDS_gh,
	output wire TMDS_gl,
	output wire TMDS_rh,
	output wire TMDS_rl
);

wire [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;  // modulus 5 counter
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;

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

always @(posedge clk_TMDS2)
begin
	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 <= (TMDS_mod5[2]) ? 3'd0 : TMDS_mod5+3'd1;
end

assign TMDS_bh = TMDS_shift_bh[0];
assign TMDS_bl = TMDS_shift_bl[0];
assign TMDS_gh = TMDS_shift_gh[0];
assign TMDS_gl = TMDS_shift_gl[0];
assign TMDS_rh = TMDS_shift_rh[0];
assign TMDS_rl = TMDS_shift_rl[0];

endmodule

module TMDS_encoder(
	input clk,
	input [7:0] VD,	// video data (red, green or blue)
	input [1:0] CD,	// control data
	input VDE,  	// video data enable, to choose between CD (when VDE=0) and VD (when VDE=1)
	output reg [9:0] TMDS = 0
);

wire [3:0] Nb1s = VD[0] + VD[1] + VD[2] + VD[3] + VD[4] + VD[5] + VD[6] + VD[7];
wire XNOR = (Nb1s>4'd4) || (Nb1s==4'd4 && VD[0]==1'b0);
wire [8:0] q_m = {~XNOR, q_m[6:0] ^ VD[7:1] ^ {7{XNOR}}, VD[0]};

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

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

endmodule

Potom sú výstupné páry privedené na výstup DDIO, ktorý postupne vytvára jednobitový signál na stúpajúcej a klesajúcej hrane.

Samotné DDIO by sa dalo opísať nasledujúcim kódom Verilog:

module ddio(
	input wire d0,
	input wire d1,
	input wire clk,
	output wire out
	);

reg r_d0;
reg r_d1;
always @(posedge clk)
begin
	r_d0 <= d0;
	r_d1 <= d1;
end
assign out = clk ? r_d0 : r_d1;
endmodule

Takto to ale s najväčšou pravdepodobnosťou fungovať nebude. Ak chcete skutočne povoliť výstupné prvky DDIO, musíte použiť megafunkciu Alter ALTDDIO_OUT. Môj projekt používa komponent knižnice ALTDDIO_OUT.

Toto všetko môže vyzerať trochu zložitejšie, ale funguje to.

Môžete si prezrieť celý zdrojový kód napísaný vo Verilog HDL tu na githube.

Kompilovaný firmvér pre FPGA je flashovaný do čipu EPCS nainštalovaného na doske Mars Rover2rpi. Keď sa teda na dosku FPGA pripojí napájanie, FPGA sa inicializuje z pamäte flash a spustí sa.

Teraz sa musíme trochu porozprávať o konfigurácii samotného Raspberry.

Robím experimenty na operačnom systéme Raspberry PI (32 bit) na základe Debian Buster, verzia: august 2020,
Dátum vydania: 2020, Verzia jadra:08.

Musíte urobiť dve veci:

  • upravte súbor config.txt;
  • vytvorte konfiguráciu X servera na prácu s dvoma monitormi.

Pri úprave súboru /boot/config.txt potrebujete:

  1. zakázať používanie i2c, i2s, spi;
  2. povoliť režim DPI pomocou prekrytia dtoverlay=dpi24;
  3. konfigurovať režim videa 1280×720 60Hz, 24 bitov na pixel na DPI;
  4. zadajte požadovaný počet framebufferov 2 (max_framebuffers=2, až potom 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 musíte vytvoriť konfiguračný súbor pre X server na používanie dvoch monitorov na dvoch framebufferoch /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

Ak ešte nie je nainštalovaný, musíte nainštalovať Xineramu. Potom sa priestor pracovnej plochy plne rozšíri na dva monitory, ako je znázornené na demonštračnom videu vyššie.

To je asi všetko. Teraz budú môcť majitelia Raspberry Pi3 používať dva monitory.

Popis a schému zapojenia dosky Mars Rover2rpi nájdete pozri sa sem.

Zdroj: hab.com