Objetivo y caso de uso
Qué construirás: Un sistema de seguimiento de objetos en tiempo real utilizando la placa Digilent Zybo Z7-20 y la cámara Pcam 5C IMX219.
Para qué sirve
- Monitoreo de líneas de producción mediante seguimiento de objetos para optimizar la logística.
- Aplicaciones de robótica móvil que requieren detección y seguimiento de objetos en entornos dinámicos.
- Desarrollo de sistemas de seguridad que utilizan seguimiento de personas o vehículos en tiempo real.
- Interacción en tiempo real en instalaciones artísticas o museos, donde los objetos son rastreados para mejorar la experiencia del visitante.
Resultado esperado
- Latencia de procesamiento de imagen inferior a 50 ms para detección y seguimiento.
- Capacidad de seguimiento de hasta 10 objetos simultáneamente con precisión superior al 90%.
- Transmisión de datos a través de HDMI con una tasa de 30 fps.
- Uso de MQTT para la comunicación de estado y métricas de seguimiento en tiempo real.
Público objetivo: Ingenieros y desarrolladores avanzados; Nivel: Avanzado
Arquitectura/flujo: Flujo de datos desde la cámara Pcam 5C a través de MIPI-CSI2 hacia el procesador ARM Cortex-A9, con procesamiento en FPGA para detección y seguimiento.
Nivel: Avanzado
Prerrequisitos
- Sistema operativo de desarrollo:
- Ubuntu 22.04.3 LTS (recomendado; también válido en 20.04.x con ajustes menores).
- Toolchain exacta:
- Xilinx Vivado WebPACK 2023.2 (sintetizado/implementación para Zynq-7000, diseño por lotes desde CLI).
- Xilinx Vitis 2023.2 (creación de aplicación bare-metal para el ARM Cortex‑A9 del Zynq PS).
- Xilinx Hardware Server 2023.2 (incluido en Vivado; para programar por JTAG).
- Utilidades:
- Git 2.34+.
- Minicom o screen para consola serie (115200 8N1).
- Python 3.10+ (opcional para scripts auxiliares).
- Requisitos de host:
- 40 GB libres en disco, 16 GB de RAM (la implementación de vídeo con IPs y Zynq es intensiva).
- Variables de entorno recomendadas:
- export XILINX_VIVADO=/opt/Xilinx/Vivado/2023.2
- export XILINX_VITIS=/opt/Xilinx/Vitis/2023.2
- export PATH=$XILINX_VIVADO/bin:$XILINX_VITIS/bin:$PATH
Notas importantes:
– Este flujo utiliza únicamente Vivado WebPACK 2023.2 (licencia gratuita) y Vitis 2023.2. El diseño se apoya en IPs de vídeo estándar (AXI4-Stream) y un pipeline Verilog propio para detección/seguimiento con overlay. Para la recepción MIPI-CSI2 desde la Pcam 5C empleamos el IP “MIPI CSI‑2 Rx Subsystem” y el PHY D‑PHY (IPs de Xilinx). Si tu instalación no cuenta con licencias de producción, puedes sintetizar en modo evaluación para fines educativos.
Materiales
- 1× Digilent Zybo Z7-20 (Zynq-7020).
- 1× Digilent Pcam 5C (sensor Sony IMX219; interfaz MIPI CSI‑2).
- 1× Cable plano MIPI para Pcam 5C (incluido con la cámara).
- 1× Monitor HDMI (o capturadora) y cable HDMI estándar.
- 1× Fuente de alimentación 5V/2A para Zybo Z7-20 (fiable; evita alimentar sólo por USB).
- 1× Cable Micro‑USB para JTAG/UART.
- Tarjeta microSD (opcional; no se requiere para este bare‑metal).
- PC con Ubuntu 22.04.3 LTS y Vivado/Vitis 2023.2.
Preparación y conexión
- Colocación física:
- Conecta la Pcam 5C al conector MIPI‑CSI2 de la Zybo Z7‑20 (marcado “PCAM” en la serigrafía). Asegura la orientación correcta del cable plano y la cerradura del conector.
- Conecta el cable HDMI desde el puerto HDMI OUT (Tx) de la Zybo al monitor.
-
Conecta Micro‑USB a la Zybo (para JTAG y UART) y la fuente 5V.
-
Conmutadores (switches) de arranque:
-
Configura el modo JTAG para programar desde Vivado (SW4 típico: JTAG). Consulta la serigrafía de la Zybo Z7‑20; para este caso práctico no usamos arranque desde microSD.
-
Interfaz y pines relevantes (a alto nivel):
- La Zybo Z7‑20 expone:
- Interfaz MIPI‑CSI2 al conector PCAM: 2 lanes + clock lane (diferenciales).
- HDMI Tx mediante ADV7511 (requiere I2C para configuración y entrada de vídeo paralelo y sincronismos).
- I2C para PCAM (IMX219) y para ADV7511 mapeados a pines del PL (usaremos IP AXI IIC).
- Usaremos Board Files de Digilent para Zybo Z7‑20 en Vivado, lo que facilita el mapeo de interfaces sin detallar cada LOC manualmente.
Tabla de conexiones lógicas (vía IP y board files):
| Bloque/Interfaz | IP/Señales principales | Conector físico en Zybo Z7‑20 | Notas |
|---|---|---|---|
| Pcam 5C (IMX219) MIPI‑CSI2 | MIPI D‑PHY Rx + CSI2 Rx Subsystem | PCAM (MIPI‑CSI2) | 2 datos + 1 reloj |
| I2C cámara (IMX219) | AXI IIC (SCL/SDA) | PCAM I2C | Dirección 7‑bit típica 0x10 |
| Pipeline de vídeo (PL) | AXI4‑Stream (TVALID,TREADY,TUSER,TLAST,TDATA) | Interno (PL) | RAW10 → Gray → Tracking → RGB888 |
| ADV7511 (HDMI Tx) | AXI4‑Stream to Video Out + VTC + AXI IIC | HDMI OUT | I2C 7‑bit típica 0x39 |
| Control umbral tracker | AXI GPIO (salida de 8‑bits) | Interno (PL) | Umbral dinámico desde PS |
| Zynq PS (ARM) | MIO/UART, JTAG, AXI GP | Micro‑USB (UART/JTAG) | App bare‑metal en Vitis |
Código completo (PL Verilog y PS C)
A continuación, incluimos módulos Verilog clave del pipeline y la aplicación C bare‑metal para el PS que configura IMX219 y ADV7511 vía I2C y ajusta el umbral del “object tracking”.
- Formato de vídeo intermedio:
- MIPI CSI‑2 Rx Subsystem entrega un AXI4‑Stream de vídeo con payload de RAW10 “unpacked” como 16 bits por píxel (bits [9:0] válidos).
- Convertimos a escala de grises de 8 bits (tomando bits [9:2]).
- El bloque de tracking calcula el centroide de la máscara binaria y superpone un rectángulo rojo sobre el stream RGB888.
- AXI4‑Stream to Video Out (Xilinx) expulsa señales para ADV7511.
Verilog: conversión RAW10→Gray y tracking con overlay (AXI4‑Stream)
Guarda el siguiente código como src/video_chain.v (incluye dos módulos: axis_raw10_to_gray8 y axis_gray8_tracker_overlay):
// axis_raw10_to_gray8: convierte TDATA RAW10 (16-bit, [9:0] válidos) a Gray8 y reempaqueta a RGB888 (gray replicado)
module axis_raw10_to_gray8 #(
parameter integer TDATA_IN_WIDTH = 16,
parameter integer TDATA_OUT_WIDTH = 24
)(
input wire aclk,
input wire aresetn,
// AXI4-Stream in (RAW10 unpacked: 1 pix/beat, 16-bit)
input wire [TDATA_IN_WIDTH-1:0] s_axis_tdata,
input wire s_axis_tvalid,
output wire s_axis_tready,
input wire s_axis_tuser, // SOF
input wire s_axis_tlast, // EOL
// AXI4-Stream out (RGB888 with gray replicated)
output reg [TDATA_OUT_WIDTH-1:0] m_axis_tdata,
output reg m_axis_tvalid,
input wire m_axis_tready,
output reg m_axis_tuser, // SOF
output reg m_axis_tlast // EOL
);
assign s_axis_tready = m_axis_tready;
wire [7:0] gray8 = s_axis_tdata[9:2]; // RAW10 -> recortamos a 8 bits
always @(posedge aclk) begin
if (!aresetn) begin
m_axis_tvalid <= 1'b0;
m_axis_tdata <= {TDATA_OUT_WIDTH{1'b0}};
m_axis_tuser <= 1'b0;
m_axis_tlast <= 1'b0;
end else begin
m_axis_tvalid <= s_axis_tvalid & s_axis_tready;
m_axis_tdata <= {gray8, gray8, gray8}; // RGB888 (R=G=B=gray)
m_axis_tuser <= s_axis_tuser;
m_axis_tlast <= s_axis_tlast;
end
end
endmodule
// axis_gray8_tracker_overlay:
// - Entrada: RGB888 con gray replicado (R=G=B=Y).
// - Máscara binaria: Y >= threshold.
// - Acumula centroides (Sx,Sy,Count) de la máscara por frame.
// - Superpone rectángulo rojo centrado en el centroide anterior.
// - El tamaño del rectángulo se fija por parámetros y se puede ajustar con registros simples si se desea.
module axis_gray8_tracker_overlay #(
parameter integer IMG_WIDTH = 1280,
parameter integer IMG_HEIGHT = 720,
parameter integer RECT_HALF_W = 30,
parameter integer RECT_HALF_H = 30
)(
input wire aclk,
input wire aresetn,
// Umbral desde AXI GPIO (8-bit)
input wire [7:0] threshold,
// AXI4-Stream in (RGB888; gray=R=G=B)
input wire [23:0] s_axis_tdata,
input wire s_axis_tvalid,
output wire s_axis_tready,
input wire s_axis_tuser,
input wire s_axis_tlast,
// AXI4-Stream out (RGB888 con overlay)
output reg [23:0] m_axis_tdata,
output reg m_axis_tvalid,
input wire m_axis_tready,
output reg m_axis_tuser,
output reg m_axis_tlast
);
assign s_axis_tready = m_axis_tready;
// Contadores de posición de píxel
reg [15:0] x;
reg [15:0] y;
// Acumuladores de centroide del frame actual
reg [31:0] sum_x;
reg [31:0] sum_y;
reg [31:0] cnt;
// Centroide latcheado del frame anterior (para overlay en el frame actual)
reg [15:0] cx_prev;
reg [15:0] cy_prev;
wire [7:0] ypix = s_axis_tdata[23:16]; // R=G=B=Y
wire mask_on = (ypix >= threshold);
// División entera sencilla para centroides al final del frame (en el SOF siguiente)
// Se actualiza al comienzo de cada frame, usando los acumuladores del frame terminado.
reg update_centroid;
always @(posedge aclk) begin
if (!aresetn) begin
x <= 0; y <= 0;
sum_x <= 0; sum_y <= 0; cnt <= 0;
cx_prev <= IMG_WIDTH/2; cy_prev <= IMG_HEIGHT/2;
update_centroid <= 1'b0;
m_axis_tvalid <= 1'b0;
m_axis_tdata <= 24'h000000;
m_axis_tuser <= 1'b0;
m_axis_tlast <= 1'b0;
end else begin
update_centroid <= 1'b0;
// Handshake
m_axis_tvalid <= s_axis_tvalid & s_axis_tready;
m_axis_tuser <= s_axis_tuser;
m_axis_tlast <= s_axis_tlast;
// Posición de pixel
if (s_axis_tvalid & s_axis_tready) begin
// Acumular para centroide
if (mask_on) begin
sum_x <= sum_x + x;
sum_y <= sum_y + y;
cnt <= cnt + 1;
end
// Cálculo de overlay (rectángulo rojo con líneas de 1 px)
// Base: pixel original (grayscale en RGB)
reg [23:0] pix_out;
pix_out = s_axis_tdata;
// Coordenadas del rectángulo (centro=previo)
integer xmin, xmax, ymin, ymax;
xmin = (cx_prev > RECT_HALF_W) ? (cx_prev - RECT_HALF_W) : 0;
xmax = cx_prev + RECT_HALF_W;
ymin = (cy_prev > RECT_HALF_H) ? (cy_prev - RECT_HALF_H) : 0;
ymax = cy_prev + RECT_HALF_H;
// Clampeo
if (xmax > IMG_WIDTH-1) xmax = IMG_WIDTH-1;
if (ymax > IMG_HEIGHT-1) ymax = IMG_HEIGHT-1;
// Dibujar borde rojo
if ((x >= xmin && x <= xmax && (y == ymin || y == ymax)) ||
(y >= ymin && y <= ymax && (x == xmin || x == xmax))) begin
pix_out = 24'hFF0000; // rojo
end
m_axis_tdata <= pix_out;
// Avance de coordenadas
if (s_axis_tlast) begin
x <= 0;
if (y == IMG_HEIGHT-1) begin
y <= 0;
end else begin
y <= y + 1;
end
end else begin
x <= x + 1;
end
end
// Detectar SOF: reset de acumuladores; latch del centroide del frame anterior
if (s_axis_tvalid & s_axis_tready & s_axis_tuser) begin
// Latch del centroide del frame anterior
if (cnt != 0) begin
cx_prev <= sum_x / cnt;
cy_prev <= sum_y / cnt;
end
// Reset acumuladores para nuevo frame
sum_x <= 0;
sum_y <= 0;
cnt <= 0;
x <= 0;
y <= 0;
end
end
end
endmodule
Explicación (breve):
– axis_raw10_to_gray8: toma el RAW10 “unpacked” (16 bits con 10 válidos) desde el MIPI CSI‑2 RX, lo recorta a 8 bits y replica el canal Y en RGB para compatibilidad con el resto del pipeline.
– axis_gray8_tracker_overlay: calcula el centroide de la mayor masa binaria basada en un umbral de luminancia. Superpone un rectángulo centrado en el centroide del frame anterior. El umbral se controla desde el PS a través de un AXI GPIO de 8 bits.
C bare‑metal: configuración I2C del IMX219 y ADV7511 y control del umbral
Guarda el siguiente código como sw/main.c. Usa Vitis 2023.2 (bsp standalone) con el driver XIic. Incluye dos tablas de configuración I2C reducidas para poner en marcha la cámara (1280×720@60) y el ADV7511 para HDMI Tx. Nota: los sensores típicamente requieren secuencias largas; aquí se incluyen secuencias representativas y mínimas para demostrar el flujo. En entornos de producción, usa las tablas completas del proveedor.
#include "xparameters.h"
#include "xiic.h"
#include "xil_printf.h"
#include "sleep.h"
#include <stdint.h>
#define IIC_CAM_DEV_ID XPAR_AXI_IIC_1_DEVICE_ID // I2C para IMX219 (ajustar al tuyo)
#define IIC_HDMI_DEV_ID XPAR_AXI_IIC_0_DEVICE_ID // I2C para ADV7511 (ajustar al tuyo)
#define IMX219_I2C_ADDR 0x10 // 7-bit
#define ADV7511_I2C_ADDR 0x39 // 7-bit
#define AXI_GPIO_BASE XPAR_AXI_GPIO_0_BASEADDR // GPIO para umbral
static XIic IicCam;
static XIic IicHdmi;
static int i2c_write8(XIic *Iic, u8 addr7, u8 reg, u8 val) {
u8 buf[2] = {reg, val};
int r = XIic_Send(Iic->BaseAddress, addr7, buf, 2, XIIC_STOP);
return (r == 2) ? 0 : -1;
}
static int i2c_write_seq(XIic *Iic, u8 addr7, const u8 (*seq)[2], size_t n) {
for (size_t i=0;i<n;i++){
if (i2c_write8(Iic, addr7, seq[i][0], seq[i][1]) != 0) return -1;
usleep(2000); // 2ms entre writes
}
return 0;
}
// Secuencia mínima de ADV7511 (Power-up + input RGB 444 + habilitar HDMI)
// Nota: Secuencias más completas incluyen ajustes EDID/HDCP; esta valdrá para monitores estándar.
static const u8 adv7511_init[][2] = {
{0x41, 0x10}, // Power-up
{0x98, 0x03}, {0x9A, 0xE0}, {0x9C, 0x30}, {0x9D, 0x61},
{0xA2, 0xA4}, {0xA3, 0xA4}, {0xE0, 0xD0}, {0xF9, 0x00},
{0x16, 0x38}, // 444, 8-bit, style 1
{0x48, 0x08}, // audio off
{0x55, 0x00}, // RGB
{0xAF, 0x06}, // HDMI mode
{0x40, 0x80}, // GC enable
{0x4C, 0x00}, // no dither
};
// Secuencia representativa IMX219 para 1280x720@60 (cropping/binning + PLL).
// Para proyectos reales: usar tablas completas del proveedor o del kernel Linux (drivers/media/i2c/imx219.c).
static const u8 imx219_720p60_init[][2] = {
{0x0103, 0x01}, // software reset
// exit standby
{0x0100, 0x00}, // standby
// PLL/settings aproximados (ejemplo)
{0x0301, 0x05}, {0x0303, 0x01}, {0x0304, 0x03}, {0x0305, 0x03},
{0x0306, 0x00}, {0x0307, 0x39}, // VT_SYS_CLK
// MIPI lanes: 2 lanes
{0x0114, 0x01}, // CSI 2-lane
// Data format RAW10
{0x0180, 0x00}, {0x0181, 0x00},
{0x0128, 0x00}, // DPC off
// Frame length / line length (aprox. para 720p@60)
{0x0162, 0x0D}, {0x0163, 0x78}, // line_length_pck
{0x0164, 0x02}, {0x0165, 0xA8}, // x_addr_start
{0x0166, 0x0A}, {0x0167, 0x27}, // x_addr_end
{0x0168, 0x02}, {0x0169, 0xB4}, // y_addr_start
{0x016A, 0x06}, {0x016B, 0xEB}, // y_addr_end
{0x016C, 0x05}, {0x016D, 0x00}, // x_output_size = 1280
{0x016E, 0x02}, {0x016F, 0xD0}, // y_output_size = 720
{0x0170, 0x01}, {0x0171, 0x01}, // binning
// Timing
{0x0157, 0x00}, // analog gain
// Start streaming
{0x0100, 0x01},
};
static void gpio_set_threshold(u8 thr) {
volatile u32 *gpio = (u32*)AXI_GPIO_BASE;
// AXI GPIO: canal 1 data (offset 0x0); por simplicidad asumimos GPIO 8-bit out.
gpio[0] = thr; // escribir umbral
}
static int iic_init_by_dev_id(XIic *Iic, u16 dev_id) {
XIic_Config *CfgPtr = XIic_LookupConfig(dev_id);
if (!CfgPtr) return -1;
if (XIic_CfgInitialize(Iic, CfgPtr, CfgPtr->BaseAddress) != XST_SUCCESS) return -1;
XIic_SetAddress(Iic, XII_ADDR_TO_SEND_TYPE, 0x00); // se setea por transacción
XIic_Start(Iic);
return 0;
}
int main() {
xil_printf("Zybo Z7-20 + Pcam5C: mipi-csi2-hdmi-object-tracking (bare-metal)\r\n");
if (iic_init_by_dev_id(&IicHdmi, IIC_HDMI_DEV_ID) != 0) {
xil_printf("Error: init I2C HDMI\r\n");
return -1;
}
if (iic_init_by_dev_id(&IicCam, IIC_CAM_DEV_ID) != 0) {
xil_printf("Error: init I2C CAM\r\n");
return -1;
}
xil_printf("Config ADV7511...\r\n");
if (i2c_write_seq(&IicHdmi, ADV7511_I2C_ADDR, adv7511_init, sizeof(adv7511_init)/2) != 0) {
xil_printf("Error ADV7511 I2C\r\n");
} else {
xil_printf("ADV7511 OK\r\n");
}
xil_printf("Config IMX219 720p60...\r\n");
if (i2c_write_seq(&IicCam, IMX219_I2C_ADDR, imx219_720p60_init, sizeof(imx219_720p60_init)/2) != 0) {
xil_printf("Error IMX219 I2C\r\n");
} else {
xil_printf("IMX219 OK (stream on)\r\n");
}
// Umbral inicial
gpio_set_threshold(120);
xil_printf("Umbral inicial = 120 (ajustable por AXI GPIO)\r\n");
while (1) {
// Bucle de servicio simple: podrías actualizar el umbral por UART si lo deseas.
usleep(500000);
}
return 0;
}
Explicación (breve):
– Se inicializan dos I2C (AXI IIC) para la cámara y el HDMI Tx.
– Se escribe una secuencia mínima en el ADV7511 para salida HDMI activa.
– Se habilita el IMX219 en modo 1280×720@60 con salida RAW10.
– Se programa el umbral de tracking por AXI GPIO (8 bits).
Preparación del proyecto Vivado (BD + IPs + integraciones)
Trabajaremos totalmente por CLI. Crea una estructura de carpetas:
- proj/
- scripts/
- src/
- sw/
Copia los ficheros anteriores en proj/src y proj/sw respectivamente.
Instala los “Board Files” de Digilent para Zybo Z7‑20 de forma que Vivado 2023.2 los detecte. Una forma segura es clonar el repositorio y exportar BOARD_REPO_PATHS:
- git clone https://github.com/Digilent/vivado-boards.git -b 2023.2 ~/vivado-boards-2023.2
- export BOARD_REPO_PATHS=~/vivado-boards-2023.2/new/board_files
Ahora crea el siguiente script Tcl para generar el diseño en Vivado: scripts/zybo_z7_mipi_hdmi_bd.tcl
# Vivado 2023.2 - creación por lotes del diseño
# Board: Digilent Zybo Z7-20
# Objetivo: MIPI-CSI2 (Pcam 5C) -> Gray -> Tracking+Overlay -> HDMI (ADV7511)
set proj_name "mipi_csi2_hdmi_tracker"
set part_board "digilentinc.com:zybo-z7-20:part0:1.1"
create_project $proj_name ./ -part xc7z020clg400-1
set_property board_part $part_board [current_project]
set_msg_config -id {BD 41-237} -new_severity {INFO}
# Agrega fuentes Verilog
add_files -fileset sources_1 ./src/video_chain.v
# Crea diseño BD
create_bd_design "bd_top"
# Zynq PS
create_bd_cell -type ip -vlnv xilinx.com:ip:processing_system7:5.5 ps7
apply_bd_automation -rule xilinx.com:bd_rule:ps7_init -config {apply_board_preset "1"} [get_bd_cells ps7]
# Relojes y resets
create_bd_cell -type ip -vlnv xilinx.com:ip:proc_sys_reset:5.0 rst_video
create_bd_cell -type ip -vlnv xilinx.com:ip:clk_wiz:6.0 clk_wiz_0
set_property -dict [list CONFIG.PRIM_IN_FREQ {100.000} CONFIG.NUM_OUT_CLKS {1} CONFIG.CLKOUT1_REQUESTED_OUT_FREQ {74.25}] [get_bd_cells clk_wiz_0]
# MIPI CSI-2 Rx Subsystem + D-PHY (versiones típicas en 2023.2; ajusta si difieren)
create_bd_cell -type ip -vlnv xilinx.com:ip:mipi_csi2_rx_subsystem:5.2 mipi_rx
# Configuración básica: 2 lanes, RAW10, 1280x720@60
set_property -dict [list CONFIG.CMN_NUM_LANES {2} CONFIG.CMN_PXL_FORMAT {RAW10} CONFIG.CMN_NUM_PIXELS {1}] [get_bd_cells mipi_rx]
# AXI IIC para ADV7511 y IMX219
create_bd_cell -type ip -vlnv xilinx.com:ip:axi_iic:2.1 iic_hdmi
create_bd_cell -type ip -vlnv xilinx.com:ip:axi_iic:2.1 iic_cam
# AXI GPIO (umbral)
create_bd_cell -type ip -vlnv xilinx.com:ip:axi_gpio:2.0 axi_gpio_thr
set_property -dict [list CONFIG.C_GPIO_WIDTH {8} CONFIG.C_ALL_OUTPUTS {1}] [get_bd_cells axi_gpio_thr]
# AXI4-Stream to Video Out + VTC
create_bd_cell -type ip -vlnv xilinx.com:ip:v_tc:6.2 vtc_0
create_bd_cell -type ip -vlnv xilinx.com:ip:axi4stream_to_video_out:4.0 a2vo
# Interconexión AXI
create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect:2.1 axi_interconnect_0
set_property -dict [list CONFIG.NUM_MI {3}] [get_bd_cells axi_interconnect_0]
# Conexiones AXI-Lite desde PS
connect_bd_intf_net [get_bd_intf_pins ps7/M_AXI_GP0] [get_bd_intf_pins axi_interconnect_0/S00_AXI]
connect_bd_intf_net [get_bd_intf_pins axi_interconnect_0/M00_AXI] [get_bd_intf_pins iic_hdmi/S_AXI]
connect_bd_intf_net [get_bd_intf_pins axi_interconnect_0/M01_AXI] [get_bd_intf_pins iic_cam/S_AXI]
connect_bd_intf_net [get_bd_intf_pins axi_interconnect_0/M02_AXI] [get_bd_intf_pins axi_gpio_thr/S_AXI]
# Conexiones de reloj y reset
connect_bd_net [get_bd_pins ps7/FCLK_CLK0] [get_bd_pins axi_interconnect_0/ACLK] [get_bd_pins iic_hdmi/s_axi_aclk] [get_bd_pins iic_cam/s_axi_aclk] [get_bd_pins axi_gpio_thr/s_axi_aclk] [get_bd_pins rst_video/slowest_sync_clk]
connect_bd_net [get_bd_pins ps7/FCLK_RESET0_N] [get_bd_pins rst_video/ext_reset_in]
# Conectar reloj de píxel
connect_bd_net [get_bd_pins clk_wiz_0/clk_out1] [get_bd_pins a2vo/aclk] [get_bd_pins vtc_0/clk]
# Vídeo: MIPI -> RAW10->Gray->Tracker->A2VO
# Instancia HDL de conversión y tracking
create_bd_cell -type module -reference axis_raw10_to_gray8 raw2gray
create_bd_cell -type module -reference axis_gray8_tracker_overlay tracker
# Conecta AXIS de mipi_rx a raw2gray
connect_bd_intf_net [get_bd_intf_pins mipi_rx/video_out] [get_bd_intf_pins raw2gray/s_axis]
# Conecta raw2gray -> tracker
connect_bd_intf_net [get_bd_intf_pins raw2gray/m_axis] [get_bd_intf_pins tracker/s_axis]
# Conecta tracker -> a2vo
connect_bd_intf_net [get_bd_intf_pins tracker/m_axis] [get_bd_intf_pins a2vo/S_AXIS_VIDEO]
# Conectar señales de control AXIS (SOF/EOL vienen en la interfaz AXIS de Xilinx como TUSER/TLAST)
# (Las conexiones anteriores de intf_net ya incluyen TDATA/TVALID/TREADY/TUSER/TLAST)
# Conectar VTC
connect_bd_net [get_bd_pins vtc_0/hsync_out] [get_bd_pins a2vo/vid_hsync]
connect_bd_net [get_bd_pins vtc_0/vsync_out] [get_bd_pins a2vo/vid_vsync]
connect_bd_net [get_bd_pins vtc_0/active_video_out] [get_bd_pins a2vo/vid_active_video]
connect_bd_net [get_bd_pins clk_wiz_0/clk_out1] [get_bd_pins vtc_0/clk]
# Reset para dominio de video
connect_bd_net [get_bd_pins rst_video/peripheral_aresetn] [get_bd_pins a2vo/aresetn]
# Conectar threshold desde AXI GPIO a tracker
create_bd_port -dir I -from 7 -to 0 threshold_gpio
connect_bd_net [get_bd_pins axi_gpio_thr/gpio_io_o] [get_bd_ports threshold_gpio]
connect_bd_net [get_bd_ports threshold_gpio] [get_bd_pins tracker/threshold]
# Conectividad física: aplica conexiones de placa (board automation) si disponible
# HDMI TX (ADV7511) - el a2vo saca señales de video (vid_data, vid_hs, vid_vs, vid_active, pixclk)
# En la Zybo Z7-20 normalmente se usa el pipeline paralelo hacia ADV7511. Usa los puertos de a2vo.
make_bd_pins_external [get_bd_pins a2vo/vid_io_out/vid_hsync]
make_bd_pins_external [get_bd_pins a2vo/vid_io_out/vid_vsync]
make_bd_pins_external [get_bd_pins a2vo/vid_io_out/vid_active_video]
make_bd_pins_external [get_bd_pins a2vo/vid_io_out/vid_data]
make_bd_pins_external [get_bd_pins clk_wiz_0/clk_out1]
# Mapea I2C a puertos externos (se conectan a HDMI Tx ADV7511 e IMX219 por XDC de la placa)
make_bd_intf_pins_external [get_bd_intf_pins iic_hdmi/IIC]
make_bd_intf_pins_external [get_bd_intf_pins iic_cam/IIC]
# Exporta XSA
validate_bd_design
save_bd_design
make_wrapper -files [get_files ./mipi_csi2_hdmi_tracker.srcs/sources_1/bd/bd_top/bd_top.bd] -top
add_files -fileset sources_1 ./mipi_csi2_hdmi_tracker.gen/sources_1/bd/bd_top/hdl/bd_top_wrapper.v
# Genera bitstream
update_compile_order -fileset sources_1
launch_runs impl_1 -to_step write_bitstream -jobs 8
wait_on_run impl_1
# Exporta hardware para Vitis (XSA con bitstream)
file mkdir ./xsa
write_hw_platform -fixed -include_bit -force -file ./xsa/zybo_z7_mipi_hdmi_tracker.xsa
Notas:
– En diseños reales con Zybo Z7‑20, la conexión “externa” de los pines de vídeo e I2C se resuelve con los XDC y board files de Digilent. Si no se asignan automáticamente, añade el XDC específico de Zybo Z7‑20 con las asignaciones del ADV7511 y PCAM.
– El bloque MIPI CSI‑2 Rx Subsystem por defecto incluye su reloj de vídeo; asegúrate de que su dominio AXIS esté correctamente puenteado a los bloques siguientes. Si Vivado te avisa de dominio de reloj cruzado, inserta un “AXIS Clock Converter” o reconfigura para usar el mismo reloj.
Compilación, programación (flash) y ejecución
Pasos reproducibles (sin GUI):
1) Clonar board files y preparar entorno (si no lo hiciste):
– git clone https://github.com/Digilent/vivado-boards.git -b 2023.2 ~/vivado-boards-2023.2
– export BOARD_REPO_PATHS=~/vivado-boards-2023.2/new/board_files
2) Ejecutar Vivado para sintetizar e implementar:
– cd proj
– vivado -mode batch -source scripts/zybo_z7_mipi_hdmi_bd.tcl | tee build.log
3) Programar la FPGA por JTAG (bitstream de la XSA):
Crea un script de programación scripts/program_hw.tcl:
open_hw
connect_hw_server
open_hw_target
current_hw_device [lindex [get_hw_devices xc7z020*] 0]
refresh_hw_device -update_hw_probes false [current_hw_device]
set_property PROGRAM.FILE [lindex [get_hw_files *.bit] 0] [current_hw_device]
program_hw_devices [current_hw_device]
exit
Ejecuta:
– vivado -mode batch -source scripts/program_hw.tcl
4) Crear y cargar la aplicación bare‑metal en Vitis 2023.2:
Crea scripts/vitis_app.tcl:
# Vitis 2023.2 XSCT script
setws ./vitis_ws
platform create -name zybo_platform -hw ./xsa/zybo_z7_mipi_hdmi_tracker.xsa -proc ps7_cortexa9_0 -os standalone
platform write
platform generate
app create -name tracker_app -platform zybo_platform -template {Empty Application} -proc ps7_cortexa9_0
importsources -name tracker_app -path ../sw
# Asegura que main.c está en ../sw/main.c
app build -name tracker_app
# Programar FPGA + cargar app por JTAG
targets -set -nocase -filter {name =~ "*Cortex-A9 MPCore #0*"}
dow ./vitis_ws/tracker_app/Debug/tracker_app.elf
con
Ejecuta:
– xsct scripts/vitis_app.tcl
5) Monitoreo por UART:
– Conecta por minicom o screen al puerto serie de la Zybo Z7‑20 (115200 8N1).
– screen /dev/ttyUSBx 115200
– Verás logs: “ADV7511 OK”, “IMX219 OK (stream on)”, “Umbral inicial = 120”.
Validación paso a paso
- Alimentación y consola:
- Confirma que el LED de “power” y “DONE” (tras programar) están activos.
-
En consola UART debe aparecer:
- Z y n q: mipi-csi2-hdmi-object-tracking (bare-metal)
- ADV7511 OK
- IMX219 OK (stream on)
- Umbral inicial = 120
-
Señal de vídeo:
- El monitor HDMI debe detectar 1280×720@60 (muchos monitores lo indican en OSD).
-
Debes visualizar la imagen en escala de grises proveniente de la Pcam 5C (escena real).
-
Detección y overlay:
- Coloca un objeto con contraste (p. ej., una cartulina clara o una linterna) frente a la cámara; con umbral 120 se resaltan objetos brillantes.
- El pipeline dibuja un recuadro rojo (aprox. 60×60 px) centrado en la masa más brillante del frame anterior. Debe “seguir” al mover el objeto.
-
Ajusta el umbral desde el código (AXI GPIO) y vuelve a programar la app si deseas refinar la sensibilidad. Opcionalmente, expón un control por UART para modificarlo en tiempo real.
-
Sincronismos:
-
Si el overlay tiembla o se desfasa, revisa que TUSER (SOF) y TLAST (EOL) del MIPI CSI‑2 estén propagándose correctamente a lo largo de los bloques Verilog y al AXI4‑Stream to Video Out.
-
Rendimiento:
- La Zybo Z7‑20 soporta 720p60 cómodamente con este pipeline. El uso del PL deberá estar por debajo del 50–60% en LUT/FF en un diseño optimizado.
Troubleshooting (errores típicos y solución)
1) Pantalla negra en HDMI (sin señal)
– Causa probable: ADV7511 no configurado por I2C.
– Solución: verifica que iic_hdmi esté mapeado a los pines correctos (XDC/board file) y que la secuencia adv7511_init se ejecute sin NACK. Comprueba con un analizador lógico I2C si es posible. Asegura 74.25 MHz de pixel clock y timings válidos del VTC.
2) Imagen con ruido, sin forma o congelada
– Causa: MIPI CSI‑2 Rx sin bloqueo (no detecta lanes o data type).
– Solución: revisa que el IMX219 esté en streaming (0x0100=0x01) y que la configuración de “num lanes” y “RAW10” coincida en el IP mipi_csi2_rx_subsystem. Verifica el cable de la Pcam (orientación, fijación).
3) “Critical Warning: clock domain crossing” y fallos intermitentes
– Causa: el MIPI Rx y el pipeline de vídeo operan en relojes distintos.
– Solución: inserta un “AXIS Clock Converter” entre mipi_rx y el resto, o ajusta para usar un único reloj de vídeo. Asegura resets sincronizados (proc_sys_reset por dominio).
4) Overlay fuera de posición o recuadro errático
– Causa: pérdida de SOF (TUSER) o recuento de x/y incorrecto por TLAST.
– Solución: confirma que mipi_csi2_rx_subsystem genera TUSER al primer píxel del frame y TLAST al último píxel de cada línea. Asegúrate de no retrasar TUSER/TLAST respecto a TDATA.
5) I2C NACK en IMX219
– Causa: dirección incorrecta o secuencia incompleta.
– Solución: confirma dirección 7-bit 0x10 y que la cámara esté alimentada. Algunas tablas requieren grandes esperas entre writes; incrementa usleep a 5–10 ms si es necesario. Revisa que los pines I2C de PCAM estén correctamente asignados.
6) Licenciamiento MIPI D‑PHY / CSI‑2 IP
– Causa: Vivado puede requerir licencia de IP.
– Solución: para prácticas educativas, usa modo evaluación. Alternativamente, explora implementaciones D‑PHY/CSI‑2 alternativas compatibles con 7‑Series o los ejemplos de Digilent específicos de Pcam 5C.
7) Frecuencia incorrecta (monitor muestra 1280×720@50 o no soportado)
– Causa: Clocking Wizard mal configurado o VTC con timings de 720p@50.
– Solución: asegura 74.25 MHz y timings estándar CEA‑861 para 720p60. Ajusta VTC a modo 1280×720@60.
8) m_axi_gp0 no mapea periféricos AXI‑Lite
– Causa: faltan rangos en Address Editor (automatización del BD).
– Solución: asigna direcciones en Address Editor para iic_hdmi, iic_cam y axi_gpio_thr. Vuelve a generar el wrapper y la XSA.
Mejoras/variantes
- Color-based tracking:
-
Implementar conversión Bayer→RGB y HSV en PL (debayer bilineal + RGB→HSV) y detectar por rango de color (H,S,V) con umbrales configurables. Se puede agregar un AXI4‑Lite con registros para Hmin/Hmax/Smin/Vmin.
-
Post-procesado en PS:
-
Volcar centroides por AXI‑Lite al PS y superponer overlay por “Video Mixer” IP o composición en PL. También se puede integrar un AXI VDMA y un frame buffer en DDR para procesado con Vitis (NEON/OpenCV).
-
Multitracking:
-
Implementar etiquetado de componentes conexas (CCA) en PL con histogramas por regiones o ventanas múltiples. Dibujar múltiples bounding boxes y contadores.
-
Resolución 1080p30:
-
Configurar IMX219 a 1920×1080@30 y ajustar clocks/timings. Verificar presupuestos de ancho de banda y timing closure.
-
Ajuste dinámico por UART:
-
Añadir menú UART para cambiar umbral sin recompilar: escribir en AXI GPIO desde el PS con entradas de usuario.
-
OSD/On‑Screen:
- Agregar texto/fps y coordenadas del centroide con un generador de caracteres en PL o mediante “Video Mixer” + “Text overlay”.
Checklist de verificación
- [ ] SO Ubuntu 22.04.3 LTS con Vivado 2023.2 y Vitis 2023.2 instalados y en PATH.
- [ ] Board Files de Digilent 2023.2 instalados (BOARD_REPO_PATHS apunta a la carpeta).
- [ ] Carpeta del proyecto creada con scripts y fuentes ubicados correctamente (src/, sw/, scripts/).
- [ ] Ejecución exitosa de vivado -mode batch -source scripts/zybo_z7_mipi_hdmi_bd.tcl sin errores de síntesis/implementación.
- [ ] Bitstream programado por JTAG (scripts/program_hw.tcl) con LED DONE activo.
- [ ] XSA exportada y plataforma Vitis generada; app bare‑metal compilada sin errores.
- [ ] App cargada por JTAG; consola UART muestra “ADV7511 OK” e “IMX219 OK (stream on)”.
- [ ] Monitor HDMI recibe 1280×720@60 y visualiza la escena en escala de grises.
- [ ] Overlay rojo (bounding box) sigue el objeto brillante al moverlo.
- [ ] Umbral ajustable vía AXI GPIO (cambios reflejados en el comportamiento del seguimiento).
Notas finales:
– Este caso práctico integra un pipeline PL de baja latencia (GRIS→TRACK→OVERLAY) con control sencillo desde el PS (I2C y umbral), logrando mipi‑csi2‑hdmi‑object‑tracking en la Zybo Z7‑20 con la Pcam 5C (IMX219). La arquitectura está pensada para extenderse hacia color-tracking y componentes más complejas (debayer completo, HSV, VDMA) según las necesidades del proyecto.
Encuentra este producto y/o libros sobre este tema en Amazon
Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.


