مانیتور دوم HDMI به Raspberry Pi3 از طریق رابط DPI و برد FPGA


این ویدئو نشان می دهد: یک برد Raspberry Pi3 که از طریق کانکتور GPIO، یک برد FPGA Mars Rover2rpi (Cyclone IV) به آن وصل شده است که یک مانیتور HDMI به آن متصل است. مانیتور دوم از طریق کانکتور استاندارد Raspberry Pi3 HDMI متصل می شود. در مجموع مانند یک سیستم مانیتور دوگانه کار می کند.

در ادامه به شما خواهم گفت که چگونه اجرا می شود.

برد محبوب Raspberry Pi3 دارای یک کانکتور GPIO است که از طریق آن می توانید بردهای توسعه مختلف را به هم متصل کنید: سنسورها، LED ها، درایورهای موتور پله ای و موارد دیگر. عملکرد خاص هر پین روی کانکتور به پیکربندی پورت بستگی دارد. پیکربندی GPIO ALT2 به شما امکان می دهد کانکتور را به حالت رابط DPI، Display Parallel Interface تغییر دهید. بردهای توسعه برای اتصال مانیتورهای VGA از طریق DPI وجود دارد. با این حال، اولا، مانیتورهای VGA دیگر به اندازه HDMI رایج نیستند و ثانیا، رابط دیجیتال بهتر از آنالوگ است. علاوه بر این، DAC در چنین کارت های توسعه VGA معمولاً به شکل زنجیره های R-2-R ساخته می شود و اغلب بیش از 6 بیت در هر رنگ نیست.

در حالت 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 و برای اتصال بیشتر وسایل جانبی به رزبری استفاده می شود. در عین حال، 4 سیگنال GPIO با این اتصال برای سیگنال های JTAG استفاده می شود تا برنامه از توزیع بتواند فریمور FPGA را در FPGA بارگذاری کند. به همین دلیل، چنین اتصال منظمی برای من مناسب نیست، 4 سیگنال DPI از بین می رود. خوشبختانه، شانه‌های اضافی روی تخته دارای پایه‌ای سازگار با Raspberry هستند. به طوری که می توانم برد را 90 درجه بچرخانم و همچنان آن را به رزبری خود وصل کنم:

مانیتور دوم HDMI به Raspberry Pi3 از طریق رابط DPI و برد FPGA

البته باید از برنامه نویس خارجی JTAG استفاده کنید، اما این مشکلی ندارد.

هنوز یک مشکل کوچک وجود دارد. هر پین FPGA را نمی توان به عنوان ورودی ساعت استفاده کرد. تنها چند پین اختصاصی وجود دارد که می توان برای این منظور استفاده کرد. بنابراین در اینجا معلوم شد که سیگنال GPIO_0 CLK به ورودی FPGA نمی رسد، که می تواند به عنوان ورودی ساعت FPGA استفاده شود. بنابراین با این وجود مجبور شدم یک پست را روی روسری بیاندازم. سیگنال GPIO_0 و KEY[1] برد را وصل می کنم:

مانیتور دوم HDMI به Raspberry Pi3 از طریق رابط DPI و برد FPGA

حالا من کمی در مورد پروژه در FPGA به شما می گویم. مشکل اصلی در شکل گیری سیگنال های HDMI فرکانس های بسیار بالا است. با نگاه کردن به پین ​​اوت کانکتور HDMI، می توانید ببینید که سیگنال های RGB اکنون سیگنال های دیفرانسیل سریال هستند:

مانیتور دوم HDMI به Raspberry Pi3 از طریق رابط DPI و برد FPGA

استفاده از سیگنال دیفرانسیل به شما امکان می دهد با نویز حالت معمول در خط انتقال مقابله کنید. در این حالت، کد هشت بیتی اصلی هر سیگنال رنگی به یک TMDS 10 بیتی (سیگنالینگ دیفرانسیل با حداقل انتقال) تبدیل می شود. این یک روش رمزگذاری ویژه برای حذف جزء DC از سیگنال و به حداقل رساندن سوئیچینگ سیگنال در خط دیفرانسیل است. از آنجایی که اکنون 10 بیت برای انتقال به ازای هر بایت رنگ از طریق خط سریال وجود دارد، معلوم می شود که فرکانس ساعت سریال ساز باید 10 برابر بیشتر از فرکانس ساعت پیکسل ها باشد. اگر به عنوان مثال حالت ویدیویی 1280x720 60Hz را در نظر بگیریم، فرکانس پیکسلی این حالت 74,25MHz است. سریال ساز باید 742,5 مگاهرتز باشد.

متأسفانه FPGAهای معمولی معمولاً قادر به این کار نیستند. با این حال، از شانس ما، FPGA ها دارای پین های DDIO داخلی هستند. اینها نتیجه گیری هایی است که قبلاً، همانطور که بود، سریالسازهای 2 به 1 هستند. یعنی می توانند دو بیت را به ترتیب در امتداد فرکانس های ساعت افزایش و کاهش تولید کنند. این بدان معناست که در پروژه FPGA شما می توانید نه از 740 مگاهرتز، بلکه 370 مگاهرتز استفاده کنید، اما باید از عناصر خروجی DDIO در FPGA استفاده کنید. در اینجا 370 مگاهرتز در حال حاضر یک فرکانس کاملا قابل دستیابی است. متأسفانه، حالت 1280×720 محدودیت است. وضوح بالاتری را نمی توان در FPGA Cyclone IV نصب شده روی برد Rover2rpi بدست آورد.

بنابراین، در پروژه، فرکانس پیکسل ورودی 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 تغذیه می‌شوند که به‌طور متوالی یک سیگنال یک بیتی در بالا و پایین شدن تولید می‌کند.

خود 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 باید از مگاتابع ALTDDIO_OUT Altera استفاده کنید. در پروژه من، جزء کتابخانه ALTDDIO_OUT استفاده می شود.

ممکن است همه چیز کمی مشکل به نظر برسد، اما کار می کند.

می توانید کل کد منبع نوشته شده در Verilog HDL را مشاهده کنید اینجا در github.

سیستم عامل کامپایل شده برای FPGA در تراشه EPCS نصب شده بر روی برد Mars Rover2rpi تعبیه شده است. بنابراین، هنگامی که برق به برد FPGA اعمال می شود، FPGA از حافظه فلش مقداردهی اولیه می شود و شروع می شود.

اکنون باید کمی در مورد پیکربندی خود Raspberry صحبت کنیم.

من در حال انجام آزمایش‌هایی بر روی Raspberry PI OS (32 بیت) بر اساس 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 60 هرتز، 24 بیت در هر نقطه در هر DPI.
  4. تعداد فریم بافرهای مورد نیاز 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 ایجاد کنید تا از دو مانیتور در دو فریم بافر /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