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



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



سأخبرك كيف يتم ذلك أكثر.



تحتوي لوحة Raspberry Pi3 الشهيرة على موصل GPIO يمكنك من خلاله توصيل لوحات توسع مختلفة: أجهزة استشعار ، ومصابيح LED ، ومحركات محرك متدرج ، وغير ذلك الكثير. تعتمد الوظيفة المحددة لكل دبوس في الموصل على تكوين المنفذ. يسمح لك تكوين ALT2 GPIO بتبديل الموصل إلى وضع واجهة DPI ، واجهة العرض المتوازية. توجد بطاقات توسعة لتوصيل شاشات VGA عبر DPI. ومع ذلك ، أولاً ، لم تعد شاشات VGA شائعة مثل HDMI ، وثانيًا ، أصبحت الواجهة الرقمية أفضل من شاشات التناظرية. علاوة على ذلك ، عادة ما يتم تصنيع DAC على بطاقات توسيع VGA المماثلة في شكل سلاسل R-2-R وغالبًا ما لا يزيد عن 6 بتات لكل لون.



في وضع ALT2 ، تحمل دبابيس GPIO المعنى التالي:



صورة



هنا قمت بتلوين دبابيس RGB للموصل باللون الأحمر والأخضر والأزرق على التوالي. الإشارات المهمة الأخرى هي إشارات المزامنة V-SYNC و H-SYNC ، بالإضافة إلى CLK. ساعة CLK هي التردد الذي يتم فيه إخراج قيم البكسل إلى الموصل ، وهي تعتمد على وضع الفيديو المحدد.



لتوصيل شاشة HDMI رقمية ، تحتاج إلى التقاط إشارات DPI وتحويلها إلى إشارات HDMI. يمكن القيام بذلك ، على سبيل المثال ، باستخدام نوع من لوحة FPGA. كما اتضح ، فإن لوح Mars rover2rpi مناسب لهذه الأغراض. في الحقيقة ، يبدو الخيار الرئيسي لتوصيل هذه اللوحة من خلال محول خاص كما يلي:



صورة



تعمل هذه اللوحة على زيادة عدد منافذ GPIO وتوصيل المزيد من الأجهزة الطرفية بتوت العليق. في نفس الوقت ، يتم استخدام 4 إشارات GPIO مع هذا الاتصال لإشارات JTAG ، بحيث يمكن للبرنامج من التوت تحميل البرامج الثابتة FPGA في FPGA. لهذا السبب ، مثل هذا الاتصال القياسي لا يناسبني ، تسقط 4 إشارات DPI. لحسن الحظ ، تحتوي الأمشاط الإضافية الموجودة على السبورة على دبوس متوافق مع Raspberry. حتى أتمكن من قلب السبورة 90 درجة مع الاستمرار في توصيلها بتوت العليق الخاص بي:







بالطبع ، لا بد لي من استخدام مبرمج JTAG خارجي ، لكن هذه ليست مشكلة.



لا تزال هناك مشكلة صغيرة. لا يمكن استخدام كل دبوس FPGA كمدخل على مدار الساعة. لا يوجد سوى عدد قليل من الدبابيس المخصصة التي يمكن استخدامها لهذا الغرض. لذا اتضح هنا أن إشارة GPIO_0 CLK لا تذهب إلى إدخال FPGA ، والذي يمكن استخدامه كمدخل تردد ساعة FPGA. لذلك كان علي أن ألقي سلكًا واحدًا على الوشاح. أقوم بتوصيل GPIO_0 وإشارة KEY [1] للوحة:



صورة



الآن سأخبرك قليلاً عن المشروع في FPGA. الصعوبة الرئيسية في تشكيل إشارات HDMI هي الترددات العالية جدًا. إذا نظرت إلى طرف موصل HDMI ، يمكنك أن ترى أن إشارات RGB هي الآن إشارات تفاضلية تسلسلية:







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



لسوء الحظ ، فإن FPGAs التقليدية ليست قادرة على ذلك. ومع ذلك ، لحسن الحظ بالنسبة لنا ، فإن FPGA لديها دبابيس DDIO مدمجة. هذه استنتاجات هي بالفعل نوع من المسلسلات 2 إلى 1. أي أنها يمكن أن تنتج بتتين متتاليتين على الحواف الصاعدة والمنخفضة لتردد الساعة. هذا يعني أنه في مشروع FPGA ، لا يمكنك استخدام 740 ميجاهرتز ، ولكن 370 ميجاهرتز ، ولكنك تحتاج إلى استخدام عناصر إخراج DDIO في FPGA. هنا تردد 370 ميغاهيرتز يمكن بلوغه بالفعل. لسوء الحظ ، فإن وضع 1280 × 720 هو الحد الأقصى. لا يمكن تحقيق دقة أعلى في FPGA Cyclone IV المثبت على لوحة Mars Rover 2rpi.



لذلك ، في المشروع ، يذهب تردد بكسل الإدخال 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 لاستخدام عناصر 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.2



هناك شيئان للقيام بهما:



  • قم بتحرير ملف 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 هنا .



All Articles