Segundo monitor HDMI para Raspberry Pi3 via interface DPI e placa FPGA



Este vídeo mostra: a placa Raspberry Pi3, a ela, através do conector GPIO, está conectada a placa Mars Rover2rpi FPGA (Cyclone IV) à qual está conectado um monitor HDMI. O segundo monitor é conectado através do conector HDMI Raspberry Pi3 padrão. Tudo junto funciona como um sistema com dois monitores.



Vou lhe dizer como isso é feito posteriormente.



A popular placa Raspberry Pi3 possui um conector GPIO através do qual você pode conectar diferentes placas de expansão: sensores, LEDs, drivers de motor de passo e muito mais. A função específica de cada pino em um conector depende da configuração da porta. A configuração ALT2 GPIO permite que você alterne o conector para o modo de interface DPI, Display Parallel Interface. Existem placas de expansão para conectar monitores VGA via DPI. Porém, em primeiro lugar, os monitores VGA não são mais tão comuns quanto o HDMI e, em segundo lugar, a interface digital está ficando melhor do que a analógica. Além disso, o DAC em placas de expansão VGA semelhantes geralmente é feito na forma de cadeias R-2-R e geralmente não mais do que 6 bits por cor.



No modo ALT2, os pinos GPIO têm o seguinte significado:



imagem



Aqui, pintei os pinos RGB do conector em vermelho, verde e azul, respectivamente. Outros sinais importantes são os sinais de sincronização de varredura V-SYNC e H-SYNC, bem como CLK. O relógio CLK é a frequência com que os valores de pixel são enviados para o conector, depende do modo de vídeo selecionado.



Para conectar um monitor HDMI digital, você precisa capturar sinais DPI e convertê-los em sinais HDMI. Isso pode ser feito, por exemplo, usando algum tipo de placa FPGA. Como se viu, a placa Mars rover2rpi é adequada para esses fins. Na verdade, a principal opção para conectar esta placa através de um adaptador especial é a seguinte:



imagem



Esta placa serve para aumentar o número de portas GPIO e conectar mais periféricos ao raspberry. Ao mesmo tempo, 4 sinais GPIO com esta conexão são usados ​​para sinais JTAG, de modo que o programa do raspberry pode carregar o firmware FPGA no FPGA. Por causa disso, essa conexão padrão não me convém, os sinais de 4 DPI caem. Felizmente, os pentes adicionais na placa têm uma pinagem compatível com o Raspberry. Para que eu possa girar a placa 90 graus e ainda conectá-la ao meu raspberry:







Claro, tenho que usar um programador JTAG externo, mas isso não é um problema.



Ainda há um pequeno problema. Nem todo pino FPGA pode ser usado como entrada de clock. Existem apenas alguns pinos dedicados que podem ser usados ​​para essa finalidade. Então aqui descobriu-se que o sinal GPIO_0 CLK não vai para a entrada do FPGA, que pode ser usada como a entrada de frequência de clock do FPGA. Mesmo assim, tive que jogar um fio no lenço. Eu conecto o GPIO_0 e o sinal KEY [1] da placa:



imagem



Agora vou contar um pouco sobre o projeto no FPGA. A principal dificuldade na formação de sinais HDMI são as frequências muito altas. Se você olhar a pinagem do conector HDMI, verá que os sinais RGB são agora sinais diferenciais seriais:







Usar um sinal diferencial permite combater o ruído de modo comum na linha de transmissão. Nesse caso, o código original de oito bits de cada sinal de cor é convertido em um TMDS de 10 bits (sinalização diferencial minimizada por transição). Esta é uma técnica de codificação especial para remover o componente DC do sinal e minimizar a comutação do sinal na linha diferencial. Como um byte de cor agora precisa transmitir 10 bits pela linha de transmissão serial, verifica-se que a frequência do clock do serializador deve ser 10 vezes maior do que a frequência do clock dos pixels. Se tomarmos, por exemplo, o modo de vídeo 1280x720 60Hz, então a frequência de pixel deste modo é 74,25 MHz. O serializador deve ter 742,5 MHz.



Infelizmente, os FPGAs convencionais não são capazes disso. No entanto, felizmente para nós, o FPGA tem pinos DDIO integrados. Estas são conclusões que já são uma espécie de serializadores 2 para 1. Ou seja, eles podem produzir dois bits consecutivos nas bordas ascendente e descendente da frequência do clock. Isso significa que no projeto FPGA, você não pode usar 740 MHz, mas 370 MHz, mas precisa usar os elementos de saída DDIO no FPGA. Aqui, 370 MHz já é uma frequência bastante atingível. Infelizmente, o modo 1280x720 é o limite. Resolução mais alta em nosso FPGA Cyclone IV instalado na placa Mars rover 2rpi não pode ser alcançada.



Assim, no projeto, a frequência de pixel de entrada CLK vai para o PLL, onde é multiplicada por 5. Nessa frequência, os bytes R, G, B são convertidos em pares de bits. Isso é feito pelo codificador TMDS. O código-fonte para Verilog HDL é parecido com este:



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


Em seguida, os pares de saída são alimentados à saída DDIO, que produz sequencialmente um sinal de um bit na ascensão e queda.



O próprio DDIO pode ser descrito com esse código 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


Mas provavelmente não funcionará dessa forma. Você precisa usar o alter megafunção ALTDDIO_OUT para realmente usar os elementos DDIO de saída. É o componente da biblioteca ALTDDIO_OUT que é usado em meu projeto.



Pode parecer um pouco complicado, mas funciona.



Você pode ver todo o código-fonte escrito em Verilog HDL aqui no github .



O firmware FPGA compilado é costurado em um chip EPCS instalado na placa Mars rover2rpi. Portanto, quando a alimentação é fornecida para a placa FPGA, o FPGA inicializa a partir da memória flash e inicia.



Agora precisamos conversar um pouco sobre a configuração do próprio Raspberry.



Estou fazendo experimentos no Raspberry PI OS (32 bits) com base no Debian Buster, versão: agosto de 2020,

Data de lançamento: 2020-08-20, versão do kernel: 5.4.



Existem duas coisas a fazer:



  • edite o arquivo config.txt;
  • crie uma configuração de servidor X para trabalhar com dois monitores.


Ao editar o arquivo /boot/config.txt, você precisa:



  1. desabilite o uso de i2c, i2s, spi;
  2. habilite o modo DPI usando a sobreposição dtoverlay = dpi24;
  3. definir modo de vídeo 1280x720 60 Hz, 24 bits por ponto por DPI;
  4. especifique o número necessário de framebuffers 2 (max_framebuffers = 2, somente então o segundo dispositivo / dev / fb1 aparecerá)


O texto completo do arquivo config.txt tem esta aparência.
# 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




Depois disso, você precisa criar um arquivo de configuração para o servidor X usar dois monitores em dois framebuffers / dev / fb0 e / dev / fb1:



Meu arquivo de configuração /usr/share/x11/xorg.conf.d/60-dualscreen.conf é assim
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




Bem, se ainda não estiver instalado, você precisa instalar o Xinerama. Em seguida, o espaço da área de trabalho será totalmente expandido para dois monitores, conforme mostrado acima no vídeo de demonstração.



Provavelmente é tudo. Agora, os proprietários do Raspberry Pi3 poderão usar dois monitores.



A descrição e o diagrama da placa Mars rover2rpi podem ser vistos aqui .



All Articles