Twadde HDMI-monitor nei Raspberry Pi3 fia DPI-ynterface en FPGA-board


Dizze fideo lit sjen: in Raspberry Pi3-boerd, dêrmei ferbûn fia de GPIO-ferbining is in FPGA-boerd Mars Rover2rpi (Cyclone IV), wêrmei in HDMI-monitor is ferbûn. De twadde monitor is ferbûn fia de standert HDMI-ferbining fan 'e Raspberry Pi3. Alles wurket gear as in dual monitor systeem.

Folgjende sil ik jo fertelle hoe't dit wurdt ymplementearre.

It populêre Raspberry Pi3-boerd hat in GPIO-ferbining wêrmei jo ferskate útwreidingskaarten kinne ferbine: sensors, LED's, steppermotorbestjoerders en folle mear. De krekte funksje fan elke pin op in ferbining hinget ôf fan 'e havenkonfiguraasje. De GPIO ALT2-konfiguraasje lit jo de ferbining wikselje nei DPI-ynterfacemodus, Display Parallel Interface. D'r binne útwreidingskaarten foar it ferbinen fan VGA-monitors fia DPI. As earste binne VGA-monitors lykwols net mear sa gewoan as HDMI, en twad is de digitale ynterface hieltyd better as de analoge. Boppedat, de DAC op sokke VGA útwreiding board wurdt meastal makke yn 'e foarm fan R-2-R keatlingen en faak net mear as 6 bits per kleur.

Yn ALT2-modus hawwe de GPIO-ferbiningspinnen de folgjende betsjutting:

Twadde HDMI-monitor nei Raspberry Pi3 fia DPI-ynterface en FPGA-board

Hjir haw ik de RGB-pins fan 'e connector respektivelik read, grien en blau kleurd. Oare wichtige sinjalen binne de V-SYNC en H-SYNC sinjalen, lykas CLK. De CLK-klokfrekwinsje is de frekwinsje wêrop pikselwearden wurde útfierd nei de ferbining; it hinget ôf fan 'e selekteare fideomodus.

Om in digitale HDMI-monitor te ferbinen, moatte jo de DPI-sinjalen fan 'e ynterface fange en konvertearje nei HDMI-sinjalen. Dit kin dien wurde, bygelyks, mei help fan in soarte fan FPGA board. As it docht bliken, is it Mars Rover2rpi board geskikt foar dizze doelen. Yn wierheid, de wichtichste opsje foar it ferbinen fan dit boerd fia in spesjale adapter sjocht der sa út:

Twadde HDMI-monitor nei Raspberry Pi3 fia DPI-ynterface en FPGA-board

Dit boerd wurdt brûkt om it oantal GPIO-poarten te fergrutsjen en mear perifeare apparaten te ferbinen mei de framboos. Tagelyk wurde 4 GPIO-sinjalen mei dizze ferbining brûkt foar JTAG-sinjalen, sadat it programma fan Raspberry de FPGA-firmware yn 'e FPGA laden kin. Hjirtroch past dizze standertferbining my net; 4 DPI-sinjalen falle út. Gelokkich hawwe de ekstra kammen op it boerd in Raspberry-kompatibele pinout. Dat ik kin it boerd 90 graden draaie en it noch ferbine mei myn framboos:

Twadde HDMI-monitor nei Raspberry Pi3 fia DPI-ynterface en FPGA-board

Fansels moatte jo in eksterne JTAG-programmeur brûke, mar dit is gjin probleem.

Der is noch in lyts probleem. Net elke FPGA-pin kin brûkt wurde as klokynfier. D'r binne mar in pear tawijde pinnen dy't foar dizze doelen kinne wurde brûkt. Sa die hjir bliken dat it GPIO_0 CLK-sinjaal net de FPGA-ynfier berikt, dy't brûkt wurde kin as in FPGA-klokynfier. Sa moast ik noch ien tried op 'e sjaal sette. Ik ferbine GPIO_0 en it KEY[1]-sinjaal fan it boerd:

Twadde HDMI-monitor nei Raspberry Pi3 fia DPI-ynterface en FPGA-board

No sil ik jo in bytsje fertelle oer it FPGA-projekt. De wichtichste muoite by it generearjen fan HDMI-sinjalen is heul hege frekwinsjes. As jo ​​​​nei de HDMI-ferbining pinout sjogge, kinne jo sjen dat de RGB-sinjalen no seriële differinsjale sinjalen binne:

Twadde HDMI-monitor nei Raspberry Pi3 fia DPI-ynterface en FPGA-board

Mei it brûken fan in differinsjaal sinjaal kinne jo bestride mienskiplike modus ynterferinsje op de oerdracht line. Yn dit gefal wurdt de oarspronklike acht-bit koade fan elk kleur sinjaal omboud ta in 10-bit TMDS (Transition-minimalisearre differinsjaaloperator signaling). Dit is in spesjale kodearingsmetoade om de DC-komponint fan it sinjaal te ferwiderjen en sinjaalwikseling yn in differinsjaalline te minimalisearjen. Sûnt 10 bits no moatte wurde oerdroegen oer de seriële line foar ien byte fan kleur, docht bliken dat de serializer klok snelheid moat wêze 10 kear heger as de piksel klok snelheid. As wy bygelyks de fideomodus nimme 1280x720 60Hz, dan is de pikselfrekwinsje fan dizze modus 74,25 MHz. De serializer moat 742,5 MHz wêze.

Reguliere FPGAs, spitigernôch, binne net by steat fan dit. Lykwols, gelokkich foar ús, hat de FPGA ynboude DDIO-pins. Dit binne konklúzjes dy't al, as it wiene, 2-op-1 serializers binne. Dat is, se kinne twa bits sequentially útfiere op 'e opkommende en fallende rânen fan' e klokfrekwinsje. Dit betsjut dat jo yn in FPGA-projekt net 740 MHz brûke kinne, mar 370 MHz, mar jo moatte DDIO-útfiereleminten brûke yn 'e FPGA. No is 370 MHz al in folslein berikbere frekwinsje. Spitigernôch, 1280x720 modus is de limyt. In hegere resolúsje kin net berikt wurde yn ús Cyclone IV FPGA ynstalleare op it Mars Rover2rpi board.

Dus, yn it ûntwerp giet de ynfierpikselfrekwinsje CLK nei de PLL, wêr't it wurdt fermannichfâldige mei 5. Op dizze frekwinsje wurde de R, G, B bytes omsetten yn bitpearen. Dit is wat de TMDS-encoder docht. De boarnekoade yn Verilog HDL sjocht der sa út:

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

Dan wurde de útfierpearen fiede nei de DDIO-útfier, dy't sequentieel in ien-bit sinjaal produsearret op 'e opkommende en fallende rânen.

DDIO sels koe wurde beskreaun mei de folgjende Verilog-koade:

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

Mar it sil nei alle gedachten net sa wurkje. Jo moatte Alter's megafunksje ALTDDIO_OUT brûke om de DDIO-útfier-eleminten feitlik yn te skeakeljen. Myn projekt brûkt de ALTDDIO_OUT bibleteek komponint.

Dit kin allegear in bytsje lestich útsjen, mar it wurket.

Jo kinne alle boarnekoade besjen skreaun yn Verilog HDL hjir op github.

De kompilearre firmware foar de FPGA wurdt flitsert yn 'e EPCS-chip ynstalleare op it Mars Rover2rpi-boerd. Sa, as macht wurdt tapast oan de FPGA board, de FPGA sil wurde inisjalisearre út flash ûnthâld en begjinne.

No moatte wy in bytsje prate oer de konfiguraasje fan 'e Raspberry sels.

Ik doch eksperiminten op Raspberry PI OS (32 bit) basearre op Debian Buster, Ferzje: augustus 2020,
Releasedatum: 2020-08-20, Kernel ferzje: 5.4.

Jo moatte twa dingen dwaan:

  • bewurkje de config.txt triem;
  • meitsje in X-tsjinner konfiguraasje om te wurkjen mei twa monitors.

By it bewurkjen fan it /boot/config.txt-bestân hawwe jo nedich:

  1. útskeakelje it brûken fan i2c, i2s, spi;
  2. DPI-modus ynskeakelje mei overlay dtoverlay = dpi24;
  3. konfigurearje video modus 1280 × 720 60Hz, 24 bits per piksel op DPI;
  4. spesifisearje it fereaske oantal framebuffers 2 (max_framebuffers=2, allinich dan sil it twadde apparaat /dev/fb1 ferskine)

De folsleine tekst fan it config.txt-bestân sjocht der sa út.

# 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

Hjirnei moatte jo in konfiguraasjetriem meitsje foar de X-tsjinner om twa monitors te brûken op twa framebuffers /dev/fb0 en /dev/fb1:

Myn konfiguraasjetriem /usr/share/x11/xorg.conf.d/60-dualscreen.conf is sa

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

No, as it net al ynstalleare is, dan moatte jo Xinerama ynstallearje. Dan sil de buroblêdromte folslein útwreide wurde nei twa monitors, lykas werjûn yn 'e demo-fideo hjirboppe.

Dat is wierskynlik alles. No sille Raspberry Pi3-eigners twa monitors kinne brûke.

Beskriuwing en circuit diagram fan de Mars Rover2rpi board kin fûn wurde Sjoch hjir.

Boarne: www.habr.com