Második HDMI monitor a Raspberry Pi3-hoz DPI interfészen és FPGA kártyán keresztül


Ezen a videón látható: egy Raspberry Pi3 kártya, amelyhez a GPIO csatlakozón keresztül csatlakozik egy Mars Rover2rpi (Cyclone IV) FPGA kártya, amelyhez HDMI monitor csatlakozik. A második monitor a Raspberry Pi3 szabványos HDMI-csatlakozóján keresztül csatlakozik. Minden úgy működik együtt, mint egy kétmonitoros rendszer.

A következőkben elmondom, hogyan valósítják meg ezt.

A népszerű Raspberry Pi3 kártya GPIO csatlakozóval rendelkezik, amelyen keresztül különféle bővítőkártyákat csatlakoztathat: érzékelők, LED-ek, léptetőmotor-meghajtók és még sok más. A csatlakozó minden érintkezőjének pontos funkciója a port konfigurációjától függ. A GPIO ALT2 konfiguráció lehetővé teszi, hogy a csatlakozót DPI interfész módba, Display Parallel Interface módba kapcsolja. Vannak bővítőkártyák a VGA-monitorok DPI-n keresztüli csatlakoztatásához. Egyrészt azonban a VGA-monitorok már nem olyan elterjedtek, mint a HDMI, másrészt a digitális interfész egyre jobb, mint az analóg. Ezenkívül az ilyen VGA bővítőkártyákon a DAC rendszerint R-2-R láncok formájában készül, és gyakran nem több, mint 6 bit színenként.

ALT2 módban a GPIO csatlakozó érintkezőinek jelentése a következő:

Második HDMI monitor a Raspberry Pi3-hoz DPI interfészen és FPGA kártyán keresztül

Itt a csatlakozó RGB érintkezőit pirosra, zöldre és kékre színeztem. További fontos jelek a V-SYNC és H-SYNC jelek, valamint a CLK. A CLK órajel frekvenciája az a frekvencia, amelyen a pixelértékek a csatlakozóhoz kerülnek; ez a kiválasztott videó módtól függ.

Digitális HDMI-monitor csatlakoztatásához rögzítenie kell az interfész DPI-jeleit, és át kell alakítania azokat HDMI-jelekké. Ez megtehető például valamilyen FPGA kártya segítségével. Mint kiderült, a Mars Rover2rpi tábla alkalmas ezekre a célokra. Valójában a kártya speciális adapteren keresztüli csatlakoztatásának fő lehetősége így néz ki:

Második HDMI monitor a Raspberry Pi3-hoz DPI interfészen és FPGA kártyán keresztül

Ez a kártya a GPIO portok számának növelésére és több periféria csatlakoztatására szolgál a Raspberry-hez. Ugyanakkor ezzel a csatlakozással 4 GPIO jelet használnak a JTAG jelekhez, így a Raspberry programja betöltheti az FPGA firmware-t az FPGA-ba. Emiatt ez a szabványos csatlakozás nem felel meg nekem, 4 DPI jel kiesik. Szerencsére a tábla további fésűinek Raspberry-kompatibilis tűje van. Így el tudom forgatni a táblát 90 fokkal, és továbbra is csatlakoztathatom a málnámhoz:

Második HDMI monitor a Raspberry Pi3-hoz DPI interfészen és FPGA kártyán keresztül

Természetesen külső JTAG programozót kell használnia, de ez nem probléma.

Még mindig van egy kis probléma. Nem minden FPGA érintkező használható órabemenetként. Csak néhány dedikált tű használható erre a célra. Itt tehát kiderült, hogy a GPIO_0 CLK jel nem éri el az FPGA órajel bemenetként használható FPGA bemenetet. Így még rá kellett raknom egy drótot a sálra. Csatlakozom a GPIO_0-t és a kártya KEY[1] jelét:

Második HDMI monitor a Raspberry Pi3-hoz DPI interfészen és FPGA kártyán keresztül

Most mesélek egy kicsit az FPGA projektről. A HDMI-jelek generálásának fő nehézségét a nagyon magas frekvenciák jelentik. Ha megnézi a HDMI-csatlakozó kivezetését, láthatja, hogy az RGB-jelek mostantól soros differenciáljelek:

Második HDMI monitor a Raspberry Pi3-hoz DPI interfészen és FPGA kártyán keresztül

A differenciáljel használata lehetővé teszi a közös módú interferencia leküzdését az átviteli vonalon. Ebben az esetben az egyes színjelek eredeti nyolc bites kódja 10 bites TMDS-vé (Transition-minimized differential signaling) alakul. Ez egy speciális kódolási módszer a DC komponens eltávolítására a jelből, és minimalizálja a jelváltást a differenciálvonalban. Mivel most 10 bitet kell továbbítani a soros vonalon egy bájt színért, kiderül, hogy a sorosító órajelének 10-szer nagyobbnak kell lennie, mint a pixel órajel. Ha például a 1280x720 60Hz videó módot vesszük, akkor ennek az üzemmódnak a pixelfrekvenciája 74,25 MHz. A sorosítónak 742,5 MHz-nek kell lennie.

A normál FPGA-k erre sajnos nem képesek. Szerencsére azonban az FPGA rendelkezik beépített DDIO érintkezőkkel. Ezek olyan következtetések, amelyek már eleve 2 az 1-hez szerializálók. Azaz két bitet tudnak egymás után kiadni az órajel frekvencia felfutó és lefutó élén. Ez azt jelenti, hogy egy FPGA projektben nem 740 MHz-et, hanem 370 MHz-et használhatunk, de az FPGA-ban DDIO kimeneti elemeket kell használni. Most a 370 MHz már teljesen elérhető frekvencia. Sajnos az 1280x720-as mód a határ. Nagyobb felbontás nem érhető el a Mars Rover2rpi kártyára telepített Cyclone IV FPGA-val.

Tehát a tervezésben a CLK bemeneti pixelfrekvencia a PLL-be kerül, ahol megszorozzák 5-tel. Ezen a frekvencián az R, G, B bájtok bitpárokká alakulnak. Ezt teszi a TMDS kódoló. A Verilog HDL forráskódja így néz ki:

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

Ezután a kimeneti párok a DDIO kimenetre kerülnek, amely szekvenciálisan egybites jelet állít elő a felfutó és lefutó éleken.

Maga a DDIO a következő Verilog kóddal írható le:

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

De ez nagy valószínűséggel nem így fog működni. Az Alter ALTDDIO_OUT megafunkcióját kell használnia a DDIO kimeneti elemek tényleges engedélyezéséhez. A projektem az ALTDDIO_OUT könyvtárkomponenst használja.

Mindez kissé trükkösnek tűnhet, de működik.

Megtekintheti az összes Verilog HDL-ben írt forráskódot itt a githubon.

Az FPGA-hoz lefordított firmware a Mars Rover2rpi kártyára telepített EPCS chipbe kerül. Így amikor az FPGA kártyát áram alá helyezik, az FPGA a flash memóriából inicializálódik és elindul.

Most beszélnünk kell egy kicsit magáról a Raspberry konfigurációjáról.

Kísérleteket végzek Raspberry PI OS-en (32 bites) Debian Buster alapú, 2020. augusztusi verzió,
Megjelenés dátuma: 2020-08-20, Kernel verzió: 5.4.

Két dolgot kell tenned:

  • szerkessze a config.txt fájlt;
  • hozzon létre egy X szerver konfigurációt, hogy két monitorral működjön.

A /boot/config.txt fájl szerkesztésekor a következőkre lesz szüksége:

  1. letiltja az i2c, i2s, spi használatát;
  2. engedélyezze a DPI módot a overlay használatával dtoverlay=dpi24;
  3. video mód konfigurálása 1280×720 60Hz, 24 bit/pixel DPI-n;
  4. adja meg a szükséges számú framebuffert 2 (max_framebuffers=2, csak ezután jelenik meg a második eszköz /dev/fb1)

A config.txt fájl teljes szövege így néz ki.

# 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

Ezután létre kell hoznia egy konfigurációs fájlt az X szerver számára, hogy két monitort használhasson két framebufferen /dev/fb0 és /dev/fb1:

A /usr/share/x11/xorg.conf.d/60-dualscreen.conf konfigurációs fájlom ilyen

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

Nos, ha még nincs telepítve, akkor telepítenie kell a Xineramát. Ezután az asztali terület teljesen kibővül két monitorra, amint azt a fenti bemutató videó is mutatja.

Valószínűleg ennyi. Mostantól két monitort használhatnak majd a Raspberry Pi3 tulajdonosok.

A Mars Rover2rpi kártya leírása és kapcsolási rajza megtalálható Nézz ide.

Forrás: will.com