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


Cette vidéo montre : la carte Raspberry Pi3, à laquelle est connectée via le port GPIO une carte FPGA Mars Rover 2rpi (Cyclone IV), ainsi qu'un moniteur HDMI. Un deuxième moniteur est connecté via le port HDMI standard du Raspberry Pi3. Le tout fonctionne comme un système à deux écrans.

Je vais maintenant vous expliquer comment cela est réalisé.

La populaire carte Raspberry Pi3 possède un port GPIO, qui permet de connecter différentes cartes d'extension : capteurs, LED, pilotes de moteurs pas à pas, et bien d'autres. La fonction spécifique de chaque broche sur le port dépend de la configuration des ports. La configuration GPIO ALT2 permet de commuter le port en mode DPI, Display Parallel Interface. Il existe des cartes d'extension pour connecter des moniteurs VGA via DPI. Cependant, premièrement, les moniteurs VGA ne sont plus aussi répandus que les HDMI, et deuxièmement, l'interface numérique est nettement meilleure que l'analogique. De plus, le DAC sur ces cartes d'extension VGA est généralement constitué de chaînes R-2-R et n'atteint souvent pas plus de 6 bits par couleur.

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

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

J'ai ici colorié les sorties RGB du port en rouge, vert et bleu. D'autres signaux importants sont les signaux de synchronisation V-SYNC et H-SYNC, ainsi que CLK. La fréquence d'horloge CLK est la fréquence à laquelle les valeurs des pixels sont émises sur le port, et elle dépend du mode vidéo sélectionné.

Pour connecter un moniteur HDMI numérique, il faut capturer les signaux de l'interface DPI et les convertir en signaux HDMI. Cela peut être fait, par exemple, avec l'aide d'une carte FPGA. Il s'avère que la carte Mars Rover 2rpi convient à ces fins. En réalité, l'option principale de connexion de cette carte via un adaptateur spécial ressemble à ceci :

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

Cette carte sert à augmenter le nombre de ports GPIO et à connecter un plus grand nombre de périphériques au Raspberry. Cependant, 4 signaux GPIO dans cette connexion sont utilisés pour des signaux JTAG, de sorte que le programme du Raspberry peut charger le firmware FPGA dans la PLIS. Pour cette raison, cette connexion standard ne me convient pas, car je perds 4 signaux DPI. Heureusement, les autres connecteurs sur la carte ont un brochage compatible avec Raspberry. Ainsi, je peux incliner la carte de 90 degrés et continuer à la connecter à mon Raspberry.

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

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

Cependant, il y a un petit problème. Toutes les sorties 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 à cet effet. Ici, le signal GPIO_0 CLK ne parvient pas à l'entrée FPGA, qui peut être utilisée comme entrée d'horloge de la PLIS. J'ai donc dû tirer un fil sur la carte. Je connecte GPIO_0 et le signal KEY[1] de la carte :

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

Je vais maintenant parler un peu du projet en PLIS. La principale difficulté dans la formation des signaux HDMI est la très haute fréquence. Si l'on regarde le câblage du connecteur HDMI, on voit que les signaux RGB sont maintenant des signaux différentiels séquentiels :

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

L'utilisation d'un signal différentiel permet de lutter contre les interférences en mode commun sur la ligne de transmission. Dans ce cas, le code original à huit bits de chaque signal de couleur est converti en TMDS à 10 bits (Transition-minimized differential signaling). C'est une méthode de codage spéciale pour supprimer la composante continue du signal et minimiser les commutations des signaux dans la ligne différentielle. Étant donné qu'un octet de couleur doit maintenant transmettre 10 bits sur la ligne de transmission séquentielle, cela signifie que la fréquence d'horloge du sérialiseur doit être 10 fois plus élevée que la fréquence des pixels. Prenons, par exemple, le mode vidéo 1280x720 à 60 Hz, la fréquence des pixels pour ce mode est de 74,25 MHz. Le sérialiseur doit donc fonctionner à 742,5 MHz.

Les FPGA ordinaires ne peuvent malheureusement pas gérer cela. Cependant, heureusement, les FPGA disposent de sorties DDIO intégrées. Ce sont des sorties qui fonctionnent déjà comme des sérialiseurs 2-en-1. Cela signifie qu'elles peuvent émettre deux bits en séquence sur le front et le creux de l'horloge. Ainsi, dans le projet FPGA, on peut utiliser 370 MHz au lieu de 740 MHz, mais il faut activer les éléments de sortie DDIO dans la PLIS. 370 MHz est une fréquence tout à fait atteignable. Malheureusement, le mode 1280×720 est la limite. Une résolution plus élevée ne peut pas être atteinte avec notre FPGA Cyclone IV installé sur la carte Mars Rover2rpi.

Ainsi, dans le projet, la fréquence d'entrée des pixels CLK arrive sur le PLL, où elle est multipliée par 5. À cette fréquence, les octets R, G, B se transforment en paires de bits. Cela est réalisé par l'encodeur TMDS. Le code source en 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;  // compteur de modulus 5
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

Les paires de sortie sont ensuite envoyées à la sortie DDIO, qui émet séquentiellement un signal unibit à l'ascendant et à la descente.

Le DDIO lui-même pourrait être décrit par le code Verilog suivant :

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 ainsi. Il est nécessaire d'utiliser la méga fonction d'Alter de type ALTDDIO_OUT pour vraiment activer les éléments de sortie DDIO. Dans mon projet, c'est le composant de bibliothèque ALTDDIO_OUT qui est utilisé.

Cela peut sembler un peu compliqué, mais ça fonctionne.

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

Le firmware compilé pour FPGA est intégré dans la puce EPCS, installée sur la carte Mars Rover2rpi. Ainsi, lorsque l'alimentation est fournie à la carte FPGA, le FPGA sera initialisé à partir de la mémoire flash et démarrera.

Il est maintenant temps de parler un peu de la configuration du Raspberry lui-même.

Je fais mes 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 faut faire deux choses :

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

Lors de l'édition du fichier /boot/config.txt, il faut :

  1. désactiver l'utilisation de i2c, i2s, spi ;
  2. activer le mode DPI à l'aide du surcouche dtoverlay=dpi24 ;
  3. configurer le mode vidéo 1280×720 à 60 Hz, 24 bits par pixel sur DPI ;
  4. spécifier le nombre nécessaire de tampons d'image 2 (max_framebuffers=2, c'est seulement à ce moment-là que le deuxième appareil /dev/fb1 apparaîtra)

Le texte complet du fichier config.txt est le suivant.

# 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, il faut créer un fichier de configuration pour le serveur X pour utiliser deux moniteurs sur deux tampons d'image /dev/fb0 et /dev/fb1 :

Mon fichier de configuration /usr/share/x11/xorg.conf.d/60-dualscreen.conf est le suivant :

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, et s'il n'est pas encore installé, il est nécessaire d'installer Xinerama. Dans ce cas, l'espace de bureau sera pleinement étendu sur deux moniteurs, comme montré ci-dessus dans la vidéo de démonstration.

C'est à peu près tout. Désormais, les propriétaires de Raspberry Pi3 pourront utiliser deux moniteurs.

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

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster