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


Ovaj video prikazuje: Raspberry Pi3 pločicu na koju je preko GPIO konektora spojena FPGA Mars Rover2rpi (Cyclone IV) pločica na koju je spojen HDMI monitor. Drugi monitor je povezan preko standardnog Raspberry Pi3 HDMI konektora. Sve zajedno radi kao sustav dvostrukog monitora.

Zatim ću vam reći kako se to provodi.

Popularna ploča Raspberry Pi3 ima GPIO konektor preko kojeg možete spojiti razne ploče za proširenje: senzore, LED diode, drajvere koračnih motora i još mnogo toga. Specifična funkcija svakog pina na konektoru ovisi o konfiguraciji priključka. Konfiguracija GPIO ALT2 omogućuje prebacivanje konektora u način rada sučelja DPI, Paralelno sučelje prikaza. Postoje ploče za proširenje za povezivanje VGA monitora putem DPI. Međutim, prvo, VGA monitori više nisu tako uobičajeni kao HDMI, a drugo, digitalno sučelje postaje sve bolje od analognog. Štoviše, DAC na takvim VGA karticama za proširenje obično je izrađen u obliku R-2-R lanaca i često ne više od 6 bita po boji.

U ALT2 modu, pinovi GPIO konektora imaju sljedeće značenje:

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

Ovdje sam obojio RGB pinove konektora u crveno, zeleno i plavo. Ostali važni signali su V-SYNC i H-SYNC sinkronizirani signali, kao i CLK. Frekvencija takta CLK je frekvencija na kojoj se vrijednosti piksela šalju na konektor i ovisi o odabranom video modu.

Za spajanje digitalnog HDMI monitora morate uhvatiti signale DPI sučelja i pretvoriti ih u HDMI signale. To se može učiniti, na primjer, pomoću bilo koje FPGA ploče. Kako se pokazalo, ploča Mars Rover2rpi je prikladna za ovu svrhu. Zapravo, glavna opcija za povezivanje ove ploče putem posebnog adaptera izgleda ovako:

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

Ova ploča služi za povećanje broja GPIO portova i za spajanje više periferije na malinu. Ujedno se 4 GPIO signala ovom vezom koriste za JTAG signale, kako bi program iz distribucije mogao učitati FPGA firmware u FPGA. Zbog toga mi takva obična veza ne odgovara, ispadaju signali od 4 DPI. Srećom, dodatni češljevi na ploči imaju pinout kompatibilan s Raspberry. Tako da mogu rotirati ploču za 90 stupnjeva i još je spojiti na svoju malinu:

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

Naravno, morat ćete koristiti vanjski 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 tu svrhu. Tako se ovdje pokazalo da GPIO_0 CLK signal ne dolazi do FPGA ulaza, koji se može koristiti kao FPGA ulaz takta. Pa svejedno, morala sam baciti jednu objavu na šal. Povezujem GPIO_0 i KEY[1] signal ploče:

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

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

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

Korištenje diferencijalnog signala omogućuje vam rješavanje uobičajenog načina buke na dalekovodu. U ovom slučaju, izvorni osam-bitni kod svakog signala boje pretvara se u 10-bitni TMDS (Transition-minimized differential signaling). Ovo je posebna metoda kodiranja za uklanjanje istosmjerne komponente iz signala i minimiziranje prebacivanja signala u diferencijalnoj liniji. Budući da sada postoji 10 bitova za prijenos po bajtu boje preko serijske linije, ispada da frekvencija takta serijalizatora mora biti 10 puta veća od frekvencije takta piksela. Ako uzmemo za primjer video mod 1280x720 60Hz, tada je frekvencija piksela ovog moda 74,25MHz. Serializator bi trebao biti 742,5 MHz.

Konvencionalni FPGA općenito nisu sposobni za to, nažalost. Međutim, na našu sreću, FPGA imaju ugrađene DDIO pinove. To su zaključci koji su već, takoreći, serijalizatori 2-na-1. To jest, oni mogu poslati dva bita u nizu duž rastuće i padajuće taktne frekvencije. To znači da u FPGA projektu ne možete koristiti 740 MHz, već 370 MHz, ali trebate koristiti DDIO izlazne elemente u FPGA. Ovdje je 370 MHz već sasvim dostižna frekvencija. Nažalost, 1280×720 način rada je granica. Veća rezolucija ne može se postići u našem FPGA Cyclone IV instaliranom na ploči Rover2rpi.

Dakle, u projektu se ulazna frekvencija piksela CLK dovodi u PLL, gdje se množi s 5. Na ovoj frekvenciji, R, G, B bajtovi se pretvaraju u parove bitova. To radi TMDS koder. Izvorni kod na Verilog HDL 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 šalju na DDIO izlaz, koji sekvencijalno proizvodi jednobitni signal na usponu i padu.

Sam DDIO mogao bi se opisati Verilog kodom ovako:

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 vjerojatno neće tako ići. Morate upotrijebiti Alterinu megafunkciju ALTDDIO_OUT da biste stvarno koristili DDIO izlazne elemente. U mom projektu koristi se komponenta knjižnice ALTDDIO_OUT.

Možda sve izgleda malo lukavo, ali djeluje.

Možete pogledati cijeli izvorni kod napisan u Verilog HDL upravo ovdje na githubu.

Sastavljeni firmware za FPGA ugrađen je u EPCS čip instaliran na ploči Mars Rover2rpi. Stoga, kada se napajanje priključi na FPGA ploču, FPGA će se pokrenuti iz flash memorije i pokrenuti.

Sada moramo malo popričati o konfiguraciji samog Raspberryja.

Radim eksperimente na Raspberry PI OS-u (32-bitnom) temeljenom na Debian Busteru, verzija: kolovoz 2020.
Datum objave: 2020. 08. 20., verzija kernela: 5.4.

Morate učiniti dvije stvari:

  • uredite datoteku config.txt;
  • stvoriti konfiguraciju X poslužitelja za rad s dva monitora.

Kada uređujete datoteku /boot/config.txt, trebate:

  1. onemogućiti korištenje i2c, i2s, spi;
  2. omogući DPI način rada s prekrivanjem dtoverlay=dpi24;
  3. postavite video mod 1280×720 60Hz, 24 bita po točki po DPI;
  4. odredite potreban broj međuspremnika okvira 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 toga trebate izraditi konfiguracijsku datoteku za X poslužitelj kako bi koristio dva monitora na dva međuspremnika okvira /dev/fb0 i /dev/fb1:

Moja konfiguracijska datoteka je /usr/share/x11/xorg.conf.d/60-dualscreen.conf ovako

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 trebate instalirati Xineramu. Tada će prostor radne površine biti u potpunosti proširen na dva monitora, kao što je prikazano u demo videu iznad.

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

Opis i dijagram ploče Mars Rover2rpi može se pogledajte ovdje.

Izvor: www.habr.com