Drugi monitor HDMI na Raspberry Pi3 prek vmesnika DPI in plošče FPGA


Ta video prikazuje: ploščo Raspberry Pi3, na katero je preko konektorja GPIO priključena plošča FPGA Mars Rover2rpi (Cyclone IV), na katero je priključen monitor HDMI. Drugi monitor je povezan prek standardnega priključka Raspberry Pi3 HDMI. Vse skupaj deluje kot sistem dvojnega monitorja.

Nato vam bom povedal, kako se izvaja.

Priljubljena plošča Raspberry Pi3 ima GPIO konektor, preko katerega lahko priključite različne razširitvene plošče: senzorje, LED diode, gonilnike koračnih motorjev in še mnogo več. Posebna funkcija vsakega zatiča na priključku je odvisna od konfiguracije vrat. Konfiguracija GPIO ALT2 omogoča preklop priključka na način vmesnika DPI, Display Parallel Interface. Obstajajo razširitvene plošče za povezavo monitorjev VGA prek DPI. Vendar, prvič, monitorji VGA niso več tako pogosti kot HDMI, in drugič, digitalni vmesnik postaja boljši od analognega. Poleg tega je DAC na takšnih razširitvenih karticah VGA običajno izdelan v obliki verig R-2-R in pogosto ne več kot 6 bitov na barvo.

V načinu ALT2 imajo nožice priključka GPIO naslednji pomen:

Drugi monitor HDMI na Raspberry Pi3 prek vmesnika DPI in plošče FPGA

Tukaj sem RGB nožice konektorja obarval rdeče, zeleno in modro. Drugi pomembni signali so V-SYNC in H-SYNC sinhronizacijski signali, pa tudi CLK. Urna frekvenca CLK je frekvenca, pri kateri se vrednosti pikslov oddajajo v priključek in je odvisna od izbranega video načina.

Če želite povezati digitalni monitor HDMI, morate zajeti signale vmesnika DPI in jih pretvoriti v signale HDMI. To je mogoče storiti na primer s katero koli ploščo FPGA. Kot se je izkazalo, je plošča Mars Rover2rpi primerna za ta namen. V resnici je glavna možnost za povezavo te plošče prek posebnega adapterja videti takole:

Drugi monitor HDMI na Raspberry Pi3 prek vmesnika DPI in plošče FPGA

Ta plošča se uporablja za povečanje števila GPIO portov in za priklop več perifernih naprav na malino. Hkrati se 4 GPIO signali s to povezavo uporabljajo za JTAG signale, tako da lahko program iz distribucije naloži FPGA firmware v FPGA. Zaradi tega mi tako redna povezava ne ustreza, 4 DPI signali izpadejo. Na srečo imajo dodatni glavniki na plošči pinout, združljiv z Raspberry. Da lahko ploščo obrnem za 90 stopinj in jo vseeno povežem z mojo malino:

Drugi monitor HDMI na Raspberry Pi3 prek vmesnika DPI in plošče FPGA

Seveda boste morali uporabiti zunanji programator JTAG, vendar to ni problem.

Še vedno obstaja majhen problem. Vsakega zatiča FPGA ni mogoče uporabiti kot vhod za uro. Obstaja le nekaj namenskih žebljičkov, ki jih je mogoče uporabiti v ta namen. Tako se je tukaj izkazalo, da signal GPIO_0 CLK ne pride do vhoda FPGA, ki se lahko uporablja kot vhod ure FPGA. Tako da sem vseeno morala vreči eno objavo na šal. Povezujem signal GPIO_0 in KEY[1] plošče:

Drugi monitor HDMI na Raspberry Pi3 prek vmesnika DPI in plošče FPGA

Zdaj vam bom povedal nekaj o projektu v FPGA. Glavna težava pri oblikovanju signalov HDMI so zelo visoke frekvence. Če pogledate pinout priključka HDMI, lahko vidite, da so signali RGB zdaj serijski diferencialni signali:

Drugi monitor HDMI na Raspberry Pi3 prek vmesnika DPI in plošče FPGA

Uporaba diferenčnega signala vam omogoča, da se spopadete s hrupom skupnega načina na prenosnem vodu. V tem primeru se prvotna osem-bitna koda vsakega barvnega signala pretvori v 10-bitno TMDS (Transition-minimized differential signaling). To je posebna metoda kodiranja za odstranitev enosmerne komponente iz signala in minimiziranje preklapljanja signala v diferencialnem vodu. Ker je zdaj na voljo 10 bitov za prenos na bajt barve po serijski liniji, se izkaže, da mora biti taktna frekvenca serializatorja 10-krat večja od taktne frekvence slikovnih pik. Če vzamemo za primer video način 1280x720 60Hz, potem je frekvenca slikovnih pik tega načina 74,25MHz. Serializator mora biti 742,5 MHz.

Običajni FPGA tega na splošno žal niso zmožni. Vendar pa imajo na našo srečo FPGA vgrajene zatiče DDIO. To so sklepi, ki so že tako rekoč serializatorji 2 proti 1. To pomeni, da lahko oddajo dva bita v zaporedju vzdolž naraščajoče in padajoče taktne frekvence. To pomeni, da v projektu FPGA lahko uporabite ne 740 MHz, ampak 370 MHz, vendar morate v FPGA uporabiti izhodne elemente DDIO. Tu je 370 MHz že kar dosegljiva frekvenca. Na žalost je način 1280×720 meja. Višje ločljivosti ni mogoče doseči v našem FPGA Cyclone IV, nameščenem na plošči Rover2rpi.

Torej se v projektu vhodna frekvenca slikovnih pik CLK dovaja v PLL, kjer se pomnoži s 5. Pri tej frekvenci se bajti R, G, B pretvorijo v bitne pare. To počne kodirnik TMDS. Izvorna koda na Verilog HDL izgleda takole:

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

Nato se izhodni pari napajajo na izhod DDIO, ki zaporedno proizvede enobitni signal ob vzponu in padcu.

Sam DDIO bi lahko opisali s kodo Verilog takole:

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

Ampak verjetno tako ne bo šlo. Za dejansko uporabo izhodnih elementov DDIO morate uporabiti megafunkcijo Altera ALTDDIO_OUT. V mojem projektu je uporabljena komponenta knjižnice ALTDDIO_OUT.

Morda je vse videti nekoliko zapleteno, vendar deluje.

Ogledate si lahko celotno izvorno kodo, napisano v Verilog HDL tukaj na githubu.

Prevedena vdelana programska oprema za FPGA je vdelana v čip EPCS, nameščen na plošči Mars Rover2rpi. Tako se ob priključitvi napajanja na ploščo FPGA FPGA inicializira iz bliskovnega pomnilnika in zažene.

Zdaj se moramo malo pogovoriti o konfiguraciji same maline.

Izvajam poskuse na Raspberry PI OS (32-bitni), ki temelji na Debian Buster, različica: avgust 2020,
Datum izdaje: 2020, različica jedra: 08.

Narediti morate dve stvari:

  • uredite datoteko config.txt;
  • ustvarite konfiguracijo strežnika X za delo z dvema monitorjema.

Ko urejate datoteko /boot/config.txt, morate:

  1. onemogočite uporabo i2c, i2s, spi;
  2. omogoči način DPI s prekrivanjem dtoverlay=dpi24;
  3. nastavite video način 1280×720 60Hz, 24 bitov na točko na DPI;
  4. določite zahtevano število okvirjev 2 (max_framebuffers=2, šele takrat se prikaže druga naprava /dev/fb1)

Celotno besedilo datoteke config.txt je videti takole.

# 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

Po tem morate ustvariti konfiguracijsko datoteko za strežnik X za uporabo dveh monitorjev na dveh medpomnilnikih okvirjev /dev/fb0 in /dev/fb1:

Moja konfiguracijska datoteka je /usr/share/x11/xorg.conf.d/60-dualscreen.conf, kot je ta

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

No, če še ni nameščen, potem morate namestiti Xinerama. Nato bo prostor na namizju v celoti razširjen na dva monitorja, kot je prikazano v zgornjem predstavitvenem videu.

To je verjetno vse. Zdaj bodo lastniki Raspberry Pi3 lahko uporabljali dva monitorja.

Opis in diagram plošče Mars Rover2rpi je lahko glej tukaj.

Vir: www.habr.com