In deze video zie je: het Raspberry Pi3 bord, waaraan een FPGA bord Marsrover2rpi (Cyclone IV) is aangesloten via de GPIO-connector, waarop een HDMI-monitor is aangesloten. Een tweede monitor is aangesloten via de standaard HDMI-connector van de Raspberry Pi3. Alles samen werkt als een systeem met twee monitoren.
Ik zal nu uitleggen hoe dit is gerealiseerd.
Op het populaire Raspberry Pi3 bord zit een GPIO-connector waarop verschillende uitbreidingsborden kunnen worden aangesloten: sensoren, LED's, stuurprogramma's voor stapelmotoren en nog veel meer. De specifieke functie van iedere pin op de connector hangt af van de poortconfiguratie. De GPIO ALT2-configuratie stelt de connector in staat om te schakelen naar de DPI-interface, Display Parallel Interface. Er zijn uitbreidingsborden voor het aansluiten van VGA-monitoren via DPI. Echter, ten eerste, VGA-monitoren zijn niet zo wijdverbreid als HDMI, en ten tweede, de digitale interface is beter dan de analoge. Bovendien is de DAC op dergelijke VGA-uitbreidingsborden meestal uitgevoerd als R-2-R ketens en vaak niet meer dan 6 bit per kleur.
In de ALT2-modus hebben de GPIO-pinnen de volgende waarden:

Ik heb hier de RGB-uitgangen van de connector ingekleurd in respectievelijk rood, groen en blauw. Andere belangrijke signalen zijn de synchronisatie signalen V-SYNC en H-SYNC, en ook CLK. De kloksnelheid CLK is de frequentie waarmee de pixelwaarden op de connector worden afgegeven, deze hangt af van de gekozen videomodus.
Om een digitale HDMI-monitor aan te sluiten, moeten de DPI-interface signalen worden vastgelegd en omgezet in HDMI-signalen. Dit kan bijvoorbeeld worden gedaan met een FPGA-bord. Het blijkt dat het bord Marsrover2rpi hiervoor geschikt is. Eerlijk gezegd ziet de belangrijkste verbindingsoptie van dit bord via een speciale adapter er zo uit:

Dit bord dient om het aantal GPIO-poorten te vergroten en om meer perifere apparaten aan de Raspberry aan te sluiten. Hierbij worden 4 GPIO-signalen voor JTAG-signalen gebruikt, zodat het programma van de Raspberry de FPGA-firmware naar de FPGA kan laden. Hierdoor is deze standaardverbinding niet geschikt voor mij, omdat 4 DPI-signalen wegvallen. Gelukkig hebben de extra pinnen op het bord een pinout die compatibel is met de Raspberry. Zodat ik het bord 90 graden kan draaien en het nog steeds aan mijn Raspberry kan aansluiten:

Natuurlijk zal ik een externe JTAG-programmeerder moeten gebruiken, maar dat is geen probleem.
Er is toch een klein probleem. Niet elke FPGA-uitgang kan worden gebruikt als klokfrequentie-ingang. Er zijn maar een paar dedicated pinnen die voor dit doel kunnen worden gebruikt. In dit geval blijkt dat het GPIO_0-signaal CLK niet op de FPGA-ingang komt, die mogelijk als klokfrequentie-ingang kan worden gebruikt. Daarom moest ik toch een draad op het bord aansluiten. Ik verbind GPIO_0 met het SIGNaal KEY[1] van het bord:

Nu zal ik iets vertellen over het FPGA-project. De grootste uitdaging bij het genereren van HDMI-signalen is de zeer hoge frequenties. Als we naar de pinout van de HDMI-connector kijken, zien we dat de RGB-signalen nu serial differential signals zijn:

Het gebruik van differentieel signaal helpt om common-mode storingen op de transmissielijn te bestrijden. Hierbij wordt de oorspronkelijke 8-bits code van elk kleursignaal omgezet in 10-bits TMDS (Transition-minimized differential signaling). Dit is een speciale coderingsmethode om de constante component uit het signaal te verwijderen en het aantal signaalovergangen op de differentiële lijn te minimaliseren. Omdat er nu 10 bits over een seriële transmissielijn moeten worden verzonden voor één byte kleur, betekent dit dat de klokfrequentie van de serializer 10 keer hoger moet zijn dan de klokfrequentie van de pixels. Als we bijvoorbeeld de video modus 1280x720 60Hz nemen, is de pixel frequentie voor deze modus 74,25 MHz. De serializer moet 742,5 MHz zijn.
Standaard FPGA's zijn hier helaas niet toe in staat. Gelukkig hebben FPGA's ingebouwde DDIO-uitgangen. Dit zijn uitgangen die feitelijk al 2-to-1 serializers zijn. Dit betekent dat ze twee bits sequentieel kunnen uitgeven op de op- en neergang van de klokfrequentie. In het FPGA-project kan dus niet 740 MHz maar 370 MHz worden gebruikt, maar de DDIO-uitgangen in de FPGA moeten worden geactiveerd. Bij 370 MHz is de frequentie al heel goed haalbaar. Helaas is de modus 1280×720 de limiet. Een hogere resolutie is niet mogelijk op onze FPGA Cyclone IV, die op de Mars Rover 2rpi is geïnstalleerd.
In het project komt de inkomende pixelsignaal CLK op de PLL, waar deze met 5 wordt vermenigvuldigd. Op deze frequentie worden de bytes R, G, B omgezet in bitparen. Dit wordt gedaan door de TMDS-encoder. De originele code in Verilog HDL ziet er als volgt uit:
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
Daarna worden de uitgangspaar weergegeven op de DDIO-uitgang, die een enkele bittestalsignaal op de op- en neergaande flank afgeeft.
De DDIO zelf kan met de volgende Verilog-code worden beschreven:
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;
endmoduleMaar zo zal het waarschijnlijk niet werken. Je moet de alternatieve megafunctie ALTDDIO_OUT gebruiken om de uitgangs-DDIO-elementen daadwerkelijk in te schakelen. In mijn project wordt precies de bibliotheekcomponent ALTDDIO_OUT gebruikt.
Misschien ziet dit er een beetje ingewikkeld uit, maar het werkt.
Je kunt de volledige broncode, geschreven in Verilog HDL, bekijken .
De gecompileerde firmware voor de FPGA wordt in de EPCS-chip geprogrammeerd, die op de Mars Rover2rpi-bord is gemonteerd. Zo wordt de FPGA geïnitialiseerd vanuit het flashgeheugen zodra de voeding op het FPGA-bord wordt ingeschakeld.
Nu moet ik een beetje vertellen over de configuratie van de Raspberry zelf.
Ik voer experimenten uit op Raspberry PI OS (32 bit) gebaseerd op Debian Buster, Versie: Augustus 2020,
Release datum: 2020-08-20, Kernelversie: 5.4.
Je moet twee dingen doen:
- het config.txt-bestand bewerken;
- een configuratie voor de X-server maken voor het werken met twee monitors.
Bij het bewerken van het bestand /boot/config.txt moet je:
- i2c, i2s, spi uitschakelen;
- de DPI-modus inschakelen met behulp van de overlay dtoverlay=dpi24;
- de video-instelling 1280×720 60Hz, 24 bits per pixel op DPI instellen;
- het vereiste aantal framebuffer 2 opgeven (max_framebuffers=2, dan verschijnt het tweede apparaat /dev/fb1)
De volledige tekst van het config.txt-bestand ziet er zo uit.
# 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
Daarna moet je een configuratiebestand voor de X-server maken voor het gebruik van twee monitors op twee framebuffers /dev/fb0 en /dev/fb1:
Mijn configuratiebestand /usr/share/x11/xorg.conf.d/60-dualscreen.conf is als volgt
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
En als het nog niet is geïnstalleerd, moet je Xinerama installeren. Dan wordt de desktopruimte volledig uitgebreid over twee monitors, zoals hierboven getoond in de demo video.
Dat is alles. Nu kunnen ook Raspberry Pi3-eigenaren met twee monitors werken.
De beschrijving en het schema van de Marsrover2rpi zijn .
Bron: habr.com
