În acest video este prezentată: placa Raspberry Pi3, la care este conectată, prin portul GPIO, o placă FPGA Marsocod2rpi (Cyclone IV), la care este conectat un monitor HDMI. Un al doilea monitor este conectat prin portul HDMI standard al Raspberry Pi3. Totul funcționează împreună ca un sistem cu două monitoare.
Mai departe, voi explica cum este realizat acest lucru.
Pe populara placă Raspberry Pi3 există un port GPIO, prin care se pot conecta diferite plăci de extensie: senzori, LED-uri, drivere pentru motoare pas cu pas și multe altele. Funcția specifică a fiecărui pin de pe port depinde de configurația acestora. Configurația GPIO ALT2 permite schimbarea portului în modul DPI (Display Parallel Interface). Există plăci de extensie pentru conectarea monitoarelor VGA prin DPI. Totuși, pe de o parte, monitoarele VGA nu mai sunt atât de răspândite ca HDMI, iar pe de altă parte, interfețele digitale sunt superioare celor analogice. Mai mult decât atât, DAC-ul de pe aceste plăci de extensie VGA este adesea realizat sub formă de lanțuri R-2-R și, de obicei, nu depășește 6 biți pe culoare.
În modul ALT2, pini portului GPIO au următoarele valori:

Aici am colorat ieșirile RGB ale portului în roșu, verde și albastru. Alte semnale importante sunt semnalele de sincronizare a imaginii V-SYNC și H-SYNC, precum și CLK. Frecvența de ceas CLK este frecvența cu care valorile pixelilor sunt transmise pe port, aceasta depinde de modul video selectat.
Pentru a conecta un monitor HDMI digital, este necesar să captăm semnalele interfeței DPI și să le convertim în semnale HDMI. Acest lucru se poate realiza, de exemplu, cu ajutorul unei plăci FPGA. Se dovedește că placa Marsocod2rpi este potrivită pentru acest scop. Adevărul este că principala variantă de conectare a acestei plăci printr-un adaptor special arată astfel:

Această placă este utilizată pentru a crește numărul de porturi GPIO și pentru a conecta un număr mai mare de dispozitive periferice la Raspberry. În acest caz, 4 semnale GPIO sunt utilizate pentru semnalele JTAG, astfel încât programul de pe Raspberry să poată încărca firmware-ul FPGA în FPGA. Din acest motiv, această conectare standard nu mi se potrivește, se pierd 4 semnale DPI. Din fericire, conectorii suplimentari de pe placă au o pinout compatibilă cu Raspberry. Așadar, pot învârti placa cu 90 de grade și totuși să o conectez la Raspberry-ul meu:

Desigur, va trebui să folosesc un programator JTAG extern, dar aceasta nu este o problemă.
Totuși, există o mică problemă. Nu toate ieșirile FPGA pot fi folosite ca intrare pentru semnalul de ceas. Există doar câteva pini dedicați care pot fi folosiți în acest scop. Așa că am ajuns în situația în care semnalul CLK de pe GPIO_0 nu ajunge la intrarea FPGA, care ar putea fi folosită ca intrare pentru semnalul de ceas al FPGA-ului. Așadar, a trebuit să conectez un fir la placă. Conectez GPIO_0 și semnalul KEY[1] al plăcii:

Acum voi povesti puțin despre proiectul în FPGA. Principală dificultate la formarea semnalelor HDMI este frecvențele foarte mari. Dacă ne uităm la pinout-ul conectorului HDMI, vedem că semnalele RGB sunt acum semnale diferențiale secvențiale:

Utilizarea semnalului diferențial permite combaterea interferențelor comune pe linia de transmisie. În acest fel, codul original de 8 biți pentru fiecare semnal de culoare este transformat într-un cod TMDS de 10 biți (Transition-minimized differential signaling). Acesta este un mod special de codare pentru eliminarea componentei continue din semnal și minimizarea comutărilor semnalelor pe linia diferențială. Deoarece pentru un byte de culoare trebuie acum să transmitem 10 biți pe linia secvențială, rezultă că frecvența de ceas a serializatorului trebuie să fie de 10 ori mai mare decât frecvența pixelilor. De exemplu, în modul video 1280x720 la 60Hz, frecvența pixelilor pentru acest mod este de 74,25MHz. Serializatorul ar trebui să aibă 742,5MHz.
FPGA-urile obișnuite, din păcate, nu sunt capabile de așa ceva. Totuși, din fericire, în FPGA există ieșiri încorporate DDIO. Acestea sunt ieșiri care funcționează practic ca serializatoare 2-la-1. Adică, ele pot transmite secvențial două biți la creșterea și descreșterea frecvenței de ceas. Asta înseamnă că în proiectul FPGA putem folosi nu 740MHz, ci 370MHz, dar trebuie activate elementele ieșirii DDIO în FPGA. Astfel, 370MHz este o frecvență realizabilă. Din păcate, modul 1280×720 este limita. O rezoluție mai mare nu poate fi realizată în FPGA-ul nostru Cyclone IV instalat pe placa Mars Rover 2rpi.
Așadar, în proiect, frecvența de intrare a pixelilor CLK ajunge la PLL, unde este multiplicată cu 5. La această frecvență, byte-urile R, G, B sunt transformate în perechi de biți. Acest lucru este realizat de encoder TMDS. Codul sursă în Verilog HDL arată astfel:
modul 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; // contor 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
Apoi, perechile de ieșire sunt trimise la ieșirea DDIO, care emite secvențial un semnal pe un singur bit la frontul și căderea semnalului.
DDIO ar putea fi descris astfel prin cod Verilog:
modul 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;
endmoduleDar astfel de lucru cu siguranță nu va funcționa. Este necesar să folosiți mega funcția ALTDDIO_OUT de la alter, pentru a activa cu adevărat elementele de ieșire DDIO. În proiectul meu este utilizat componenta bibliotecară ALTDDIO_OUT.
Poate că totul aceasta pare puțin complicat, dar funcționează.
Puteți vizualiza tot codul sursă scris în Verilog HDL .
Firmware-ul compilat pentru FPGA este încărcat în cipul EPCS, instalat pe placa Mars Rover 2rpi. Astfel, la alimentarea plăcii FPGA, FPGA va fi inițializat din memoria flash și va porni.
Acum trebuie să discut puțin despre configurarea Raspberry-ului în sine.
Fac experimente pe Raspberry PI OS (32 bit) bazat pe Debian Buster, versiunea: August 2020,
Data lansării: 2020-08-20, versiunea kernel: 5.4.
Trebuie să facem două lucruri:
- să edităm fișierul config.txt;
- să creăm o configurație X server pentru a lucra cu două monitoare.
Când editați fișierul /boot/config.txt trebuie să:
- dezactivați utilizarea i2c, i2s, spi;
- activați modul DPI cu ajutorul overlay-ului dtoverlay=dpi24;
- configurați modul video 1280×720 60Hz, 24 biți pe pixel pe DPI;
- specificați numărul necesar de framebuffers 2 (max_framebuffers=2, doar atunci va apărea al doilea dispozitiv /dev/fb1)
Textul complet al fișierului config.txt arată astfel.
# 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
După aceasta, trebuie să creați un fișier de configurație pentru X server pentru a utiliza două monitoare pe cele două framebuffers /dev/fb0 și /dev/fb1:
Fișierul meu de configurație /usr/share/x11/xorg.conf.d/60-dualscreen.conf arată astfel
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
Ei bine, și dacă nu este deja instalat, trebuie să instalați Xinerama. Atunci, spațiul de lucru va fi extins complet pe două monitoare, așa cum este arătat mai sus în videoclipul demo.
Asta ar fi tot. Acum, și proprietarii de Raspberry Pi3 pot folosi două monitoare.
Descrierea și schema plăcii Marsoched2rpi pot fi .
Sursa: habr.com
