Deuxième moniteur HDMI vers Raspberry Pi3 via l'interface DPI et la carte FPGA



Cette vidéo montre: la carte Raspberry Pi3, à elle, via le connecteur GPIO, la carte FPGA Mars Rover2rpi (Cyclone IV) est connectée à laquelle un moniteur HDMI est connecté. Le deuxième moniteur est connecté via le connecteur HDMI Raspberry Pi3 standard. Tout fonctionne ensemble comme un système avec deux moniteurs.



Je vais vous dire comment cela se fait plus loin.



La célèbre carte Raspberry Pi3 dispose d'un connecteur GPIO à travers lequel vous pouvez connecter différentes cartes d'extension: capteurs, LED, pilotes de moteur pas à pas, etc. La fonction spécifique de chaque broche sur un connecteur dépend de la configuration du port. La configuration ALT2 GPIO vous permet de basculer le connecteur en mode d'interface DPI, Display Parallel Interface. Il existe des cartes d'extension pour connecter des moniteurs VGA via DPI. Cependant, d'une part, les moniteurs VGA ne sont plus aussi courants que HDMI, et d'autre part, l'interface numérique s'améliore par rapport à l'analogique. De plus, le DAC sur des cartes d'extension VGA similaires est généralement réalisé sous la forme de chaînes R-2-R et souvent pas plus de 6 bits par couleur.



En mode ALT2, les broches GPIO ont la signification suivante:



image



Ici, j'ai coloré les broches RVB du connecteur en rouge, vert et bleu, respectivement. D'autres signaux importants sont les signaux de synchronisation de balayage V-SYNC et H-SYNC, ainsi que CLK. L'horloge CLK est la fréquence à laquelle les valeurs de pixels sont envoyées au connecteur, cela dépend du mode vidéo sélectionné.



Pour connecter un moniteur HDMI numérique, vous devez capturer les signaux DPI de l'interface et les convertir en signaux HDMI. Cela peut être fait, par exemple, en utilisant n'importe quelle carte FPGA. Il s'est avéré que la carte Mars rover2rpi convient à ces fins. En vérité, l'option principale pour connecter cette carte via un adaptateur spécial ressemble à ceci:



image



Cette carte sert à augmenter le nombre de ports GPIO et à connecter plus de périphériques à la framboise. Dans le même temps, 4 signaux GPIO avec cette connexion sont utilisés pour les signaux JTAG, de sorte que le programme de la framboise puisse charger le firmware FPGA dans le FPGA. Pour cette raison, une telle connexion standard ne me convient pas, 4 signaux DPI tombent. Heureusement, les peignes supplémentaires sur la carte ont un brochage compatible Raspberry. Pour que je puisse faire pivoter la carte de 90 degrés et toujours la connecter à ma framboise:







Bien sûr, je dois utiliser un programmeur JTAG externe, mais ce n'est pas un problème.



Il y a encore un petit problème. Toutes les broches FPGA ne peuvent pas être utilisées comme entrée d'horloge. Il n'y a que quelques broches dédiées qui peuvent être utilisées à cette fin. Il s'est donc avéré ici que le signal GPIO_0 CLK ne va pas à l'entrée FPGA, qui peut être utilisée comme entrée de fréquence d'horloge FPGA. Alors j'ai tout de même dû jeter un fil sur l'écharpe. Je connecte GPIO_0 et le signal KEY [1] de la carte:



image



Je vais maintenant vous parler un peu du projet dans le FPGA. La principale difficulté dans la formation de signaux HDMI réside dans les très hautes fréquences. En regardant le brochage du connecteur HDMI, vous pouvez voir que les signaux RVB sont maintenant des signaux différentiels série:







L'utilisation d'un signal différentiel vous permet de lutter contre le bruit de mode commun sur la ligne de transmission. Ainsi, le code original de huit bits de chaque signal de couleur est converti en TMDS 10 bits (signalisation différentielle minimisée par transition). Il s'agit d'une technique de codage spéciale pour supprimer la composante CC du signal et minimiser la commutation du signal sur la ligne différentielle. Puisqu'un octet de couleur doit maintenant transmettre 10 bits sur la ligne de transmission série, il s'avère que la fréquence d'horloge du sérialiseur doit être 10 fois supérieure à la fréquence d'horloge des pixels. Si nous prenons, par exemple, le mode vidéo 1280x720 60Hz, alors la fréquence de pixel de ce mode est de 74,25 MHz. Le sérialiseur doit avoir 742,5 MHz.



Malheureusement, les FPGA conventionnels ne sont pas capables de cela. Heureusement pour nous, cependant, le FPGA a des broches DDIO intégrées. Ce sont les conclusions qui sont déjà en quelque sorte des sérialiseurs 2 en 1. Autrement dit, ils peuvent émettre deux bits consécutifs sur les fronts montants et descendants de la fréquence d'horloge. Cela signifie que dans le projet FPGA, vous pouvez utiliser non pas 740 MHz, mais 370 MHz, mais vous devez utiliser les éléments de sortie DDIO dans le FPGA. Ici, 370 MHz est déjà une fréquence tout à fait atteignable. Malheureusement, le mode 1280x720 est la limite. Une résolution plus élevée dans notre FPGA Cyclone IV installé sur la carte Mars rover 2rpi ne peut pas être obtenue.



Ainsi, dans le projet, la fréquence de pixel d'entrée CLK va à la PLL, où elle est multipliée par 5. A cette fréquence, les octets R, V, B sont convertis en paires de bits. Ceci est fait par l'encodeur TMDS. Le code source de Verilog HDL ressemble à ceci:



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


Ensuite, les paires de sorties sont envoyées à la sortie DDIO, qui émet séquentiellement un signal d'un bit sur les fronts montants et descendants.



DDIO lui-même pourrait être décrit avec un tel code 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


Mais cela ne fonctionnera probablement pas de cette façon. Vous devez utiliser l'alter mégafonction ALTDDIO_OUT pour utiliser réellement les éléments DDIO de sortie. C'est le composant de bibliothèque ALTDDIO_OUT qui est utilisé dans mon projet.



Cela peut sembler un peu délicat, mais cela fonctionne.



Vous pouvez voir tout le code source écrit en Verilog HDL ici sur github .



Le micrologiciel FPGA compilé est assemblé dans une puce EPCS installée sur la carte Mars rover2rpi. Ainsi, lorsque l'alimentation est fournie à la carte FPGA, le FPGA s'initialise à partir de la mémoire flash et démarre.



Nous devons maintenant parler un peu de la configuration du Raspberry lui-même.



Je fais des expériences sur Raspberry PI OS (32 bits) basé sur Debian Buster, Version: août 2020,

Date de sortie: 2020-08-20, version du noyau: 5.4.



Il y a deux choses à faire:



  • éditez le fichier config.txt;
  • créer une configuration de serveur X pour fonctionner avec deux moniteurs.


Lors de la modification du fichier /boot/config.txt, vous devez:



  1. désactiver l'utilisation de i2c, i2s, spi;
  2. activer le mode DPI en utilisant la superposition dtoverlay = dpi24;
  3. définir le mode vidéo 1280x720 60Hz, 24 bits par point par DPI;
  4. spécifier le nombre requis de framebuffers 2 (max_framebuffers = 2, alors seulement le deuxième périphérique / dev / fb1 apparaîtra)


Le texte complet du fichier config.txt ressemble à ceci.
# 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




Après cela, vous devez créer un fichier de configuration pour que le serveur X utilise deux moniteurs sur deux framebuffers / dev / fb0 et / dev / fb1:



Mon fichier de configuration /usr/share/x11/xorg.conf.d/60-dualscreen.conf est comme ça
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




Eh bien, s'il n'est pas déjà installé, vous devez installer Xinerama. Ensuite, l'espace de bureau sera entièrement étendu à deux moniteurs, comme indiqué ci-dessus dans la vidéo de démonstration.



C'est probablement tout. Désormais, les propriétaires de Raspberry Pi3 pourront utiliser deux moniteurs.



La description et le schéma de la carte Mars rover2rpi peuvent être consultés ici .



All Articles