Dua HDMI-ekrano al Raspberry Pi3 per DPI-interfaco kaj FPGA-tabulo


Ĉi tiu video montras: Raspberry Pi3-tabulo, al kiu, per la GPIO-konektilo, estas konektita FPGA Mars Rover2rpi (Cyclone IV)-tabulo, al kiu estas konektita HDMI-ekrano. La dua monitoro estas konektita per la norma Raspberry Pi3 HDMI-konektilo. Tute kune ĝi funkcias kiel duobla monitora sistemo.

Poste mi rakontos al vi kiel ĝi estas efektivigita.

La populara Raspberry Pi3-tabulo havas GPIO-konektilon per kiu vi povas konekti diversajn ekspansiajn tabulojn: sensilojn, LED-ojn, paŝomotorajn ŝoforojn kaj multe pli. La specifa funkcio de ĉiu pinglo sur la konektilo dependas de la havena agordo. La agordo GPIO ALT2 ebligas al vi ŝanĝi la konektilon al la DPI-interfaca reĝimo, Montru Paralela Interfaco. Estas ekspansiotabuloj por konekti VGA-ekrulojn per DPI. Tamen, unue, VGA-ekranoj ne plu estas tiel oftaj kiel HDMI, kaj due, la cifereca interfaco pliboniĝas ol analoga. Krome, la DAC sur tiaj VGA-vastiĝkartoj estas kutime farita en la formo de R-2-R-ĉenoj kaj ofte ne pli ol 6 bitoj per koloro.

En ALT2-reĝimo, la pingloj de la GPIO-konektilo havas la sekvan signifon:

Dua HDMI-ekrano al Raspberry Pi3 per DPI-interfaco kaj FPGA-tabulo

Ĉi tie mi kolorigis la RGB-pinglojn de la konektilo respektive ruĝa, verda kaj blua. Aliaj gravaj signaloj estas la V-SYNC kaj H-SYNC balaitaj sinkronigaj signaloj, same kiel CLK. La CLK-horloĝfrekvenco estas la frekvenco, je kiu pikselaj valoroj estas eligitaj al la konektilo kaj dependas de la elektita videoreĝimo.

Por konekti ciferecan HDMI-moniton, vi devas kapti DPI-interfacajn signalojn kaj konverti ilin al HDMI-signaloj. Ĉi tio povas esti farita, ekzemple, uzante ajnan FPGA-tabulon. Kiel evidentiĝis, la Mars Rover2rpi-tabulo taŭgas por ĉi tiu celo. Verdire, la ĉefa opcio por konekti ĉi tiun tabulo per speciala adaptilo aspektas jene:

Dua HDMI-ekrano al Raspberry Pi3 per DPI-interfaco kaj FPGA-tabulo

Ĉi tiu tabulo estas uzata por pliigi la nombron da GPIO-havenoj kaj por konekti pli da ekstercentraj al la frambo. Samtempe, 4 GPIO-signaloj kun ĉi tiu konekto estas uzataj por JTAG-signaloj, tiel ke la programo de la distribuo povas ŝarĝi la FPGA-firmware en la FPGA. Pro tio, tia regula konekto ne konvenas al mi, 4 DPI-signaloj foriĝas. Feliĉe, la ekstraj kombiloj sur la tabulo havas frambokongruan pinton. Por ke mi povu turni la tabulon 90 gradojn kaj ankoraŭ konekti ĝin al mia frambo:

Dua HDMI-ekrano al Raspberry Pi3 per DPI-interfaco kaj FPGA-tabulo

Kompreneble, vi devos uzi eksteran JTAG-programiston, sed ĉi tio ne estas problemo.

Estas ankoraŭ malgranda problemo. Ne ĉiu FPGA-stifto povas esti uzata kiel horloĝa enigo. Estas nur kelkaj dediĉitaj pingloj, kiuj povas esti uzataj por ĉi tiu celo. Do montriĝis ĉi tie, ke la GPIO_0 CLK-signalo ne atingas la FPGA-enigon, kiu povas esti uzata kiel FPGA-horloĝ-enigo. Do tute egale, mi devis ĵeti unu afiŝon sur koltukon. Mi konektas GPIO_0 kaj KEY[1] signalon de la tabulo:

Dua HDMI-ekrano al Raspberry Pi3 per DPI-interfaco kaj FPGA-tabulo

Nun mi rakontos al vi iom pri la projekto en la FPGA. La ĉefa malfacilaĵo en la formado de HDMI-signaloj estas tre altaj frekvencoj. Rigardante la pinout de la HDMI-konektilo, vi povas vidi, ke la RGB-signaloj nun estas seriaj diferencigaj signaloj:

Dua HDMI-ekrano al Raspberry Pi3 per DPI-interfaco kaj FPGA-tabulo

La uzo de diferenciga signalo permesas al vi trakti komunan reĝiman bruon sur la transmisilinio. En ĉi tiu kazo, la origina ok-bita kodo de ĉiu kolorsignalo estas konvertita en 10-bitan TMDS (Transir-minimigita diferenciala signalado). Ĉi tio estas speciala kodiga metodo por forigi la DC-komponenton de la signalo kaj minimumigi signalŝanĝon en la diferenciga linio. Ĉar nun estas 10 bitoj por transdoni po bajto de koloro super la seria linio, rezultas, ke la horloĝfrekvenco de la seriigilo devas esti 10 fojojn pli alta ol la horloĝfrekvenco de la pikseloj. Se ni prenas ekzemple la videoreĝimon 1280x720 60Hz, tiam la piksela ofteco de ĉi tiu reĝimo estas 74,25MHz. La seriigilo devus esti 742,5 MHz.

Konvenciaj FPGAoj ĝenerale ne kapablas tion, bedaŭrinde. Tamen, al nia bonŝanco, FPGA-oj havas enkonstruitajn DDIO-pinglojn. Ĉi tiuj estas konkludoj, kiuj jam estas kvazaŭ 2-al-1 seriigiloj. Tio estas, ili povas eligi du bitojn en sinsekvo laŭ la altiĝantaj kaj malkreskantaj horloĝfrekvencoj. Ĉi tio signifas, ke en la FPGA-projekto vi povas uzi ne 740 MHz, sed 370 MHz, sed vi devas uzi la DDIO-eligelementojn en la FPGA. Ĉi tie 370 MHz jam estas sufiĉe atingebla frekvenco. Bedaŭrinde, la 1280×720 reĝimo estas la limo. Pli alta rezolucio ne povas esti atingita en nia FPGA Cyclone IV instalita sur la Rover2rpi-tabulo.

Do, en la projekto, la eniga piksela frekvenco CLK estas provizita al la PLL, kie ĝi estas multobligita per 5. Je ĉi tiu frekvenco, la R, G, B-bajtoj estas konvertitaj en bitajn parojn. Jen kion faras la TMDS-kodilo. La fontkodo sur Verilog HDL aspektas jene:

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

Tiam la produktaĵparoj estas provizitaj al la DDIO-produktaĵo, kiu sinsekve produktas unu-bitan signalon sur la pliiĝo kaj falo.

DDIO mem povus esti priskribita per Verilog-kodo jene:

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

Sed verŝajne ĝi ne funkcios tiel. Vi devas uzi la megafunkcion ALTDDIO_OUT de Altera por efektive uzi la eligelementojn de DDIO. En mia projekto, la biblioteko komponanto ALTDDIO_OUT estas uzata.

Eble ĉio aspektas iom malfacila, sed ĝi funkcias.

Vi povas vidi la tutan fontkodon skribitan en Verilog HDL ĝuste ĉi tie sur github.

La kompilita firmvaro por la FPGA estas enigita en la EPCS-peceton instalitan sur la Mars Rover2rpi-tabulo. Tiel, kiam potenco estas aplikita al la FPGA-tabulo, la FPGA pravalorigos de fulmmemoro kaj komenciĝos.

Nun ni devas iomete paroli pri la agordo de la Frambo mem.

Mi faras eksperimentojn pri Raspberry PI OS (32 bitoj) bazitaj sur Debian Buster, Versio:aŭgusto 2020,
Eldondato:2020-08-20, Kerna versio:5.4.

Vi devas fari du aferojn:

  • redakti la dosieron config.txt;
  • kreu X-servilan agordon por labori kun du ekranoj.

Kiam vi redaktas la dosieron /boot/config.txt, vi devas:

  1. malŝalti la uzon de i2c, i2s, spi;
  2. ebligu DPI-reĝimon kun overlay dtoverlay=dpi24;
  3. agordu videoreĝimon 1280×720 60Hz, 24 bitojn po punkto per DPI;
  4. specifu la bezonatan nombron da framebuffers 2 (max_framebuffers=2, nur tiam aperos la dua aparato /dev/fb1)

La plena teksto de la dosiero config.txt aspektas tiel.

# 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

Post tio, vi devas krei agordan dosieron por la X-servilo por uzi du ekranojn sur du framebuffer /dev/fb0 kaj /dev/fb1:

Mia agorda dosiero estas /usr/share/x11/xorg.conf.d/60-dualscreen.conf tia

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

Nu, se ne jam instalite, tiam vi devas instali Xinerama. Tiam la labortabla spaco estos plene etendita al du ekranoj, kiel montrite en la demo-video supre.

Tio verŝajne estas ĉio. Nun, posedantoj de Raspberry Pi3 povos uzi du ekranojn.

Priskribo kaj diagramo de la Mars Rover2rpi-tabulo povas esti vidu ĉi tie.

fonto: www.habr.com