Toinen HDMI-näyttö Raspberry Pi3:een DPI-liitännän ja FPGA-kortin kautta


Tällä videolla näkyy: Raspberry Pi3 -kortti, johon on liitetty GPIO-liittimen kautta FPGA Mars Rover2rpi (Cyclone IV) -kortti, johon on liitetty HDMI-näyttö. Toinen näyttö liitetään tavallisen Raspberry Pi3 HDMI -liittimen kautta. Kaiken kaikkiaan se toimii kuin kahden näytön järjestelmä.

Seuraavaksi kerron kuinka se toteutetaan.

Suositussa Raspberry Pi3 -kortissa on GPIO-liitin, jonka kautta voit liittää erilaisia ​​laajennuskortteja: antureita, LEDejä, askelmoottorin ajureita ja paljon muuta. Liittimen kunkin nastan erityistoiminto riippuu portin kokoonpanosta. GPIO ALT2 -kokoonpanon avulla voit vaihtaa liittimen DPI-liitäntätilaan, Display Parallel Interface -tilaan. VGA-näyttöjen liittämistä varten DPI:n kautta on laajennuskortteja. Ensinnäkin VGA-näytöt eivät kuitenkaan ole enää yhtä yleisiä kuin HDMI, ja toiseksi digitaalinen käyttöliittymä on paranemassa kuin analoginen. Lisäksi tällaisten VGA-laajennuskorttien DAC on yleensä valmistettu R-2-R-ketjujen muodossa ja usein enintään 6 bittiä väriä kohden.

ALT2-tilassa GPIO-liittimen nastoilla on seuraava merkitys:

Toinen HDMI-näyttö Raspberry Pi3:een DPI-liitännän ja FPGA-kortin kautta

Tässä värjäsin liittimen RGB-nastat punaiseksi, vihreäksi ja siniseksi. Muita tärkeitä signaaleja ovat V-SYNC- ja H-SYNC-pyyhkäisysynkronointisignaalit sekä CLK. CLK-kellotaajuus on taajuus, jolla pikseliarvot lähetetään liittimeen ja riippuu valitusta videotilasta.

Digitaalisen HDMI-näytön kytkemiseksi sinun on kaapattava DPI-liitännän signaalit ja muunnettava ne HDMI-signaaleiksi. Tämä voidaan tehdä esimerkiksi millä tahansa FPGA-kortilla. Kuten kävi ilmi, Mars Rover2rpi -levy sopii tähän tarkoitukseen. Itse asiassa päävaihtoehto tämän levyn liittämiseksi erityisen sovittimen kautta näyttää tältä:

Toinen HDMI-näyttö Raspberry Pi3:een DPI-liitännän ja FPGA-kortin kautta

Tätä korttia käytetään lisäämään GPIO-porttien määrää ja liittämään lisää oheislaitteita Raspberryyn. Samalla 4 GPIO-signaalia tällä liitännällä käytetään JTAG-signaaleille, jotta jakelun ohjelma voi ladata FPGA-laiteohjelmiston FPGA:han. Tästä johtuen tällainen tavallinen yhteys ei sovi minulle, 4 DPI signaalia putoaa. Onneksi laudan ylimääräisissä kammoissa on Raspberry-yhteensopiva pinout. Jotta voin kääntää levyä 90 astetta ja silti yhdistää sen vadelmaani:

Toinen HDMI-näyttö Raspberry Pi3:een DPI-liitännän ja FPGA-kortin kautta

Tietenkin sinun on käytettävä ulkoista JTAG-ohjelmoijaa, mutta tämä ei ole ongelma.

Pieni ongelma on edelleen olemassa. Jokaista FPGA-nastaa ei voi käyttää kellotulona. Tähän tarkoitukseen voidaan käyttää vain muutamia erityisiä nastoja. Joten tässä kävi ilmi, että GPIO_0 CLK signaali ei pääse FPGA-tuloon, jota voidaan käyttää FPGA-kellotulona. Joten kaikesta huolimatta minun piti heittää yksi viesti huiviin. Yhdistän kortin GPIO_0- ja KEY[1]-signaalin:

Toinen HDMI-näyttö Raspberry Pi3:een DPI-liitännän ja FPGA-kortin kautta

Nyt kerron teille hieman projektista FPGA:ssa. Suurin vaikeus HDMI-signaalien muodostamisessa on erittäin korkeat taajuudet. Kun katsot HDMI-liittimen liitäntää, voit nähdä, että RGB-signaalit ovat nyt sarjadifferentiaalisignaaleja:

Toinen HDMI-näyttö Raspberry Pi3:een DPI-liitännän ja FPGA-kortin kautta

Differentiaalisignaalin käyttö mahdollistaa siirtojohdon yhteismoodihäiriöiden käsittelyn. Tässä tapauksessa kunkin värisignaalin alkuperäinen kahdeksanbittinen koodi muunnetaan 10-bittiseksi TMDS:ksi (Transition-minimized differential signaling). Tämä on erityinen koodausmenetelmä DC-komponentin poistamiseksi signaalista ja signaalin vaihtamisen minimoimiseksi differentiaalilinjassa. Koska sarjalinjalla on nyt 10 bittiä lähetettävää väritavua kohden, käy ilmi, että sarjoittajan kellotaajuuden on oltava 10 kertaa suurempi kuin pikselien kellotaajuuden. Jos otamme esimerkiksi videotilan 1280x720 60Hz, niin tämän tilan pikselitaajuus on 74,25MHz. Sarjasoittimen tulee olla 742,5 MHz.

Perinteiset FPGA:t eivät valitettavasti yleensä pysty tähän. Onneksemme FPGA:ssa on kuitenkin sisäänrakennetut DDIO-nastat. Nämä ovat johtopäätöksiä, jotka ovat jo ikään kuin 2-1-serialisoijia. Toisin sanoen ne voivat tuottaa kaksi bittiä peräkkäin nousevilla ja laskevilla kellotaajuuksilla. Tämä tarkoittaa, että FPGA-projektissa ei voi käyttää 740 MHz:n, vaan 370 MHz:n taajuutta, mutta sinun on käytettävä DDIO-lähtöelementtejä FPGA:ssa. Täällä 370 MHz on jo melko saavutettavissa oleva taajuus. Valitettavasti 1280 × 720 -tila on rajana. Korkeampaa resoluutiota ei voida saavuttaa Rover2rpi-kortille asennetulla FPGA Cyclone IV:llä.

Joten projektissa tulopikselitaajuus CLK syötetään PLL:ään, jossa se kerrotaan 5:llä. Tällä taajuudella R-, G-, B-tavut muunnetaan bitipareiksi. Tätä TMDS-kooderi tekee. Verilog HDL:n lähdekoodi näyttää tältä:

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

Sitten lähtöparit syötetään DDIO-lähtöön, joka tuottaa peräkkäin yksibittisen signaalin nousun ja laskun aikana.

Itse DDIO voitaisiin kuvata Verilog-koodilla seuraavasti:

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

Mutta se ei todennäköisesti toimi niin. Sinun on käytettävä Alteran ALTDDIO_OUT-megafunktiota, jotta voit käyttää DDIO-lähtöelementtejä. Projektissani käytetään kirjastokomponenttia ALTDDIO_OUT.

Kaikki saattaa näyttää hieman hankalalta, mutta se toimii.

Voit tarkastella koko lähdekoodia, joka on kirjoitettu Verilog HDL:llä täällä githubissa.

FPGA:lle käännetty laiteohjelmisto on upotettu Mars Rover2rpi -kortille asennettuun EPCS-siruun. Näin ollen, kun FPGA-korttiin kytketään virta, FPGA alustetaan flash-muistista ja käynnistyy.

Nyt meidän on puhuttava hieman itse Raspberryn kokoonpanosta.

Teen kokeiluja Raspberry PI OS:llä (32-bittinen), joka perustuu Debian Busteriin, Versio:Elokuu 2020,
Julkaisupäivä: 2020-08-20, Ytimen versio: 5.4.

Sinun on tehtävä kaksi asiaa:

  • muokkaa config.txt-tiedostoa;
  • Luo X-palvelinkokoonpano, joka toimii kahden näytön kanssa.

Kun muokkaat /boot/config.txt-tiedostoa, sinun on:

  1. poista käytöstä i2c, i2s, spi;
  2. ota DPI-tila käyttöön peittokuvalla dtoverlay=dpi24;
  3. aseta videotila 1280×720 60Hz, 24 bittiä per piste per DPI;
  4. määritä tarvittava määrä kehyspuskureita 2 (max_framebuffers=2, vasta sitten tulee näkyviin toinen laite /dev/fb1)

Config.txt-tiedoston koko teksti näyttää tältä.

# 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

Tämän jälkeen sinun on luotava konfiguraatiotiedosto X-palvelimelle, jotta se voi käyttää kahta näyttöä kahdessa kehyspuskurissa /dev/fb0 ja /dev/fb1:

Määritystiedostoni on /usr/share/x11/xorg.conf.d/60-dualscreen.conf, kuten tämä

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

No, jos sitä ei ole vielä asennettu, sinun on asennettava Xinerama. Sitten työpöytätila laajenee täysin kahdelle näytölle, kuten yllä olevassa esittelyvideossa näkyy.

Siinä varmaan kaikki. Nyt Raspberry Pi3 -omistajat voivat käyttää kahta näyttöä.

Kuvaus ja kaavio Mars Rover2rpi -levystä voi olla Katso tästä.

Lähde: will.com