Antrasis HDMI monitorius prie Raspberry Pi3 per DPI sąsają ir FPGA plokštę


Šiame vaizdo įraše parodyta: Raspberry Pi3 plokštė, prie jos prijungta per GPIO jungtį, yra FPGA plokštė Mars Rover2rpi (Cyclone IV), prie kurios prijungtas HDMI monitorius. Antrasis monitorius jungiamas per standartinę Raspberry Pi3 HDMI jungtį. Viskas veikia kartu kaip dviejų monitorių sistema.

Toliau papasakosiu, kaip tai įgyvendinama.

Populiari Raspberry Pi3 plokštė turi GPIO jungtį, per kurią galima prijungti įvairias išplėtimo plokštes: jutiklius, šviesos diodus, žingsninio variklio tvarkykles ir daug daugiau. Tiksli kiekvieno jungties kaiščio funkcija priklauso nuo prievado konfigūracijos. GPIO ALT2 konfigūracija leidžia perjungti jungtį į DPI sąsajos režimą, Display Parallel Interface. Yra išplėtimo plokštės, skirtos VGA monitoriams prijungti per DPI. Tačiau, pirma, VGA monitoriai nebėra tokie įprasti kaip HDMI, antra, skaitmeninė sąsaja vis geresnė nei analoginė. Be to, tokių VGA išplėtimo plokščių DAC paprastai gaminamas R-2-R grandinių pavidalu ir dažnai ne daugiau kaip 6 bitai vienai spalvai.

ALT2 režimu GPIO jungties kaiščiai turi tokią reikšmę:

Antrasis HDMI monitorius prie Raspberry Pi3 per DPI sąsają ir FPGA plokštę

Čia aš nuspalvinau jungties RGB kaiščius atitinkamai raudonai, žaliai ir mėlynai. Kiti svarbūs signalai yra V-SYNC ir H-SYNC signalai, taip pat CLK. CLK laikrodžio dažnis yra dažnis, kuriuo pikselių reikšmės išvedamos į jungtį; tai priklauso nuo pasirinkto vaizdo režimo.

Norėdami prijungti skaitmeninį HDMI monitorių, turite užfiksuoti sąsajos DPI signalus ir konvertuoti juos į HDMI signalus. Tai galima padaryti, pavyzdžiui, naudojant kokią nors FPGA plokštę. Kaip paaiškėjo, Mars Rover2rpi plokštė yra tinkama šiems tikslams. Tiesą sakant, pagrindinė šios plokštės prijungimo per specialų adapterį galimybė atrodo taip:

Antrasis HDMI monitorius prie Raspberry Pi3 per DPI sąsają ir FPGA plokštę

Ši plokštė naudojama GPIO prievadų skaičiui padidinti ir prie raspberry prijungti daugiau periferinių įrenginių. Tuo pačiu metu JTAG signalams naudojami 4 GPIO signalai su šia jungtimi, kad programa iš Raspberry galėtų įkelti FPGA programinę įrangą į FPGA. Dėl to ši standartinė jungtis man netinka, iškrenta 4 DPI signalai. Laimei, papildomos lentos šukos turi su Raspberry suderinamą smeigtuką. Taigi galiu pasukti plokštę 90 laipsnių ir vis tiek prijungti ją prie savo avietės:

Antrasis HDMI monitorius prie Raspberry Pi3 per DPI sąsają ir FPGA plokštę

Žinoma, turėsite naudoti išorinį JTAG programuotoją, tačiau tai nėra problema.

Vis dar yra nedidelė problema. Ne kiekvienas FPGA kaištis gali būti naudojamas kaip laikrodžio įvestis. Yra tik keli specialūs kaiščiai, kuriuos galima naudoti šiems tikslams. Taigi čia pasirodė, kad GPIO_0 CLK signalas nepasiekia FPGA įvesties, kurią galima naudoti kaip FPGA laikrodžio įvestį. Taigi vis tiek teko uždėti vieną laidą ant skarelės. Prijungiu GPIO_0 ir plokštės KEY[1] signalą:

Antrasis HDMI monitorius prie Raspberry Pi3 per DPI sąsają ir FPGA plokštę

Dabar aš jums šiek tiek papasakosiu apie FPGA projektą. Pagrindinis sunkumas generuojant HDMI signalus yra labai aukšti dažniai. Jei pažvelgsite į HDMI jungties išvestį, pamatysite, kad RGB signalai dabar yra nuoseklūs diferencialiniai signalai:

Antrasis HDMI monitorius prie Raspberry Pi3 per DPI sąsają ir FPGA plokštę

Diferencialinio signalo naudojimas leidžia kovoti su bendrojo režimo trukdžiais perdavimo linijoje. Tokiu atveju originalus kiekvieno spalvos signalo aštuonių bitų kodas konvertuojamas į 10 bitų TMDS (perėjimo sumažintą diferencialinį signalizavimą). Tai specialus kodavimo metodas, skirtas pašalinti nuolatinės srovės komponentą iš signalo ir sumažinti signalo perjungimą diferencialinėje linijoje. Kadangi dabar nuosekliąja linija reikia perduoti 10 bitų vienam spalvos baitui, paaiškėja, kad serializatoriaus laikrodžio greitis turi būti 10 kartų didesnis nei pikselių dažnis. Jei paimsime, pavyzdžiui, vaizdo režimą 1280x720 60Hz, tai šio režimo pikselių dažnis yra 74,25 MHz. Serializatorius turėtų būti 742,5 MHz.

Įprasti FPGA, deja, to negali. Tačiau, mūsų laimei, FPGA turi įmontuotus DDIO kaiščius. Tai išvados, kurios jau yra tarsi 2-1 serializatoriai. Tai reiškia, kad jie gali išvesti du bitus paeiliui kylančiose ir mažėjančiose laikrodžio dažnio kraštinėse. Tai reiškia, kad FPGA projekte galite naudoti ne 740 MHz, o 370 MHz, tačiau FPGA reikia naudoti DDIO išvesties elementus. Dabar 370 MHz jau yra visiškai pasiekiamas dažnis. Deja, 1280 x 720 režimas yra riba. Didesnės raiškos negalima pasiekti naudojant „Cyclone IV FPGA“, įdiegtą „Mars Rover2rpi“ plokštėje.

Taigi projektuojant įvesties pikselių dažnis CLK patenka į PLL, kur jis padauginamas iš 5. Šiuo dažniu R, G, B baitai paverčiami bitų poromis. Tai daro TMDS kodavimo įrenginys. Verilog HDL šaltinio kodas atrodo taip:

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

Tada išvesties poros tiekiamos į DDIO išvestį, kuri nuosekliai sukuria vieno bito signalą kylančiose ir krintančiose briaunose.

Pats DDIO gali būti apibūdintas tokiu Verilog kodu:

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

Bet greičiausiai tai neveiks. Norėdami iš tikrųjų įjungti DDIO išvesties elementus, turite naudoti „Alter“ megafunkciją ALTDDIO_OUT. Mano projekte naudojamas ALTDDIO_OUT bibliotekos komponentas.

Visa tai gali atrodyti šiek tiek sudėtinga, bet tai veikia.

Galite peržiūrėti visą šaltinio kodą, parašytą Verilog HDL čia github.

Sukompiliuota FPGA programinė įranga perkeliama į EPCS lustą, įdiegtą Mars Rover2rpi plokštėje. Taigi, kai į FPGA plokštę tiekiamas maitinimas, FPGA bus inicijuojamas iš „flash“ atminties ir bus paleistas.

Dabar turime šiek tiek pakalbėti apie pačios Raspberry konfigūraciją.

Atlieku eksperimentus su Raspberry PI OS (32 bitų), pagrįsta Debian Buster, versija: 2020 m. rugpjūčio mėn.
Išleidimo data: 2020-08-20, branduolio versija: 5.4.

Turite atlikti du dalykus:

  • redaguoti config.txt failą;
  • sukurti X serverio konfigūraciją, kad galėtumėte dirbti su dviem monitoriais.

Redaguojant /boot/config.txt failą jums reikia:

  1. išjungti i2c, i2s, spi naudojimą;
  2. įjungti DPI režimą naudojant perdangą dtoverlay=dpi24;
  3. sukonfigūruoti vaizdo režimą 1280×720 60Hz, 24 bitai pikselyje DPI;
  4. nurodykite reikiamą kadrų buferių skaičių 2 (max_framebuffers=2, tik tada pasirodys antrasis įrenginys /dev/fb1)

Visas config.txt failo tekstas atrodo taip.

# 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

Po to turite sukurti konfigūracijos failą, kad X serveris naudotų du monitorius dviejuose kadrų buferiuose /dev/fb0 ir /dev/fb1:

Mano konfigūracijos failas /usr/share/x11/xorg.conf.d/60-dualscreen.conf yra toks

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

Na, jei jis dar neįdiegtas, tuomet reikia įdiegti Xineramą. Tada darbalaukio erdvė bus visiškai išplėsta iki dviejų monitorių, kaip parodyta aukščiau esančiame demonstraciniame vaizdo įraše.

Tai turbūt ir viskas. Dabar Raspberry Pi3 savininkai galės naudotis dviem monitoriais.

Galite rasti Mars Rover2rpi plokštės aprašymą ir grandinės schemą paziurek cia.

Šaltinis: www.habr.com