Den här videon visar: ett Raspberry Pi3-kort, anslutet till det via GPIO-kontakten, är ett FPGA-kort Mars Rover2rpi (Cyclone IV), till vilket en HDMI-skärm är ansluten. Den andra bildskärmen ansluts via standard HDMI-kontakten på Raspberry Pi3. Allt fungerar tillsammans som ett system med dubbla monitorer.
Härnäst ska jag berätta hur detta implementeras.
Det populära Raspberry Pi3-kortet har en GPIO-kontakt genom vilken du kan ansluta olika expansionskort: sensorer, lysdioder, stegmotordrivrutiner och mycket mer. Den exakta funktionen för varje stift på en kontakt beror på portkonfigurationen. GPIO ALT2-konfigurationen låter dig byta kontakten till DPI-gränssnittsläge, Display Parallel Interface. Det finns expansionskort för att ansluta VGA-skärmar via DPI. Men för det första är VGA-skärmar inte längre lika vanliga som HDMI, och för det andra är det digitala gränssnittet allt bättre än det analoga. Dessutom görs DAC på sådana VGA-expansionskort vanligtvis i form av R-2-R-kedjor och ofta inte mer än 6 bitar per färg.
I ALT2-läge har GPIO-kontaktstiften följande betydelse:
Här har jag färgat RGB-stiften på kontakten röda, gröna respektive blå. Andra viktiga signaler är V-SYNC- och H-SYNC-signalerna, såväl som CLK. CLK-klockfrekvensen är den frekvens vid vilken pixelvärden matas ut till kontakten; det beror på det valda videoläget.
För att ansluta en digital HDMI-skärm måste du fånga DPI-signalerna från gränssnittet och konvertera dem till HDMI-signaler. Detta kan till exempel göras med någon form av FPGA-kort. Som det visar sig är Mars Rover2rpi-brädan lämplig för dessa ändamål. I själva verket ser huvudalternativet för att ansluta detta kort genom en speciell adapter ut så här:
Detta kort används för att öka antalet GPIO-portar och för att ansluta fler kringutrustning till hallonet. Samtidigt används 4 GPIO-signaler med denna anslutning för JTAG-signaler, så att programmet från Raspberry kan ladda FPGA-firmwaren i FPGA:n. På grund av detta passar denna standardanslutning inte mig, 4 DPI-signaler faller ut. Lyckligtvis har de extra kammarna på brädan en hallonkompatibel pinout. Så jag kan rotera brädan 90 grader och fortfarande ansluta den till mitt hallon:
Naturligtvis måste du använda en extern JTAG-programmerare, men detta är inget problem.
Det finns fortfarande ett litet problem. Inte varje FPGA-stift kan användas som en klockingång. Det finns bara ett fåtal dedikerade stift som kan användas för dessa ändamål. Så det visade sig här att GPIO_0 CLK-signalen inte når FPGA-ingången, som kan användas som en FPGA-klockingång. Så jag var fortfarande tvungen att sätta en tråd på halsduken. Jag ansluter GPIO_0 och kortets KEY[1]-signal:
Nu ska jag berätta lite om FPGA-projektet. Den största svårigheten med att generera HDMI-signaler är mycket höga frekvenser. Om du tittar på HDMI-kontaktens pinout kan du se att RGB-signalerna nu är seriella differentialsignaler:
Användningen av en differentialsignal gör att du kan bekämpa common mode-störningar på transmissionslinjen. I detta fall omvandlas den ursprungliga åttabitarskoden för varje färgsignal till en 10-bitars TMDS (Transition-minimized differential signaling). Detta är en speciell kodningsmetod för att ta bort DC-komponenten från signalen och minimera signalväxling i en differentialledning. Eftersom 10 bitar nu behöver sändas över serielinjen för en byte av färg, visar det sig att serializerns klockhastighet måste vara 10 gånger högre än pixelns klockhastighet. Om vi till exempel tar videoläget 1280x720 60Hz, så är pixelfrekvensen för detta läge 74,25 MHz. Serializern ska vara 742,5 MHz.
Vanliga FPGA:er är tyvärr inte kapabla till detta. Men lyckligtvis för oss har FPGA:n inbyggda DDIO-stift. Det här är slutsatser som redan så att säga är 2-till-1 serialiserare. Det vill säga de kan mata ut två bitar sekventiellt på klockfrekvensens stigande och fallande flanker. Det betyder att du i ett FPGA-projekt inte kan använda 740 MHz, utan 370 MHz, utan du behöver använda DDIO-utgångselement i FPGA:n. Nu är 370 MHz redan en helt uppnåelig frekvens. Tyvärr är 1280x720-läget gränsen. En högre upplösning kan inte uppnås i vår Cyclone IV FPGA installerad på Mars Rover2rpi-kortet.
Så i designen går ingångspixelfrekvensen CLK till PLL, där den multipliceras med 5. Vid denna frekvens omvandlas R, G, B byte till bitpar. Detta är vad TMDS-kodaren gör. Källkoden i Verilog HDL ser ut så här:
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
Därefter matas utgångsparen till DDIO-utgången, som sekventiellt producerar en enbitssignal på de stigande och fallande flankerna.
DDIO själv kan beskrivas med följande Verilog-kod:
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
Men det kommer troligen inte att fungera så. Du måste använda Alters megafunktion ALTDDIO_OUT för att faktiskt aktivera DDIO-utgångselementen. Mitt projekt använder ALTDDIO_OUT-bibliotekskomponenten.
Det här kan se lite knepigt ut, men det fungerar.
Du kan se all källkod skriven i Verilog HDL
Den kompilerade firmwaren för FPGA:n flashas in i EPCS-chippet som är installerat på Mars Rover2rpi-kortet. Sålunda, när ström tillförs FPGA-kortet, kommer FPGA:n att initieras från flashminnet och starta.
Nu måste vi prata lite om konfigurationen av själva hallonet.
Jag gör experiment på Raspberry PI OS (32 bitar) baserat på Debian Buster, Version: Augusti 2020,
Utgivningsdatum: 2020-08-20, kärnversion: 5.4.
Du måste göra två saker:
- redigera filen config.txt;
- skapa en X-serverkonfiguration för att fungera med två bildskärmar.
När du redigerar filen /boot/config.txt behöver du:
- inaktivera användningen av i2c, i2s, spi;
- aktivera DPI-läge med överlägg dtoverlay=dpi24;
- konfigurera videoläge 1280×720 60Hz, 24 bitar per pixel på DPI;
- ange det nödvändiga antalet framebuffers 2 (max_framebuffers=2, först då kommer den andra enheten /dev/fb1 att visas)
Hela texten i filen config.txt ser ut så här.
# 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
Efter detta måste du skapa en konfigurationsfil för X-servern för att använda två bildskärmar på två rambuffertar /dev/fb0 och /dev/fb1:
Min konfigurationsfil /usr/share/x11/xorg.conf.d/60-dualscreen.conf är så här
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
Tja, om det inte redan är installerat måste du installera Xinerama. Sedan kommer skrivbordsutrymmet att utökas helt till två bildskärmar, som visas i demovideon ovan.
Det är nog allt. Nu kommer Raspberry Pi3-ägare att kunna använda två skärmar.
Beskrivning och kretsschema för Mars Rover2rpi-kortet kan hittas
Källa: will.com