In questo video sono mostrati: la scheda Raspberry Pi3, alla quale è collegata tramite l'interfaccia GPIO la scheda FPGA Mars rover2rpi (Cyclone IV), a cui è collegato un monitor HDMI. Un secondo monitor è connesso tramite la porta HDMI standard della Raspberry Pi3. Tutto funziona insieme come un sistema con due monitor.
Successivamente ti spiegherò come è stata realizzata questa configurazione.
Sulla popolare scheda Raspberry Pi3 è presente un'interfaccia GPIO, attraverso la quale è possibile collegare varie schede di espansione: sensori, LED, driver per motori passo-passo e molto altro. La funzione specifica di ogni pin sull'interfaccia dipende dalla configurazione delle porte. La configurazione GPIO ALT2 consente di attivare l'interfaccia DPI, Display Parallel Interface. Esistono schede di espansione per il collegamento di 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 è sempre migliore di quella analogica. Inoltre, i DAC su tali schede di espansione VGA sono generalmente realizzati con catene R-2-R e spesso non superano i 6 bit per colore.
In modalità ALT2, i pin dell'interfaccia GPIO hanno il seguente valore:

Qui ho colorato le uscite RGB del connettore rispettivamente in rosso, verde e blu. Altri segnali importanti sono i segnali di sincronizzazione del refresh V-SYNC e H-SYNC, oltre al CLK. La frequenza del CLK è la frequenza con cui i valori dei pixel vengono forniti al connettore, e dipende dalla modalità video selezionata.
Per collegare un monitor HDMI digitale, è necessario catturare i segnali dell'interfaccia DPI e convertirli in segnali HDMI. Questo può essere fatto, ad esempio, utilizzando qualche scheda FPGA. A quanto pare, la scheda Mars Rover 2rpi è adatta a questi scopi. A dire il vero, il principale metodo di connessione di questa scheda tramite un adattatore speciale appare così:

Questa scheda serve ad aumentare il numero di porte GPIO e a collegare un numero maggiore di dispositivi periferici al Raspberry. In questo caso, 4 segnali GPIO vengono utilizzati per i segnali JTAG, in modo che il programma dal Raspberry possa caricare il firmware FPGA nel chip. Per questo motivo, questa connessione standard non mi va bene, perdendo 4 segnali DPI. Fortunatamente, le ulteriori intestazioni sulla scheda hanno una pinout compatibile con Raspberry. Così, posso ruotare la scheda di 90 gradi e collegarla comunque alla mia Raspberry:

Certo, dovrò utilizzare un programmatore JTAG esterno, ma non è un problema.
C'è comunque un piccolo problema. Non ogni pin FPGA può essere utilizzato come ingresso per il segnale di clock. Ci sono solo alcuni pin dedicati che possono essere utilizzati per questi scopi. Così, in questo caso, il segnale GPIO_0 CLK non entra nel FPGA, il quale potrebbe essere usato come ingresso per il segnale di clock. Quindi, alla fine ho dovuto collegare un filo alla scheda. Collego GPIO_0 al segnale KEY[1] della scheda:

Ora vi parlerò un po' del progetto in FPGA. La principale difficoltà nella generazione dei segnali HDMI è rappresentata dalle frequenze molto elevate. Se osserviamo il pinout del connettore HDMI, possiamo notare che i segnali RGB sono diventati segnali differenziali sequenziali:

L'uso di un segnale differenziale consente di combattere le interferenze comuni nella linea di trasmissione. A tal fine, il codice a otto bit di ciascun segnale di colore viene convertito in un TMDS a 10 bit (Transition-minimized differential signaling). Questo è un metodo speciale di codifica per eliminare la componente continua dal segnale e minimizzare le commutazioni nei segnali della linea differenziale. Poiché ora per un byte di colore si devono trasmettere 10 bit attraverso la linea di trasmissione sequenziale, la frequenza di clock del serializzatore deve essere 10 volte superiore alla frequenza dei pixel. Prendiamo ad esempio la modalità video 1280x720 a 60Hz, in cui la frequenza dei pixel è di 74,25MHz. Sul serializzatore dovrebbe esserci una frequenza di 742,5MHz.
Le FPGA standard, purtroppo, non sono in grado di gestire questo. Tuttavia, fortunatamente, i FPGA hanno delle uscite DDIO integrate. Queste sono uscite che funzionano come dei serializzatori 2-a-1. Possono quindi fornire due bit in modo sequenziale sia sul fronte che sul retro del ciclo di clock. Ciò significa che nel progetto FPGA si può utilizzare non 740 MHz, ma 370 MHz, ma è necessario attivare gli elementi di uscita DDIO nella FPGA. A 370 MHz, la frequenza è già raggiungibile. Sfortunatamente, la modalità 1280×720 è il limite. Non è possibile ottenere una risoluzione più alta nel nostro FPGA Cyclone IV installato sulla scheda Marsrover2rpi.
Nel progetto, la frequenza di ingresso dei pixel CLK viene inviata al 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 è il seguente:
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 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
Poi le uscite DDIO vengono fornite all'uscita DDIO, che emette un segnale unibit per il fronte e il retro.
Il DDIO potrebbe essere descritto con il seguente codice 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;
endmoduleMa probabilmente non funzionerà in questo modo. È necessario utilizzare la megafunzione Altera ALTDDIO_OUT per attivare realmente gli elementi di uscita DDIO. Nel mio progetto viene utilizzato il componente di libreria ALTDDIO_OUT.
Forse tutto ciò può sembrare un po' complicato, ma funziona.
Puoi visualizzare l'intero codice sorgente scritto in Verilog HDL .
Il firmware compilato per l'FPGA viene caricato nel chip EPCS, installato sulla scheda Mars Rover2rpi. In questo modo, quando si alimenta la scheda FPGA, il FPGA verrà inizializzato dalla memoria flash e avviato.
Ora è necessario parlare un po' della configurazione del Raspberry stesso.
Sto conducendo 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.
È necessario fare due cose:
- modificare il file config.txt;
- creare una configurazione del server X per lavorare 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 risoluzione 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 appare così.
# 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 ciò, è 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 è così
Sezione "Dispositivo"
Identificativo "LCD"
Driver "fbturbo"
Opzione "fbdev" "/dev/fb0"
Opzione "ShadowFB" "off"
Opzione "SwapbuffersWait" "true"
EndSection
Sezione "Dispositivo"
Identificativo "HDMI"
Driver "fbturbo"
Opzione "fbdev" "/dev/fb1"
Opzione "ShadowFB" "off"
Opzione "SwapbuffersWait" "true"
EndSection
Sezione "Monitor"
Identificativo "LCD-monitor"
Opzione "Primario" "true"
EndSection
Sezione "Monitor"
Identificativo "HDMI-monitor"
Opzione "A destra di" "LCD-monitor"
EndSection
Sezione "Schermo"
Identificativo "screen0"
Dispositivo "LCD"
Monitor "LCD-monitor"
EndSection
Sezione "Schermo"
Identificativo "screen1"
Dispositivo "HDMI"
Monitor "HDMI-monitor"
EndSection
Sezione "ServerLayout"
Identificativo "default"
Opzione "Xinerama" "on"
Opzione "Clone" "off"
Schermo 0 "screen0"
Schermo 1 "screen1" A destra di "screen0"
EndSection
E se non è ancora installato, è necessario installare Xinerama. Così, lo spazio del desktop sarà completamente esteso su due monitor, come mostrato sopra nel video dimostrativo.
Ecco, penso sia tutto. Ora anche i possessori di Raspberry Pi3 potranno utilizzare due monitor.
La descrizione e lo schema della scheda Mars Rover2rpi possono .
Fonte: habr.com
