Segon monitor HDMI a Raspberry Pi3 mitjançant interfície DPI i placa FPGA


Aquest vídeo mostra: una placa Raspberry Pi3, connectada a ella mitjançant el connector GPIO, és una placa FPGA Mars Rover2rpi (Cyclone IV), a la qual està connectat un monitor HDMI. El segon monitor es connecta mitjançant el connector HDMI estàndard del Raspberry Pi3. Tot funciona conjuntament com un sistema de monitor dual.

A continuació us explicaré com s'implementa.

La popular placa Raspberry Pi3 té un connector GPIO a través del qual podeu connectar diverses targetes d'expansió: sensors, LED, controladors de motor pas a pas i molt més. La funció exacta de cada pin d'un connector depèn de la configuració del port. La configuració de GPIO ALT2 us permet canviar el connector al mode d'interfície DPI, Display Parallel Interface. Hi ha targetes d'expansió per connectar monitors VGA mitjançant DPI. Tanmateix, en primer lloc, els monitors VGA ja no són tan habituals com l'HDMI i, en segon lloc, la interfície digital és cada cop més millor que l'analògica. A més, el DAC d'aquestes plaques d'expansió VGA es fa normalment en forma de cadenes R-2-R i sovint no més de 6 bits per color.

En mode ALT2, els pins del connector GPIO tenen el significat següent:

Segon monitor HDMI a Raspberry Pi3 mitjançant interfície DPI i placa FPGA

Aquí he pintat els pins RGB del connector de vermell, verd i blau respectivament. Altres senyals importants són els senyals V-SYNC i H-SYNC, així com CLK. La freqüència de rellotge CLK és la freqüència amb què els valors de píxels s'envien al connector; depèn del mode de vídeo seleccionat.

Per connectar un monitor HDMI digital, cal capturar els senyals DPI de la interfície i convertir-los en senyals HDMI. Això es pot fer, per exemple, utilitzant algun tipus de placa FPGA. Com a resultat, la placa Mars Rover2rpi és adequada per a aquests propòsits. De fet, l'opció principal per connectar aquesta placa mitjançant un adaptador especial és la següent:

Segon monitor HDMI a Raspberry Pi3 mitjançant interfície DPI i placa FPGA

Aquesta placa s'utilitza per augmentar el nombre de ports GPIO i per connectar més dispositius perifèrics al gerd. Al mateix temps, s'utilitzen 4 senyals GPIO amb aquesta connexió per a senyals JTAG, de manera que el programa de Raspberry pugui carregar el firmware FPGA a l'FPGA. Per això, aquesta connexió estàndard no em convé; els senyals de 4 DPI cauen. Afortunadament, les pintes addicionals del tauler tenen un pinout compatible amb Raspberry. Així que puc girar el tauler 90 graus i encara connectar-lo al meu gerd:

Segon monitor HDMI a Raspberry Pi3 mitjançant interfície DPI i placa FPGA

Per descomptat, haureu d'utilitzar un programador JTAG extern, però això no és un problema.

Encara hi ha un petit problema. No tots els pins FPGA es poden utilitzar com a entrada de rellotge. Només hi ha uns quants pins dedicats que es poden utilitzar per a aquests propòsits. Així que aquí va resultar que el senyal GPIO_0 CLK no arriba a l'entrada FPGA, que es pot utilitzar com a entrada de rellotge FPGA. Així que encara havia de posar un cable a la bufanda. Connecto GPIO_0 i el senyal KEY[1] de la placa:

Segon monitor HDMI a Raspberry Pi3 mitjançant interfície DPI i placa FPGA

Ara us parlaré una mica del projecte FPGA. La principal dificultat per generar senyals HDMI són les freqüències molt altes. Si mireu el pinout del connector HDMI, podeu veure que els senyals RGB són ara senyals diferencials en sèrie:

Segon monitor HDMI a Raspberry Pi3 mitjançant interfície DPI i placa FPGA

L'ús d'un senyal diferencial us permet combatre les interferències de mode comú a la línia de transmissió. En aquest cas, el codi original de vuit bits de cada senyal de color es converteix en un TMDS de 10 bits (senyalització diferencial minimitzada per la transició). Aquest és un mètode de codificació especial per eliminar el component DC del senyal i minimitzar la commutació del senyal en una línia diferencial. Com que ara s'han de transmetre 10 bits a través de la línia sèrie per a un byte de color, resulta que la velocitat de rellotge del serialitzador ha de ser 10 vegades superior a la velocitat de rellotge de píxels. Si prenem per exemple el mode de vídeo 1280x720 60Hz, la freqüència de píxels d'aquest mode és de 74,25 MHz. El serialitzador ha de ser de 742,5 MHz.

Malauradament, els FPGA habituals no són capaços d'això. Tanmateix, afortunadament per a nosaltres, l'FPGA té pins DDIO integrats. Aquestes són conclusions que ja són, per dir-ho, serialitzadors 2 a 1. És a dir, poden emetre dos bits seqüencialment a les vores ascendents i descendents de la freqüència de rellotge. Això vol dir que en un projecte FPGA no podeu utilitzar 740 MHz, sinó 370 MHz, però cal utilitzar elements de sortida DDIO a l'FPGA. Ara 370 MHz ja és una freqüència completament assolible. Malauradament, el mode 1280x720 és el límit. No es pot aconseguir una resolució més alta al nostre FPGA Cyclone IV instal·lat a la placa Mars Rover2rpi.

Així, en el disseny, la freqüència de píxel d'entrada CLK va al PLL, on es multiplica per 5. A aquesta freqüència, els bytes R, G, B es converteixen en parells de bits. Això és el que fa el codificador TMDS. El codi font de Verilog HDL té aquest aspecte:

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

A continuació, els parells de sortida s'alimenten a la sortida DDIO, que produeix seqüencialment un senyal d'un bit a les vores ascendents i descendents.

El propi DDIO es podria descriure amb el següent codi Verilog:

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

Però el més probable és que no funcioni així. Heu d'utilitzar la megafunció ALTDDIO_OUT d'Alter per habilitar realment els elements de sortida DDIO. El meu projecte utilitza el component de la biblioteca ALTDDIO_OUT.

Tot això pot semblar una mica complicat, però funciona.

Podeu veure tot el codi font escrit en Verilog HDL aquí a github.

El microprogramari compilat per a l'FPGA es mostra al xip EPCS instal·lat a la placa Mars Rover2rpi. Així, quan s'aplica energia a la placa FPGA, l'FPGA s'inicializarà des de la memòria flaix i s'iniciarà.

Ara hem de parlar una mica de la configuració del mateix Raspberry.

Estic fent experiments amb Raspberry PI OS (32 bits) basats en Debian Buster, versió: agost de 2020,
Data de llançament: 2020-08-20, Versió del nucli: 5.4.

Heu de fer dues coses:

  • editar el fitxer config.txt;
  • creeu una configuració de servidor X per treballar amb dos monitors.

Quan editeu el fitxer /boot/config.txt necessiteu:

  1. desactivar l'ús de i2c, i2s, spi;
  2. habiliteu el mode DPI mitjançant la superposició dtoverlay=dpi24;
  3. configurar el mode de vídeo 1280×720 60Hz, 24 bits per píxel en DPI;
  4. especifiqueu el nombre necessari de framebuffers 2 (max_framebuffers=2, només llavors apareixerà el segon dispositiu /dev/fb1)

El text complet del fitxer config.txt té aquest aspecte.

# 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

Després d'això, heu de crear un fitxer de configuració perquè el servidor X utilitzi dos monitors en dos framebuffers /dev/fb0 i /dev/fb1:

El meu fitxer de configuració /usr/share/x11/xorg.conf.d/60-dualscreen.conf és així

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

Bé, si encara no està instal·lat, cal que instal·leu Xinerama. A continuació, l'espai de l'escriptori s'ampliarà completament a dos monitors, tal com es mostra al vídeo de demostració anterior.

Això és probablement tot. Ara, els propietaris de Raspberry Pi3 podran utilitzar dos monitors.

Es pot trobar la descripció i l'esquema de circuit de la placa Mars Rover2rpi mira aquí.

Font: www.habr.com