Druhý HDMI monitor k Raspberry Pi3 přes DPI rozhraní a FPGA desku


Toto video ukazuje: deska Raspberry Pi3, k ní připojená přes konektor GPIO, je deska FPGA Mars Rover2rpi (Cyclone IV), ke které je připojen HDMI monitor. Druhý monitor je připojen přes standardní HDMI konektor Raspberry Pi3. Vše funguje společně jako systém dvou monitorů.

Dále vám řeknu, jak je to implementováno.

Populární deska Raspberry Pi3 má konektor GPIO, přes který můžete připojit různé rozšiřující karty: senzory, LED diody, ovladače krokových motorů a mnoho dalšího. Přesná funkce každého pinu na konektoru závisí na konfiguraci portu. Konfigurace GPIO ALT2 umožňuje přepnout konektor do režimu rozhraní DPI, Display Parallel Interface. Pro připojení VGA monitorů přes DPI existují rozšiřující karty. Nicméně za prvé, VGA monitory již nejsou tak běžné jako HDMI, a za druhé, digitální rozhraní je stále lepší než to analogové. Navíc DAC na takových rozšiřujících deskách VGA je obvykle vyroben ve formě řetězců R-2-R a často ne více než 6 bitů na barvu.

V režimu ALT2 mají piny konektoru GPIO následující význam:

Druhý HDMI monitor k Raspberry Pi3 přes DPI rozhraní a FPGA desku

Zde jsem obarvil RGB kolíky konektoru červeně, zeleně a modře. Dalšími důležitými signály jsou signály V-SYNC a H-SYNC a také CLK. Hodinová frekvence CLK je frekvence, při které jsou hodnoty pixelů vyvedeny na konektor; závisí na zvoleném režimu videa.

Chcete-li připojit digitální monitor HDMI, musíte zachytit signály DPI rozhraní a převést je na signály HDMI. To lze provést například pomocí nějaké desky FPGA. Jak se ukazuje, deska Mars Rover2rpi je pro tyto účely vhodná. Ve skutečnosti hlavní možnost připojení této desky přes speciální adaptér vypadá takto:

Druhý HDMI monitor k Raspberry Pi3 přes DPI rozhraní a FPGA desku

Tato deska slouží k navýšení počtu GPIO portů a k připojení více periferních zařízení k malině. Zároveň jsou 4 signály GPIO s tímto zapojením použity pro signály JTAG, aby program od Raspberry mohl nahrát firmware FPGA do FPGA. Z tohoto důvodu mi toto standardní zapojení nevyhovuje, vypadávají signály 4 DPI. Naštěstí další hřebeny na desce mají pinout kompatibilní s Raspberry. Takže mohu otočit desku o 90 stupňů a stále ji připojit k mé malině:

Druhý HDMI monitor k Raspberry Pi3 přes DPI rozhraní a FPGA desku

Samozřejmě budete muset použít externí JTAG programátor, ale to není problém.

Ještě je tu malý problém. Ne každý pin FPGA lze použít jako hodinový vstup. Existuje pouze několik vyhrazených pinů, které lze pro tyto účely použít. Zde se tedy ukázalo, že signál GPIO_0 CLK nedosahuje na vstup FPGA, který lze použít jako hodinový vstup FPGA. Takže jsem ještě musela dát jeden drát na šátek. Připojuji GPIO_0 a signál KEY[1] desky:

Druhý HDMI monitor k Raspberry Pi3 přes DPI rozhraní a FPGA desku

Nyní vám řeknu něco o projektu FPGA. Hlavním problémem při generování signálů HDMI jsou velmi vysoké frekvence. Pokud se podíváte na piny konektoru HDMI, můžete vidět, že signály RGB jsou nyní sériové diferenciální signály:

Druhý HDMI monitor k Raspberry Pi3 přes DPI rozhraní a FPGA desku

Použití diferenciálního signálu umožňuje bojovat proti rušení běžného režimu na přenosové lince. V tomto případě je původní osmibitový kód každého barevného signálu převeden na 10bitový TMDS (Transition-minimized Differential signaling). Jedná se o speciální metodu kódování pro odstranění stejnosměrné složky ze signálu a minimalizaci přepínání signálu v diferenciálním vedení. Vzhledem k tomu, že nyní je třeba po sériové lince přenést 10 bitů pro jeden bajt barvy, ukázalo se, že rychlost hodin serializátoru musí být 10krát vyšší než rychlost hodin pixelů. Vezmeme-li například video režim 1280x720 60Hz, pak pixelová frekvence tohoto režimu je 74,25 MHz. Serializátor by měl být 742,5 MHz.

Běžné FPGA toho bohužel nejsou schopny. Naštěstí pro nás má FPGA vestavěné piny DDIO. Toto jsou závěry, které jsou již jakoby serializátory 2:1. To znamená, že mohou vydávat dva bity postupně na vzestupné a sestupné hraně hodinové frekvence. To znamená, že v projektu FPGA můžete použít nikoli 740 MHz, ale 370 MHz, ale musíte použít výstupní prvky DDIO v FPGA. Nyní je již 370 MHz zcela dosažitelná frekvence. Bohužel režim 1280x720 je limit. Vyššího rozlišení nelze dosáhnout v našem FPGA Cyclone IV nainstalovaném na desce Mars Rover2rpi.

Takže v návrhu jde vstupní frekvence pixelů CLK do PLL, kde se vynásobí 5. Při této frekvenci se bajty R, G, B převedou na páry bitů. To dělá kodér TMDS. Zdrojový kód ve Verilog HDL vypadá takto:

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

Poté jsou výstupní páry přivedeny na výstup DDIO, který postupně vytváří jednobitový signál na náběžné a sestupné hraně.

Samotné DDIO lze popsat následujícím kódem Verilog:

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

Takhle to ale s největší pravděpodobností fungovat nebude. Chcete-li skutečně aktivovat výstupní prvky DDIO, musíte použít megafunkci Alter ALTDDIO_OUT. Můj projekt používá komponentu knihovny ALTDDIO_OUT.

To vše může vypadat trochu složitě, ale funguje to.

Můžete si prohlédnout veškerý zdrojový kód napsaný ve Verilog HDL zde na githubu.

Kompilovaný firmware pro FPGA je flashován do čipu EPCS nainstalovaného na desce Mars Rover2rpi. Když je tedy na desku FPGA přivedeno napájení, FPGA se inicializuje z paměti flash a spustí se.

Nyní si musíme něco málo promluvit o konfiguraci samotného Raspberry.

Provádím experimenty na Raspberry PI OS (32bitový) založený na Debian Buster, verze: srpen 2020,
Datum vydání:2020-08-20, Verze jádra:5.4.

Musíte udělat dvě věci:

  • upravit soubor config.txt;
  • vytvořte konfiguraci X serveru pro práci se dvěma monitory.

Při úpravě souboru /boot/config.txt potřebujete:

  1. zakázat používání i2c, i2s, spi;
  2. povolit režim DPI pomocí překrytí dtoverlay=dpi24;
  3. konfigurace režimu videa 1280×720 60Hz, 24 bitů na pixel na DPI;
  4. zadejte požadovaný počet framebufferů 2 (max_framebuffers=2, teprve potom se objeví druhé zařízení /dev/fb1)

Celý text souboru config.txt vypadá takto.

# 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

Poté musíte vytvořit konfigurační soubor pro X server, aby mohl používat dva monitory na dvou framebufferech /dev/fb0 a /dev/fb1:

Můj konfigurační soubor /usr/share/x11/xorg.conf.d/60-dualscreen.conf je takový

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

No, pokud ještě není nainstalován, musíte nainstalovat Xineramu. Poté se plocha pracovní plochy plně rozšíří na dva monitory, jak ukazuje ukázkové video výše.

To je asi vše. Nyní budou moci majitelé Raspberry Pi3 používat dva monitory.

Popis a schéma zapojení desky Mars Rover2rpi naleznete podívej se sem.

Zdroj: www.habr.com