DPI インターフェイスと FPGA ボードを介して Raspberry Pi3 に接続する XNUMX 番目の HDMI モニター


このビデオでは、GPIO コネクタを介して Raspberry Pi3 ボードに接続された FPGA ボード Mars Rover2rpi (Cyclone IV) に HDMI モニターが接続されていることが示されています。 3 番目のモニターは、R​​aspberry PiXNUMX の標準 HDMI コネクタ経由で接続されます。 すべてがデュアル モニター システムのように連携して動作します。

次に、これがどのように実装されるかを説明します。

人気の Raspberry Pi3 ボードには GPIO コネクタがあり、センサー、LED、ステッピング モーター ドライバーなどのさまざまな拡張カードを接続できます。 コネクタの各ピンの正確な機能は、ポート構成によって異なります。 GPIO ALT2 構成により、コネクタを DPI インターフェイス モード、ディスプレイ パラレル インターフェイスに切り替えることができます。 DPI 経由で VGA モニターを接続するための拡張カードがあります。 しかし、第一に、VGA モニターはもはや HDMI ほど一般的ではなくなっており、第二に、デジタル インターフェイスはアナログ インターフェイスよりもますます優れています。 さらに、このような VGA 拡張ボード上の DAC は通常、R-2-R チェーンの形式で作成され、多くの場合 6 色あたり XNUMX ビット以下です。

ALT2 モードでは、GPIO コネクタ ピンは次の意味を持ちます。

DPI インターフェイスと FPGA ボードを介して Raspberry Pi3 に接続する XNUMX 番目の HDMI モニター

ここでは、コネクタの RGB ピンをそれぞれ赤、緑、青に色付けしました。 その他の重要な信号は、CLK と同様に、V-SYNC 信号と H-SYNC 信号です。 CLK クロック周波数は、ピクセル値がコネクタに出力される周波数であり、選択したビデオ モードによって異なります。

デジタル HDMI モニターを接続するには、インターフェイスの DPI 信号をキャプチャし、HDMI 信号に変換する必要があります。 これは、たとえば、ある種の FPGA ボードを使用して実行できます。 結局のところ、Mars Rover2rpi ボードはこれらの目的に適していることがわかります。 実際、特別なアダプターを介してこのボードを接続するための主なオプションは次のようになります。

DPI インターフェイスと FPGA ボードを介して Raspberry Pi3 に接続する XNUMX 番目の HDMI モニター

このボードは、GPIO ポートの数を増やし、より多くの周辺デバイスをラズベリーに接続するために使用されます。 同時に、この接続の 4 つの GPIO 信号が JTAG 信号に使用されるため、Raspberry からのプログラムが FPGA ファームウェアを FPGA にロードできるようになります。 そのため、この標準接続は私には合わず、4 DPI 信号がドロップアウトします。 幸いなことに、ボード上の追加のコームには Raspberry 互換のピン配置があります。 したがって、ボードを 90 度回転しても、ラズベリーに接続できます。

DPI インターフェイスと FPGA ボードを介して Raspberry Pi3 に接続する XNUMX 番目の HDMI モニター

もちろん、外部 JTAG プログラマを使用する必要がありますが、これは問題ありません。

まだ小さな問題が残っています。 すべての FPGA ピンをクロック入力として使用できるわけではありません。 これらの目的に使用できる専用ピンは少数しかありません。 ここで、GPIO_0 CLK 信号が FPGA クロック入力として使用できる FPGA 入力に到達していないことがわかりました。 したがって、スカーフにワイヤーを0本取り付ける必要がありました。 GPIO_1 とボードの KEY[XNUMX] 信号を接続します。

DPI インターフェイスと FPGA ボードを介して Raspberry Pi3 に接続する XNUMX 番目の HDMI モニター

ここで、FPGA プロジェクトについて少し説明します。 HDMI 信号を生成する際の主な問題は、周波数が非常に高いことです。 HDMI コネクタのピン配置を見ると、RGB 信号がシリアル差動信号になっていることがわかります。

DPI インターフェイスと FPGA ボードを介して Raspberry Pi3 に接続する XNUMX 番目の HDMI モニター

差動信号を使用すると、伝送ライン上のコモンモード干渉に対処できます。 この場合、各色信号の元の 10 ビット コードは 10 ビットの TMDS (Transition-minimized Differential Signaling) に変換されます。 これは、信号から DC 成分を除去し、差動ラインでの信号の切り替えを最小限に抑えるための特別なコーディング方法です。 10 バイトのカラーに対して 1280 ビットをシリアル ライン経由で送信する必要があるため、シリアライザのクロック速度はピクセル クロック速度の 720 倍でなければならないことがわかります。 たとえば、ビデオ モード 60x74,25 742,5Hz の場合、このモードのピクセル周波数は XNUMX MHz です。 シリアライザは XNUMX MHz である必要があります。

残念ながら、通常の FPGA にはこれができません。 ただし、幸いなことに、FPGA には DDIO ピンが組み込まれています。 これらは、いわば、すでに 2 対 1 シリアライザーである結論です。 つまり、クロック周波数の立ち上がりエッジと立ち下がりエッジで 740 ビットを連続して出力できます。 これは、FPGA プロジェクトでは 370 MHz ではなく 370 MHz を使用できますが、FPGA で DDIO 出力要素を使用する必要があることを意味します。 現在、1280 MHz はすでに完全に達成可能な周波数です。 残念ながら、720x2 モードが限界です。 Mars RoverXNUMXrpi ボードにインストールされた Cyclone IV FPGA では、より高い解像度を達成することはできません。

したがって、設計では、入力ピクセル周波数 CLK が PLL に送られ、そこで 5 倍にされます。この周波数で、R、G、B バイトがビット ペアに変換されます。 これが 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 出力に供給され、立ち上がりエッジと立ち下がりエッジで XNUMX ビットの信号が順次生成されます。

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

しかし、おそらくそのようにはうまくいきません。 実際に DDIO 出力要素を有効にするには、Alter のメガファンクション ALTDDIO_OUT を使用する必要があります。 私のプロジェクトでは ALTDDIO_OUT ライブラリ コンポーネントを使用しています。

これは少し難しく見えるかもしれませんが、うまくいきます。

Verilog HDLで書かれたすべてのソースコードを表示できます ここgithubにあります.

FPGA 用にコンパイルされたファームウェアは、Mars Rover2rpi ボードにインストールされている EPCS チップにフラッシュされます。 したがって、FPGA ボードに電源が投入されると、FPGA はフラッシュ メモリから初期化されて起動します。

次に、Raspberry 自体の構成について少し説明する必要があります。

Debian Buster、バージョン: 32 年 2020 月に基づく Raspberry PI OS (XNUMX ビット) で実験を行っています。
リリース日:2020-08-20、カーネルバージョン:5.4。

これには2つのことがあります:

  • config.txt ファイルを編集します。
  • XNUMX つのモニターで動作する X サーバー構成を作成します。

/boot/config.txt ファイルを編集する場合は、次のものが必要です。

  1. i2c、i2s、spi の使用を無効にします。
  2. オーバーレイを使用して DPI モードを有効にする dtoverlay=dpi24;
  3. ビデオ モード 1280×720 60Hz、DPI でピクセルあたり 24 ビットを構成します。
  4. 必要なフレームバッファ数を指定します 2 (max_framebuffers=2、その場合のみ 1 番目のデバイス /dev/fbXNUMX が表示されます)

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 サーバーが 0 つのフレームバッファ /dev/fb1 および /dev/fbXNUMX で XNUMX つのモニターを使用するように構成ファイルを作成する必要があります。

私の構成ファイル/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 をインストールする必要があります。 その後、上のデモ ビデオに示されているように、デスクトップ スペースが XNUMX 台のモニタに完全に拡張されます。

おそらくそれだけです。 これで、Raspberry Pi3 の所有者は XNUMX つのモニターを使用できるようになります。

Mars Rover2rpi ボードの説明と回路図が見つかります。 ここを見て.

出所: habr.com