Otrais HDMI monitors uz Raspberry Pi3, izmantojot DPI interfeisu un FPGA plati


Šajā video redzams: Raspberry Pi3 plate, kurai caur GPIO savienotāju ir pievienota FPGA Mars Rover2rpi (Cyclone IV) plate, kurai pievienots HDMI monitors. Otrais monitors ir pievienots, izmantojot standarta Raspberry Pi3 HDMI savienotāju. Tas viss kopā darbojas kā divu monitoru sistēma.

Tālāk es jums pastāstīŔu, kā tas tiek īstenots.

Populārajai Raspberry Pi3 platei ir GPIO savienotājs, caur kuru var pieslēgt dažādas paplaÅ”ināŔanas plates: sensorus, LED, pakāpju motora draiverus un daudz ko citu. Katras savienotāja tapas Ä«paŔā funkcija ir atkarÄ«ga no porta konfigurācijas. GPIO ALT2 konfigurācija ļauj pārslēgt savienotāju uz DPI interfeisa režīmu Display Parallel Interface. Ir paplaÅ”ināŔanas plates VGA monitoru pievienoÅ”anai, izmantojot DPI. Tomēr, pirmkārt, VGA monitori vairs nav tik izplatÄ«ti kā HDMI, un, otrkārt, digitālais interfeiss kļūst labāks par analogo. Turklāt Ŕādu VGA paplaÅ”ināŔanas karÅ”u DAC parasti tiek izgatavots R-2-R ķēžu veidā un bieži vien ne vairāk kā 6 biti katrā krāsā.

ALT2 režīmā GPIO savienotāja tapām ir Ŕāda nozīme:

Otrais HDMI monitors uz Raspberry Pi3, izmantojot DPI interfeisu un FPGA plati

Å eit es krāsoju savienotāja RGB tapas attiecÄ«gi sarkanā, zaļā un zilā krāsā. Citi svarÄ«gi signāli ir V-SYNC un H-SYNC slaucÄ«Å”anas sinhronizācijas signāli, kā arÄ« CLK. CLK pulksteņa frekvence ir frekvence, kurā pikseļu vērtÄ«bas tiek izvadÄ«tas uz savienotāju, un tas ir atkarÄ«gs no izvēlētā video režīma.

Lai pievienotu digitālo HDMI monitoru, ir jāuztver DPI interfeisa signāli un jāpārvērÅ” tie par HDMI signāliem. To var izdarÄ«t, piemēram, izmantojot jebkuru FPGA plati. Kā izrādÄ«jās, Mars Rover2rpi dēlis ir piemērots Å”im nolÅ«kam. PatiesÄ«bā galvenā Ŕīs plates pievienoÅ”anas iespēja caur Ä«paÅ”u adapteri izskatās Ŕādi:

Otrais HDMI monitors uz Raspberry Pi3, izmantojot DPI interfeisu un FPGA plati

Å o plati izmanto, lai palielinātu GPIO portu skaitu un pievienotu vairāk perifērijas ierīču aveņu. Tajā paŔā laikā JTAG signāliem tiek izmantoti 4 GPIO signāli ar Å”o savienojumu, lai programma no izplatÄ«Å”anas varētu ielādēt FPGA programmaparatÅ«ru FPGA. Sakarā ar to tik regulārs savienojums man neder, izkrÄ«t 4 DPI signāli. Par laimi, papildu Ä·emmēm uz dēļa ir ar Raspberry saderÄ«gs spraudnis. Lai es varētu pagriezt dēli par 90 grādiem un joprojām savienot to ar savu aveņu:

Otrais HDMI monitors uz Raspberry Pi3, izmantojot DPI interfeisu un FPGA plati

Protams, jums būs jāizmanto ārējs JTAG programmētājs, taču tā nav problēma.

Joprojām ir neliela problēma. Ne katru FPGA tapu var izmantot kā pulksteņa ieeju. Å im nolÅ«kam var izmantot tikai dažas Ä«paÅ”as tapas. Tātad Å”eit izrādÄ«jās, ka GPIO_0 CLK signāls nenokļūst FPGA ieejā, kuru var izmantot kā FPGA pulksteņa ieeju. Tāpēc man nācās uzmest vienu sludinājumu uz Å”alles. Es savienoju plates GPIO_0 un KEY[1] signālu:

Otrais HDMI monitors uz Raspberry Pi3, izmantojot DPI interfeisu un FPGA plati

Tagad es jums pastāstÄ«Å”u nedaudz par projektu FPGA. Galvenās grÅ«tÄ«bas HDMI signālu veidoÅ”anā ir ļoti augstas frekvences. AplÅ«kojot HDMI savienotāja spraudni, varat redzēt, ka RGB signāli tagad ir seriālie diferenciālie signāli:

Otrais HDMI monitors uz Raspberry Pi3, izmantojot DPI interfeisu un FPGA plati

Diferenciālā signāla izmantoÅ”ana ļauj tikt galā ar kopēja režīma troksni pārvades lÄ«nijā. Å ajā gadÄ«jumā katra krāsu signāla sākotnējais astoņu bitu kods tiek pārveidots par 10 bitu TMDS (minimizēta pārejas diferenciālā signalizācija). Å Ä« ir Ä«paÅ”a kodÄ“Å”anas metode, lai noņemtu lÄ«dzstrāvas komponentu no signāla un samazinātu signāla pārslēgÅ”anu diferenciālajā lÄ«nijā. Tā kā tagad ir 10 biti, kas jāpārraida uz vienu krāsas baitu pa seriālo lÄ«niju, izrādās, ka serializētāja takts frekvencei jābÅ«t 10 reizes lielākai par pikseļu takts frekvenci. Ja ņemam, piemēram, video režīmu 1280x720 60Hz, tad Ŕī režīma pikseļu frekvence ir 74,25MHz. Serializatoram jābÅ«t 742,5 MHz.

Diemžēl parastie FPGA parasti to nespēj. Tomēr mÅ«su veiksmei FPGA ir iebÅ«vētas DDIO tapas. Tie ir secinājumi, kas jau it kā ir serializētāji 2 pret 1. Tas nozÄ«mē, ka tie var izvadÄ«t divus bitus pēc kārtas pa pieaugoŔām un krÄ«toŔām pulksteņa frekvencēm. Tas nozÄ«mē, ka FPGA projektā jÅ«s varat izmantot nevis 740 MHz, bet 370 MHz, bet jums ir jāizmanto DDIO izvades elementi FPGA. Å eit 370 MHz jau ir diezgan sasniedzama frekvence. Diemžēl 1280 Ɨ 720 režīms ir ierobežojums. Augstāku izŔķirtspēju nevar sasniegt ar mÅ«su FPGA Cyclone IV, kas uzstādÄ«ts uz Rover2rpi plates.

Tātad projektā ievades pikseļu frekvence CLK tiek ievadÄ«ta PLL, kur to reizina ar 5. Å ajā frekvencē R, G, B baiti tiek pārvērsti bitu pāros. To dara TMDS kodētājs. Verilog HDL avota kods izskatās Ŕādi:

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

Pēc tam izejas pāri tiek ievadīti DDIO izejā, kas secīgi rada viena bita signālu pieauguma un krituma laikā.

DDIO paŔu var aprakstīt ar Verilog kodu Ŕādi:

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 droÅ”i vien tas nedarbosies tā. Lai faktiski izmantotu DDIO izvades elementus, jums ir jāizmanto Altera ALTDDIO_OUT megafunkcija. Manā projektā tiek izmantots bibliotēkas komponents ALTDDIO_OUT.

Tas viss var izskatīties nedaudz sarežģīti, bet tas darbojas.

Varat skatīt visu Verilog HDL rakstīto avota kodu tepat githubā.

Apkopotā programmaparatūra FPGA ir iegulta EPCS mikroshēmā, kas uzstādīta uz Mars Rover2rpi plates. Tādējādi, kad FPGA platei tiek pieslēgta strāva, FPGA tiks inicializēts no zibatmiņas un sāksies.

Tagad mums nedaudz jāparunā par paŔa Raspberry konfigurāciju.

Es veicu eksperimentus ar Raspberry PI OS (32 bitu), pamatojoties uz Debian Buster, versija: 2020. gada augusts,
IzlaiŔanas datums: 2020-08-20, Kodola versija: 5.4.

Jums ir jādara divas lietas:

  • rediģēt failu config.txt;
  • izveidot X servera konfigurāciju darbam ar diviem monitoriem.

Rediģējot failu /boot/config.txt, jums ir nepiecieÅ”ams:

  1. atspējot i2c, i2s, spi lietoÅ”anu;
  2. iespējot DPI režīmu ar pārklājumu dtoverlay=dpi24;
  3. iestatÄ«t video režīmu 1280Ɨ720 60Hz, 24 biti uz punktu vienā DPI;
  4. norādiet nepiecieÅ”amo kadru buferu skaitu 2 (max_framebuffers=2, tikai tad parādÄ«sies otrā ierÄ«ce /dev/fb1)

Pilns faila config.txt teksts izskatās Ŕādi.

# 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

Pēc tam jums ir jāizveido konfigurācijas fails X serverim, lai izmantotu divus monitorus divos kadru buferos /dev/fb0 un /dev/fb1:

Mans konfigurācijas fails ir /usr/share/x11/xorg.conf.d/60-dualscreen.conf kā Ŕis

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

Nu, ja vēl nav instalēts, tad jums ir jāinstalē Xinerama. Pēc tam darbvirsmas vieta tiks pilnÄ«bā paplaÅ”ināta lÄ«dz diviem monitoriem, kā parādÄ«ts iepriekÅ” esoÅ”ajā demonstrācijas videoklipā.

Tas laikam arÄ« viss. Tagad Raspberry Pi3 Ä«paÅ”nieki varēs izmantot divus monitorus.

Mars Rover2rpi dēļa apraksts un diagramma var bÅ«t skatÄ«t Å”eit.

Avots: www.habr.com