Secondu monitor HDMI à Raspberry Pi3 via interfaccia DPI è scheda FPGA


Stu video mostra: una scheda Raspberry Pi3, à quale, per via di u cunnessu GPIO, hè cunnessu una scheda FPGA Mars Rover2rpi (Cyclone IV), à quale hè cunnessu un monitor HDMI. U secondu monitoru hè cunnessu cù u cunnessu standard Raspberry Pi3 HDMI. Tuttu inseme funziona cum'è un sistema di monitor duale.

Dopu vi dicu cumu hè implementatu.

U pupulari Raspberry Pi3 hà un cunnessu GPIO per mezu di quale pudete cunnette diverse schede di espansione: sensori, LED, driver di mutore stepper è assai più. A funzione specifica di ogni pin in u connector dipende da a cunfigurazione di u portu. A cunfigurazione GPIO ALT2 permette di cambià u connettore à u modu di interfaccia DPI, Display Parallel Interface. Ci sò schede di espansione per cunnette monitors VGA via DPI. In ogni casu, prima, i monitori VGA ùn sò più cumunu cum'è HDMI, è in segundu, l'interfaccia digitale hè megliu cà l'analogicu. Inoltre, u DAC in tali carte di espansione VGA hè generalmente fattu in forma di catene R-2-R è spessu micca più di 6 bits per culore.

In u modu ALT2, i pins di u connettore GPIO anu u significatu seguente:

Secondu monitor HDMI à Raspberry Pi3 via interfaccia DPI è scheda FPGA

Quì aghju culuritu i pins RGB di u connettore rossu, verde è blu rispettivamente. Altri signali impurtanti sò i segnali di sincronia di scansione V-SYNC è H-SYNC, è ancu CLK. A frequenza di u clock CLK hè a frequenza à a quale i valori di pixel sò emessi à u connettore è dipende da u modu video sceltu.

Per cunnette un monitor HDMI digitale, avete bisognu di catturà signali di l'interfaccia DPI è cunvertisce in signali HDMI. Questu pò esse fattu, per esempiu, usendu qualsiasi FPGA board. Cum'è s'hè risultatu, u bordu Mars Rover2rpi hè adattatu per questu scopu. In verità, l'opzione principale per cunnette sta scheda à traversu un adattatore speciale hè cusì:

Secondu monitor HDMI à Raspberry Pi3 via interfaccia DPI è scheda FPGA

Questa tavola hè aduprata per aumentà u nùmeru di porti GPIO è per cunnette più periferiche à u raspberry. À u listessu tempu, i signali 4 GPIO cù questa cunnessione sò usati per i signali JTAG, perchè u prugramma da a distribuzione pò carricà u firmware FPGA in a FPGA. Per via di questu, una cunnessione cusì regulare ùn mi cunvene micca, i signali 4 DPI abbanduneghjanu. Per furtuna, i pettini extra nantu à a tavula anu un pinout compatibile cù Raspberry. Per pudè rotà a tavula 90 gradi è ancu cunnette à u mo raspberry:

Secondu monitor HDMI à Raspberry Pi3 via interfaccia DPI è scheda FPGA

Di sicuru, duverete aduprà un programatore JTAG esternu, ma questu ùn hè micca un prublema.

Ci hè sempre un picculu prublema. Ùn ogni pin FPGA pò esse usatu cum'è input di clock. Ci hè solu uni pochi di pins dedicati chì ponu esse usatu per questu scopu. Allora hè risultatu quì chì u signale GPIO_0 CLK ùn vene micca à l'input FPGA, chì pò esse usatu cum'è un input clock FPGA. Allora tuttu u listessu, aghju avutu à scaccià un postu nantu à una sciarpa. Aghju cunnessu GPIO_0 è KEY[1] signale di u bordu:

Secondu monitor HDMI à Raspberry Pi3 via interfaccia DPI è scheda FPGA

Avà vi dicu un pocu di u prugettu in u FPGA. A principal difficultà in a furmazione di signali HDMI hè frequenze assai alte. Fighjendu u pinout di u connettore HDMI, pudete vede chì i signali RGB sò oghji signali differenziali seriali:

Secondu monitor HDMI à Raspberry Pi3 via interfaccia DPI è scheda FPGA

L'usu di un signalu differenziale permette di trattà cù u rumore di modu cumuni nantu à a linea di trasmissione. In questu casu, u codice uriginale di ottu bit di ogni signale di culore hè cunvertitu in un TMDS 10-bit (signalizazione differenziale minimizzata da a transizione). Questu hè un metudu di codificazione speciale per sguassà u cumpunente DC da u signale è minimizzà u cambiamentu di signale in a linea differenziale. Siccomu ci sò avà 10 bits per trasmette per byte di culore nantu à a linea seriale, risulta chì a freccia di u clock di u serializatore deve esse 10 volte più altu ch'è a freccia di clock di i pixel. Se pigghiamu per esempiu u modu video 1280x720 60Hz, allura a freccia di pixel di questu modu hè 74,25MHz. U serializatore deve esse 742,5 MHz.

I FPGA convenzionali sò generalmente micca capaci di questu, sfurtunatamenti. Tuttavia, à a nostra furtuna, i FPGA anu pins DDIO integrati. Quessi sò cunclusioni chì sò digià, per esse, serializzatori 2-à-1. Vale à dì, ponu pruduce dui bits in sequenza longu à e frequenze di clock crescente è discendente. Questu significa chì in u prughjettu FPGA pudete aduprà micca 740 MHz, ma 370 MHz, ma avete bisognu di utilizà l'elementi di output DDIO in a FPGA. Quì 370 MHz hè digià una frequenza abbastanza raggiungibile. Sfurtunatamente, u modu 1280 × 720 hè u limitu. Una risuluzione più alta ùn pò esse ottenuta in u nostru FPGA Cyclone IV installatu nantu à a scheda Rover2rpi.

Allora, in u prugettu, a freccia di pixel di input CLK hè alimentata à u PLL, induve hè multiplicatu da 5. À questa freccia, i bytes R, G, B sò cunvertiti in bit pairs. Questu hè ciò chì face l'encoder TMDS. U codice fonte nantu à Verilog HDL hè cusì:

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

Allora i coppie di output sò alimentati à l'output DDIO, chì produce in sequenza un signalu di un bit in crescita è caduta.

DDIO stessu puderia esse descrittu cù u codice Verilog cusì:

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

Ma probabilmente ùn funziona micca cusì. Avete bisognu di utilizà a megafunzione ALTDDIO_OUT d'Altera per aduprà veramente l'elementi di output DDIO. In u mo prughjettu, u cumpunente di biblioteca ALTDDIO_OUT hè utilizatu.

Tuttu pò pare un pocu complicatu, ma funziona.

Pudete vede tuttu u codice fonte scrittu in Verilog HDL ghjustu quì nantu à github.

U firmware compilatu per a FPGA hè incrustatu in u chip EPCS installatu nantu à a scheda Mars Rover2rpi. Cusì, quandu u putere hè appiicatu à a scheda FPGA, u FPGA inizializzarà da a memoria flash è principià.

Avà avemu bisognu di parlà un pocu di a cunfigurazione di u Raspberry stessu.

Facciu esperimenti nantu à Raspberry PI OS (32 bit) basatu annantu à Debian Buster, Versione: Aostu 2020,
Data di liberazione: 2020-08-20, Versione di u Kernel: 5.4.

Avete bisognu di fà duie cose:

  • edità u schedariu config.txt;
  • crea una cunfigurazione di u servitore X per travaglià cù dui monitori.

Quandu editate u schedariu /boot/config.txt, avete bisognu di:

  1. disattivà l'usu di i2c, i2s, spi;
  2. attivà u modu DPI cù overlay dtoverlay = dpi24;
  3. set video mode 1280 × 720 60Hz, 24 bits per point per DPI;
  4. Specificate u numeru necessariu di framebuffers 2 (max_framebuffers = 2, solu dopu apparirà u sicondu dispositivu /dev/fb1)

U testu sanu di u schedariu config.txt pare cusì.

# 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

Dopu questu, avete bisognu di creà un schedariu di cunfigurazione per u servore X per utilizà dui monitori nantu à dui framebuffer /dev/fb0 è /dev/fb1:

U mo schedariu di cunfigurazione hè /usr/share/x11/xorg.conf.d/60-dualscreen.conf cusì

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

Ebbè, s'ellu ùn hè digià stallatu, allura vi tuccherà à stallà Xinerama. Allora u spaziu di u desktop sarà cumplettamente allargatu à dui monitori, cum'è mostra in u video demo sopra.

Hè probabilmente tuttu. Avà, i pruprietarii di Raspberry Pi3 puderanu aduprà dui monitori.

Descrizzione è schema di u bordu Mars Rover2rpi pò esse vede quì.

Source: www.habr.com