Monitori i dytë HDMI në Raspberry Pi3 nëpërmjet ndërfaqes DPI dhe bordit FPGA


Kjo video tregon: një tabelë Raspberry Pi3, e lidhur me të nëpërmjet lidhësit GPIO është një tabelë FPGA Mars Rover2rpi (Cyclone IV), me të cilën është lidhur një monitor HDMI. Monitori i dytë është i lidhur nëpërmjet lidhësit standard HDMI të Raspberry Pi3. Gjithçka funksionon së bashku si një sistem monitor i dyfishtë.

Më pas do t'ju tregoj se si zbatohet kjo.

Pllaka popullore Raspberry Pi3 ka një lidhës GPIO përmes të cilit mund të lidhni karta të ndryshme zgjerimi: sensorë, LED, drejtues motori stepper dhe shumë më tepër. Funksioni i saktë i çdo pin në një lidhës varet nga konfigurimi i portit. Konfigurimi GPIO ALT2 ju lejon të kaloni lidhësin në modalitetin e ndërfaqes DPI, Ndërfaqja paralele e ekranit. Ka karta zgjerimi për lidhjen e monitorëve VGA përmes DPI. Sidoqoftë, së pari, monitorët VGA nuk janë më aq të zakonshëm sa HDMI, dhe së dyti, ndërfaqja dixhitale është gjithnjë e më e mirë se ajo analoge. Për më tepër, DAC në pllaka të tilla zgjerimi VGA zakonisht bëhet në formën e zinxhirëve R-2-R dhe shpesh jo më shumë se 6 bit për ngjyrë.

Në modalitetin ALT2, kunjat e lidhësit GPIO kanë kuptimin e mëposhtëm:

Monitori i dytë HDMI në Raspberry Pi3 nëpërmjet ndërfaqes DPI dhe bordit FPGA

Këtu i kam ngjyrosur kunjat RGB të lidhësit përkatësisht të kuqe, jeshile dhe blu. Sinjale të tjera të rëndësishme janë sinjalet V-SYNC dhe H-SYNC, si dhe CLK. Frekuenca e orës CLK është frekuenca në të cilën vlerat e pikselit dalin në lidhës; kjo varet nga mënyra e zgjedhur e videos.

Për të lidhur një monitor dixhital HDMI, duhet të kapni sinjalet DPI të ndërfaqes dhe t'i konvertoni ato në sinjale HDMI. Kjo mund të bëhet, për shembull, duke përdorur një lloj bordi FPGA. Siç rezulton, bordi Mars Rover2rpi është i përshtatshëm për këto qëllime. Në të vërtetë, opsioni kryesor për lidhjen e këtij bordi përmes një përshtatësi të veçantë duket si ky:

Monitori i dytë HDMI në Raspberry Pi3 nëpërmjet ndërfaqes DPI dhe bordit FPGA

Kjo tabelë përdoret për të rritur numrin e portave GPIO dhe për të lidhur më shumë pajisje periferike me raspberry. Në të njëjtën kohë, 4 sinjale GPIO me këtë lidhje përdoren për sinjalet JTAG, në mënyrë që programi nga Raspberry të mund të ngarkojë firmuerin FPGA në FPGA. Për shkak të kësaj, kjo lidhje standarde nuk më përshtatet; 4 sinjale DPI bien. Për fat të mirë, krehrat shtesë në tabelë kanë një pinout të pajtueshëm me Raspberry. Kështu që unë mund ta rrotulloj tabelën 90 gradë dhe ende ta lidh atë me mjedrën time:

Monitori i dytë HDMI në Raspberry Pi3 nëpërmjet ndërfaqes DPI dhe bordit FPGA

Sigurisht, do t'ju duhet të përdorni një programues të jashtëm JTAG, por ky nuk është problem.

Ka ende një problem të vogël. Jo çdo pin FPGA mund të përdoret si hyrje e orës. Ka vetëm disa kunja të dedikuara që mund të përdoren për këto qëllime. Pra, këtu doli që sinjali GPIO_0 CLK nuk arrin hyrjen FPGA, e cila mund të përdoret si një hyrje e orës FPGA. Kështu që më duhej të vendosja një tel në shall. Lidh GPIO_0 dhe sinjalin KEY[1] të bordit:

Monitori i dytë HDMI në Raspberry Pi3 nëpërmjet ndërfaqes DPI dhe bordit FPGA

Tani do t'ju tregoj pak për projektin FPGA. Vështirësia kryesore në gjenerimin e sinjaleve HDMI është frekuenca shumë e lartë. Nëse shikoni pikën e lidhësit HDMI, mund të shihni se sinjalet RGB tani janë sinjale diferenciale serike:

Monitori i dytë HDMI në Raspberry Pi3 nëpërmjet ndërfaqes DPI dhe bordit FPGA

Përdorimi i një sinjali diferencial ju lejon të luftoni ndërhyrjen e modalitetit të zakonshëm në linjën e transmetimit. Në këtë rast, kodi origjinal tetë-bit i çdo sinjali me ngjyra konvertohet në një TMDS 10-bitësh (sinjalizim diferencial të minimizuar nga tranzicioni). Kjo është një metodë e veçantë kodimi për të hequr komponentin DC nga sinjali dhe për të minimizuar kalimin e sinjalit në një linjë diferenciale. Meqenëse 10 bit tani duhet të transmetohen në linjën serike për një bajt ngjyrë, rezulton se shpejtësia e orës së serializuesit duhet të jetë 10 herë më e lartë se shpejtësia e orës së pikselit. Nëse marrim për shembull modalitetin e videos 1280x720 60Hz, atëherë frekuenca e pikselëve të këtij modaliteti është 74,25 MHz. Serializuesi duhet të jetë 742,5 MHz.

FPGA-të e rregullta, për fat të keq, nuk janë të afta për këtë. Megjithatë, për fat të mirë për ne, FPGA ka kunja DDIO të integruara. Këto janë përfundime që tashmë janë, si të thuash, serializues 2-me-1. Kjo do të thotë, ata mund të nxjerrin dy bit në mënyrë sekuenciale në skajet në rritje dhe në rënie të frekuencës së orës. Kjo do të thotë që në një projekt FPGA mund të përdorni jo 740 MHz, por 370 MHz, por duhet të përdorni elementë dalës DDIO në FPGA. Tani 370 MHz është tashmë një frekuencë plotësisht e arritshme. Fatkeqësisht, modaliteti 1280x720 është kufiri. Një rezolucion më i lartë nuk mund të arrihet në Cyclone IV FPGA tonë të instaluar në tabelën Mars Rover2rpi.

Pra, në dizajn, frekuenca e pikselit hyrës CLK shkon në PLL, ku shumëzohet me 5. Në këtë frekuencë, bajtet R, G, B konvertohen në çifte bit. Kjo është ajo që bën koduesi TMDS. Kodi burimor në Verilog HDL duket si ky:

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

Pastaj çiftet e daljes futen në daljen DDIO, e cila prodhon në mënyrë sekuenciale një sinjal një-bit në skajet në rritje dhe në rënie.

Vetë DDIO mund të përshkruhet me kodin e mëposhtëm 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 me shumë mundësi nuk do të funksionojë në këtë mënyrë. Ju duhet të përdorni megafunksionin ALTDDIO_OUT të Alter për të aktivizuar në fakt elementët e daljes DDIO. Projekti im përdor komponentin e bibliotekës ALTDDIO_OUT.

E gjithë kjo mund të duket pak e ndërlikuar, por funksionon.

Mund të shikoni të gjithë kodin burimor të shkruar në Verilog HDL këtu në github.

Firmware i përpiluar për FPGA është ndezur në çipin EPCS të instaluar në tabelën Mars Rover2rpi. Kështu, kur fuqia aplikohet në bordin FPGA, FPGA do të inicializohet nga memoria flash dhe do të fillojë.

Tani duhet të flasim pak për konfigurimin e vetë Raspberry.

Unë jam duke bërë eksperimente në Raspberry PI OS (32 bit) bazuar në Debian Buster, Versioni: Gusht 2020,
Data e publikimit: 2020-08-20, versioni i kernelit: 5.4.

Ju duhet të bëni dy gjëra:

  • modifikoni skedarin config.txt;
  • krijoni një konfigurim të serverit X për të punuar me dy monitorë.

Kur redaktoni skedarin /boot/config.txt ju duhet:

  1. çaktivizoni përdorimin e i2c, i2s, spi;
  2. aktivizoni modalitetin DPI duke përdorur mbivendosjen dtoverlay=dpi24;
  3. konfiguroni modalitetin e videos 1280×720 60Hz, 24 bit për pixel në DPI;
  4. specifikoni numrin e kërkuar të framebuffers 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, ju duhet të krijoni një skedar konfigurimi për serverin X që të përdorë dy monitorë në dy framebuffer /dev/fb0 dhe /dev/fb1:

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

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

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

Kjo është ndoshta e gjitha. Tani, pronarët e Raspberry Pi3 do të jenë në gjendje të përdorin dy monitorë.

Mund të gjendet përshkrimi dhe diagrami i qarkut të bordit Mars Rover2rpi Shikoni këtu.

Burimi: www.habr.com