Dieses Video zeigt: Die Raspberry Pi3-Karte, über den GPIO-Anschluss die Mars Rover2rpi-FPGA-Karte (Cyclone IV), an die ein HDMI-Monitor angeschlossen ist. Der zweite Monitor wird über den Standard-HDMI-Raspberry-Pi3-Anschluss angeschlossen. Alles zusammen funktioniert wie ein System mit zwei Monitoren.
Ich werde Ihnen sagen, wie dies weiter gemacht wird.
Die beliebte Raspberry Pi3-Karte verfügt über einen GPIO-Anschluss, über den Sie verschiedene Erweiterungskarten anschließen können: Sensoren, LEDs, Schrittmotortreiber und mehr. Die spezifische Funktion jedes Pins an einem Anschluss hängt von der Anschlusskonfiguration ab. Mit der ALT2 GPIO-Konfiguration können Sie den Anschluss in den DPI-Schnittstellenmodus "Parallele Schnittstelle anzeigen" schalten. Es gibt Erweiterungskarten zum Anschließen von VGA-Monitoren über DPI. Erstens sind VGA-Monitore nicht mehr so verbreitet wie HDMI, und zweitens wird die digitale Schnittstelle besser als die analoge. Darüber hinaus wird der DAC auf ähnlichen VGA-Erweiterungskarten normalerweise in Form von R-2-R-Ketten und häufig nicht mehr als 6 Bit pro Farbe hergestellt.
Im ALT2-Modus haben die GPIO-Pins die folgende Bedeutung:
Hier habe ich die RGB-Pins des Steckers rot, grün und blau gefärbt. Andere wichtige Signale sind V-SYNC- und H-SYNC-Sweep-Sync-Signale sowie CLK. Der CLK-Takt ist die Frequenz, mit der die Pixelwerte an den Anschluss ausgegeben werden. Dies hängt vom ausgewählten Videomodus ab.
Um einen digitalen HDMI-Monitor anzuschließen, müssen Sie DPI-Signale erfassen und in HDMI-Signale umwandeln. Dies kann beispielsweise mit einer Art FPGA-Karte erfolgen. Wie sich herausstellte, ist das Mars rover2rpi Board für diese Zwecke geeignet. In Wahrheit sieht die Hauptoption zum Anschließen dieser Karte über einen speziellen Adapter folgendermaßen aus:
Diese Karte dient dazu, die Anzahl der GPIO-Ports zu erhöhen und mehr Peripheriegeräte an die Himbeere anzuschließen. Gleichzeitig werden 4 GPIO-Signale mit dieser Verbindung für JTAG-Signale verwendet, damit das Programm der Himbeere die FPGA-Firmware in das FPGA laden kann. Aus diesem Grund passt eine solche Standardverbindung nicht zu mir, 4 DPI-Signale fallen aus. Glücklicherweise haben die zusätzlichen Kämme auf der Platine eine Himbeer-kompatible Pinbelegung. Damit ich das Board um 90 Grad drehen und trotzdem an meine Himbeere anschließen kann:
Natürlich muss ich einen externen JTAG-Programmierer verwenden, aber das ist kein Problem.
Es gibt immer noch ein kleines Problem. Nicht jeder FPGA-Pin kann als Takteingang verwendet werden. Es gibt nur wenige dedizierte Pins, die für diesen Zweck verwendet werden können. Hier stellte sich also heraus, dass das GPIO_0 CLK-Signal nicht an den FPGA-Eingang geht, der als FPGA-Taktfrequenzeingang verwendet werden kann. Trotzdem musste ich einen Draht auf den Schal werfen. Ich verbinde GPIO_0 und das KEY [1] -Signal der Karte:
Jetzt werde ich Ihnen ein wenig über das Projekt im FPGA erzählen. Die Hauptschwierigkeit bei der Erzeugung von HDMI-Signalen sind sehr hohe Frequenzen. Wenn Sie sich die Pinbelegung des HDMI-Anschlusses ansehen, sehen Sie, dass die RGB-Signale jetzt serielle Differenzsignale sind:
Durch die Verwendung eines Differenzsignals können Sie Gleichtaktstörungen auf der Übertragungsleitung bekämpfen. Somit wird der ursprüngliche 8-Bit-Code jedes Farbsignals in 10-Bit-TMDS (Transition-Minimated Differential Signaling) umgewandelt. Dies ist eine spezielle Codierungstechnik, um die Gleichstromkomponente aus dem Signal zu entfernen und das Signalschalten auf der Differenzleitung zu minimieren. Da nun ein Farbbyte 10 Bit über die serielle Übertragungsleitung übertragen muss, sollte die Taktfrequenz des Serialisierers zehnmal höher sein als die Taktfrequenz der Pixel. Wenn wir zum Beispiel den Videomodus 1280x720 60Hz nehmen, beträgt die Pixelfrequenz dieses Modus 74,25 MHz. Der Serializer sollte 742,5 MHz haben.
Leider sind herkömmliche FPGAs dazu nicht in der Lage. Zum Glück verfügt das FPGA jedoch über integrierte DDIO-Pins. Dies sind Schlussfolgerungen, die bereits eine Art 2-zu-1-Serialisierer sind. Das heißt, sie können zwei aufeinanderfolgende Bits an den ansteigenden und abfallenden Flanken der Taktfrequenz ausgeben. Dies bedeutet, dass Sie im FPGA-Projekt nicht 740 MHz, sondern 370 MHz verwenden können, aber Sie müssen die DDIO-Ausgangselemente im FPGA verwenden. Hier ist 370 MHz bereits durchaus erreichbare Frequenz. Leider ist der 1280x720-Modus die Grenze. Eine höhere Auflösung in unserem FPGA Cyclone IV, der auf dem Mars Rover 2rpi-Board installiert ist, kann nicht erreicht werden.
In dem Projekt geht die Eingangspixelfrequenz CLK zur PLL, wo sie mit 5 multipliziert wird. Bei dieser Frequenz werden die Bytes R, G, B in Bitpaare umgewandelt. Dies erfolgt durch den TMDS-Encoder. Der Quellcode für Verilog HDL sieht folgendermaßen aus:
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
Dann werden die Ausgangspaare dem DDIO-Ausgang zugeführt, der nacheinander ein Ein-Bit-Signal beim Anstieg und Abfall erzeugt.
DDIO selbst könnte mit einem solchen Verilog-Code beschrieben werden:
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
Aber so wird es wahrscheinlich nicht funktionieren. Sie müssen die alter megafunction ALTDDIO_OUT verwenden, um die ausgegebenen DDIO-Elemente tatsächlich zu verwenden. Es ist die Bibliothekskomponente ALTDDIO_OUT, die in meinem Projekt verwendet wird.
Das klingt vielleicht etwas knifflig, funktioniert aber.
Sie können den gesamten in Verilog HDL geschriebenen Quellcode hier auf github anzeigen .
Die kompilierte FPGA-Firmware ist in einen EPCS-Chip integriert, der auf der Mars rover2rpi-Karte installiert ist. Wenn die FPGA-Karte mit Strom versorgt wird, wird das FPGA aus dem Flash-Speicher initialisiert und gestartet.
Jetzt müssen wir ein wenig über die Konfiguration der Himbeere selbst sprechen.
Ich mache Experimente mit Raspberry PI OS (32 Bit) basierend auf Debian Buster, Version: August 2020,
Veröffentlichungsdatum: 2020-08-20, Kernel-Version: 5.4.
Es gibt zwei Dinge zu tun:
- Bearbeiten Sie die Datei config.txt.
- Erstellen Sie eine X-Serverkonfiguration für zwei Monitore.
Wenn Sie die Datei /boot/config.txt bearbeiten, müssen Sie:
- Deaktivieren Sie die Verwendung von i2c, i2s, spi.
- Aktivieren Sie den DPI-Modus mit dem Overlay dtoverlay = dpi24.
- Stellen Sie den Videomodus auf 1280 x 720, 60 Hz, 24 Bit pro Punkt und DPI ein.
- Geben Sie die erforderliche Anzahl von Framebuffern 2 an (max_framebuffers = 2, nur dann wird das zweite Gerät / dev / fb1 angezeigt).
Der vollständige Text der Datei config.txt sieht folgendermaßen aus.
# 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
Danach müssen Sie eine Konfigurationsdatei für den X-Server erstellen, um zwei Monitore auf zwei Framebuffern / dev / fb0 und / dev / fb1 zu verwenden:
Meine Konfigurationsdatei /usr/share/x11/xorg.conf.d/60-dualscreen.conf sieht so aus
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
Wenn dies noch nicht installiert ist, müssen Sie Xinerama installieren. Anschließend wird der Desktop-Bereich vollständig auf zwei Monitore erweitert, wie oben im Demo-Video gezeigt.
Das ist wahrscheinlich alles. Jetzt können Raspberry Pi3-Besitzer zwei Monitore verwenden.
Die Beschreibung und das Diagramm des Mars rover2rpi-Boards können hier eingesehen werden .