Este video muestra: la placa Raspberry Pi3, a ella, a través del conector GPIO, está conectada la placa FPGA Mars Rover2rpi (Cyclone IV) a la que está conectado un monitor HDMI. El segundo monitor se conecta a través del conector estándar HDMI Raspberry Pi3. Todo junto funciona como un sistema con dos monitores.
Les diré cómo se hace esto más adelante.
La popular placa Raspberry Pi3 tiene un conector GPIO a través del cual puede conectar diferentes placas de expansión: sensores, LED, controladores de motor paso a paso y mucho más. La función específica de cada pin de un conector depende de la configuración del puerto. La configuración de ALT2 GPIO le permite cambiar el conector al modo de interfaz DPI, Display Parallel Interface. Hay tarjetas 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 HDMI y, en segundo lugar, la interfaz digital es cada vez mejor que la analógica. Además, el DAC en tarjetas de expansión VGA similares generalmente se hace en forma de cadenas R-2-R y, a menudo, no más de 6 bits por color.
En el modo ALT2, los pines GPIO tienen el siguiente significado:
Aquí coloreé los pines RGB del conector en rojo, verde y azul, respectivamente. Otras señales importantes son las señales de sincronización de barrido V-SYNC y H-SYNC, así como CLK. El reloj CLK es la frecuencia a la que se envían los valores de los píxeles al conector, depende del modo de vídeo seleccionado.
Para conectar un monitor HDMI digital, debe capturar las señales DPI de la interfaz y convertirlas en señales HDMI. Esto se puede hacer, por ejemplo, usando algún tipo de placa FPGA. Al final resultó que, la placa Mars rover2rpi es adecuada para estos fines. En realidad, la opción principal para conectar esta placa a través de un adaptador especial se ve así:
Esta placa sirve para aumentar la cantidad de puertos GPIO y para conectar más periféricos a la frambuesa. Al mismo tiempo, 4 señales GPIO con esta conexión se utilizan para señales JTAG, de modo que el programa de la frambuesa pueda cargar el firmware FPGA en la FPGA. Debido a esto, una conexión tan estándar no me conviene, las señales de 4 DPI se pierden. Afortunadamente, los peines adicionales en la placa tienen un pinout compatible con Raspberry. Para poder girar la placa 90 grados y aún así conectarla a mi frambuesa: por
supuesto, tengo que usar un programador JTAG externo, pero esto no es un problema.
Todavía hay un pequeño problema. No todos los pines FPGA se pueden usar como entrada de reloj. Solo hay unos pocos pines dedicados que se pueden usar para este propósito. Entonces resultó aquí que la señal GPIO_0 CLK no va a la entrada FPGA, que se puede usar como entrada de frecuencia de reloj FPGA. Así que de todos modos tuve que tirar un alambre sobre la bufanda. Conecto GPIO_0 y la señal KEY [1] de la placa:
Ahora les cuento un poco sobre el proyecto en la FPGA. La principal dificultad para formar señales HDMI son las frecuencias muy altas. Al observar el pinout del conector HDMI, puede ver que las señales RGB ahora son señales diferenciales en serie:
El uso de una señal diferencial le permite combatir el ruido de 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 un TMDS (señalización diferencial de transición minimizada) de 10 bits. Esta es una técnica de codificación especial para eliminar el componente de CC de la señal y minimizar el cambio de señal en la línea diferencial. Dado que un byte de color ahora necesita transmitir 10 bits a través de la línea de transmisión en serie, resulta que la frecuencia de reloj del serializador debería ser 10 veces mayor que la frecuencia de reloj de los píxeles. Si tomamos, por ejemplo, el modo de video 1280x720 60Hz, entonces la frecuencia de píxeles de este modo es 74.25 MHz. El serializador debe tener 742,5 MHz.
Desafortunadamente, los FPGA convencionales no son capaces de hacer esto. Sin embargo, afortunadamente para nosotros, la FPGA tiene pines DDIO incorporados. Estas son conclusiones que ya son una especie de serializadores 2 a 1. Es decir, pueden generar dos bits consecutivos en los flancos ascendente y descendente de la frecuencia del reloj. Esto significa que en el proyecto FPGA, puede usar no 740 MHz, sino 370 MHz, pero necesita usar los elementos de salida DDIO en la FPGA. Aquí 370 MHz ya es una frecuencia bastante alcanzable. Desafortunadamente, el modo 1280x720 es el límite. No se puede lograr una resolución más alta en nuestro FPGA Cyclone IV instalado en la placa Mars rover 2rpi.
Entonces, en el proyecto, la frecuencia de píxeles de entrada CLK va 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 de Verilog HDL se ve así:
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
Luego, los pares de salida se envían a la salida DDIO, que genera secuencialmente una señal de un bit en los bordes ascendente y descendente.
El propio DDIO podría describirse con dicho 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
Pero probablemente no funcionará de esa manera. Debe usar la megafunción alter ALTDDIO_OUT para usar realmente los elementos DDIO de salida. Es el componente de la biblioteca ALTDDIO_OUT que se utiliza en mi proyecto.
Todo puede parecer un poco complicado, pero funciona.
Puede ver todo el código fuente escrito en Verilog HDL aquí en github .
El firmware FPGA compilado está cosido en un chip EPCS instalado en la placa Mars Rover2rpi. Por lo tanto, cuando se suministra energía a la placa FPGA, la FPGA se inicializará desde la memoria flash y comenzará.
Ahora tenemos que hablar un poco sobre la configuración de la propia Raspberry.
Estoy haciendo experimentos en Raspberry PI OS (32 bits) basados en Debian Buster, versión: agosto de 2020,
Fecha de lanzamiento: 20 de agosto de 2020, versión de Kernel: 5.4.
Hay dos cosas que hacer:
- edite el archivo config.txt;
- Cree una configuración de servidor X para trabajar con dos monitores.
Al editar el archivo /boot/config.txt, debe:
- desactivar el uso de i2c, i2s, spi;
- habilite el modo DPI usando la superposición dtoverlay = dpi24;
- configurar el modo de video 1280x720 60Hz, 24 bits por punto por DPI;
- especifique el número requerido de framebuffers 2 (max_framebuffers = 2, solo entonces aparecerá el segundo dispositivo / dev / fb1)
El texto completo del archivo config.txt se ve 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 eso, necesita crear un archivo de configuración para que el servidor X use dos monitores en dos framebuffers / dev / fb0 y / dev / fb1:
Mi archivo de configuración /usr/share/x11/xorg.conf.d/60-dualscreen.conf es así
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
Bueno, si aún no está instalado, entonces necesita instalar Xinerama. Luego, el espacio del escritorio se expandirá completamente a dos monitores, como se muestra arriba en el video de demostración.
Probablemente eso es todo. Ahora, los propietarios de Raspberry Pi3 podrán utilizar dos monitores.
La descripción y el diagrama de la placa Mars rover2rpi se pueden ver aquí .