Tweede HDMI-monitor na Raspberry Pi3 via DPI-koppelvlak en FPGA-bord


Hierdie video wys: 'n Raspberry Pi3-bord, wat daaraan gekoppel is via die GPIO-aansluiting, is 'n FPGA-bord Mars Rover2rpi (Cyclone IV), waaraan 'n HDMI-monitor gekoppel is. Die tweede monitor is gekoppel via die standaard HDMI-aansluiting van die Raspberry Pi3. Alles werk saam soos 'n dubbelmonitorstelsel.

Volgende sal ek jou vertel hoe dit geïmplementeer word.

Die gewilde Raspberry Pi3-bord het 'n GPIO-aansluiting waardeur jy verskeie uitbreidingskaarte kan koppel: sensors, LED's, stapmotorbestuurders en nog baie meer. Die presiese funksie van elke pen op 'n verbinding hang af van die poortkonfigurasie. Die GPIO ALT2-konfigurasie laat jou toe om die verbinding oor te skakel na DPI-koppelvlakmodus, Display Parallel Interface. Daar is uitbreidingskaarte vir die koppeling van VGA-monitors via DPI. Eerstens is VGA-monitors egter nie meer so algemeen soos HDMI nie, en tweedens is die digitale koppelvlak al hoe beter as die analoog. Boonop word die DAC op sulke VGA-uitbreidingsborde gewoonlik gemaak in die vorm van R-2-R-kettings en dikwels nie meer as 6 bisse per kleur nie.

In ALT2-modus het die GPIO-koppelpenne die volgende betekenis:

Tweede HDMI-monitor na Raspberry Pi3 via DPI-koppelvlak en FPGA-bord

Hier het ek die RGB-penne van die connector onderskeidelik rooi, groen en blou ingekleur. Ander belangrike seine is die V-SYNC en H-SYNC seine, sowel as CLK. Die CLK-klokfrekwensie is die frekwensie waarteen pixelwaardes na die aansluiting uitgevoer word; dit hang af van die geselekteerde videomodus.

Om 'n digitale HDMI-monitor te koppel, moet jy die DPI-seine van die koppelvlak vasvang en dit omskakel na HDMI-seine. Dit kan byvoorbeeld gedoen word deur 'n soort FPGA-bord te gebruik. Soos dit blyk, is die Mars Rover2rpi-bord geskik vir hierdie doeleindes. Om die waarheid te sê, die hoofopsie om hierdie bord deur 'n spesiale adapter te koppel, lyk soos volg:

Tweede HDMI-monitor na Raspberry Pi3 via DPI-koppelvlak en FPGA-bord

Hierdie bord word gebruik om die aantal GPIO-poorte te vermeerder en om meer randtoestelle aan die framboos te koppel. Terselfdertyd word 4 GPIO-seine met hierdie verbinding vir JTAG-seine gebruik, sodat die program van Raspberry die FPGA-firmware in die FPGA kan laai. As gevolg hiervan pas hierdie standaardverbinding my nie; 4 DPI-seine val uit. Gelukkig het die bykomende kamme op die bord 'n framboos-versoenbare pinout. So ek kan die bord 90 grade draai en dit steeds aan my framboos koppel:

Tweede HDMI-monitor na Raspberry Pi3 via DPI-koppelvlak en FPGA-bord

Natuurlik sal jy 'n eksterne JTAG-programmeerder moet gebruik, maar dit is nie 'n probleem nie.

Daar is nog 'n klein probleempie. Nie elke FPGA-pen kan as 'n klokinvoer gebruik word nie. Daar is slegs 'n paar toegewyde penne wat vir hierdie doeleindes gebruik kan word. Dit het dus hier geblyk dat die GPIO_0 CLK-sein nie die FPGA-invoer bereik nie, wat as 'n FPGA-klokinvoer gebruik kan word. So ek moes nog een draad op die serp sit. Ek verbind GPIO_0 en die bord se SLEUTEL[1] sein:

Tweede HDMI-monitor na Raspberry Pi3 via DPI-koppelvlak en FPGA-bord

Nou sal ek jou 'n bietjie vertel oor die FPGA-projek. Die grootste probleem met die opwekking van HDMI-seine is baie hoë frekwensies. As jy na die HDMI-konneksie-penout kyk, kan jy sien dat die RGB-seine nou seriële differensiële seine is:

Tweede HDMI-monitor na Raspberry Pi3 via DPI-koppelvlak en FPGA-bord

Die gebruik van 'n differensiële sein laat jou toe om algemene modus-interferensie op die transmissielyn te bekamp. In hierdie geval word die oorspronklike agt-bis-kode van elke kleursein omgeskakel na 'n 10-bis TMDS (Transition-minimized differential signaling). Dit is 'n spesiale koderingsmetode om die GS-komponent van die sein te verwyder en seinwisseling in 'n differensiële lyn te minimaliseer. Aangesien 10 bisse nou oor die reekslyn gestuur moet word vir een greep kleur, blyk dit dat die serialiseerder-klokspoed 10 keer hoër as die pixel-klokspoed moet wees. As ons byvoorbeeld die videomodus 1280x720 60Hz neem, dan is die pixelfrekwensie van hierdie modus 74,25 MHz. Die serializer moet 742,5 MHz wees.

Gereelde FPGA's is ongelukkig nie daartoe in staat nie. Gelukkig vir ons het die FPGA egter ingeboude DDIO-penne. Dit is gevolgtrekkings wat reeds as 't ware 2-tot-1-reeksvormers is. Dit wil sê, hulle kan twee bisse opeenvolgend op die stygende en dalende rande van die klokfrekwensie uitvoer. Dit beteken dat jy in 'n FPGA-projek nie 740 MHz kan gebruik nie, maar 370 MHz, maar jy moet DDIO-uitsetelemente in die FPGA gebruik. Nou is 370 MHz reeds 'n heeltemal haalbare frekwensie. Ongelukkig is 1280x720-modus die limiet. 'n Hoër resolusie kan nie bereik word in ons Cyclone IV FPGA wat op die Mars Rover2rpi-bord geïnstalleer is nie.

Dus, in die ontwerp, gaan die inset pixel frekwensie CLK na die PLL, waar dit vermenigvuldig word met 5. By hierdie frekwensie word die R, G, B grepe omgeskakel na bispare. Dit is wat die TMDS-enkodeerder doen. Die bronkode in Verilog HDL lyk soos volg:

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

Dan word die uitsetpare na die DDIO-uitset gevoer, wat opeenvolgend 'n eenbis-sein op die stygende en dalende rande produseer.

DDIO self kan beskryf word met die volgende Verilog-kode:

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

Maar dit sal heel waarskynlik nie so werk nie. Jy moet Alter se megafunksie ALTDDIO_OUT gebruik om die DDIO-uitsetelemente werklik te aktiveer. My projek gebruik die ALTDDIO_OUT-biblioteekkomponent.

Dit kan alles 'n bietjie lastig lyk, maar dit werk.

U kan al die bronkode sien wat in Verilog HDL geskryf is hier op github.

Die saamgestelde firmware vir die FPGA word in die EPCS-skyfie geflits wat op die Mars Rover2rpi-bord geïnstalleer is. Dus, wanneer krag op die FPGA-bord toegepas word, sal die FPGA vanaf flitsgeheue geïnisialiseer word en begin.

Nou moet ons 'n bietjie praat oor die konfigurasie van die Framboos self.

Ek doen eksperimente op Raspberry PI OS (32 bis) gebaseer op Debian Buster, Weergawe: Augustus 2020,
Vrystellingsdatum: 2020-08-20, kernweergawe: 5.4.

Jy moet twee dinge doen:

  • wysig die config.txt-lêer;
  • skep 'n X-bedienerkonfigurasie om met twee monitors te werk.

Wanneer jy die /boot/config.txt-lêer wysig benodig jy:

  1. deaktiveer die gebruik van i2c, i2s, spi;
  2. aktiveer DPI-modus met behulp van overlay dtoverlay=dpi24;
  3. konfigureer videomodus 1280×720 60Hz, 24 bisse per pixel op DPI;
  4. spesifiseer die vereiste aantal raambuffers 2 (max_framebuffers=2, eers dan sal die tweede toestel /dev/fb1 verskyn)

Die volledige teks van die config.txt-lêer lyk so.

# 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

Hierna moet jy 'n konfigurasielêer vir die X-bediener skep om twee monitors op twee raambuffers /dev/fb0 en /dev/fb1 te gebruik:

My konfigurasielêer /usr/share/x11/xorg.conf.d/60-dualscreen.conf is so

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

Wel, as dit nie reeds geïnstalleer is nie, moet jy Xinerama installeer. Dan sal die lessenaarspasie volledig uitgebrei word na twee monitors, soos getoon in die demo-video hierbo.

Dis seker al. Raspberry Pi3-eienaars sal nou twee monitors kan gebruik.

Beskrywing en stroombaandiagram van die Mars Rover2rpi-bord kan gevind word kyk hier.

Bron: will.com