Zweet HDMI Monitor zu Raspberry Pi3 iwwer DPI Interface an FPGA Verwaltungsrot


Dëse Video weist: e Raspberry Pi3 Board, deen iwwer de GPIO Connector ugeschloss ass, ass e FPGA Board Mars Rover2rpi (Cyclone IV), mat deem en HDMI Monitor ugeschloss ass. Den zweeten Monitor ass iwwer de Standard HDMI Connector vum Raspberry Pi3 verbonnen. Alles funktionnéiert zesummen wéi en Dual Monitor System.

Als nächst wäert ech Iech soen wéi dëst ëmgesat gëtt.

De populäre Raspberry Pi3 Board huet e GPIO Connector, duerch deen Dir verschidde Expansiounskaarte verbënnt: Sensoren, LEDs, Steppermotortreiber a vill méi. Déi exakt Funktioun vun all Pin op engem Stecker hänkt vun der Portkonfiguratioun of. D'GPIO ALT2 Konfiguratioun erlaabt Iech de Connector op DPI Interface Modus ze wiesselen, Display Parallel Interface. Et gi Expansiounskaarte fir VGA Monitore iwwer DPI ze verbannen. Awer éischtens sinn VGA Monitore net méi sou heefeg wéi HDMI, an zweetens ass déi digital Interface ëmmer méi besser wéi déi analog. Ausserdeem gëtt den DAC op esou VGA Expansiounsplaten normalerweis a Form vu R-2-R Ketten gemaach an dacks net méi wéi 6 Bits pro Faarf.

Am ALT2 Modus hunn d'GPIO Connector Pins déi folgend Bedeitung:

Zweet HDMI Monitor zu Raspberry Pi3 iwwer DPI Interface an FPGA Verwaltungsrot

Hei hunn ech d'RGB Pins vum Connector rout, gréng a blo respektiv faarweg. Aner wichteg Signaler sinn d'V-SYNC an H-SYNC Signaler, souwéi CLK. D'CLK Auerfrequenz ass d'Frequenz op där Pixelwäerter op de Connector erausginn; et hänkt vum gewielte Videomodus of.

Fir en digitale HDMI Monitor ze verbannen, musst Dir d'DPI Signaler vun der Interface erfaassen an se an HDMI Signaler konvertéieren. Dëst kann zum Beispill gemaach ginn, mat enger Zort FPGA-Verwaltungsrot. Wéi et sech erausstellt, ass de Mars Rover2rpi Board fir dës Zwecker gëeegent. Tatsächlech gesäit d'Haaptoptioun fir dëse Board duerch e speziellen Adapter ze verbannen esou aus:

Zweet HDMI Monitor zu Raspberry Pi3 iwwer DPI Interface an FPGA Verwaltungsrot

Dëse Board gëtt benotzt fir d'Zuel vun de GPIO Ports ze erhéijen a méi Peripheriegeräter mat der Himbeer ze verbannen. Zur selwechter Zäit gi 4 GPIO Signaler mat dëser Verbindung fir JTAG Signaler benotzt, sou datt de Programm vu Raspberry d'FPGA Firmware an d'FPGA luede kann. Dofir passt dës Standardverbindung mir net; 4 DPI Signaler falen eraus. Glécklecherweis hunn déi zousätzlech Kamm um Bord e Raspberry-kompatibel Pinout. Also ech kann de Board 90 Grad rotéieren an et nach ëmmer mat mengem Himbeer verbannen:

Zweet HDMI Monitor zu Raspberry Pi3 iwwer DPI Interface an FPGA Verwaltungsrot

Natierlech musst Dir en externen JTAG Programméierer benotzen, awer dëst ass kee Problem.

Et gëtt nach e klenge Problem. Net all FPGA Pin kann als Auer Input benotzt ginn. Et ginn nëmmen e puer dedizéierten Pins déi fir dës Zwecker benotzt kënne ginn. Also huet et hei erausgestallt datt de GPIO_0 CLK Signal net den FPGA Input erreecht, deen als FPGA Auer Input benotzt ka ginn. Also hunn ech nach een Drot op de Schal missen setzen. Ech verbannen GPIO_0 an de KEY[1] Signal vum Bord:

Zweet HDMI Monitor zu Raspberry Pi3 iwwer DPI Interface an FPGA Verwaltungsrot

Elo wäert ech Iech e bëssen iwwer de FPGA Projet soen. D'Haaptschwieregkeet fir HDMI Signaler ze generéieren ass ganz héich Frequenzen. Wann Dir den HDMI Connector Pinout kuckt, kënnt Dir gesinn datt d'RGB Signaler elo seriell Differenziell Signaler sinn:

Zweet HDMI Monitor zu Raspberry Pi3 iwwer DPI Interface an FPGA Verwaltungsrot

D'Benotzung vun engem Differenzial Signal erlaabt Iech gemeinsam Modus Stéierungen op der Transmissioun Linn ze bekämpfen. An dësem Fall gëtt den ursprénglechen Aacht-Bit Code vun all Faarfsignal an en 10-Bit TMDS (Transition-minimized differential signaling) ëmgewandelt. Dëst ass eng speziell Kodéierungsmethod fir den DC Komponent aus dem Signal ze läschen an d'Signalschalter an enger Differenzlinn ze minimiséieren. Zënter 10 Bits mussen elo iwwer d'Seriallinn fir e Byte vu Faarf iwwerdroe ginn, stellt sech eraus datt d'Serializer-Auergeschwindegkeet 10 Mol méi héich muss sinn wéi d'Pixel-Auergeschwindegkeet. Wa mir zum Beispill de Videomodus 1280x720 60Hz huelen, dann ass d'Pixelfrequenz vun dësem Modus 74,25 MHz. De Serializer soll 742,5 MHz sinn.

Regelméisseg FPGAs, leider, sinn net kapabel vun dëser. Wéi och ëmmer, glécklecherweis fir eis, huet d'FPGA gebaut-an DDIO Pins. Dëst sinn Conclusiounen déi schonn, wéi et war, 2-zu-1 Serializer sinn. Dat ass, si kënnen zwee Bits sequenziell op déi eropgaang a falen Kante vun der Auerfrequenz ausginn. Dëst bedeit datt Dir an engem FPGA Projet net 740 MHz, mee 370 MHz benotze kënnt, awer Dir musst DDIO Output Elementer an der FPGA benotzen. Elo ass 370 MHz schonn eng komplett erreechbar Frequenz. Leider ass 1280x720 Modus d'Limite. Eng méi héich Opléisung kann net an eisem Cyclone IV FPGA op der Mars Rover2rpi Board installéiert ginn.

Also, am Design geet d'Input-Pixelfrequenz CLK op de PLL, wou se mat 5 multiplizéiert gëtt. Bei dëser Frequenz ginn d'R, G, B-Bytes a Bitpair ëmgewandelt. Dëst ass wat den TMDS Encoder mécht. De Quellcode am Verilog HDL gesäit esou aus:

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

Dann ginn d'Ausgangspaaren op den DDIO-Output gefüttert, wat sequenziell e One-Bit-Signal op den erop- a falende Kanten produzéiert.

DDIO selwer kéint mat de folgende Verilog Code beschriwwe ginn:

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

Awer et wäert héchstwahrscheinlech net esou funktionnéieren. Dir musst Alter d'Megafunktioun ALTDDIO_OUT benotzen fir tatsächlech d'DDIO Output Elementer z'aktivéieren. Mäi Projet benotzt der ALTDDIO_OUT Bibliothéik Komponente.

Dëst kann alles e bësse komplizéiert ausgesinn, awer et funktionnéiert.

Dir kënnt all Quellcode gesinn, déi am Verilog HDL geschriwwe sinn hei op github.

Déi kompiléiert Firmware fir d'FPGA gëtt an den EPCS Chip op der Mars Rover2rpi Board installéiert. Also, wann d'Kraaft op de FPGA Board applizéiert gëtt, gëtt d'FPGA vum Flash Memory initialiséiert a fänkt un.

Elo musse mir e bëssen iwwer d'Konfiguratioun vum Raspberry selwer schwätzen.

Ech maachen Experimenter op Raspberry PI OS (32 Bit) baséiert op Debian Buster, Versioun: August 2020,
Verëffentlechungsdatum: 2020-08-20, Kernel Versioun: 5.4.

Dir musst zwou Saache maachen:

  • d'config.txt Datei änneren;
  • eng X-Serverkonfiguratioun erstellen fir mat zwee Monitore ze schaffen.

Wann Dir d'Datei /boot/config.txt ännert braucht Dir:

  1. deaktivéieren d'Benotzung vun i2c, i2s, spi;
  2. aktivéiert DPI Modus mat Iwwerlagerung dtoverlay = dpi24;
  3. Video Modus konfiguréieren 1280 × 720 60Hz, 24 Bits pro Pixel op DPI;
  4. spezifizéiert déi erfuerderlech Zuel vu Framebuffers 2 (max_framebuffers = 2, nëmmen dann erschéngt den zweeten Apparat /dev/fb1)

De ganzen Text vun der config.txt Datei gesäit esou aus.

# 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

Duerno musst Dir eng Konfiguratiounsdatei fir den X Server erstellen fir zwee Monitore op zwee Framebuffers /dev/fb0 an /dev/fb1 ze benotzen:

Meng Konfiguratiounsdatei /usr/share/x11/xorg.conf.d/60-dualscreen.conf ass esou

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

Gutt, wann et net schonn installéiert ass, da musst Dir Xinerama installéieren. Da gëtt den Desktopraum komplett op zwee Monitore erweidert, wéi am Demo Video hei uewen gewisen.

Dat ass wahrscheinlech alles. Elo kënnen Raspberry Pi3 Besëtzer zwee Monitore benotzen.

Beschreiwung an Circuit Diagramm vun der Mars Rover2rpi Verwaltungsrot kann fonnt ginn kuckt hei.

Source: will.com