Pangalawang HDMI monitor sa Raspberry Pi3 sa pamamagitan ng DPI interface at FPGA board


Ipinapakita ng video na ito ang: isang Raspberry Pi3 board, na konektado dito sa pamamagitan ng GPIO connector ay isang FPGA board Mars Rover2rpi (Cyclone IV), kung saan nakakonekta ang isang HDMI monitor. Ang pangalawang monitor ay konektado sa pamamagitan ng karaniwang HDMI connector ng Raspberry Pi3. Ang lahat ay gumagana nang magkasama tulad ng isang dual monitor system.

Susunod, sasabihin ko sa iyo kung paano ito ipinatupad.

Ang sikat na Raspberry Pi3 board ay may GPIO connector kung saan maaari mong ikonekta ang iba't ibang expansion card: mga sensor, LED, stepper motor driver at marami pang iba. Ang eksaktong function ng bawat pin sa isang connector ay depende sa configuration ng port. Ang configuration ng GPIO ALT2 ay nagbibigay-daan sa iyo na ilipat ang connector sa DPI interface mode, Display Parallel Interface. May mga expansion card para sa pagkonekta ng mga VGA monitor sa pamamagitan ng DPI. Gayunpaman, una, ang mga monitor ng VGA ay hindi na karaniwan sa HDMI, at pangalawa, ang digital na interface ay lalong mas mahusay kaysa sa analogue. Bukod dito, ang DAC sa naturang mga VGA expansion board ay karaniwang ginagawa sa anyo ng mga R-2-R chain at madalas na hindi hihigit sa 6 bits bawat kulay.

Sa ALT2 mode, ang GPIO connector pin ay may sumusunod na kahulugan:

Pangalawang HDMI monitor sa Raspberry Pi3 sa pamamagitan ng DPI interface at FPGA board

Dito ko nilagyan ng kulay ang mga RGB pin ng connector na pula, berde at asul ayon sa pagkakabanggit. Ang iba pang mahahalagang signal ay ang V-SYNC at H-SYNC signal, pati na rin ang CLK. Ang dalas ng orasan ng CLK ay ang dalas kung saan ang mga halaga ng pixel ay na-output sa connector; depende ito sa napiling video mode.

Upang ikonekta ang isang digital HDMI monitor, kailangan mong makuha ang mga DPI signal ng interface at i-convert ang mga ito sa HDMI signal. Magagawa ito, halimbawa, gamit ang ilang uri ng FPGA board. Sa lumalabas, ang Mars Rover2rpi board ay angkop para sa mga layuning ito. Sa katotohanan, ang pangunahing opsyon para sa pagkonekta sa board na ito sa pamamagitan ng isang espesyal na adaptor ay ganito ang hitsura:

Pangalawang HDMI monitor sa Raspberry Pi3 sa pamamagitan ng DPI interface at FPGA board

Ginagamit ang board na ito upang madagdagan ang bilang ng mga GPIO port at para ikonekta ang higit pang mga peripheral device sa raspberry. Kasabay nito, 4 na signal ng GPIO na may ganitong koneksyon ang ginagamit para sa mga signal ng JTAG, upang mai-load ng program mula sa Raspberry ang firmware ng FPGA sa FPGA. Dahil dito, ang karaniwang koneksyon na ito ay hindi nababagay sa akin; 4 na DPI signal ang bumababa. Sa kabutihang palad, ang mga karagdagang suklay sa board ay may Raspberry-compatible na pinout. Kaya maaari kong paikutin ang board ng 90 degrees at ikonekta pa rin ito sa aking raspberry:

Pangalawang HDMI monitor sa Raspberry Pi3 sa pamamagitan ng DPI interface at FPGA board

Siyempre, kakailanganin mong gumamit ng panlabas na JTAG programmer, ngunit hindi ito problema.

May konting problema pa. Hindi lahat ng FPGA pin ay maaaring gamitin bilang input ng orasan. Mayroon lamang ilang nakatutok na pin na maaaring gamitin para sa mga layuning ito. Kaya lumabas dito na ang signal ng GPIO_0 CLK ay hindi umabot sa input ng FPGA, na maaaring magamit bilang isang input ng orasan ng FPGA. Kaya kinailangan ko pang maglagay ng isang wire sa scarf. Ikinonekta ko ang GPIO_0 at ang signal ng KEY[1] ng board:

Pangalawang HDMI monitor sa Raspberry Pi3 sa pamamagitan ng DPI interface at FPGA board

Ngayon sasabihin ko sa iyo ng kaunti ang tungkol sa proyekto ng FPGA. Ang pangunahing kahirapan sa pagbuo ng mga signal ng HDMI ay napakataas na frequency. Kung titingnan mo ang HDMI connector pinout, makikita mo na ang mga RGB signal ay serial differential signal na ngayon:

Pangalawang HDMI monitor sa Raspberry Pi3 sa pamamagitan ng DPI interface at FPGA board

Ang paggamit ng isang differential signal ay nagbibigay-daan sa iyo upang labanan ang karaniwang mode interference sa linya ng paghahatid. Sa kasong ito, ang orihinal na walong-bit na code ng bawat signal ng kulay ay na-convert sa isang 10-bit na TMDS (Transition-minimized differential signaling). Ito ay isang espesyal na paraan ng coding upang alisin ang bahagi ng DC mula sa signal at mabawasan ang paglipat ng signal sa isang linya ng kaugalian. Dahil ang 10 bits ay kailangan na ngayong maipadala sa serial line para sa isang byte ng kulay, lumalabas na ang serializer clock speed ay dapat na 10 beses na mas mataas kaysa sa pixel clock speed. Kung kukunin natin halimbawa ang video mode 1280x720 60Hz, kung gayon ang dalas ng pixel ng mode na ito ay 74,25 MHz. Ang serializer ay dapat na 742,5 MHz.

Ang mga regular na FPGA, sa kasamaang-palad, ay hindi kaya nito. Gayunpaman, sa kabutihang palad para sa amin, ang FPGA ay may built-in na DDIO pin. Ito ay mga konklusyon na, kumbaga, 2-to-1 na mga serializer. Iyon ay, maaari silang mag-output ng dalawang bit nang sunud-sunod sa pagtaas at pagbaba ng mga gilid ng dalas ng orasan. Nangangahulugan ito na sa isang proyekto ng FPGA maaari mong gamitin ang hindi 740 MHz, ngunit 370 MHz, ngunit kailangan mong gumamit ng mga elemento ng output ng DDIO sa FPGA. Ngayon ang 370 MHz ay ​​isa nang ganap na makakamit na dalas. Sa kasamaang palad, 1280x720 mode ang limitasyon. Hindi makakamit ang mas mataas na resolution sa aming Cyclone IV FPGA na naka-install sa Mars Rover2rpi board.

Kaya, sa disenyo, ang dalas ng input ng pixel na CLK ay napupunta sa PLL, kung saan ito ay pinarami ng 5. Sa dalas na ito, ang R, G, B byte ay na-convert sa mga pares ng bit. Ito ang ginagawa ng TMDS encoder. Ang source code sa Verilog HDL ay ganito ang hitsura:

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

Pagkatapos ang mga pares ng output ay ipapakain sa output ng DDIO, na sunud-sunod na gumagawa ng isang-bit na signal sa tumataas at bumabagsak na mga gilid.

Ang DDIO mismo ay maaaring ilarawan sa sumusunod na Verilog code:

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

Ngunit malamang na hindi ito gagana sa ganoong paraan. Kailangan mong gamitin ang megafunction ng Alter na ALTDDIO_OUT upang aktwal na paganahin ang mga elemento ng output ng DDIO. Ginagamit ng aking proyekto ang bahagi ng library ng ALTDIO_OUT.

Ang lahat ng ito ay maaaring mukhang medyo nakakalito, ngunit ito ay gumagana.

Maaari mong tingnan ang lahat ng source code na nakasulat sa Verilog HDL dito sa github.

Ang pinagsama-samang firmware para sa FPGA ay na-flash sa EPCS chip na naka-install sa Mars Rover2rpi board. Kaya, kapag ang kapangyarihan ay inilapat sa FPGA board, ang FPGA ay masisimulan mula sa flash memory at magsisimula.

Ngayon kailangan nating pag-usapan nang kaunti tungkol sa pagsasaayos ng Raspberry mismo.

Gumagawa ako ng mga eksperimento sa Raspberry PI OS (32 bit) batay sa Debian Buster, Bersyon: Agosto 2020,
Petsa ng paglabas:2020-08-20, bersyon ng kernel:5.4.

Kailangan mong gawin ang dalawang bagay:

  • i-edit ang config.txt file;
  • lumikha ng X server configuration upang gumana sa dalawang monitor.

Kapag nag-e-edit ng /boot/config.txt file kailangan mo:

  1. huwag paganahin ang paggamit ng i2c, i2s, spi;
  2. paganahin ang DPI mode gamit ang overlay dtoverlay=dpi24;
  3. i-configure ang video mode 1280 Γ— 720 60Hz, 24 bits bawat pixel sa DPI;
  4. tukuyin ang kinakailangang bilang ng mga framebuffer 2 (max_framebuffers=2, saka lang lalabas ang pangalawang device /dev/fb1)

Ang buong text ng config.txt file ay ganito ang hitsura.

# 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

Pagkatapos nito, kailangan mong lumikha ng configuration file para sa X server na gumamit ng dalawang monitor sa dalawang framebuffers /dev/fb0 at /dev/fb1:

Ang aking configuration file /usr/share/x11/xorg.conf.d/60-dualscreen.conf ay ganito

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

Well, kung hindi pa ito naka-install, kailangan mong i-install ang Xinerama. Pagkatapos ay ganap na palalawakin ang espasyo sa desktop sa dalawang monitor, tulad ng ipinapakita sa demo na video sa itaas.

Malamang yun lang. Ngayon, ang mga may-ari ng Raspberry Pi3 ay makakagamit na ng dalawang monitor.

Ang paglalarawan at circuit diagram ng Mars Rover2rpi board ay matatagpuan tumingin dito.

Pinagmulan: www.habr.com