จอภาพ HDMI ตัวที่สองไปยัง Raspberry Pi3 ผ่านอินเทอร์เฟซ DPI และบอร์ด FPGA


วิดีโอนี้แสดง: บอร์ด Raspberry Pi3 ซึ่งเชื่อมต่อกับบอร์ด FPGA Mars Rover2rpi (Cyclone IV) ผ่านตัวเชื่อมต่อ GPIO ซึ่งเชื่อมต่อกับจอภาพ 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 คือความถี่ที่ค่าพิกเซลจะถูกส่งออกไปยังตัวเชื่อมต่อและขึ้นอยู่กับโหมดวิดีโอที่เลือก

ในการเชื่อมต่อจอภาพ Digital HDMI คุณต้องจับสัญญาณอินเทอร์เฟซ DPI และแปลงเป็นสัญญาณ HDMI ซึ่งสามารถทำได้ เช่น โดยใช้บอร์ด FPGA เมื่อปรากฎว่าบอร์ด Mars Rover2rpi เหมาะสำหรับจุดประสงค์นี้ ในความเป็นจริงตัวเลือกหลักสำหรับการเชื่อมต่อบอร์ดนี้ผ่านอะแดปเตอร์พิเศษมีลักษณะดังนี้:

จอภาพ HDMI ตัวที่สองไปยัง Raspberry Pi3 ผ่านอินเทอร์เฟซ DPI และบอร์ด FPGA

บอร์ดนี้ใช้เพื่อเพิ่มจำนวนพอร์ต GPIO และเชื่อมต่ออุปกรณ์ต่อพ่วงกับราสเบอร์รี่มากขึ้น ในเวลาเดียวกันสัญญาณ 4 GPIO พร้อมการเชื่อมต่อนี้ใช้สำหรับสัญญาณ JTAG เพื่อให้โปรแกรมจากการแจกจ่ายสามารถโหลดเฟิร์มแวร์ FPGA ลงใน FPGA ด้วยเหตุนี้ การเชื่อมต่อแบบปกติจึงไม่เหมาะกับฉัน สัญญาณ DPI 4 ตัวดร็อปเอาต์ โชคดีที่หวีเสริมบนบอร์ดมี pinout ที่เข้ากันได้กับ 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 บิต (Transition-minimized differential signaling) นี่เป็นวิธีการเข้ารหัสแบบพิเศษเพื่อลบส่วนประกอบ DC ออกจากสัญญาณและลดการสลับสัญญาณในสายดิฟเฟอเรนเชียล เนื่องจากขณะนี้มี 10 บิตในการส่งต่อไบต์ของสีบนสายซีเรียล ปรากฎว่าความถี่สัญญาณนาฬิกาของซีเรียลไลเซอร์ต้องสูงกว่าความถี่สัญญาณนาฬิกาของพิกเซลถึง 10 เท่า ถ้าเรายกตัวอย่างโหมดวิดีโอ 1280x720 60Hz ความถี่พิกเซลของโหมดนี้คือ 74,25MHz Serializer ควรเป็น 742,5 MHz

น่าเสียดายที่ FPGA ทั่วไปไม่สามารถทำเช่นนี้ได้ อย่างไรก็ตาม เพื่อความโชคดีของเรา FPGA มีพิน DDIO ในตัว สิ่งเหล่านี้คือข้อสรุปที่เป็นซีเรียลไลเซอร์แบบ 2 ต่อ 1 อยู่แล้ว นั่นคือสามารถส่งออกสองบิตตามลำดับตามความถี่สัญญาณนาฬิกาที่เพิ่มขึ้นและลดลง ซึ่งหมายความว่าในโครงการ FPGA คุณไม่สามารถใช้ 740 MHz แต่เป็น 370 MHz แต่คุณต้องใช้องค์ประกอบเอาต์พุต DDIO ใน FPGA ที่นี่ 370 MHz เป็นความถี่ที่ทำได้อยู่แล้ว น่าเสียดายที่โหมด 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 ที่นี่บน 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 60Hz, 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 ได้ ดูที่นี่.

ที่มา: will.com