DPI interfeysi və FPGA lövhəsi vasitəsilə Raspberry Pi3-ə ikinci HDMI monitor


Bu videoda göstərilir: GPIO konnektoru vasitəsilə ona qoşulmuş Raspberry Pi3 lövhəsi, HDMI monitorunun qoşulduğu FPGA lövhəsi Mars Rover2rpi (Cyclone IV) lövhəsidir. İkinci monitor Raspberry Pi3-ün standart HDMI konnektoru vasitəsilə birləşdirilir. Hər şey ikili monitor sistemi kimi birlikdə işləyir.

Bundan sonra bunun necə həyata keçirildiyini sizə xəbər verəcəyəm.

Populyar Raspberry Pi3 lövhəsində müxtəlif genişləndirmə kartlarını birləşdirə biləcəyiniz GPIO konnektoru var: sensorlar, LEDlər, pilləli mühərrik sürücüləri və daha çox. Konnektordakı hər bir pin dəqiq funksiyası port konfiqurasiyasından asılıdır. GPIO ALT2 konfiqurasiyası konnektoru DPI interfeys rejiminə, Paralel İnterfeysi göstərməyə keçməyə imkan verir. DPI vasitəsilə VGA monitorları birləşdirmək üçün genişləndirmə kartları var. Bununla belə, birincisi, VGA monitorları artıq HDMI kimi adi deyil, ikincisi, rəqəmsal interfeys analoqdan getdikcə daha yaxşıdır. Üstəlik, belə VGA genişləndirmə lövhələrindəki DAC adətən R-2-R zəncirləri şəklində hazırlanır və çox vaxt hər rəng üçün 6 bitdən çox deyil.

ALT2 rejimində GPIO konnektor sancaqları aşağıdakı mənaya malikdir:

DPI interfeysi və FPGA lövhəsi vasitəsilə Raspberry Pi3-ə ikinci HDMI monitor

Burada konnektorun RGB sancaqlarını müvafiq olaraq qırmızı, yaşıl və mavi rəngə boyadım. Digər mühüm siqnallar V-SYNC və H-SYNC siqnalları, həmçinin CLK-dır. CLK saat tezliyi, piksel dəyərlərinin konnektora çıxma tezliyidir; seçilmiş video rejimindən asılıdır.

Rəqəmsal HDMI monitorunu qoşmaq üçün interfeysin DPI siqnallarını tutmalı və onları HDMI siqnallarına çevirməlisiniz. Bu, məsələn, bir növ FPGA lövhəsindən istifadə etməklə edilə bilər. Göründüyü kimi, Mars Rover2rpi lövhəsi bu məqsədlər üçün uyğundur. Əslində, bu lövhəni xüsusi bir adapter vasitəsilə birləşdirmək üçün əsas seçim belə görünür:

DPI interfeysi və FPGA lövhəsi vasitəsilə Raspberry Pi3-ə ikinci HDMI monitor

Bu lövhə GPIO portlarının sayını artırmaq və daha çox periferik cihazları moruqa qoşmaq üçün istifadə olunur. Eyni zamanda, bu əlaqə ilə 4 GPIO siqnalı JTAG siqnalları üçün istifadə olunur ki, Raspberry-dən olan proqram FPGA proqram təminatını FPGA-ya yükləyə bilsin. Buna görə bu standart əlaqə mənə uyğun gəlmir; 4 DPI siqnalı çıxır. Xoşbəxtlikdən, lövhədəki əlavə daraqlarda Raspberry-ə uyğun pinout var. Beləliklə, mən lövhəni 90 dərəcə döndərə bilərəm və hələ də onu moruquma bağlaya bilərəm:

DPI interfeysi və FPGA lövhəsi vasitəsilə Raspberry Pi3-ə ikinci HDMI monitor

Əlbəttə ki, siz xarici JTAG proqramçısından istifadə etməli olacaqsınız, lakin bu problem deyil.

Hələ kiçik bir problem var. Hər FPGA pinindən saat girişi kimi istifadə edilə bilməz. Bu məqsədlər üçün istifadə edilə bilən yalnız bir neçə xüsusi sancaqlar var. Beləliklə, burada məlum oldu ki, GPIO_0 CLK siqnalı FPGA saat girişi kimi istifadə edilə bilən FPGA girişinə çatmır. Ona görə də yenə də şərfin üstünə bir məftil çəkməli oldum. Mən GPIO_0 və lövhənin KEY[1] siqnalını birləşdirirəm:

DPI interfeysi və FPGA lövhəsi vasitəsilə Raspberry Pi3-ə ikinci HDMI monitor

İndi sizə FPGA layihəsi haqqında bir az məlumat verəcəyəm. HDMI siqnallarının yaradılmasında əsas çətinlik çox yüksək tezliklərdir. HDMI konnektorunun pin çıxışına baxsanız, RGB siqnallarının indi serial diferensial siqnallar olduğunu görə bilərsiniz:

DPI interfeysi və FPGA lövhəsi vasitəsilə Raspberry Pi3-ə ikinci HDMI monitor

Diferensial siqnalın istifadəsi ötürmə xəttində ümumi rejim müdaxiləsi ilə mübarizə aparmağa imkan verir. Bu halda, hər bir rəng siqnalının orijinal səkkiz bitlik kodu 10 bitlik TMDS-ə (Transition-minimized diferensial signaling) çevrilir. Bu, DC komponentini siqnaldan çıxarmaq və diferensial xəttdə siqnal keçidini minimuma endirmək üçün xüsusi kodlaşdırma üsuludur. İndi bir bayt rəng üçün seriya xətti üzərindən 10 bit ötürülməli olduğundan, serializatorun saat sürətinin piksel saat sürətindən 10 dəfə yüksək olması lazım olduğu ortaya çıxır. Məsələn, 1280x720 60Hz video rejimini götürsək, bu rejimin piksel tezliyi 74,25 MHz-dir. Serializer 742,5 MHz olmalıdır.

Təəssüf ki, müntəzəm FPGA-lar buna qadir deyil. Ancaq xoşbəxtlikdən bizim üçün FPGA-da daxili DDIO pinləri var. Bunlar artıq, sanki, 2-dən 1-ə qədər serializatorlar olan nəticələrdir. Yəni onlar saat tezliyinin yüksələn və enən kənarlarında ardıcıl olaraq iki bit çıxara bilirlər. Bu o deməkdir ki, bir FPGA layihəsində siz 740 MHz deyil, 370 MHz istifadə edə bilərsiniz, lakin FPGA-da DDIO çıxış elementlərindən istifadə etməlisiniz. İndi 370 MHz artıq tamamilə əldə edilə bilən bir tezlikdir. Təəssüf ki, 1280x720 rejimi limitdir. Mars Rover2rpi lövhəsində quraşdırılmış Cyclone IV FPGA-da daha yüksək qətnamə əldə edilə bilməz.

Belə ki, dizaynda CLK giriş piksel tezliyi PLL-ə gedir, burada 5-ə vurulur. Bu tezlikdə R, G, B baytları bit cütlərinə çevrilir. TMDS kodlayıcısı bunu edir. Verilog HDL-də mənbə kodu belə görünür:

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

Sonra çıxış cütləri yüksələn və enən kənarlarda ardıcıl olaraq bir bitlik siqnal istehsal edən DDIO çıxışına verilir.

DDIO-nun özü aşağıdakı Verilog kodu ilə təsvir edilə bilər:

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

Ancaq çox güman ki, bu şəkildə işləməyəcək. DDIO çıxış elementlərini həqiqətən aktivləşdirmək üçün Alter-in meqafunksiyasından ALTDDIO_OUT istifadə etməlisiniz. Layihəm ALTDDIO_OUT kitabxana komponentindən istifadə edir.

Bütün bunlar bir az çətin görünə bilər, amma işləyir.

Verilog HDL-də yazılmış bütün mənbə koduna baxa bilərsiniz burada github-da.

FPGA üçün tərtib edilmiş proqram təminatı Mars Rover2rpi lövhəsində quraşdırılmış EPCS çipinə daxil edilir. Beləliklə, FPGA lövhəsinə enerji verildikdə, FPGA flash yaddaşdan işə salınacaq və işə salınacaq.

İndi Raspberry-nin özünün konfiqurasiyası haqqında bir az danışmaq lazımdır.

Debian Buster əsasında Raspberry PI OS (32 bit) üzərində təcrübələr edirəm, Versiya: Avqust 2020,
Buraxılış tarixi: 2020-08-20, Kernel versiyası: 5.4.

İki şey etməlisiniz:

  • config.txt faylını redaktə edin;
  • iki monitorla işləmək üçün X server konfiqurasiyasını yaradın.

/boot/config.txt faylını redaktə edərkən sizə lazımdır:

  1. i2c, i2s, spi istifadəsini söndürün;
  2. overlay istifadə edərək DPI rejimini aktivləşdirin dtoverlay=dpi24;
  3. video rejimini 1280×720 60Hz, DPI-də piksel başına 24 bit konfiqurasiya edin;
  4. lazımi sayda çərçivə buferini təyin edin 2 (max_framebuffers=2, yalnız bundan sonra ikinci cihaz /dev/fb1 görünəcək)

config.txt faylının tam mətni belə görünür.

# 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

Bundan sonra, iki /dev/fb0 və /dev/fb1 çərçivə buferlərində iki monitordan istifadə etmək üçün X serveri üçün konfiqurasiya faylı yaratmalısınız:

Konfiqurasiya faylım /usr/share/x11/xorg.conf.d/60-dualscreen.conf belədir

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

Yaxşı, əgər o, artıq quraşdırılmayıbsa, onda Xinerama-nı quraşdırmalısınız. Sonra iş masası sahəsi yuxarıdakı demo videoda göstərildiyi kimi tam olaraq iki monitora qədər genişləndiriləcək.

Yəqin ki, hamısı budur. İndi Raspberry Pi3 sahibləri iki monitordan istifadə edə biləcəklər.

Mars Rover2rpi lövhəsinin təsviri və dövrə diaqramı ilə tanış ola bilərsiniz bura bax.

Mənbə: www.habr.com