Drugi HDMI monitor na Raspberry Pi3 preko DPI interfejsa i FPGA ploče


Ovaj video prikazuje: Raspberry Pi3 ploča, spojena na nju preko GPIO konektora je FPGA ploča Mars Rover2rpi (Cyclone IV), na koju je povezan HDMI monitor. Drugi monitor je povezan preko standardnog HDMI konektora Raspberry Pi3. Sve funkcioniše zajedno kao sistem sa dvostrukim monitorom.

U nastavku ću vam reći kako se ovo implementira.

Popularna Raspberry Pi3 ploča ima GPIO konektor preko kojeg možete povezati različite kartice za proširenje: senzore, LED diode, drajvere koračnih motora i još mnogo toga. Tačna funkcija svakog pina na konektoru ovisi o konfiguraciji porta. GPIO ALT2 konfiguracija vam omogućava da prebacite konektor u režim DPI interfejsa, Display Parallel Interface. Postoje kartice za proširenje za povezivanje VGA monitora preko DPI. Međutim, prvo, VGA monitori više nisu uobičajeni kao HDMI, a drugo, digitalni interfejs je sve bolji od analognog. Štaviše, DAC na takvim VGA pločama za proširenje obično je napravljen u obliku R-2-R lanaca i često ne više od 6 bita po boji.

U ALT2 načinu rada, pinovi GPIO konektora imaju sljedeće značenje:

Drugi HDMI monitor na Raspberry Pi3 preko DPI interfejsa i FPGA ploče

Ovdje sam obojio RGB pinove konektora crvenom, zelenom i plavom bojom. Ostali važni signali su V-SYNC i H-SYNC signali, kao i CLK. CLK taktna frekvencija je frekvencija na kojoj se vrijednosti piksela izlaze na konektor; ovisi o odabranom video modu.

Da biste povezali digitalni HDMI monitor, morate uhvatiti DPI signale sučelja i pretvoriti ih u HDMI signale. To se može učiniti, na primjer, pomoću neke vrste FPGA ploče. Kako se ispostavilo, Mars Rover2rpi ploča je pogodna za ove svrhe. Istina, glavna opcija za povezivanje ove ploče putem posebnog adaptera izgleda ovako:

Drugi HDMI monitor na Raspberry Pi3 preko DPI interfejsa i FPGA ploče

Ova ploča se koristi za povećanje broja GPIO portova i za povezivanje više perifernih uređaja na malinu. Istovremeno, 4 GPIO signala sa ovom vezom koriste se za JTAG signale, tako da program iz Raspberryja može učitati FPGA firmware u FPGA. Zbog toga mi ova standardna veza ne odgovara, ispadaju 4 DPI signala. Srećom, dodatni češljevi na ploči imaju pinout kompatibilan sa Raspberry. Tako da mogu rotirati ploču za 90 stepeni i dalje je spojiti na svoju malinu:

Drugi HDMI monitor na Raspberry Pi3 preko DPI interfejsa i FPGA ploče

Naravno, moraćete da koristite eksterni JTAG programator, ali to nije problem.

Još uvijek postoji mali problem. Ne može se svaki FPGA pin koristiti kao ulaz takta. Postoji samo nekoliko namjenskih pinova koji se mogu koristiti u ove svrhe. Tako se ovdje ispostavilo da GPIO_0 CLK signal ne dolazi do FPGA ulaza, koji se može koristiti kao FPGA ulaz takta. Tako da sam ipak morala staviti jednu žicu na šal. Povezujem GPIO_0 i signal KEY[1] ploče:

Drugi HDMI monitor na Raspberry Pi3 preko DPI interfejsa i FPGA ploče

Sada ću vam reći nešto o FPGA projektu. Glavna poteškoća u generiranju HDMI signala su vrlo visoke frekvencije. Ako pogledate pinout HDMI konektora, možete vidjeti da su RGB signali sada serijski diferencijalni signali:

Drugi HDMI monitor na Raspberry Pi3 preko DPI interfejsa i FPGA ploče

Upotreba diferencijalnog signala omogućava vam da se borite protiv smetnji zajedničkog moda na dalekovodu. U ovom slučaju, originalni osmobitni kod svakog signala u boji se pretvara u 10-bitni TMDS (diferencijalna signalizacija minimizirana tranzicijom). Ovo je posebna metoda kodiranja za uklanjanje DC komponente iz signala i minimiziranje prebacivanja signala u diferencijalnoj liniji. Pošto 10 bitova sada treba da se prenesu preko serijske linije za jedan bajt boje, ispada da brzina takta serijalizatora mora biti 10 puta veća od brzine takta piksela. Ako uzmemo na primjer video mod 1280x720 60Hz, onda je frekvencija piksela ovog moda 74,25 MHz. Serializator bi trebao biti 742,5 MHz.

Obični FPGA, nažalost, nisu sposobni za to. Međutim, na našu sreću, FPGA ima ugrađene DDIO pinove. Ovo su zaključci koji su već takoreći serijalizatori 2 prema 1. To jest, oni mogu emitovati dva bita uzastopno na rastućoj i opadajućoj ivici frekvencije takta. To znači da u FPGA projektu možete koristiti ne 740 MHz, već 370 MHz, ali morate koristiti DDIO izlazne elemente u FPGA. Sada je 370 MHz već potpuno dostižna frekvencija. Nažalost, 1280x720 režim je ograničenje. Veća rezolucija se ne može postići u našem Cyclone IV FPGA instaliranom na Mars Rover2rpi ploči.

Dakle, u dizajnu, ulazna frekvencija piksela CLK ide u PLL, gdje se množi sa 5. Na ovoj frekvenciji, R, G, B bajtovi se pretvaraju u parove bitova. To je ono što radi TMDS enkoder. Izvorni kod u Verilog HDL-u izgleda ovako:

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

Zatim se izlazni parovi dovode do DDIO izlaza, koji sekvencijalno proizvodi jednobitni signal na rastućoj i opadajućoj ivici.

Sam DDIO bi se mogao opisati sljedećim Verilog kodom:

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

Ali najvjerovatnije to neće funkcionirati na taj način. Morate koristiti Alterovu megafunkciju ALTDDIO_OUT da biste zapravo omogućili DDIO izlazne elemente. Moj projekat koristi komponentu biblioteke ALTDDIO_OUT.

Sve ovo može izgledati malo nezgodno, ali funkcionira.

Možete vidjeti sav izvorni kod napisan u Verilog HDL ovdje na github-u.

Prevedeni firmver za FPGA se flešuje u EPCS čip instaliran na Mars Rover2rpi ploči. Dakle, kada se napajanje priključi na FPGA ploču, FPGA će se inicijalizirati iz flash memorije i pokrenuti.

Sada moramo malo razgovarati o konfiguraciji samog Raspberryja.

Radim eksperimente na Raspberry PI OS-u (32 bit) baziranom na Debian Busteru, verzija: kolovoz 2020.,
Datum objave: 2020, verzija kernela: 08.

Morate uraditi dvije stvari:

  • uredite datoteku config.txt;
  • kreirajte konfiguraciju X servera za rad sa dva monitora.

Prilikom uređivanja /boot/config.txt datoteke trebate:

  1. onemogući upotrebu i2c, i2s, spi;
  2. omogući DPI način rada koristeći preklapanje dtoverlay=dpi24;
  3. konfigurišite video režim 1280×720 60Hz, 24 bita po pikselu na DPI;
  4. navedite potreban broj framebuffera 2 (max_framebuffers=2, tek tada će se pojaviti drugi uređaj /dev/fb1)

Cijeli tekst datoteke config.txt izgleda ovako.

# 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

Nakon ovoga, morate kreirati konfiguracijsku datoteku za X server da koristi dva monitora na dva framebuffera /dev/fb0 i /dev/fb1:

Moj konfiguracijski fajl /usr/share/x11/xorg.conf.d/60-dualscreen.conf je ovakav

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

Pa, ako već nije instaliran, onda morate instalirati Xinerama. Tada će radni prostor biti u potpunosti proširen na dva monitora, kao što je prikazano u demo videu iznad.

To je vjerovatno sve. Sada će vlasnici Raspberry Pi3 moći koristiti dva monitora.

Opis i dijagram kola Mars Rover2rpi ploče se mogu naći pogledati ovdje.

izvor: www.habr.com