شاشة HDMI الثانية لـ Raspberry Pi3 عبر واجهة DPI ولوحة FPGA


يُظهر هذا الفيديو: لوحة Raspberry Pi3 ، والتي ، عبر موصل GPIO ، يتم توصيل لوحة FPGA Mars Rover2rpi (Cyclone IV) ، والتي يتم توصيل شاشة HDMI بها. الشاشة الثانية متصلة عبر موصل Raspberry Pi3 HDMI القياسي. تعمل جميعها معًا مثل نظام الشاشة المزدوجة.

بعد ذلك سأخبرك كيف يتم تنفيذه.

تحتوي لوحة Raspberry Pi3 الشهيرة على موصل GPIO يمكنك من خلاله توصيل العديد من لوحات التوسعة: أجهزة الاستشعار ، ومصابيح LED ، ومحركات السائر والمزيد. تعتمد الوظيفة المحددة لكل دبوس في الموصل على تكوين المنفذ. يسمح لك تكوين GPIO ALT2 بتبديل الموصل إلى وضع واجهة DPI ، واجهة العرض المتوازية. توجد لوحات توسعة لتوصيل شاشات 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

يتيح لك استخدام الإشارة التفاضلية التعامل مع ضوضاء الوضع الشائع على خط النقل. في هذه الحالة ، يتم تحويل الشفرة الأصلية المكونة من ثماني بتات لكل إشارة لونية إلى 10 بت TMDS (إشارة تفاضلية انتقالية مصغرة). هذه طريقة تشفير خاصة لإزالة مكون DC من الإشارة وتقليل تبديل الإشارة في الخط التفاضلي. نظرًا لوجود 10 بتات الآن للإرسال لكل بايت من اللون عبر الخط التسلسلي ، فقد اتضح أن تردد الساعة للمسلسل يجب أن يكون أعلى بعشر مرات من تردد ساعة البكسل. إذا أخذنا على سبيل المثال وضع الفيديو 10 × 1280 720 هرتز ، فإن تردد البكسل لهذا الوضع هو 60 ميجا هرتز. يجب أن يكون جهاز التسلسل 74,25 ميجا هرتز.

عادة ما تكون FPGAs التقليدية غير قادرة على ذلك ، لسوء الحظ. ومع ذلك ، لحسن الحظ ، تحتوي FPGAs على دبابيس 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

لكن ربما لن تعمل بهذه الطريقة. تحتاج إلى استخدام دالة ALTDDIO_OUT الضخمة في Altera لاستخدام عناصر إخراج DDIO بالفعل. في مشروعي ، يتم استخدام مكون المكتبة ALTDDIO_OUT.

قد يبدو كل شيء معقدًا بعض الشيء ، لكنه يعمل.

يمكنك عرض كود المصدر بالكامل مكتوبًا في Verilog HDL هنا على جيثب.

تم تضمين البرامج الثابتة المجمعة لـ FPGA في شريحة EPCS المثبتة على لوحة Mars Rover2rpi. وبالتالي ، عند تطبيق الطاقة على لوحة FPGA ، ستبدأ FPGA من ذاكرة الفلاش وتبدأ.

الآن نحن بحاجة إلى التحدث قليلاً عن تكوين Raspberry نفسه.

أقوم بإجراء تجارب على نظام Raspberry PI OS (32 بت) استنادًا إلى Debian Buster ، الإصدار: أغسطس 2020 ،
تاريخ الإصدار: 2020-08-20، إصدار Kernel: 5.4.

عليك القيام بأمرين:

  • قم بتحرير ملف config.txt ؛
  • إنشاء تكوين خادم X للعمل مع شاشتين.

عند تحرير ملف /boot/config.txt ، تحتاج إلى:

  1. تعطيل استخدام i2c و i2s و spi ؛
  2. تمكين وضع DPI مع تراكب dtoverlay = dpi24 ؛
  3. ضبط وضع الفيديو 1280 × 720 60 هرتز ، 24 بت لكل نقطة لكل نقطة في البوصة ؛
  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