Monitor HDMI kedua ke Raspberry Pi3 melalui antarmuka DPI dan papan FPGA


Video ini menunjukkan: papan Raspberry Pi3, yang terhubung melalui konektor GPIO, papan FPGA Mars Rover2rpi (Cyclone IV), yang terhubung dengan monitor HDMI. Monitor kedua terhubung melalui konektor HDMI Raspberry Pi3 standar. Semuanya bekerja seperti sistem monitor ganda.

Selanjutnya saya akan memberi tahu Anda bagaimana penerapannya.

Papan Raspberry Pi3 yang populer memiliki konektor GPIO di mana Anda dapat menghubungkan berbagai papan ekspansi: sensor, LED, driver motor stepper, dan banyak lagi. Fungsi spesifik dari setiap pin pada konektor bergantung pada konfigurasi port. Konfigurasi GPIO ALT2 memungkinkan Anda mengalihkan konektor ke mode antarmuka DPI, Display Parallel Interface. Ada papan ekspansi untuk menghubungkan monitor VGA melalui DPI. Namun, pertama, monitor VGA tidak lagi biasa seperti HDMI, dan kedua, antarmuka digital menjadi lebih baik daripada analog. Selain itu, DAC pada kartu ekspansi VGA tersebut biasanya dibuat dalam bentuk rantai R-2-R dan seringkali tidak lebih dari 6 bit per warna.

Dalam mode ALT2, pin konektor GPIO memiliki arti sebagai berikut:

Monitor HDMI kedua ke Raspberry Pi3 melalui antarmuka DPI dan papan FPGA

Di sini saya mewarnai pin RGB konektor masing-masing merah, hijau dan biru. Sinyal penting lainnya adalah sinyal sinkronisasi sapuan V-SYNC dan H-SYNC, serta CLK. Frekuensi jam CLK adalah frekuensi di mana nilai piksel dikeluarkan ke konektor dan bergantung pada mode video yang dipilih.

Untuk menyambungkan monitor HDMI digital, Anda perlu menangkap sinyal antarmuka DPI dan mengubahnya menjadi sinyal HDMI. Ini dapat dilakukan, misalnya, menggunakan papan FPGA apa pun. Ternyata, papan Mars Rover2rpi cocok untuk tujuan ini. Sebenarnya, opsi utama untuk menghubungkan papan ini melalui adaptor khusus terlihat seperti ini:

Monitor HDMI kedua ke Raspberry Pi3 melalui antarmuka DPI dan papan FPGA

Papan ini digunakan untuk menambah jumlah port GPIO dan menghubungkan lebih banyak periferal ke raspberry. Pada saat yang sama, 4 sinyal GPIO dengan koneksi ini digunakan untuk sinyal JTAG, sehingga program dari distribusi dapat memuat firmware FPGA ke dalam FPGA. Karena itu, koneksi biasa seperti itu tidak cocok untuk saya, sinyal 4 DPI terputus. Untungnya, sisir ekstra di papan memiliki pinout yang kompatibel dengan Raspberry. Agar saya dapat memutar papan 90 derajat dan tetap menghubungkannya ke raspberry saya:

Monitor HDMI kedua ke Raspberry Pi3 melalui antarmuka DPI dan papan FPGA

Tentu saja, Anda harus menggunakan pemrogram JTAG eksternal, tetapi ini bukan masalah.

Masih ada masalah kecil. Tidak setiap pin FPGA dapat digunakan sebagai masukan jam. Hanya ada beberapa pin khusus yang dapat digunakan untuk tujuan ini. Jadi ternyata di sini sinyal CLK GPIO_0 tidak sampai ke input FPGA, yang bisa digunakan sebagai input clock FPGA. Jadi tetap saja, saya harus melempar satu posting ke syal. Saya menghubungkan sinyal GPIO_0 dan KEY[1] dari papan:

Monitor HDMI kedua ke Raspberry Pi3 melalui antarmuka DPI dan papan FPGA

Sekarang saya akan bercerita sedikit tentang proyek di FPGA. Kesulitan utama dalam pembentukan sinyal HDMI adalah frekuensi yang sangat tinggi. Melihat pinout konektor HDMI, Anda dapat melihat bahwa sinyal RGB sekarang menjadi sinyal diferensial serial:

Monitor HDMI kedua ke Raspberry Pi3 melalui antarmuka DPI dan papan FPGA

Penggunaan sinyal diferensial memungkinkan Anda menangani noise mode umum pada saluran transmisi. Dalam hal ini, kode delapan-bit asli dari setiap sinyal warna diubah menjadi TMDS 10-bit (pensinyalan diferensial yang diminimalkan transisi). Ini adalah metode pengkodean khusus untuk menghapus komponen DC dari sinyal dan meminimalkan peralihan sinyal pada saluran diferensial. Karena sekarang ada 10 bit untuk ditransmisikan per byte warna melalui jalur serial, ternyata frekuensi clock serializer harus 10 kali lebih tinggi dari frekuensi clock piksel. Jika kita ambil contoh mode video 1280x720 60Hz, maka frekuensi piksel mode ini adalah 74,25MHz. Serializer harus 742,5 MHz.

Sayangnya, FPGA konvensional umumnya tidak mampu melakukan ini. Namun, untungnya, FPGA memiliki pin DDIO bawaan. Ini adalah kesimpulan yang seolah-olah merupakan serializer 2-ke-1. Artinya, mereka dapat mengeluarkan dua bit secara berurutan di sepanjang frekuensi jam naik dan turun. Ini berarti bahwa dalam proyek FPGA Anda tidak dapat menggunakan 740 MHz, tetapi 370 MHz, tetapi Anda perlu menggunakan elemen keluaran DDIO di FPGA. Di sini 370 MHz sudah merupakan frekuensi yang cukup dapat dicapai. Sayangnya, mode 1280Γ—720 adalah batasnya. Resolusi yang lebih tinggi tidak dapat dicapai di FPGA Cyclone IV kami yang dipasang di papan Rover2rpi.

Jadi, dalam proyek tersebut, frekuensi piksel masukan CLK diumpankan ke PLL, yang dikalikan dengan 5. Pada frekuensi ini, byte R, G, B diubah menjadi pasangan bit. Inilah yang dilakukan pembuat enkode TMDS. Kode sumber pada Verilog HDL terlihat 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 diumpankan ke keluaran DDIO, yang secara berurutan menghasilkan sinyal satu bit naik dan turun.

DDIO sendiri dapat digambarkan dengan kode Verilog seperti ini:

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

Tapi itu mungkin tidak akan berhasil seperti itu. Anda perlu menggunakan megafungsi ALTDDIO_OUT Altera untuk benar-benar menggunakan elemen keluaran DDIO. Dalam proyek saya, komponen pustaka ALTDDIO_OUT digunakan.

Semuanya mungkin terlihat sedikit rumit, tetapi berhasil.

Anda dapat melihat seluruh kode sumber yang ditulis dalam Verilog HDL di sini di github.

Firmware yang dikompilasi untuk FPGA disematkan ke dalam chip EPCS yang dipasang di papan Mars Rover2rpi. Jadi, saat daya dialirkan ke papan FPGA, FPGA akan menginisialisasi dari memori flash dan memulai.

Sekarang kita perlu berbicara sedikit tentang konfigurasi Raspberry itu sendiri.

Saya sedang melakukan eksperimen pada Raspberry PI OS (32 bit) berbasis Debian Buster, Versi: Agustus 2020,
Tanggal rilis:2020-08-20, versi Kernel:5.4.

Anda perlu melakukan dua hal:

  • edit file config.txt;
  • buat konfigurasi server X untuk bekerja dengan dua monitor.

Saat mengedit file /boot/config.txt, Anda perlu:

  1. nonaktifkan penggunaan i2c, i2s, spi;
  2. aktifkan mode DPI dengan overlay dtoverlay=dpi24;
  3. atur mode video 1280Γ—720 60Hz, 24 bit per titik per DPI;
  4. tentukan jumlah framebuffers 2 yang diperlukan (max_framebuffers=2, baru perangkat kedua /dev/fb1 akan muncul)

Teks lengkap dari file config.txt terlihat 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

Setelah itu, Anda perlu membuat file konfigurasi untuk server X untuk menggunakan dua monitor pada dua framebuffer /dev/fb0 dan /dev/fb1:

File konfigurasi saya adalah /usr/share/x11/xorg.conf.d/60-dualscreen.conf 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 belum terinstal, maka Anda perlu menginstal Xinerama. Kemudian ruang desktop akan diperluas sepenuhnya menjadi dua monitor, seperti yang ditunjukkan pada video demo di atas.

Mungkin itu saja. Sekarang, pemilik Raspberry Pi3 akan dapat menggunakan dua monitor.

Deskripsi dan diagram papan Mars Rover2rpi bisa Lihat disini.

Sumber: www.habr.com