Anden HDMI-monitor til Raspberry Pi3 via DPI-interface og FPGA-kort


Denne video viser: Raspberry Pi3-kortet, til hvilket FPGA-kortet Mars Rover2rpi (Cyclone IV) er tilsluttet via GPIO-stikket, og som har et HDMI-skærm tilsluttet. Den anden monitor er tilsluttet via det originale HDMI-stik på Raspberry Pi3. Alt fungerer sammen som et system med to skærme.

Jeg vil nu forklare, hvordan dette er implementeret.

På det populære Raspberry Pi3-kort er der et GPIO-stik, hvorigennem forskellige udvidelseskort kan tilsluttes: sensorer, lysdioder, driver til trinmotorer og meget mere. Den specifikke funktion af hver pin på stikket afhænger af portkonfigurationen. GPIO ALT2-konfigurationen tillader at skifte stikket til DPI-interface, Display Parallel Interface. Der findes udvidelseskort til tilslutning af VGA-skærme via DPI. Men for det første er VGA-skærme ikke så udbredte som HDMI, og for det andet er digital interface hele tiden bedre end analog. Særligt da DAC'en på sådanne VGA-udvidelseskort typisk laves som R-2-R kæder og sjældent har mere end 6 bit pr. farve.

I ALT2-tilstand har GPIO-stikkets pins følgende værdier:

Anden HDMI-monitor til Raspberry Pi3 via DPI-interface og FPGA-kort

Jeg har her farvet RGB-udgangene på stikket i henholdsvis rød, grøn og blå. Andre vigtige signaler er synkroniseringssignalerne V-SYNC og H-SYNC samt CLK. Klokfrekvensen for CLK er den frekvens, hvormed pixelværdierne gives til stikket, og den afhænger af den valgte videoopløsning.

For at tilslutte en digital HDMI-skærm skal signalerne fra DPI-interfacet fanges og konverteres til HDMI-signaler. Dette kan gøres for eksempel ved hjælp af et FPGA-kort. Det viste sig, at kortet Mars Rover2rpi er velegnet til disse formål. For at være ærlig ser den mest almindelige tilslutningsmetode for dette kort gennem en speciel adapter således ud:

Anden HDMI-monitor til Raspberry Pi3 via DPI-interface og FPGA-kort

Dette kort bruges til at øge antallet af GPIO-porte og til at tilslutte flere periferiudstyr til Raspberry. I denne forbindelse bruges 4 GPIO-signaler til JTAG-signaler, således at programmet fra Raspberry kan uploade FPGA-firmware til PLSX. På grund af dette er denne standardtilslutning ikke passende for mig, da 4 DPI-signaler falder ud. Heldigvis har ekstra headers på kortet en pin-udlægning, der er kompatibel med Raspberry. Således kan jeg dreje kortet 90 grader og stadig tilslutte det til min Raspberry Pi:

Anden HDMI-monitor til Raspberry Pi3 via DPI-interface og FPGA-kort

Selvfølgelig skal jeg bruge en ekstern JTAG-programmør, men det er ikke et problem.

Der er dog et lille problem. Ikke alle FPGA-udgange kan bruges som kloksignal-indgange. Der er kun et par dedikerede ben, som kan anvendes til dette formål. Således var det også her, at GPIO_0-clock-signalet ikke ramte FPGAens indgang, som kunne bruges som indgang til kloksignalet. Derfor måtte jeg alligevel trække en ledning til printpladen. Jeg forbinder GPIO_0 med KEY[1]-signalet på printpladen:

Anden HDMI-monitor til Raspberry Pi3 via DPI-interface og FPGA-kort

Nu vil jeg fortælle lidt om projektet i FPGA. Den største udfordring ved at generere HDMI-signaler er de meget høje frekvenser. Hvis man ser på HDMI-stikkets pinout, kan man se, at RGB-signalerne nu er serielle differentielle signaler:

Anden HDMI-monitor til Raspberry Pi3 via DPI-interface og FPGA-kort

Brugen af differentielle signaler hjælper med at bekæmpe fælles-modulation interferens på transmissionslinjen. Her konverteres den originale otte-bit farvekode for hvert signal til 10-bit TMDS (Transition-minimized differential signaling). Dette er en særlig kodningsmetode til at fjerne den konstante komponent fra signalet og minimere signal-skift i den differentielle linje. Da der nu skal overføres 10 bit pr. farvebyte via den serielle transmissionslinje, betyder det, at klokfrekvensen for serializereren skal være 10 gange højere end klokfrekvensen for pixelene. For eksempel, i videoformatet 1280x720 ved 60Hz, er pixel-frekvensen for dette format 74,25MHz. Serializereren skal arbejde ved 742,5MHz.

Almindelige FPGA'er er desværre ikke i stand til dette. Heldigvis findes der indbyggede DDIO-udgange i FPGA'en. Disse udgange fungerer som serializerer 2-to-1. Det betyder, at de kan sende to bits sekventielt på både stigende og faldende kloksignal. Derfor kan projektet i FPGA'en bruge 370MHz i stedet for 740MHz, men DDIO-udgange skal aktiveres i FPGA'en. 370MHz er en frekvens, der allerede er ganske opnåelig. Desværre er 1280×720 det maksimale opløsning, der kan opnås med vores Cyclone IV FPGA, der er installeret på Mars Rover 2 printpladen.

Så, i projektet, kommer indgangs-clockfrekvensen CLK til PLL, hvor den multipliceres med 5. Ved denne frekvens konverteres bytes R, G, B til bit-par. Dette udføres af TMDS-koderen. Kildekoden i Verilog HDL ser sådan ud:

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 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

Derefter sendes udgangsparrene til DDIO-udgangen, som sekventielt sender et enkelt bit-signal ved både stigende og faldende kant.

Selve DDIO kan beskrives med følgende Verilog-kode:

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

Men kan forvente, at det ikke vil fungere på denne måde. Det er nødvendigt at bruge altero's mega-funktion ALTDDIO_OUT for faktisk at aktivere udgangs-DDIO-elementerne. I mit projekt anvendes netop bibliotekskomponenten ALTDDIO_OUT.

Måske ser alt dette lidt kompliceret ud, men det fungerer.

Du kan se hele koden, der er skrevet i Verilog HDL, her, på github..

Den kompilerede firmware til FPGA'en bliver indlæst i EPCS-chippen, der er installeret på Mars Rover2rpi-kortet. Således, når kortet får strøm, vil FPGA'en blive initialiseret fra flash-hukommelsen og starte.

Nu er det tid til at tale lidt om konfigurationen af Raspberry'en selv.

Jeg eksperimenterer med Raspberry PI OS (32 bit) baseret på Debian Buster, version: August 2020,
Udgivelsesdato: 2020-08-20, kerneversion: 5.4.

Der er to ting, der skal gøres:

  • redigere filen config.txt;
  • oprette konfigurationen af X-serveren til brug med to skærme.

Når du redigerer filen /boot/config.txt, skal du:

  1. deaktivere brug af i2c, i2s, spi;
  2. aktivere DPI-tilstand ved hjælp af overlay dtoverlay=dpi24;
  3. konfigurere videotilstand 1280×720 60Hz, 24 bit per pixel på DPI;
  4. angive det nødvendige antal framebufferne, 2 (max_framebuffers=2, kun da vises den anden enhed /dev/fb1)

Den komplette tekst af filen config.txt ser således ud.

# 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

Derefter skal du oprette en konfigurationsfil til X-serveren for at bruge to skærme på to framebufferne /dev/fb0 og /dev/fb1:

Min konfigurationsfil /usr/share/x11/xorg.conf.d/60-dualscreen.conf ser sådan ud:

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

Og hvis det ikke allerede er installeret, skal Xinerama installeres. Således vil skrivebordsområdet være ordentligt udvidet til to skærme, som vist ovenfor i demo-videoen.

Det er vist det hele. Nu kan ejere af Raspberry Pi3 også benytte to skærme.

Du kan se beskrivelsen og diagrammet af Mars Rover2rpi her.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster