Monitor HDMI kedua ke Raspberry Pi3 melalui antara muka DPI dan papan FPGA


Video ini menunjukkan: papan Raspberry Pi3, yang disambungkan kepadanya melalui penyambung GPIO ialah papan FPGA Mars Rover2rpi (Cyclone IV), yang mana monitor HDMI disambungkan. Monitor kedua disambungkan melalui penyambung HDMI standard Raspberry Pi3. Semuanya berfungsi bersama seperti sistem dwi monitor.

Seterusnya saya akan memberitahu anda bagaimana ini dilaksanakan.

Papan Raspberry Pi3 yang popular mempunyai penyambung GPIO yang melaluinya anda boleh menyambungkan pelbagai kad pengembangan: penderia, LED, pemacu motor stepper dan banyak lagi. Fungsi tepat setiap pin pada penyambung bergantung pada konfigurasi port. Konfigurasi GPIO ALT2 membolehkan anda menukar penyambung kepada mod antara muka DPI, Paparan Antara Muka Selari. Terdapat kad pengembangan untuk menyambungkan monitor VGA melalui DPI. Walau bagaimanapun, pertama, monitor VGA tidak lagi sama seperti HDMI, dan kedua, antara muka digital semakin baik daripada analog. Selain itu, DAC pada papan pengembangan VGA tersebut biasanya dibuat dalam bentuk rantai R-2-R dan selalunya tidak lebih daripada 6 bit setiap warna.

Dalam mod ALT2, pin penyambung GPIO mempunyai makna berikut:

Monitor HDMI kedua ke Raspberry Pi3 melalui antara muka DPI dan papan FPGA

Di sini saya telah mewarnakan pin RGB penyambung masing-masing merah, hijau dan biru. Isyarat penting lain ialah isyarat V-SYNC dan H-SYNC, serta CLK. Kekerapan jam CLK ialah kekerapan di mana nilai piksel dikeluarkan kepada penyambung; ia bergantung pada mod video yang dipilih.

Untuk menyambungkan monitor HDMI digital, anda perlu menangkap isyarat DPI antara muka dan menukarnya kepada isyarat HDMI. Ini boleh dilakukan, sebagai contoh, menggunakan beberapa jenis papan FPGA. Ternyata, papan Mars Rover2rpi sesuai untuk tujuan ini. Sebenarnya, pilihan utama untuk menyambungkan papan ini melalui penyesuai khas kelihatan seperti ini:

Monitor HDMI kedua ke Raspberry Pi3 melalui antara muka DPI dan papan FPGA

Papan ini digunakan untuk menambah bilangan port GPIO dan untuk menyambungkan lebih banyak peranti persisian ke raspberi. Pada masa yang sama, 4 isyarat GPIO dengan sambungan ini digunakan untuk isyarat JTAG, supaya program daripada Raspberry boleh memuatkan perisian tegar FPGA ke dalam FPGA. Oleh sebab itu, sambungan standard ini tidak sesuai dengan saya; 4 isyarat DPI tercicir. Nasib baik, sikat tambahan pada papan mempunyai pinout yang serasi dengan Raspberry. Jadi saya boleh memutarkan papan 90 darjah dan masih menyambungkannya ke raspberi saya:

Monitor HDMI kedua ke Raspberry Pi3 melalui antara muka DPI dan papan FPGA

Sudah tentu, anda perlu menggunakan pengaturcara JTAG luaran, tetapi ini tidak menjadi masalah.

Masih ada masalah kecil. Tidak setiap pin FPGA boleh digunakan sebagai input jam. Terdapat hanya beberapa pin khusus yang boleh digunakan untuk tujuan ini. Jadi ternyata di sini bahawa isyarat GPIO_0 CLK tidak mencapai input FPGA, yang boleh digunakan sebagai input jam FPGA. Jadi saya masih perlu meletakkan satu wayar pada selendang. Saya menyambungkan GPIO_0 dan isyarat KEY[1] papan:

Monitor HDMI kedua ke Raspberry Pi3 melalui antara muka DPI dan papan FPGA

Sekarang saya akan memberitahu anda sedikit tentang projek FPGA. Kesukaran utama dalam menjana isyarat HDMI adalah frekuensi yang sangat tinggi. Jika anda melihat pada pinout penyambung HDMI, anda boleh melihat bahawa isyarat RGB kini adalah isyarat pembezaan bersiri:

Monitor HDMI kedua ke Raspberry Pi3 melalui antara muka DPI dan papan FPGA

Penggunaan isyarat pembezaan membolehkan anda memerangi gangguan mod biasa pada talian penghantaran. Dalam kes ini, kod lapan-bit asal bagi setiap isyarat warna ditukar kepada TMDS 10-bit (Isyarat pembezaan diminimumkan peralihan). Ini ialah kaedah pengekodan khas untuk mengeluarkan komponen DC daripada isyarat dan meminimumkan pensuisan isyarat dalam talian pembezaan. Memandangkan 10 bit kini perlu dihantar melalui talian bersiri untuk satu bait warna, ternyata kelajuan jam serializer mestilah 10 kali lebih tinggi daripada kelajuan jam piksel. Jika kita mengambil contoh mod video 1280x720 60Hz, maka kekerapan piksel mod ini ialah 74,25 MHz. Penyeri bersiri hendaklah 742,5 MHz.

FPGA biasa, malangnya, tidak mampu melakukan ini. Walau bagaimanapun, nasib baik bagi kami, FPGA mempunyai pin DDIO terbina dalam. Ini adalah kesimpulan yang sudah, seolah-olah, penyeri bersiri 2-ke-1. Iaitu, mereka boleh mengeluarkan dua bit secara berurutan pada tepi naik dan turun frekuensi jam. Ini bermakna bahawa dalam projek FPGA anda tidak boleh menggunakan 740 MHz, tetapi 370 MHz, tetapi anda perlu menggunakan elemen output DDIO dalam FPGA. Kini 370 MHz sudah menjadi frekuensi yang boleh dicapai sepenuhnya. Malangnya, mod 1280x720 adalah hadnya. Resolusi yang lebih tinggi tidak boleh dicapai dalam FPGA Cyclone IV kami yang dipasang pada papan Mars Rover2rpi.

Jadi, dalam reka bentuk, frekuensi piksel input CLK pergi ke PLL, di mana ia didarab dengan 5. Pada frekuensi ini, bait R, G, B ditukar menjadi pasangan bit. Inilah yang dilakukan oleh pengekod TMDS. Kod sumber dalam Verilog HDL kelihatan seperti ini:

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

Kemudian pasangan keluaran disalurkan ke output DDIO, yang secara berurutan menghasilkan isyarat satu bit pada tepi naik dan turun.

DDIO sendiri boleh diterangkan dengan kod Verilog berikut:

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

Tetapi kemungkinan besar ia tidak akan berfungsi seperti itu. Anda perlu menggunakan fungsi mega Alter ALTDIO_OUT untuk benar-benar mendayakan elemen output DDIO. Projek saya menggunakan komponen perpustakaan ALTDIO_OUT.

Ini semua mungkin kelihatan agak rumit, tetapi ia berkesan.

Anda boleh melihat semua kod sumber yang ditulis dalam Verilog HDL di sini di github.

Perisian tegar yang disusun untuk FPGA dipancarkan ke dalam cip EPCS yang dipasang pada papan Mars Rover2rpi. Oleh itu, apabila kuasa digunakan pada papan FPGA, FPGA akan dimulakan daripada memori denyar dan dimulakan.

Sekarang kita perlu bercakap sedikit tentang konfigurasi Raspberry itu sendiri.

Saya sedang melakukan percubaan pada Raspberry PI OS (32 bit) berdasarkan Debian Buster, Versi:Ogos 2020,
Tarikh keluaran:2020-08-20, Versi kernel:5.4.

Anda perlu melakukan dua perkara:

  • edit fail config.txt;
  • buat konfigurasi pelayan X untuk berfungsi dengan dua monitor.

Apabila mengedit fail /boot/config.txt yang anda perlukan:

  1. lumpuhkan penggunaan i2c, i2s, spi;
  2. dayakan mod DPI menggunakan tindanan dtoverlay=dpi24;
  3. konfigurasikan mod video 1280Γ—720 60Hz, 24 bit setiap piksel pada DPI;
  4. nyatakan bilangan framebuffers 2 yang diperlukan (max_framebuffers=2, barulah peranti kedua /dev/fb1 muncul)

Teks penuh fail config.txt kelihatan seperti ini.

# 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

Selepas ini, anda perlu mencipta fail konfigurasi untuk pelayan X menggunakan dua monitor pada dua framebuffers /dev/fb0 dan /dev/fb1:

Fail konfigurasi saya /usr/share/x11/xorg.conf.d/60-dualscreen.conf adalah seperti ini

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

Nah, jika ia belum dipasang, maka anda perlu memasang Xinerama. Kemudian ruang desktop akan dikembangkan sepenuhnya kepada dua monitor, seperti yang ditunjukkan dalam video demo di atas.

Itu sahaja mungkin. Kini, pemilik Raspberry Pi3 akan dapat menggunakan dua monitor.

Penerangan dan gambar rajah litar papan Mars Rover2rpi boleh didapati tengok sini.

Sumber: www.habr.com