Δεύτερη οθόνη HDMI στο Raspberry Pi3 μέσω διασύνδεσης DPI και πλακέτας FPGA


Αυτό το βίντεο δείχνει: μια πλακέτα Raspberry Pi3, που συνδέεται με αυτήν μέσω της υποδοχής GPIO είναι μια πλακέτα FPGA Mars Rover2rpi (Cyclone IV), στην οποία είναι συνδεδεμένη μια οθόνη HDMI. Η δεύτερη οθόνη συνδέεται μέσω της τυπικής υποδοχής HDMI του Raspberry Pi3. Όλα λειτουργούν μαζί σαν ένα σύστημα διπλής οθόνης.

Στη συνέχεια θα σας πω πώς εφαρμόζεται αυτό.

Η δημοφιλής πλακέτα Raspberry Pi3 διαθέτει υποδοχή GPIO μέσω της οποίας μπορείτε να συνδέσετε διάφορες κάρτες επέκτασης: αισθητήρες, LED, προγράμματα οδήγησης βηματικού κινητήρα και πολλά άλλα. Η ακριβής λειτουργία κάθε ακίδας σε έναν σύνδεσμο εξαρτάται από τη διαμόρφωση της θύρας. Η διαμόρφωση GPIO ALT2 σάς επιτρέπει να αλλάξετε την υποδοχή σύνδεσης σε λειτουργία διασύνδεσης DPI, Παράλληλη διεπαφή εμφάνισης. Υπάρχουν κάρτες επέκτασης για τη σύνδεση οθονών VGA μέσω DPI. Ωστόσο, πρώτον, οι οθόνες VGA δεν είναι πλέον τόσο κοινές όσο το HDMI και, δεύτερον, η ψηφιακή διεπαφή είναι ολοένα και καλύτερη από την αναλογική. Επιπλέον, το DAC σε τέτοιες πλακέτες επέκτασης VGA κατασκευάζεται συνήθως με τη μορφή αλυσίδων R-2-R και συχνά όχι περισσότερα από 6 bit ανά χρώμα.

Στη λειτουργία ALT2, οι ακίδες σύνδεσης GPIO έχουν την εξής σημασία:

Δεύτερη οθόνη HDMI στο Raspberry Pi3 μέσω διασύνδεσης DPI και πλακέτας FPGA

Εδώ έχω χρωματίσει τις ακίδες RGB του βύσματος κόκκινο, πράσινο και μπλε αντίστοιχα. Άλλα σημαντικά σήματα είναι τα σήματα V-SYNC και H-SYNC, καθώς και το CLK. Η συχνότητα ρολογιού CLK είναι η συχνότητα στην οποία εξάγονται οι τιμές των εικονοστοιχείων στην υποδοχή σύνδεσης· εξαρτάται από την επιλεγμένη λειτουργία βίντεο.

Για να συνδέσετε μια ψηφιακή οθόνη HDMI, πρέπει να καταγράψετε τα σήματα DPI της διεπαφής και να τα μετατρέψετε σε σήματα HDMI. Αυτό μπορεί να γίνει, για παράδειγμα, χρησιμοποιώντας κάποιο είδος πλακέτας FPGA. Όπως αποδεικνύεται, η πλακέτα Mars Rover2rpi είναι κατάλληλη για αυτούς τους σκοπούς. Στην πραγματικότητα, η κύρια επιλογή για τη σύνδεση αυτής της πλακέτας μέσω ενός ειδικού προσαρμογέα μοιάζει με αυτό:

Δεύτερη οθόνη HDMI στο Raspberry Pi3 μέσω διασύνδεσης DPI και πλακέτας FPGA

Αυτή η πλακέτα χρησιμοποιείται για την αύξηση του αριθμού των θυρών GPIO και για τη σύνδεση περισσότερων περιφερειακών συσκευών στο raspberry. Ταυτόχρονα, 4 σήματα GPIO με αυτή τη σύνδεση χρησιμοποιούνται για σήματα JTAG, έτσι ώστε το πρόγραμμα από το Raspberry να μπορεί να φορτώσει το υλικολογισμικό FPGA στο FPGA. Εξαιτίας αυτού, αυτή η τυπική σύνδεση δεν μου ταιριάζει· τα σήματα 4 DPI πέφτουν. Ευτυχώς, οι πρόσθετες χτένες στον πίνακα έχουν ένα pinout συμβατό με Raspberry. Μπορώ λοιπόν να περιστρέψω την πλακέτα κατά 90 μοίρες και να τη συνδέσω με το raspberry μου:

Δεύτερη οθόνη HDMI στο Raspberry Pi3 μέσω διασύνδεσης DPI και πλακέτας FPGA

Φυσικά, θα πρέπει να χρησιμοποιήσετε έναν εξωτερικό προγραμματιστή JTAG, αλλά αυτό δεν είναι πρόβλημα.

Υπάρχει ακόμα ένα μικρό πρόβλημα. Δεν μπορεί να χρησιμοποιηθεί κάθε pin FPGA ως είσοδος ρολογιού. Υπάρχουν μόνο μερικές αποκλειστικές καρφίτσες που μπορούν να χρησιμοποιηθούν για αυτούς τους σκοπούς. Έτσι αποδείχθηκε εδώ ότι το σήμα GPIO_0 CLK δεν φτάνει στην είσοδο FPGA, η οποία μπορεί να χρησιμοποιηθεί ως είσοδος ρολογιού FPGA. Έπρεπε λοιπόν να βάλω ένα σύρμα στο κασκόλ. Συνδέω το GPIO_0 και το σήμα KEY[1] του πίνακα:

Δεύτερη οθόνη HDMI στο Raspberry Pi3 μέσω διασύνδεσης DPI και πλακέτας FPGA

Τώρα θα σας πω λίγα λόγια για το έργο FPGA. Η κύρια δυσκολία στη δημιουργία σημάτων HDMI είναι οι πολύ υψηλές συχνότητες. Αν κοιτάξετε το pinout της υποδοχής HDMI, μπορείτε να δείτε ότι τα σήματα RGB είναι πλέον σειριακά διαφορικά σήματα:

Δεύτερη οθόνη HDMI στο Raspberry Pi3 μέσω διασύνδεσης DPI και πλακέτας FPGA

Η χρήση διαφορικού σήματος σάς επιτρέπει να καταπολεμήσετε τις παρεμβολές κοινής λειτουργίας στη γραμμή μεταφοράς. Σε αυτήν την περίπτωση, ο αρχικός κωδικός οκτώ bit κάθε σήματος χρώματος μετατρέπεται σε TMDS 10 bit (ελαχιστοποιημένη διαφορική σηματοδότηση μετάβασης). Αυτή είναι μια ειδική μέθοδος κωδικοποίησης για την αφαίρεση του στοιχείου DC από το σήμα και την ελαχιστοποίηση της εναλλαγής σήματος σε μια διαφορική γραμμή. Δεδομένου ότι 10 bit πρέπει τώρα να μεταδοθούν μέσω της σειριακής γραμμής για ένα byte χρώματος, αποδεικνύεται ότι η ταχύτητα ρολογιού του σειριακού προγράμματος πρέπει να είναι 10 φορές υψηλότερη από την ταχύτητα ρολογιού pixel. Αν πάρουμε για παράδειγμα τη λειτουργία βίντεο 1280x720 60Hz, τότε η συχνότητα pixel αυτής της λειτουργίας είναι 74,25 MHz. Ο σειριοποιητής πρέπει να είναι 742,5 MHz.

Τα κανονικά FPGA στην πραγματικότητα δεν είναι ικανά για αυτό, δυστυχώς. Ωστόσο, ευτυχώς για εμάς, το FPGA έχει ενσωματωμένες ακίδες DDIO. Αυτά είναι συμπεράσματα που είναι ήδη, σαν να λέγαμε, σειριοποιητές 2-προς-1. Δηλαδή, μπορούν να εξάγουν δύο bit διαδοχικά στις ακμές ανόδου και πτώσης της συχνότητας ρολογιού. Αυτό σημαίνει ότι σε ένα έργο FPGA μπορείτε να χρησιμοποιήσετε όχι 740 MHz, αλλά 370 MHz, αλλά πρέπει να χρησιμοποιήσετε στοιχεία εξόδου DDIO στο FPGA. Τώρα τα 370 MHz είναι ήδη μια εντελώς εφικτή συχνότητα. Δυστυχώς, η λειτουργία 1280x720 είναι το όριο. Δεν μπορεί να επιτευχθεί υψηλότερη ανάλυση στο Cyclone IV FPGA που είναι εγκατεστημένο στην πλακέτα Mars Rover2rpi.

Έτσι, στη σχεδίαση, η συχνότητα εικονοστοιχείων εισόδου CLK πηγαίνει στο PLL, όπου πολλαπλασιάζεται επί 5. Σε αυτή τη συχνότητα, τα byte R, G, B μετατρέπονται σε ζεύγη bit. Αυτό κάνει ο κωδικοποιητής TMDS. Ο πηγαίος κώδικας στο Verilog HDL μοιάζει με αυτό:

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

Στη συνέχεια, τα ζεύγη εξόδου τροφοδοτούνται στην έξοδο DDIO, η οποία παράγει διαδοχικά ένα σήμα ενός bit στις ακμές ανόδου και πτώσης.

Το ίδιο το DDIO θα μπορούσε να περιγραφεί με τον ακόλουθο κώδικα 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;
endmodule

Αλλά πιθανότατα δεν θα λειτουργήσει έτσι. Πρέπει να χρησιμοποιήσετε τη μεγασυνάρτηση του Alter ALTDDIO_OUT για να ενεργοποιήσετε πραγματικά τα στοιχεία εξόδου DDIO. Το έργο μου χρησιμοποιεί το στοιχείο βιβλιοθήκης ALTDDIO_OUT.

Μπορεί όλο αυτό να φαίνεται λίγο δύσκολο, αλλά λειτουργεί.

Μπορείτε να δείτε όλο τον πηγαίο κώδικα που είναι γραμμένος σε Verilog HDL εδώ στο github.

Το μεταγλωττισμένο υλικολογισμικό για το FPGA αναβοσβήνει στο τσιπ EPCS που είναι εγκατεστημένο στην πλακέτα Mars Rover2rpi. Έτσι, όταν εφαρμόζεται ρεύμα στην πλακέτα FPGA, το FPGA θα αρχικοποιηθεί από τη μνήμη flash και θα ξεκινήσει.

Τώρα πρέπει να μιλήσουμε λίγο για τη διαμόρφωση του ίδιου του Raspberry.

Κάνω πειράματα στο Raspberry PI OS (32 bit) με βάση το Debian Buster, Έκδοση:Αύγουστος 2020,
Ημερομηνία κυκλοφορίας: 2020-08-20, Έκδοση πυρήνα: 5.4.

Πρέπει να κάνετε δύο πράγματα:

  • επεξεργαστείτε το αρχείο config.txt.
  • δημιουργήστε μια διαμόρφωση διακομιστή X για εργασία με δύο οθόνες.

Κατά την επεξεργασία του αρχείου /boot/config.txt χρειάζεστε:

  1. απενεργοποιήστε τη χρήση των i2c, i2s, spi.
  2. ενεργοποιήστε τη λειτουργία DPI χρησιμοποιώντας επικάλυψη dtoverlay=dpi24;
  3. Διαμόρφωση λειτουργίας βίντεο 1280×720 60Hz, 24 bit ανά pixel στο DPI.
  4. καθορίστε τον απαιτούμενο αριθμό framebuffers 2 (max_framebuffers=2, μόνο τότε θα εμφανιστεί η δεύτερη συσκευή /dev/fb1)

Το πλήρες κείμενο του αρχείου config.txt μοιάζει με αυτό.

# 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

Μετά από αυτό, πρέπει να δημιουργήσετε ένα αρχείο διαμόρφωσης για τον διακομιστή X ώστε να χρησιμοποιεί δύο οθόνες σε δύο framebuffer /dev/fb0 και /dev/fb1:

Το αρχείο διαμόρφωσής μου /usr/share/x11/xorg.conf.d/60-dualscreen.conf είναι κάπως έτσι

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

Λοιπόν, εάν δεν είναι ήδη εγκατεστημένο, τότε πρέπει να εγκαταστήσετε το Xinerama. Στη συνέχεια, ο χώρος της επιφάνειας εργασίας θα επεκταθεί πλήρως σε δύο οθόνες, όπως φαίνεται στο παραπάνω βίντεο επίδειξης.

Μάλλον αυτό είναι όλο. Τώρα, οι κάτοχοι Raspberry Pi3 θα μπορούν να χρησιμοποιούν δύο οθόνες.

Μπορείτε να βρείτε την περιγραφή και το διάγραμμα κυκλώματος της πλακέτας Mars Rover2rpi κοιτάξτε εδώ.

Πηγή: www.habr.com