In questo video vengono mostrati: la scheda Raspberry Pi3, a cui è collegata tramite il connettore GPIO la scheda FPGA Mars Rover2rpi (Cyclone IV), alla quale è collegato un monitor HDMI. Il secondo monitor è collegato tramite il connettore HDMI nativo del Raspberry Pi3. Tutto insieme funziona come un sistema con due monitor.
Dopo vi spiegherò come è stato realizzato.
Sulla popolare scheda Raspberry Pi3 è presente un connettore GPIO, attraverso il quale è possibile collegare vari moduli di espansione: sensori, LED, driver per motori passo-passo e molto altro. La funzione specifica di ciascun pin sul connettore dipende dalla configurazione delle porte. La configurazione GPIO ALT2 permette di commutare il connettore in modalità interfaccia DPI, Display Parallel Interface. Ci sono moduli di espansione per collegare monitor VGA tramite DPI. Tuttavia, da un lato, i monitor VGA non sono più così diffusi come gli HDMI, e dall'altro, l'interfaccia digitale è molto migliore di quella analogica. Inoltre, il DAC su tali schede di espansione VGA è solitamente realizzato sotto forma di catene R-2-R e spesso non supera i 6 bit per colore.
In modalità ALT2, i pin del connettore GPIO hanno il seguente significato:

Qui ho colorato i pin RGB del connettore rispettivamente in rosso, verde e blu. Altri segnali importanti sono i segnali di sincronizzazione V-SYNC e H-SYNC, oltre al CLK. La frequenza di clock CLK è la frequenza con cui i valori dei pixel vengono inviati al connettore, ed è influenzata dalla modalità video selezionata.
Per collegare un monitor HDMI digitale è necessario acquisire i segnali dell'interfaccia DPI e convertirli in segnali HDMI. Questo può essere fatto, ad esempio, utilizzando una scheda FPGA. Si è scoperto che la scheda Mars Rover2rpi è adatta a tali scopi. A dire il vero, la principale opzione di connessione per questa scheda tramite un adattatore speciale appare in questo modo:

Questa scheda serve per aumentare il numero di porte GPIO e per collegare un numero maggiore di dispositivi periferici al Raspberry. Tuttavia, 4 segnali GPIO in questo tipo di collegamento sono utilizzati per segnali JTAG, in modo che il programma dal Raspberry possa caricare il firmware FPGA nella PLD. A causa di ciò, questo collegamento nativo non mi si addice, poiché mancano 4 segnali DPI. Per fortuna, i pin aggiuntivi sulla scheda hanno una pinout compatibile con Raspberry. Così posso ruotare la scheda di 90 gradi e comunque collegarla alla mia Raspberry:

Certo, dovremo usare un programmatore JTAG esterno, ma non è un problema.
C'è però un piccolo problema. Non ogni pin dell'FPGA può essere utilizzato come ingresso per il clock. Ci sono solo alcuni pin dedicati che possono essere utilizzati per questi scopi. Qui è successo che il segnale GPIO_0 CLK non entra nel pin dell’FPGA che può essere utilizzato come ingresso per il clock dell'FPGA. Quindi, alla fine, ho dovuto collegare un cavo alla scheda. Sto collegando GPIO_0 al segnale KEY[1] della scheda:

Ora parlerò un po' del progetto nell'FPGA. La principale difficoltà nella generazione dei segnali HDMI è rappresentata dalle frequenze molto elevate. Se guardiamo il pinout del connettore HDMI, si può vedere che i segnali RGB sono ora segnali differenziali seriali:

L'uso di un segnale differenziale consente di combattere le interferenze comuni sulla linea di trasmissione. In questo caso, il codice originale a otto bit di ciascun segnale di colore viene convertito in segnali TMDS a 10 bit (Transition-minimized differential signaling). Questo è un metodo speciale di codifica per rimuovere la componente continua dal segnale e minimizzare le transizioni dei segnali nella linea differenziale. Poiché ora, per un byte di colore, bisogna trasmettere 10 bit sulla linea di trasmissione seriale, risulta che la frequenza di clock del serializzatore deve essere 10 volte superiore rispetto alla frequenza di clock dei pixel. Prendendo ad esempio la modalità video 1280x720 a 60 Hz, la frequenza di pixel in questa modalità è di 74,25 MHz. Sul serializzatore deve essere di 742,5 MHz.
Gli FPGA normali, purtroppo, non sono in grado di gestire tutto ciò. Tuttavia, per nostra fortuna, nell'FPGA ci sono uscite DDIO integrate. Queste sono uscite che sono come dei serializzatori 2-a-1. Cioè, possono emettere sequenzialmente due bit sul fronte e sul bordo della clock. Quindi, nel progetto FPGA, si può utilizzare non 740 MHz, ma 370 MHz, ma è necessario attivare gli elementi di uscita DDIO nell'FPGA. Ecco, 370 MHz è una frequenza decisamente raggiungibile. Sfortunatamente, la modalità 1280×720 è il limite. Non è possibile raggiungere una risoluzione più alta nel nostro FPGA Cyclone IV installato sulla scheda Mars Rover 2RPI.
Quindi, nel progetto, la frequenza di ingresso dei pixel CLK entra nel PLL, dove viene moltiplicata per 5. A questa frequenza, i byte R, G, B vengono convertiti in coppie di bit. Questo è fatto dal codificatore TMDS. Il codice sorgente in Verilog HDL appare così:
modulo hdmi(
input wire pixclk, // 74MHz
input wire clk_TMDS2, // 370MHz
input wire hsync,
input wire vsync,
input wire attivo,
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(attivo), .TMDS(TMDS_red));
TMDS_encoder encode_G(.clk(pixclk), .VD(green), .CD({vsync,hsync}), .VDE(attivo), .TMDS(TMDS_green));
TMDS_encoder encode_B(.clk(pixclk), .VD(blue ), .CD({vsync,hsync}), .VDE(attivo), .TMDS(TMDS_blue));
reg [2:0] TMDS_mod5=0; // contatore modulo 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
Successivamente, le coppie in uscita vengono inviate all'uscita DDIO, che emette sequentially un segnale a bit singolo sia sul fronte che sul retro.
Il DDIO stesso potrebbe essere descritto con il seguente codice Verilog:
modulo 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;
endmoduleMa molto probabilmente non funzionerà in questo modo. È necessario utilizzare la megafunzione ALTDDIO_OUT di Altera per attivare effettivamente gli elementi DDIO in uscita. Nel mio progetto viene utilizzato il componente di libreria ALTDDIO_OUT.
Forse tutto ciò appare un po' complicato, ma funziona.
Puoi visualizzare l'intero codice sorgente scritto in Verilog HDL .
Il firmware compilato per FPGA è caricato nel chip EPCS installato sulla scheda Mars Rover 2rpi. In questo modo, quando si fornisce alimentazione alla scheda FPGA, il FPGA sarà inizializzato dalla memoria flash e avviato.
Ora è necessario parlare un po' della configurazione del Raspberry stesso.
Sto facendo esperimenti su Raspberry PI OS (32 bit) basato su Debian Buster, Versione: agosto 2020,
Data di rilascio: 2020-08-20, versione del kernel: 5.4.
Bisogna fare due cose:
- modificare il file config.txt;
- creare una configurazione del server X per funzionare con due monitor.
Quando si modifica il file /boot/config.txt è necessario:
- disattivare l'uso di i2c, i2s, spi;
- attivare la modalità DPI utilizzando l'overlay dtoverlay=dpi24;
- configurare la modalità video 1280×720 60Hz, 24 bit per pixel su DPI;
- specificare il numero necessario di frame buffer 2 (max_framebuffers=2, solo allora apparirà il secondo dispositivo /dev/fb1)
Il testo completo del file config.txt è il seguente.
# 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
Dopo di che, è necessario creare un file di configurazione per il server X per utilizzare due monitor su due frame buffer /dev/fb0 e /dev/fb1:
Il mio file di configurazione /usr/share/x11/xorg.conf.d/60-dualscreen.conf è il seguente
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
E, se non è già installata, è necessario installare Xinerama. In tal modo, lo spazio del desktop sarà completamente esteso su due monitor, come mostrato sopra nel video dimostrativo.
Questo è tutto. Ora anche i proprietari di Raspberry Pi3 possono utilizzare due monitor.
La descrizione e lo schema della scheda Marsoched2rpi possono .
Fonte: habr.com
