Երկրորդ 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-ն, և երկրորդ, թվային ինտերֆեյսը գնալով ավելի լավն է, քան անալոգայինը: Ավելին, նման VGA ընդլայնման տախտակների DAC-ը սովորաբար պատրաստվում է 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 ազդանշանների համար, որպեսզի Raspberry-ի ծրագիրը կարողանա բեռնել 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-ի (Transition-minimized differential signaling): Սա հատուկ կոդավորման մեթոդ է DC բաղադրիչը ազդանշանից հեռացնելու և դիֆերենցիալ գծում ազդանշանի անջատումը նվազագույնի հասցնելու համար: Քանի որ այժմ 10 բիթ պետք է փոխանցվի սերիական գծով մեկ բայթ գույնի համար, պարզվում է, որ սերիալիզատորի ժամացույցի արագությունը պետք է լինի 10 անգամ ավելի բարձր, քան պիքսելային ժամացույցի արագությունը: Եթե ​​օրինակ վերցնենք վիդեո ռեժիմը 1280x720 60Հց, ապա այս ռեժիմի պիքսելային հաճախականությունը 74,25 ՄՀց է։ Սերիալիզատորը պետք է լինի 742,5 ՄՀց:

Սովորական FPGA-ները, ցավոք, ի վիճակի չեն դրան: Այնուամենայնիվ, բարեբախտաբար մեզ համար, FPGA-ն ունի ներկառուցված DDIO կապիչներ: Սրանք եզրակացություններ են, որոնք արդեն, ասես, 2-ից 1 սերիալիզատորներ են: Այսինքն, նրանք կարող են հաջորդաբար երկու բիթ թողարկել ժամացույցի հաճախականության բարձրացող և իջնող եզրերին: Սա նշանակում է, որ FPGA նախագծում դուք կարող եք օգտագործել ոչ թե 740 ՄՀց, այլ 370 ՄՀց, բայց դուք պետք է օգտագործեք DDIO ելքային տարրեր FPGA-ում: Այժմ 370 ՄՀց-ն արդեն լիովին հասանելի հաճախականություն է։ Ցավոք, 1280x720 ռեժիմը սահմանն է: Ավելի բարձր լուծաչափի հնարավոր չէ հասնել մեր Cyclone IV FPGA-ում, որը տեղադրված է Mars 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

Բայց դա, ամենայն հավանականությամբ, այդպես չի աշխատի: Դուք պետք է օգտագործեք Alter-ի ALTDDIO_OUT մեգաֆունկցիան՝ DDIO ելքային տարրերն իրականում միացնելու համար: Իմ նախագիծն օգտագործում է ALTDDIO_OUT գրադարանի բաղադրիչը:

Այս ամենը կարող է մի փոքր բարդ թվալ, բայց այն աշխատում է:

Դուք կարող եք դիտել Verilog HDL-ում գրված բոլոր սկզբնական կոդը այստեղ github-ում.

FPGA-ի համար կազմված որոնվածը տեղադրվում է Mars Rover2rpi տախտակի վրա տեղադրված EPCS չիպի մեջ: Այսպիսով, երբ 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 ռեժիմը՝ օգտագործելով overlay 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 տախտակի նկարագրությունը և սխեման կարելի է գտնել նայեք այստեղ.

Source: www.habr.com