DPI интерфейси жана FPGA тактасы аркылуу Raspberry Pi3 үчүн экинчи HDMI монитор


Бул видеодо: Raspberry Pi3 тактасы, ага GPIO туташтыргычы аркылуу туташтырылган FPGA тактасы Mars Rover2rpi (Циклон IV), ага HDMI монитору туташты. Экинчи монитор Raspberry Pi3 стандарттык HDMI туташтыргычы аркылуу туташтырылган. Баары кош монитор системасы сыяктуу чогуу иштейт.

Эми бул кантип ишке ашарын айтып берем.

Популярдуу Raspberry Pi3 тактасында GPIO туташтыргычы бар, ал аркылуу ар кандай кеңейтүү карталарын туташтыра аласыз: сенсорлор, диоддор, тепкич мотор драйверлери жана башкалар. Туташтыргычтагы ар бир пиндин так функциясы порттун конфигурациясынан көз каранды. GPIO ALT2 конфигурациясы туташтыргычты DPI интерфейс режимине которууга мүмкүндүк берет, Параллель интерфейсти көрсөтүү. DPI аркылуу VGA мониторлорун туташтыруу үчүн кеңейтүү карталары бар. Бирок, биринчиден, VGA мониторлору HDMI сыяктуу кеңири таралган эмес, экинчиден, санарип интерфейси аналогдукка караганда барган сайын жакшыраак. Мындан тышкары, мындай VGA кеңейтүү такталарындагы DAC, адатта, R-2-R чынжырчалары түрүндө жасалат жана көбүнчө түс үчүн 6 биттен ашпайт.

ALT2 режиминде, GPIO туташтыргычы төөнөгүчтөр төмөнкү мааниге ээ:

DPI интерфейси жана FPGA тактасы аркылуу Raspberry Pi3 үчүн экинчи HDMI монитор

Бул жерде мен туташтыргычтын RGB пиндерин кызыл, жашыл жана көк түскө боёдум. Башка маанилүү сигналдар V-SYNC жана H-SYNC сигналдары, ошондой эле CLK болуп саналат. CLK саат жыштыгы - бул пикселдик маанилердин туташтыргычка чыгарылган жыштыгы; ал тандалган видео режимине жараша болот.

Санариптик HDMI мониторду туташтыруу үчүн интерфейстин DPI сигналдарын кармап, аларды HDMI сигналдарына айландырышыңыз керек. Муну, мисалы, кандайдыр бир FPGA тактасын колдонуу менен жасоого болот. Көрсө, Mars Rover2rpi тактасы бул максаттарга ылайыктуу. Чынында, бул тактаны атайын адаптер аркылуу туташтыруунун негизги варианты төмөнкүдөй көрүнөт:

DPI интерфейси жана FPGA тактасы аркылуу Raspberry Pi3 үчүн экинчи HDMI монитор

Бул такта GPIO портторунун санын көбөйтүү жана малинага көбүрөөк перифериялык түзүлүштөрдү туташтыруу үчүн колдонулат. Ошол эле учурда, бул байланыш менен 4 GPIO сигналдары JTAG сигналдары үчүн колдонулат, андыктан Raspberry программасы FPGA микропрограммасын FPGAга жүктөй алат. Ушундан улам, бул стандарттуу байланыш мага туура келбейт; 4 DPI сигналдары чыгып кетет. Бактыга жараша, тактадагы кошумча тарактарда Raspberry менен шайкеш pinout бар. Ошентип, мен тактаны 90 градуска буруп, аны малинама туташтыра алам:

DPI интерфейси жана FPGA тактасы аркылуу Raspberry Pi3 үчүн экинчи HDMI монитор

Албетте, сиз тышкы JTAG программисти колдонушуңуз керек болот, бирок бул көйгөй эмес.

Дагы деле кичинекей көйгөй бар. Ар бир FPGA пинди саат киргизүү катары колдонууга болбойт. Бул максаттар үчүн колдонулушу мүмкүн болгон бир нече атайын төөнөгүчтөр бар. Ошентип, бул жерде GPIO_0 CLK сигналы FPGA саат киргизүүсү катары колдонула турган FPGA киришине жетпей турганы белгилүү болду. Ошентип, мен дагы эле жоолукка бир зым салууга туура келди. Мен GPIO_0 менен тактанын KEY[1] сигналын туташтырам:

DPI интерфейси жана FPGA тактасы аркылуу Raspberry Pi3 үчүн экинчи HDMI монитор

Эми мен сизге FPGA долбоору жөнүндө бир аз айтып берем. HDMI сигналдарын жаратуудагы негизги кыйынчылык - бул өтө жогорку жыштыктар. Эгер сиз HDMI туташтыргычынын пинутуна карасаңыз, RGB сигналдары азыр сериялык дифференциалдык сигналдар экенин көрө аласыз:

DPI интерфейси жана FPGA тактасы аркылуу Raspberry Pi3 үчүн экинчи HDMI монитор

Дифференциалдык сигналды колдонуу электр берүү линиясында жалпы режимдин кийлигишүүсү менен күрөшүүгө мүмкүндүк берет. Бул учурда, ар бир түстүү сигналдын баштапкы сегиз биттик коду 10 биттик TMDSке (Transition-minimized differential signaling) айланат. Бул сигналдан DC компонентин алып салуу жана дифференциалдык линияда сигналдын которуштурууну азайтуу үчүн атайын коддоо ыкмасы. Эми бир байт түс үчүн сериялык линия аркылуу 10 бит берилиши керек болгондуктан, сериализатордун саат ылдамдыгы пикселдик саат ылдамдыгынан 10 эсе жогору болушу керек экен. Мисалы, 1280x720 60 Гц видео режимин алсак, анда бул режимдин пикселдик жыштыгы 74,25 МГц. Сериализатор 742,5 МГц болушу керек.

Кадимки FPGAs, тилекке каршы, буга жөндөмдүү эмес. Бирок, бактыга жараша, биз үчүн FPGA орнотулган DDIO пиндери бар. Бул тыянактар, буга чейин эле, 2ден 1ге чейин сериализаторлор. Башкача айтканда, алар саат жыштыгынын көтөрүлүп жаткан жана түшүүчү четтерине эки бит ырааттуу түрдө чыгара алышат. Бул FPGA долбоорунда сиз 740 МГц эмес, 370 МГц колдоно аласыз, бирок FPGAда DDIO чыгаруу элементтерин колдонушуңуз керек дегенди билдирет. Азыр 370 MHz - бул толугу менен жетүүгө мүмкүн болгон жыштык. Тилекке каршы, 1280x720 режими чек болуп саналат. Mars Rover2rpi тактасында орнотулган биздин Cyclone IV FPGAда жогорураак чечимге жетишүү мүмкүн эмес.

Ошентип, дизайнда 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

Бирок, балким, мындай иштебейт. DDIO чыгаруу элементтерин иш жүзүндө иштетүү үчүн Alterдин ALTDDIO_OUT мегафункциясын колдонушуңуз керек. Менин долбоорум ALTDDIO_OUT китепкана компонентин колдонот.

Мунун баары бир аз татаал көрүнүшү мүмкүн, бирок ал иштейт.

Verilog HDLде жазылган бардык баштапкы кодду көрө аласыз бул жерде github.

FPGA үчүн түзүлгөн микропрограмма Mars Rover2rpi тактасында орнотулган EPCS чипине жарк этет. Ошентип, FPGA тактасына кубат берилгенде, FPGA флеш эстутумдан инициализацияланат жана башталат.

Эми биз Raspberry өзүнүн конфигурациясы жөнүндө бир аз сүйлөшүшүбүз керек.

Мен Raspberry PI OS (32 бит) боюнча эксперименттерди жасап жатам, Debian Buster, Version: August 2020,
Чыгарылган күнү: 2020-08-20, ядро ​​​​версиясы: 5.4.

Сиз эки нерсени кылышыңыз керек:

  • config.txt файлын түзөтүү;
  • эки монитор менен иштөө үчүн X сервер конфигурациясын түзүңүз.

/boot/config.txt файлын түзөтүүдө сизге керек:

  1. i2c, i2s, spi колдонууну өчүрүү;
  2. DPI режимин иштетүү dtoverlay=dpi24;
  3. видео режимин конфигурациялоо 1280×720 60Hz, DPI боюнча пикселге 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

Андан кийин, эки /dev/fb0 жана /dev/fb1 фреймбуферлеринде эки мониторду колдонуу үчүн X сервери үчүн конфигурация файлын түзүшүңүз керек:

Менин конфигурация файлым /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