Andre HDMI-skjerm til Raspberry Pi3 via DPI-grensesnitt og FPGA-kort


Denne videoen viser: et Raspberry Pi3-kort, koblet til det via GPIO-kontakten, er et FPGA-kort Mars Rover2rpi (Cyclone IV), som en HDMI-skjerm er koblet til. Den andre skjermen kobles til via standard HDMI-kontakten til Raspberry Pi3. Alt fungerer sammen som et system med to skjermer.

Deretter skal jeg fortelle deg hvordan dette implementeres.

Det populære Raspberry Pi3-kortet har en GPIO-kontakt som du kan koble til ulike utvidelseskort gjennom: sensorer, lysdioder, trinnmotordrivere og mye mer. Den nøyaktige funksjonen til hver pinne på en kontakt avhenger av portkonfigurasjonen. GPIO ALT2-konfigurasjonen lar deg bytte kontakten til DPI-grensesnittmodus, Display Parallel Interface. Det finnes utvidelseskort for tilkobling av VGA-skjermer via DPI. For det første er imidlertid ikke lenger VGA-skjermer like vanlig som HDMI, og for det andre er det digitale grensesnittet stadig bedre enn det analoge. Dessuten er DAC-en på slike VGA-utvidelseskort vanligvis laget i form av R-2-R-kjeder og ofte ikke mer enn 6 bits per farge.

I ALT2-modus har GPIO-kontaktpinnene følgende betydning:

Andre HDMI-skjerm til Raspberry Pi3 via DPI-grensesnitt og FPGA-kort

Her har jeg farget RGB-pinnene til kontakten henholdsvis rød, grønn og blå. Andre viktige signaler er V-SYNC og H-SYNC signaler, samt CLK. CLK-klokkefrekvensen er frekvensen som pikselverdier sendes ut til kontakten; den avhenger av den valgte videomodusen.

For å koble til en digital HDMI-skjerm, må du fange opp DPI-signalene til grensesnittet og konvertere dem til HDMI-signaler. Dette kan for eksempel gjøres ved å bruke et slags FPGA-kort. Som det viser seg, er Mars Rover2rpi-brettet egnet for disse formålene. I sannhet ser hovedalternativet for å koble til dette brettet gjennom en spesiell adapter slik ut:

Andre HDMI-skjerm til Raspberry Pi3 via DPI-grensesnitt og FPGA-kort

Dette kortet brukes til å øke antall GPIO-porter og til å koble flere perifere enheter til bringebæret. Samtidig brukes 4 GPIO-signaler med denne tilkoblingen til JTAG-signaler, slik at programmet fra Raspberry kan laste FPGA-fastvaren inn i FPGA. På grunn av dette passer ikke denne standardtilkoblingen meg; 4 DPI-signaler faller ut. Heldigvis har de ekstra kammene på brettet en Raspberry-kompatibel pinout. Så jeg kan rotere brettet 90 grader og fortsatt koble det til bringebæret mitt:

Andre HDMI-skjerm til Raspberry Pi3 via DPI-grensesnitt og FPGA-kort

Selvfølgelig må du bruke en ekstern JTAG-programmerer, men dette er ikke et problem.

Det er fortsatt et lite problem. Ikke alle FPGA-pinner kan brukes som en klokkeinngang. Det er bare noen få dedikerte pinner som kan brukes til disse formålene. Så her viste det seg at GPIO_0 CLK-signalet ikke når FPGA-inngangen, som kan brukes som en FPGA-klokkeinngang. Så jeg måtte fortsatt sette en ledning på skjerfet. Jeg kobler til GPIO_0 og kortets KEY[1]-signal:

Andre HDMI-skjerm til Raspberry Pi3 via DPI-grensesnitt og FPGA-kort

Nå skal jeg fortelle deg litt om FPGA-prosjektet. Den største vanskeligheten med å generere HDMI-signaler er svært høye frekvenser. Hvis du ser på HDMI-kontaktens pinout, kan du se at RGB-signalene nå er serielle differensialsignaler:

Andre HDMI-skjerm til Raspberry Pi3 via DPI-grensesnitt og FPGA-kort

Bruken av et differensialsignal lar deg bekjempe common mode interferens på overføringslinjen. I dette tilfellet konverteres den originale åttebitskoden til hvert fargesignal til en 10-biters TMDS (Transition-minimized differential signaling). Dette er en spesiell kodemetode for å fjerne DC-komponenten fra signalet og minimere signalbytte i en differensiallinje. Siden 10 bits nå må overføres over serielinjen for én byte med farge, viser det seg at serialiseringsklokkehastigheten må være 10 ganger høyere enn pikselklokkehastigheten. Hvis vi for eksempel tar videomodusen 1280x720 60Hz, så er pikselfrekvensen til denne modusen 74,25 MHz. Serializeren skal være 742,5 MHz.

Vanlige FPGA-er er dessverre ikke i stand til dette. Men heldigvis for oss har FPGA innebygde DDIO-pinner. Dette er konklusjoner som allerede så å si er 2-til-1 serializers. Det vil si at de kan sende ut to bits sekvensielt på de stigende og fallende kantene til klokkefrekvensen. Dette betyr at i et FPGA-prosjekt kan du ikke bruke 740 MHz, men 370 MHz, men du må bruke DDIO-utgangselementer i FPGA. Nå er 370 MHz allerede en fullstendig oppnåelig frekvens. Dessverre er 1280x720-modus grensen. En høyere oppløsning kan ikke oppnås i vår Cyclone IV FPGA installert på Mars Rover2rpi-kortet.

Så i designet går inngangspikselfrekvensen CLK til PLL, hvor den multipliseres med 5. Ved denne frekvensen konverteres R, G, B byte til bitpar. Dette er hva TMDS-koderen gjør. Kildekoden i Verilog HDL ser slik ut:

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

Deretter mates utgangsparene til DDIO-utgangen, som sekvensielt produserer et en-bits signal på de stigende og fallende kantene.

DDIO i seg selv 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 det vil mest sannsynlig ikke fungere slik. Du må bruke Alters megafunksjon ALTDDIO_OUT for å faktisk aktivere DDIO-utgangselementene. Prosjektet mitt bruker ALTDDIO_OUT-bibliotekskomponenten.

Det hele ser kanskje litt vanskelig ut, men det fungerer.

Du kan se all kildekoden skrevet i Verilog HDL her på github.

Den kompilerte fastvaren for FPGA-en flashes inn i EPCS-brikken som er installert på Mars Rover2rpi-kortet. Når strøm tilføres FPGA-kortet, vil FPGA-en bli initialisert fra flashminnet og starte.

Nå må vi snakke litt om konfigurasjonen av selve bringebæret.

Jeg gjør eksperimenter på Raspberry PI OS (32 bit) basert på Debian Buster, versjon: august 2020,
Utgivelsesdato:2020-08-20, kjerneversjon:5.4.

Du må gjøre to ting:

  • rediger config.txt-filen;
  • opprette en X-serverkonfigurasjon for å fungere med to skjermer.

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

  1. deaktiver bruken av i2c, i2s, spi;
  2. aktiver DPI-modus ved å bruke overlegg dtoverlay=dpi24;
  3. konfigurere videomodus 1280×720 60Hz, 24 biter per piksel på DPI;
  4. spesifiser det nødvendige antallet framebuffers 2 (max_framebuffers=2, først da vil den andre enheten /dev/fb1 vises)

Den fullstendige teksten til config.txt-filen ser slik ut.

# 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

Etter dette må du opprette en konfigurasjonsfil for X-serveren for å bruke to skjermer på to rammebuffere /dev/fb0 og /dev/fb1:

Min konfigurasjonsfil /usr/share/x11/xorg.conf.d/60-dualscreen.conf er slik

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

Vel, hvis det ikke allerede er installert, må du installere Xinerama. Deretter vil skrivebordsplassen bli fullstendig utvidet til to skjermer, som vist i demovideoen ovenfor.

Det er nok alt. Nå vil Raspberry Pi3-eiere kunne bruke to skjermer.

Beskrivelse og kretsskjema for Mars Rover2rpi-kortet kan bli funnet se her.

Kilde: www.habr.com