Segundo monitor HDMI para Raspberry Pi3 a través de la interfaz DPI y placa FPGA


En este video se muestra: la placa Raspberry Pi3, a la que está conectada, a través del conector GPIO, la placa FPGA Mars Rover2rpi (Cyclone IV), a la que está conectado un monitor HDMI. Un segundo monitor está conectado a través del conector HDMI estándar de Raspberry Pi3. Todo funciona junto como un sistema con dos monitores.

A continuación, explicaré cómo se implementa esto.

En la popular placa Raspberry Pi3 hay un conector GPIO, a través del cual se pueden conectar diferentes placas de expansión: sensores, LEDs, controladores de motores paso a paso y mucho más. La función concreta de cada salida en el conector depende de la configuración de los puertos. La configuración GPIO ALT2 permite cambiar el conector al modo del interfaz DPI, Display Parallel Interface. Existen placas de expansión para conectar monitores VGA a través de DPI. Sin embargo, en primer lugar, los monitores VGA ya no son tan comunes como los HDMI, y en segundo lugar, la interfaz digital es cada vez mejor que la analógica. Además, el DAC en esas placas de expansión VGA suele estar hecho en forma de cadenas R-2-R y a menudo no supera los 6 bits por color.

En el modo ALT2, los pines del conector GPIO tienen el siguiente valor:

Segundo monitor HDMI para Raspberry Pi3 a través de la interfaz DPI y placa FPGA

He coloreado aquí las salidas RGB del conector en rojo, verde y azul. Otras señales importantes son las señales de sincronización de barrido V-SYNC y H-SYNC, así como CLK. La frecuencia del reloj CLK es la frecuencia con la que se envían los valores de píxeles al conector, depende del modo de video seleccionado.

Para conectar un monitor HDMI digital, es necesario capturar las señales del interfaz DPI y convertirlas en señales HDMI. Esto se puede hacer, por ejemplo, usando alguna placa FPGA. Resulta que la placa Mars Rover2rpi es adecuada para estos fines. A decir verdad, la opción principal de conexión de esta placa a través de un adaptador especial se ve así:

Segundo monitor HDMI para Raspberry Pi3 a través de la interfaz DPI y placa FPGA

Esta placa se utiliza para aumentar el número de puertos GPIO y para conectar un mayor número de dispositivos periféricos a Raspberry. Sin embargo, 4 señales GPIO en dicha conexión se utilizan para señales JTAG, de modo que el programa de Raspberry puede cargar el firmware FPGA en la PLD. Debido a esto, esta conexión estándar no me sirve, ya que faltan 4 señales DPI. Por suerte, las cabeceras adicionales en la placa tienen un pinout compatible con Raspberry. Así que puedo girar la placa 90 grados y todavía conectarla a mi Raspberry:

Segundo monitor HDMI para Raspberry Pi3 a través de la interfaz DPI y placa FPGA

Por supuesto, tendré que usar un programador JTAG externo, pero eso no es un problema.

Sin embargo, hay un pequeño problema. No todas las salidas de FPGA pueden usarse como entradas de frecuencia de reloj. Solo hay algunos pines dedicados que se pueden usar para estos fines. Así que ocurrió que la señal GPIO_0 CLK no llega a la entrada de la FPGA, que podría usarse como entrada de frecuencia de reloj. Así que tuve que conectar un cable a la placa. Estoy conectando GPIO_0 y la señal KEY[1] de la placa:

Segundo monitor HDMI para Raspberry Pi3 a través de la interfaz DPI y placa FPGA

Ahora hablaré un poco sobre el proyecto en FPGA. La principal dificultad al generar señales HDMI son las frecuencias muy altas. Si miramos la disposición del conector HDMI, se puede ver que las señales RGB ahora son señales diferenciales secuenciales:

Segundo monitor HDMI para Raspberry Pi3 a través de la interfaz DPI y placa FPGA

El uso de señales diferenciales permite combatir las interferencias en modo común en la línea de transmisión. En este caso, el código original de ocho bits de cada señal de color se convierte en TMDS de 10 bits (Transmisión minimizada de señales diferenciales). Este es un método de codificación especial para eliminar la componente de corriente continua de la señal y minimizar las transiciones de señales en la línea diferencial. Dado que ahora hay que transmitir 10 bits por cada byte de color a través de la línea de transmisión secuencial, la frecuencia de reloj del serializador debe ser 10 veces mayor que la frecuencia de los píxeles. Si tomamos, por ejemplo, el modo de video 1280x720 a 60Hz, la frecuencia de píxeles para ese modo es de 74,25 MHz. El serializador debe tener 742,5 MHz.

Los FPGA comunes, desafortunadamente, no son capaces de esto. Sin embargo, afortunadamente, en el FPGA hay salidas DDIO incorporadas. Estas son salidas que actúan como serializadores 2-a-1. Es decir, pueden emitir secuencialmente dos bits en el flanco de subida y bajada de la frecuencia de reloj. Por lo tanto, en el proyecto FPGA se puede usar no 740 MHz, sino 370 MHz, pero es necesario activar los elementos de salida DDIO en el FPGA. Esa frecuencia de 370 MHz ya es bastante alcanzable. Desafortunadamente, el modo 1280×720 es el límite. No se puede alcanzar una resolución más alta en nuestro FPGA Cyclone IV instalado en la placa Mars Rover2rpi.

Así que, en el proyecto, la frecuencia de entrada de los píxeles CLK llega al PLL, donde se multiplica por 5. A esta frecuencia, los bytes R, G, B se convierten en pares de bits. Esto lo hace el codificador TMDS. El código fuente en Verilog HDL se ve así:

módulo hdmi(
	entrada wire pixclk,		// 74MHz
	entrada wire clk_TMDS2,	// 370MHz
	entrada wire hsync,
	entrada wire vsync,
	entrada wire activo,
	entrada wire [7:0]rojo,
	entrada wire [7:0]verde,
	entrada wire [7:0]azul,
	salida wire TMDS_bh,
	salida wire TMDS_bl,
	salida wire TMDS_gh,
	salida wire TMDS_gl,
	salida wire TMDS_rh,
	salida wire TMDS_rl
);

wire [9:0] TMDS_rojo, TMDS_verde, TMDS_azul;
TMDS_encoder encode_R(.clk(pixclk), .VD(rojo), .CD({vsync,hsync}), .VDE(activo), .TMDS(TMDS_rojo));
TMDS_encoder encode_G(.clk(pixclk), .VD(verde), .CD({vsync,hsync}), .VDE(activo), .TMDS(TMDS_verde));
TMDS_encoder encode_B(.clk(pixclk), .VD(azul), .CD({vsync,hsync}), .VDE(activo), .TMDS(TMDS_azul));

reg [2:0] TMDS_mod5=0;  // contador del módulo 5
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_azul_l  = {TMDS_azul[9],TMDS_azul[7],TMDS_azul[5],TMDS_azul[3],TMDS_azul[1]};
wire [4:0] TMDS_azul_h  = {TMDS_azul[8],TMDS_azul[6],TMDS_azul[4],TMDS_azul[2],TMDS_azul[0]};
wire [4:0] TMDS_verde_l = {TMDS_verde[9],TMDS_verde[7],TMDS_verde[5],TMDS_verde[3],TMDS_verde[1]};
wire [4:0] TMDS_verde_h = {TMDS_verde[8],TMDS_verde[6],TMDS_verde[4],TMDS_verde[2],TMDS_verde[0]};
wire [4:0] TMDS_rojo_l   = {TMDS_rojo[9],TMDS_rojo[7],TMDS_rojo[5],TMDS_rojo[3],TMDS_rojo[1]};
wire [4:0] TMDS_rojo_h   = {TMDS_rojo[8],TMDS_rojo[6],TMDS_rojo[4],TMDS_rojo[2],TMDS_rojo[0]};

siempre @(posedge clk_TMDS2)
begin
	TMDS_shift_bh <= TMDS_mod5[2] ? TMDS_azul_h  : TMDS_shift_bh  [4:1];
	TMDS_shift_bl <= TMDS_mod5[2] ? TMDS_azul_l  : TMDS_shift_bl  [4:1];
	TMDS_shift_gh <= TMDS_mod5[2] ? TMDS_verde_h : TMDS_shift_gh  [4:1];
	TMDS_shift_gl <= TMDS_mod5[2] ? TMDS_verde_l : TMDS_shift_gl  [4:1];
	TMDS_shift_rh <= TMDS_mod5[2] ? TMDS_rojo_h   : TMDS_shift_rh  [4:1];
	TMDS_shift_rl <= TMDS_mod5[2] ? TMDS_rojo_l   : TMDS_shift_rl  [4:1];
	TMDS_mod5 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);

siempre @(posedge clk) TMDS <= VDE ? TMDS_data : TMDS_code;
siempre @(posedge clk) balance_acc <= VDE ? balance_acc_new : 4'h0;

finalizar módulo

Luego, los pares de salida se envían a la salida DDIO, que emite secuencialmente una señal de un bit en el flanco ascendiente y descendiente.

El propio DDIO podría describirse con este código Verilog:

módulo ddio(
	entrada wire d0,
	entrada wire d1,
	entrada wire clk,
	 salida wire out
	);

reg r_d0;
reg r_d1;
siempre @(posedge clk)
begin
	r_d0 <= d0;
	r_d1 <= d1;
end
asignar out = clk ? r_d0 : r_d1;
finalizar módulo

Sin embargo, es poco probable que esto funcione así. Es necesario utilizar la mega función de Altero ALTDDIO_OUT para realmente activar los elementos de salida DDIO. En mi proyecto, se utiliza precisamente este componente de biblioteca ALTDDIO_OUT.

Puede parecer que todo esto es un poco complicado, pero funciona.

Puedes ver todo el código fuente escrito en Verilog HDL aquí mismo, en github.

El firmware compilado para la FPGA se carga en el chip EPCS instalado en la placa Mars Rover2rpi. Así, al encender la placa FPGA, el FPGA se inicializará desde la memoria flash y comenzará a funcionar.

Ahora es necesario hablar un poco sobre la configuración de la Raspberry.

Estoy experimentando con Raspberry PI OS (32 bits) basado en Debian Buster, Versión: Agosto 2020,
Fecha de lanzamiento: 2020-08-20, Versión del núcleo: 5.4.

Es necesario hacer dos cosas:

  • editar el archivo config.txt;
  • crear una configuración del servidor X para trabajar con dos monitores.

Al editar el archivo /boot/config.txt es necesario:

  1. desactivar el uso de i2c, i2s, spi;
  2. activar el modo DPI usando la superposición dtoverlay=dpi24;
  3. configurar el modo de video 1280×720 60Hz, 24 bits por píxel en DPI;
  4. indicar el número necesario de marcos de búfer 2 (max_framebuffers=2, solo entonces aparecerá el segundo dispositivo /dev/fb1)

El texto completo del archivo config.txt es así.

# 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

Después de esto, es necesario crear un archivo de configuración para el servidor X para utilizar dos monitores en dos búferes de cuadro /dev/fb0 y /dev/fb1:

Mi archivo de configuración /usr/share/x11/xorg.conf.d/60-dualscreen.conf es el siguiente

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

Y, si aún no está instalado, es necesario instalar Xinerama. Entonces, el espacio de trabajo se expandirá completamente en dos monitores, como se muestra arriba en el video de demostración.

Eso es todo. Ahora, los propietarios de Raspberry Pi3 podrán usar dos monitores.

La descripción y el esquema de la placa Marsoched2rpi se pueden ver aquí.

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster