Tweede HDMI-monitor naar Raspberry Pi3 via DPI-interface en FPGA-kaart


Deze video laat zien: een Raspberry Pi3-bord, daarop aangesloten via de GPIO-connector, is een FPGA-bord Mars Rover2rpi (Cyclone IV), waarop een HDMI-monitor is aangesloten. De tweede monitor wordt aangesloten via de standaard HDMI-connector van de Raspberry Pi3. Alles werkt samen als een dubbel monitorsysteem.

Vervolgens zal ik je vertellen hoe dit wordt geïmplementeerd.

Het populaire Raspberry Pi3-bord heeft een GPIO-connector waarmee je verschillende uitbreidingskaarten kunt aansluiten: sensoren, LED's, stappenmotordrivers en nog veel meer. De exacte functie van elke pin op een connector hangt af van de poortconfiguratie. Met de GPIO ALT2-configuratie kunt u de connector overschakelen naar de DPI-interfacemodus, Display Parallel Interface. Er zijn uitbreidingskaarten voor het aansluiten van VGA-monitoren via DPI. Ten eerste zijn VGA-monitoren echter niet meer zo gebruikelijk als HDMI, en ten tweede wordt de digitale interface steeds beter dan de analoge. Bovendien wordt de DAC op dergelijke VGA-uitbreidingskaarten meestal gemaakt in de vorm van R-2-R-ketens en vaak niet meer dan 6 bits per kleur.

In de ALT2-modus hebben de GPIO-connectorpinnen de volgende betekenis:

Tweede HDMI-monitor naar Raspberry Pi3 via DPI-interface en FPGA-kaart

Hier heb ik de RGB-pinnen van de connector respectievelijk rood, groen en blauw gekleurd. Andere belangrijke signalen zijn de V-SYNC- en H-SYNC-signalen, evenals CLK. De CLK-klokfrequentie is de frequentie waarmee pixelwaarden naar de connector worden verzonden; deze is afhankelijk van de geselecteerde videomodus.

Om een ​​digitale HDMI-monitor aan te sluiten, moet u de DPI-signalen van de interface opvangen en deze omzetten in HDMI-signalen. Dit kan bijvoorbeeld worden gedaan met behulp van een soort FPGA-bord. Het blijkt dat het Mars Rover2rpi-bord geschikt is voor deze doeleinden. In werkelijkheid ziet de belangrijkste optie om dit bord via een speciale adapter aan te sluiten er als volgt uit:

Tweede HDMI-monitor naar Raspberry Pi3 via DPI-interface en FPGA-kaart

Dit bord wordt gebruikt om het aantal GPIO-poorten te vergroten en om meer randapparatuur op de Raspberry aan te sluiten. Tegelijkertijd worden met deze aansluiting 4 GPIO-signalen gebruikt voor JTAG-signalen, zodat het programma van Raspberry de FPGA-firmware in de FPGA kan laden. Hierdoor bevalt deze standaard aansluiting mij niet; er vallen 4 DPI signalen weg. Gelukkig hebben de extra kammen op het bord een Raspberry-compatibele pin-out. Dus ik kan het bord 90 graden draaien en toch verbinden met mijn framboos:

Tweede HDMI-monitor naar Raspberry Pi3 via DPI-interface en FPGA-kaart

Uiteraard zul je een externe JTAG-programmeur moeten gebruiken, maar dit is geen probleem.

Er is nog steeds een klein probleem. Niet elke FPGA-pin kan als klokingang worden gebruikt. Er zijn slechts een paar speciale pinnen die voor deze doeleinden kunnen worden gebruikt. Hier bleek dus dat het GPIO_0 CLK-signaal de FPGA-ingang, die als FPGA-klokingang kan worden gebruikt, niet bereikt. Ik moest dus nog één draad aan de sjaal doen. Ik verbind GPIO_0 en het KEY[1]-signaal van het bord:

Tweede HDMI-monitor naar Raspberry Pi3 via DPI-interface en FPGA-kaart

Nu zal ik je iets vertellen over het FPGA-project. De grootste moeilijkheid bij het genereren van HDMI-signalen zijn de zeer hoge frequenties. Als je naar de pinout van de HDMI-connector kijkt, kun je zien dat de RGB-signalen nu seriële differentiële signalen zijn:

Tweede HDMI-monitor naar Raspberry Pi3 via DPI-interface en FPGA-kaart

Door het gebruik van een differentieel signaal kunt u common-mode-interferentie op de transmissielijn tegengaan. In dit geval wordt de oorspronkelijke acht-bits code van elk kleursignaal omgezet in een 10-bits TMDS (Transition-minimized Differential Signaling). Dit is een speciale coderingsmethode om de DC-component uit het signaal te verwijderen en signaalwisselingen in een differentiële lijn te minimaliseren. Omdat er nu voor één byte kleur 10 bits over de seriële lijn moeten worden verzonden, blijkt dat de kloksnelheid van de serialisator 10 keer hoger moet zijn dan de pixelkloksnelheid. Als we bijvoorbeeld de videomodus 1280x720 60Hz nemen, dan is de pixelfrequentie van deze modus 74,25 MHz. De serialisator moet 742,5 MHz zijn.

Reguliere FPGA's zijn hiertoe helaas niet in staat. Gelukkig voor ons heeft de FPGA echter ingebouwde DDIO-pinnen. Dit zijn conclusies die als het ware al 2-op-1 serializers zijn. Dat wil zeggen dat ze twee bits opeenvolgend kunnen uitvoeren op de stijgende en dalende flanken van de klokfrequentie. Dit betekent dat je in een FPGA-project niet 740 MHz, maar 370 MHz kunt gebruiken, maar dat je DDIO-uitvoerelementen in de FPGA moet gebruiken. Nu is 370 MHz al een volledig haalbare frequentie. Helaas is de 1280x720-modus de limiet. Een hogere resolutie kan niet worden bereikt in onze Cyclone IV FPGA die op het Mars Rover2rpi-bord is geïnstalleerd.

In het ontwerp gaat de ingangspixelfrequentie CLK dus naar de PLL, waar deze met 5 wordt vermenigvuldigd. Bij deze frequentie worden de R-, G- en B-bytes omgezet in bitparen. Dit is wat de TMDS-encoder doet. De broncode in Verilog HDL ziet er als volgt uit:

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

Vervolgens worden de uitgangsparen naar de DDIO-uitgang gevoerd, die opeenvolgend een signaal van één bit produceert op de stijgende en dalende flanken.

DDIO zelf kan worden beschreven met de volgende Verilog-code:

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

Maar zo zal het waarschijnlijk niet werken. Je moet de megafunctie ALTDDIO_OUT van Alter gebruiken om de DDIO-uitvoerelementen daadwerkelijk in te schakelen. Mijn project maakt gebruik van de bibliotheekcomponent ALTDDIO_OUT.

Dit ziet er misschien allemaal een beetje lastig uit, maar het werkt.

U kunt alle broncode bekijken die in Verilog HDL is geschreven hier op github.

De gecompileerde firmware voor de FPGA wordt in de EPCS-chip geflasht die op het Mars Rover2rpi-bord is geïnstalleerd. Wanneer er dus stroom wordt ingeschakeld op het FPGA-bord, wordt de FPGA geïnitialiseerd vanuit het flashgeheugen en gestart.

Nu moeten we wat praten over de configuratie van de Raspberry zelf.

Ik doe experimenten met Raspberry PI OS (32 bit) gebaseerd op Debian Buster, versie: augustus 2020,
Releasedatum: 2020-08-20, kernelversie: 5.4.

Je moet twee dingen doen:

  • bewerk het config.txt-bestand;
  • maak een X-serverconfiguratie om met twee monitoren te werken.

Bij het bewerken van het bestand /boot/config.txt heeft u het volgende nodig:

  1. schakel het gebruik van i2c, i2s, spi uit;
  2. schakel de DPI-modus in met overlay dtoverlay=dpi24;
  3. configureer videomodus 1280×720 60Hz, 24 bits per pixel op DPI;
  4. specificeer het vereiste aantal framebuffers 2 (max_framebuffers=2, alleen dan zal het tweede apparaat /dev/fb1 verschijnen)

De volledige tekst van het bestand config.txt ziet er als volgt uit.

# 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

Hierna moet u een configuratiebestand maken voor de X-server om twee monitoren op twee framebuffers /dev/fb0 en /dev/fb1 te gebruiken:

Mijn configuratiebestand /usr/share/x11/xorg.conf.d/60-dualscreen.conf is als volgt

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

Als het nog niet is geïnstalleerd, moet je Xinerama installeren. Vervolgens wordt de bureaubladruimte volledig uitgebreid naar twee monitoren, zoals te zien is in de demovideo hierboven.

Dat is waarschijnlijk alles. Nu kunnen Raspberry Pi3-bezitters twee monitoren gebruiken.

Beschrijving en schakelschema van het Mars Rover2rpi-bord zijn te vinden kijk hier.

Bron: www.habr.com