Bigarren HDMI monitorea Raspberry Pi3-ra DPI interfazearen eta FPGA plakaren bidez


Bideo honek erakusten du: Raspberry Pi3 plaka bat, hari GPIO konektorearen bidez konektatuta FPGA plaka bat da Mars Rover2rpi (Cyclone IV), zeinari HDMI monitore bat konektatua dago. Bigarren monitorea Raspberry Pi3-ren HDMI konektore estandarraren bidez konektatuta dago. Dena elkarrekin funtzionatzen du monitore bikoitzeko sistema bat bezala.

Jarraian hau nola gauzatzen den esango dizut.

Raspberry Pi3 plaka ezagunak GPIO konektore bat du eta horren bidez hainbat hedapen-txartel konekta ditzakezu: sentsoreak, LEDak, urratseko motor kontrolatzaileak eta askoz gehiago. Konektore bateko pin bakoitzaren funtzio zehatza atakaren konfigurazioaren araberakoa da. GPIO ALT2 konfigurazioak konektorea DPI interfaze modura aldatzeko aukera ematen du, bistaratu interfaze paraleloa. VGA monitoreak DPI bidez konektatzeko hedapen txartelak daude. Hala ere, lehenik eta behin, VGA monitoreak jada ez dira HDMI bezain ohikoak, eta bigarrenik, interfaze digitala gero eta hobea da analogikoa baino. Gainera, VGA hedapen-plaketako DAC-a normalean R-2-R kateen moduan egiten da eta askotan kolore bakoitzeko 6 bit baino gehiago ez.

ALT2 moduan, GPIO konektorearen pinek esanahi hau dute:

Bigarren HDMI monitorea Raspberry Pi3-ra DPI interfazearen eta FPGA plakaren bidez

Hemen konektorearen RGB pinak margotu ditut gorriz, berdez eta urdinez hurrenez hurren. Beste seinale garrantzitsu batzuk V-SYNC eta H-SYNC seinaleak dira, baita CLK ere. CLK erlojuaren maiztasuna pixelen balioak konektorera ateratzen diren maiztasuna da; hautatutako bideo moduaren araberakoa da.

HDMI monitore digital bat konektatzeko, interfazearen DPI seinaleak harrapatu eta HDMI seinale bihurtu behar dituzu. Hau egin daiteke, adibidez, FPGA plaka motaren bat erabiliz. Ikusten denez, Mars Rover2rpi plaka egokia da helburu horietarako. Egia esan, plaka hau egokitzaile berezi baten bidez konektatzeko aukera nagusia honelakoa da:

Bigarren HDMI monitorea Raspberry Pi3-ra DPI interfazearen eta FPGA plakaren bidez

Plaka hau GPIO ataken kopurua handitzeko eta mugurdira gailu periferiko gehiago konektatzeko erabiltzen da. Aldi berean, konexio honekin 4 GPIO seinale erabiltzen dira JTAG seinaleetarako, Raspberry-ren programak FPGA firmwarea FPGAn kargatu dezan. Horregatik, konexio estandar hau ez zait egokitzen; 4 DPI seinaleak kentzen dira. Zorionez, taulako orrazi gehigarriek Raspberry-rekin bateragarria den pinout bat dute. Beraz, taula 90 gradu biratu dezaket eta oraindik konektatu nire mugurdira:

Bigarren HDMI monitorea Raspberry Pi3-ra DPI interfazearen eta FPGA plakaren bidez

Jakina, kanpoko JTAG programatzaile bat erabili beharko duzu, baina hau ez da arazorik.

Oraindik arazo txiki bat dago. FPGA pin guztiak ezin dira erlojuaren sarrera gisa erabili. Helburu horietarako erabil daitezkeen pin dedikatu batzuk baino ez daude. Beraz, hemen ondorioztatu zen GPIO_0 CLK seinalea ez dela FPGA sarrerara iristen, FPGA erloju sarrera gisa erabil daitekeen. Beraz, oraindik alanbre bat jarri behar izan nuen zapiari. GPIO_0 eta arbelaren KEY[1] seinalea konektatzen ditut:

Bigarren HDMI monitorea Raspberry Pi3-ra DPI interfazearen eta FPGA plakaren bidez

Orain FPGA proiektuari buruz pixka bat kontatuko dizuet. HDMI seinaleak sortzeko zailtasun nagusia maiztasun oso altuak dira. HDMI konektorearen pinout-a ikusten baduzu, ikus dezakezu RGB seinaleak serieko seinale diferentzialak direla:

Bigarren HDMI monitorea Raspberry Pi3-ra DPI interfazearen eta FPGA plakaren bidez

Seinale diferentziala erabiltzeak transmisio-linean modu arrunteko interferentziari aurre egiteko aukera ematen du. Kasu honetan, kolore-seinale bakoitzaren jatorrizko zortzi biteko kodea 10 biteko TMDS (Transition-minimized diferentzial seinaleztapena) bihurtzen da. Kodetze metodo berezi bat da DC osagaia seinaletik kentzeko eta linea diferentzial batean seinale-aldaketa minimizatzeko. Orain 10 bit koloreko byte baterako serie-lerroaren bidez transmititu behar direnez, bihurtzen da serializatzailearen erlojuaren abiadura pixeleko erlojuaren abiadura baino 10 aldiz handiagoa izan behar dela. Adibidez, bideo modua 1280x720 60Hz hartzen badugu, orduan modu honen pixel maiztasuna 74,25 MHz da. Serializatzaileak 742,5 MHz izan behar du.

FPGA arruntak, zoritxarrez, ez dira horretarako gai. Hala ere, zorionez guretzat, FPGAk DDIO pinak ditu. Dagoeneko, nolabait esateko, 2 eta 1 serializatzaileak diren ondorioak dira. Hau da, bi bit atera ditzakete sekuentzialki erlojuaren maiztasunaren goranzko eta beherako ertzetan. Horrek esan nahi du FPGA proiektu batean 740 MHz ez erabil ditzakezula, 370 MHz baizik, baina FPGAn DDIO irteerako elementuak erabili behar dituzula esan nahi du. Orain 370 MHz dagoeneko guztiz lor daitekeen maiztasuna da. Zoritxarrez, 1280x720 modua da muga. Mars Rover2rpi plakan instalatutako gure Cyclone IV FPGAn ezin da bereizmen handiagoa lortu.

Beraz, diseinuan, sarrerako pixel maiztasuna CLK PLLra doa, non 5ez biderkatzen den. Maiztasun horretan, R, G, B byteak bit-pare bihurtzen dira. Hau da TMDS kodetzaileak egiten duena. Verilog HDL-n iturburu-kodea honelakoa da:

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

Ondoren, irteera-bikoteak DDIO irteerara elikatzen dira, eta horrek sekuentzialki bit bateko seinalea sortzen du goranzko eta beherako ertzetan.

DDIO bera deskriba liteke Verilog kode honekin:

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

Baina ziurrenik ez du horrela funtzionatuko. Alter-en ALTDDIO_OUT megafuntzioa erabili behar duzu DDIO irteerako elementuak benetan gaitzeko. Nire proiektuak ALTDDIO_OUT liburutegiko osagaia erabiltzen du.

Horrek guztiak apur bat delikatua dirudi, baina funtzionatzen du.

Verilog HDL-n idatzitako iturburu-kode guztia ikus dezakezu hemen github-en.

FPGArako konpilatutako firmwarea Mars Rover2rpi plakan instalatutako EPCS txiparen barruan sartzen da. Horrela, FPGA plakari boterea aplikatzen zaionean, FPGA flash memoriatik hasieratuko da eta martxan jarriko da.

Orain pixka bat hitz egin behar dugu Raspberry-ren beraren konfigurazioaz.

Esperimentuak egiten ari naiz Raspberry PI OS (32 bit) Debian Buster-en oinarrituta, Bertsioa: 2020ko abuztua,
Argitaratze data: 2020-08-20, Kernelaren bertsioa: 5.4.

Bi gauza egin behar dituzu:

  • editatu config.txt fitxategia;
  • sortu X zerbitzariaren konfigurazio bat bi monitoreekin lan egiteko.

/boot/config.txt fitxategia editatzerakoan behar duzu:

  1. desgaitu i2c, i2s, spi erabilera;
  2. Gaitu DPI modua gainjartzea erabiliz dtoverlay=dpi24;
  3. konfiguratu bideo modua 1280Γ—720 60Hz, 24 bit pixel bakoitzeko DPIn;
  4. zehaztu behar den framebuffer 2 kopurua (max_framebuffers=2, orduan bakarrik agertuko da bigarren gailua /dev/fb1)

Config.txt fitxategiaren testu osoa honelakoa da.

# 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

Horren ondoren, X zerbitzarirako konfigurazio fitxategi bat sortu behar duzu bi monitore bi framebuffer /dev/fb0 eta /dev/fb1 erabiltzeko:

Nire /usr/share/x11/xorg.conf.d/60-dualscreen.conf konfigurazio fitxategia honelakoa da

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

Beno, dagoeneko instalatuta ez badago, Xinerama instalatu behar duzu. Ondoren, mahaigaineko espazioa guztiz zabalduko da bi monitoretara, goiko demo bideoan erakusten den moduan.

Hori da seguruenik dena. Orain, Raspberry Pi3 jabeek bi monitore erabili ahal izango dituzte.

Mars Rover2rpi plakaren deskribapena eta zirkuitu diagrama aurki daitezke begiratu hemen.

Iturria: www.habr.com