Monitori i dytë HDMI për Raspberry Pi3 përmes ndërfaqes DPI dhe bordit FPGA


Në këtë video shfaqen: pllaka Raspberry Pi3, e lidhur përmes lidhësit GPIO me pllakën FPGA Marsokhod2rpi (Cyclone IV), e cila është e lidhur me një monitor HDMI. Monitori i dytë është lidhur përmes lidhësit standard HDMI të Raspberry Pi3. E gjithë kjo funksionon si një sistem me dy monitorë.

Më poshtë do të shpjegoj se si është realizuar kjo.

Pllaka e njohur Raspberry Pi3 ka një lidhës GPIO, përmes të cilit mund të lidhen karta të ndryshme zgjerimi: sensorë, LED, drejtues për motorë hap pas hapi dhe shumë të tjera. Funksioni specifik i secilit pin në lidhës varet nga konfigurimi i porteve. Konfigurimi GPIO ALT2 lejon kalimin e lidhësit në modalitetin e ndërfaqes DPI, Display Parallel Interface. Ekzistojnë edhe karta zgjerimi për lidhjen e monitorëve VGA përmes DPI. Megjithatë, së pari, monitorët VGA nuk janë më aq të përhapur sa HDMI dhe, së dyti, një ndërfaqe digjitale është më e mirë se ajo analoge. Aq më tepër që DAC në këto karta zgjerimi VGA zakonisht realizohet në formën e zinxhirëve R-2-R dhe shpesh nuk ofron më shumë se 6 bit për ngjyrë.

Në modalitetin ALT2, pinët e lidhësit GPIO kanë këtë funksion:

Monitori i dytë HDMI për Raspberry Pi3 përmes ndërfaqes DPI dhe bordit FPGA

Këtu i kam ngjyrosur daljet RGB të lidhësit përkatësisht me të kuqe, jeshile dhe blu. Sinjale të tjera të rëndësishme janë sinjalet e sinkronizimit të skanimit V-SYNC dhe H-SYNC, si edhe CLK. Frekuenca e orës CLK është frekuenca me të cilën vlerat e pikselëve dërgohen në lidhës dhe ajo varet nga modaliteti i zgjedhur i videos.

Për të lidhur një monitor digjital HDMI, duhet të kapen sinjalet e ndërfaqes DPI dhe të shndërrohen në sinjale HDMI. Kjo mund të bëhet, për shembull, me ndihmën e ndonjë pllake FPGA. Siç doli, pllaka Marsokhod2rpi është e përshtatshme për këtë qëllim. Të them të drejtën, mënyra kryesore e lidhjes së kësaj pllake përmes një përshtatësi të posaçëm duket kështu:

Monitori i dytë HDMI për Raspberry Pi3 përmes ndërfaqes DPI dhe bordit FPGA

Kjo pllakë shërben për të rritur numrin e porteve GPIO dhe për të lidhur më shumë pajisje periferike me Raspberry. Në këtë lidhje, 4 sinjale GPIO përdoren si sinjale JTAG, kështu që programi nga Raspberry mund të ngarkojë firmware-in e FPGA në PLIS. Për këtë arsye, kjo lidhje standarde nuk më përshtatet, sepse humbasin 4 sinjale DPI. Për fat, konektorët shtesë në pllakë kanë një pinout të pajtueshëm me Raspberry. Kështu që mund ta rrotulloj pllakën me 90 gradë dhe ta lidh gjithsesi me Raspberry tim:

Monitori i dytë HDMI për Raspberry Pi3 përmes ndërfaqes DPI dhe bordit FPGA

Sigurisht, do të duhet të përdoret një programues i jashtëm JTAG, por kjo nuk është problem.

Megjithatë ka një problem të vogël. Jo çdo dalje e FPGA mund të përdoret si hyrje e sinjalit të orës. Ka vetëm disa pin-e dedicated që mund të përdoren për këtë qëllim. Edhe këtu ndodhi që sinjali CLK i GPIO_0 nuk shkon te hyrja e FPGA që mund të përdoret si hyrje e sinjalit të orës së PLIS. Prandaj, gjithsesi u desh të shtohej një tel i vogël në pllakë. Unë lidh GPIO_0 me sinjalin KEY[1] të pllakës:

Monitori i dytë HDMI për Raspberry Pi3 përmes ndërfaqes DPI dhe bordit FPGA

Tani do të tregoj shkurtimisht për projektin në PLIS. Vështirësia kryesore në formimin e sinjaleve HDMI janë frekuencat shumë të larta. Nëse shohim pinout-in e lidhësit HDMI, bëhet e qartë se sinjalet RGB tani janë sinjale diferenciale serike:

Monitori i dytë HDMI për Raspberry Pi3 përmes ndërfaqes DPI dhe bordit FPGA

PĂ«rdorimi i njĂ« sinjali diferencial ndihmon nĂ« luftimin e ndĂ«rhyrjeve me regjim tĂ« pĂ«rbashkĂ«t nĂ« linjĂ«n e transmetimit. NĂ« tĂ« njĂ«jtĂ«n kohĂ«, kodi fillestar tetĂ«bitĂ«sh i secilit sinjal ngjyre shndĂ«rrohet nĂ« TMDS 10-bitĂ«sh (Transition-minimized differential signaling). Kjo Ă«shtĂ« njĂ« mĂ«nyrĂ« e veçantĂ« kodimi pĂ«r tĂ« eliminuar komponentin DC nga sinjali dhe pĂ«r tĂ« minimizuar kalimet e sinjalit nĂ« linjĂ«n diferenciale. MeqenĂ«se tani pĂ«r njĂ« bajt ngjyre duhet tĂ« transmetohen 10 bit nĂ« linjĂ«n serike tĂ« transmetimit, rezulton se frekuenca e orĂ«s sĂ« serializer-it duhet tĂ« jetĂ« 10 herĂ« mĂ« e lartĂ« se frekuenca e orĂ«s sĂ« pikselĂ«ve. NĂ«se marrim si shembull modalitetin video 1280×720 60Hz, frekuenca e pikselĂ«ve pĂ«r kĂ«tĂ« modalitet Ă«shtĂ« 74,25 MHz. NĂ« serializer duhet tĂ« jenĂ« 742,5 MHz.

FPGA-tĂ« e zakonshme, pĂ«r fat tĂ« keq, nĂ« pĂ«rgjithĂ«si nuk e pĂ«rballojnĂ« kĂ«tĂ«. MegjithatĂ«, pĂ«r fatin tonĂ«, FPGA ka dalje tĂ« integruara DDIO. KĂ«to janĂ« dalje qĂ« nĂ« thelb funksionojnĂ« si serializer 2-nĂ«-1. DomethĂ«nĂ«, ato mund tĂ« nxjerrin nĂ« mĂ«nyrĂ« serike dy bit nĂ« frontin ngritĂ«s dhe zbritĂ«s tĂ« frekuencĂ«s sĂ« orĂ«s. Kjo do tĂ« thotĂ« se nĂ« projektin FPGA mund tĂ« pĂ«rdoret jo 740 MHz, por 370 MHz, por duhet tĂ« aktivizohen elementet dalĂ«se DDIO nĂ« PLIS. NdĂ«rsa 370 MHz Ă«shtĂ« tashmĂ« njĂ« frekuencĂ« plotĂ«sisht e arritshme. FatkeqĂ«sisht, modaliteti 1280×720 Ă«shtĂ« kufiri. Rezolucion mĂ« i lartĂ« nĂ« FPGA-nĂ« tonĂ« Cyclone IV tĂ« instaluar nĂ« pllakĂ«n Marsokhod2rpi nuk mund tĂ« arrihet.

Pra, në projekt, frekuenca hyrëse e pikselëve CLK dërgohet te PLL, ku shumëzohet me 5. Në këtë frekuencë, bajtet R, G, B shndërrohen në çifte bitësh. Këtë e bën enkoderi TMDS. Kodi burimor në Verilog HDL duket kështu:

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;  // numërues modulo 5
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 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

Më pas çiftet dalëse dërgohen në daljen DDIO, e cila nxjerr në mënyrë sekuenciale një sinjal njëbitësh në frontin në rritje dhe në rënie.

Vetë DDIO mund të përshkruhet me këtë kod 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

Por kështu me shumë gjasa nuk do të funksionojë. Duhet të përdorni megafunksionin ALTDDIO_OUT të Altera-s, që të aktivizohen realisht elementet dalëse DDIO. Në projektin tim përdoret pikërisht komponenti i bibliotekës ALTDDIO_OUT.

Ndoshta e gjithë kjo duket pak e ndërlikuar, por funksionon.

Kodi i plotë burimor, i shkruar në Verilog HDL, mund të shihet këtu, në github.

Firmware-i i përpiluar për FPGA shkruhet në çipin EPCS të instaluar në pllakën Marsokhod2rpi. Kështu, kur pllaka FPGA furnizohet me energji, FPGA do të inicializohet nga memoria flash dhe do të niset.

Tani duhet të shpjegojmë pak konfigurimin e vetë Raspberry.

Unë po kryej eksperimente në Raspberry PI OS (32 bit) based on Debian Buster, Version:August 2020,
Data e lëshimit:2020-08-20, versioni i kernelit:5.4.

Duhet të bëhen dy gjëra:

  • tĂ« redaktohet skedari config.txt;
  • tĂ« krijohet konfigurimi i serverit X pĂ«r punĂ« me dy monitorĂ«.

Gjatë redaktimit të skedarit /boot/config.txt duhet:

  1. të çaktivizohet përdorimi i i2c, i2s, spi;
  2. të aktivizohet modaliteti DPI me anë të overlay dtoverlay=dpi24;
  3. tĂ« konfigurohet modaliteti video 1280×720 60Hz, 24 bit pĂ«r piksel nĂ« DPI;
  4. të përcaktohet numri i nevojshëm i framebuffer-ëve 2 (max_framebuffers=2, vetëm atëherë do të shfaqet pajisja e dytë /dev/fb1)

Teksti i plotë i skedarit config.txt duket kështu.

# 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

Pas kësaj, duhet të krijoni një skedar konfigurimi për serverin X për të përdorur dy monitorë në dy framebuffer-a /dev/fb0 dhe /dev/fb1:

Skedari im i konfigurimit /usr/share/x11/xorg.conf.d/60-dualscreen.conf është i tillë

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

Gjithashtu, nëse nuk është instaluar ende, duhet të instaloni Xinerama. Atëherë hapësira e desktopit do të zgjerohet plotësisht në dy monitorë, siç tregohet më sipër në videon demo.

Kjo është, me sa duket, e gjitha. Tani edhe pronarët e Raspberry Pi3 do të mund të përdorin dy monitorë.

PĂ«rshkrimin dhe skemĂ«n e pllakĂ«s Marsohod2rpi mund t’i shihni kĂ«tu.

Burimi: habr.com

Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Blini hosting tĂ« besueshĂ«m pĂ«r faqe interneti me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster