DPI arayüzü ve FPGA kartı aracılığıyla Raspberry Pi3'e ikinci HDMI monitör


Bu videoda şunlar gösterilmektedir: GPIO konektörü aracılığıyla kendisine bağlanan Raspberry Pi3 kartı, bir HDMI monitörün bağlandığı bir FPGA kartı Mars Rover2rpi'dir (Cyclone IV). İkinci monitör, Raspberry Pi3'ün standart HDMI konektörü aracılığıyla bağlanır. Her şey ikili monitör sistemi gibi birlikte çalışır.

Daha sonra size bunun nasıl uygulandığını anlatacağım.

Popüler Raspberry Pi3 kartı, çeşitli genişletme kartlarını bağlayabileceğiniz bir GPIO konektörüne sahiptir: sensörler, LED'ler, step motor sürücüleri ve çok daha fazlası. Bir konnektördeki her pinin tam işlevi port konfigürasyonuna bağlıdır. GPIO ALT2 yapılandırması, konektörü DPI arayüz moduna, Paralel Arayüzü Görüntüle'ye değiştirmenize olanak tanır. VGA monitörlerini DPI aracılığıyla bağlamak için genişletme kartları vardır. Ancak öncelikle VGA monitörler artık HDMI kadar yaygın değil ve ikincisi, dijital arayüz analog arayüzden giderek daha iyi hale geliyor. Ayrıca, bu tür VGA genişletme kartlarındaki DAC genellikle R-2-R zincirleri biçiminde yapılır ve genellikle renk başına 6 bitten fazla olmaz.

ALT2 modunda GPIO konnektör pinleri şu anlama gelir:

DPI arayüzü ve FPGA kartı aracılığıyla Raspberry Pi3'e ikinci HDMI monitör

Burada konektörün RGB pinlerini sırasıyla kırmızı, yeşil ve mavi renklendirdim. Diğer önemli sinyaller V-SYNC ve H-SYNC sinyallerinin yanı sıra CLK'dır. CLK saat frekansı, piksel değerlerinin konektöre gönderildiği frekanstır; seçilen video moduna bağlıdır.

Dijital bir HDMI monitör bağlamak için arayüzün DPI sinyallerini yakalamanız ve bunları HDMI sinyallerine dönüştürmeniz gerekir. Bu, örneğin bir tür FPGA kartı kullanılarak yapılabilir. Görünüşe göre Mars Rover2rpi kartı bu amaçlar için uygundur. Gerçekte, bu kartı özel bir adaptör aracılığıyla bağlamanın ana seçeneği şuna benzer:

DPI arayüzü ve FPGA kartı aracılığıyla Raspberry Pi3'e ikinci HDMI monitör

Bu kart GPIO portlarının sayısını arttırmak ve Raspberry'e daha fazla çevresel cihaz bağlamak için kullanılır. Aynı zamanda bu bağlantıyla 4 GPIO sinyali JTAG sinyalleri için kullanılır, böylece Raspberry'den gelen program FPGA ürün yazılımını FPGA'ya yükleyebilir. Bu nedenle bu standart bağlantı bana uymuyor, 4 DPI sinyali kesiliyor. Neyse ki, tahtadaki ek tarakların Raspberry uyumlu bir pin yapısı var. Böylece tahtayı 90 derece döndürebilir ve yine de ahududuma bağlayabilirim:

DPI arayüzü ve FPGA kartı aracılığıyla Raspberry Pi3'e ikinci HDMI monitör

Elbette harici bir JTAG programlayıcı kullanmanız gerekecek ancak bu bir sorun değil.

Hala küçük bir sorun var. Her FPGA pini saat girişi olarak kullanılamaz. Bu amaçlar için kullanılabilecek yalnızca birkaç özel pin vardır. Yani burada GPIO_0 CLK sinyalinin, FPGA saat girişi olarak kullanılabilecek FPGA girişine ulaşmadığı ortaya çıktı. Bu yüzden hala atkıya bir tel takmak zorunda kaldım. GPIO_0 ile anakartın KEY[1] sinyalini birbirine bağlıyorum:

DPI arayüzü ve FPGA kartı aracılığıyla Raspberry Pi3'e ikinci HDMI monitör

Şimdi size biraz FPGA projesinden bahsedeceğim. HDMI sinyalleri üretmenin ana zorluğu çok yüksek frekanslardır. HDMI konektörünün pin çıkışına bakarsanız RGB sinyallerinin artık seri diferansiyel sinyaller olduğunu görebilirsiniz:

DPI arayüzü ve FPGA kartı aracılığıyla Raspberry Pi3'e ikinci HDMI monitör

Diferansiyel sinyalin kullanılması, iletim hattındaki ortak mod parazitiyle mücadele etmenize olanak tanır. Bu durumda, her renk sinyalinin orijinal sekiz bitlik kodu, 10 bitlik bir TMDS'ye (Geçişi en aza indirilmiş diferansiyel sinyalleme) dönüştürülür. Bu, DC bileşenini sinyalden çıkarmak ve diferansiyel hattaki sinyal değişimini en aza indirmek için özel bir kodlama yöntemidir. Artık bir bayt renk için seri hat üzerinden 10 bitin iletilmesi gerektiğinden, seri hale getirici saat hızının piksel saat hızından 10 kat daha yüksek olması gerektiği ortaya çıkıyor. Örneğin 1280x720 60Hz video modunu alırsak, bu modun piksel frekansı 74,25 MHz'dir. Serileştirici 742,5 MHz olmalıdır.

Normal FPGA'ler ne yazık ki bunu yapamaz. Ancak ne mutlu ki bizim için FPGA yerleşik DDIO pinlerine sahiptir. Bunlar zaten 2'ye 1 serileştirici olan sonuçlardır. Yani saat frekansının yükselen ve düşen kenarlarında ardışık olarak iki bit çıktısı verebilirler. Bu, bir FPGA projesinde 740 MHz değil 370 MHz kullanabileceğiniz ancak FPGA'da DDIO çıkış elemanlarını kullanmanız gerektiği anlamına gelir. Artık 370 MHz zaten tamamen ulaşılabilir bir frekans. Maalesef 1280x720 modu sınırdır. Mars Rover2rpi kartına kurulan Cyclone IV FPGA'mızda daha yüksek bir çözünürlük elde edilememektedir.

Yani tasarımda giriş piksel frekansı CLK, PLL'ye gider ve burada 5 ile çarpılır. Bu frekansta R, G, B baytları bit çiftlerine dönüştürülür. TMDS kodlayıcının yaptığı budur. Verilog HDL'deki kaynak kodu şuna benzer:

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

Daha sonra çıkış çiftleri, yükselen ve düşen kenarlarda sırayla bir bitlik sinyal üreten DDIO çıkışına beslenir.

DDIO'nun kendisi aşağıdaki Verilog koduyla tanımlanabilir:

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

Ancak büyük olasılıkla bu şekilde çalışmayacaktır. DDIO çıkış elemanlarını gerçekten etkinleştirmek için Alter'in ALTDDIO_OUT megafonksiyonunu kullanmanız gerekir. Projem ALTDDIO_OUT kütüphane bileşenini kullanıyor.

Bunların hepsi biraz zor görünebilir, ancak işe yarıyor.

Verilog HDL'de yazılan tüm kaynak kodlarını görüntüleyebilirsiniz. burada github'da.

FPGA için derlenen ürün yazılımı, Mars Rover2rpi kartına takılı EPCS çipine aktarılır. Böylece FPGA kartına güç verildiğinde FPGA flash bellekten başlatılacak ve başlatılacaktır.

Şimdi biraz Raspberry'nin konfigürasyonundan bahsetmemiz gerekiyor.

Raspberry PI OS (32 bit) üzerinde Debian Buster Sürümü: Ağustos 2020'yi temel alan deneyler yapıyorum.
Yayın tarihi:2020-08-20, Çekirdek sürümü:5.4.

Yapılması gereken iki şey var:

  • config.txt dosyasını düzenleyin;
  • iki monitörle çalışacak bir X sunucu yapılandırması oluşturun.

/boot/config.txt dosyasını düzenlerken ihtiyacınız olan:

  1. i2c, i2s, spi kullanımını devre dışı bırakın;
  2. overlay kullanarak DPI modunu etkinleştirin dtoverlay=dpi24;
  3. video modunu 1280×720 60Hz, DPI'da piksel başına 24 bit olarak yapılandırın;
  4. gerekli çerçeve ara bellek sayısını belirtin 2 (max_framebuffers=2, ancak o zaman ikinci aygıt /dev/fb1 görünecektir)

Config.txt dosyasının tam metni şu şekilde görünür.

# 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

Bundan sonra, X sunucusunun iki monitörü /dev/fb0 ve /dev/fb1 çerçeve arabelleğinde kullanması için bir yapılandırma dosyası oluşturmanız gerekir:

Yapılandırma dosyam /usr/share/x11/xorg.conf.d/60-dualscreen.conf şu şekilde

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

Zaten yüklü değilse Xinerama'yı yüklemeniz gerekir. Daha sonra masaüstü alanı yukarıdaki demo videoda gösterildiği gibi tamamen iki monitöre genişletilecektir.

Muhtemelen hepsi bu. Artık Raspberry Pi3 sahipleri iki monitör kullanabilecek.

Mars Rover2rpi kartının açıklaması ve devre şemasını burada bulabilirsiniz buraya bak.

Kaynak: habr.com