Volver al inicio

Controlador USB en FPGA: desarrollo para dispositivos HID usando el ejemplo de un teclado

El artículo está dedicado al desarrollo de un controlador USB 1.1 basado en FPGA para conectar dispositivos HID como un teclado. Cubre arquitectura modular en Verilog, proceso de inicialización de dispositivos, importancia de la transmisión oportuna de paquetes SOF y análisis de descriptores HID.

Creación de controlador USB en FPGA: desde la teoría hasta un teclado funcional
Advertisement 728x90

# Construyendo un controlador USB 1.1 en FPGA: Guía práctica para integrar teclado HID

Desarrollar tu propio controlador USB en un FPGA requiere un buen dominio del protocolo y sus peculiaridades de hardware. Esta guía detalla la implementación de un controlador USB 1.1 que conecta un teclado, cubriendo módulos clave, inicialización del dispositivo y manejo de descriptores HID.

Características de hardware de USB 1.1

A diferencia de USB 2.0, USB 1.1 usa solo dos líneas digitales (D+ y D-) que operan en modo diferencial, lo que permite conectarlas directamente a pines de FPGA. Tiene dos velocidades: Baja Velocidad (1.5 Mbps) y Velocidad Plena (12 Mbps). El dispositivo indica su velocidad tirando D- a alto para Baja Velocidad o D+ a alto para Velocidad Plena durante la conexión.

Consideraciones clave de hardware:

Google AdInline article slot
  • Las líneas de datos necesitan resistencias de pull-down a tierra en el lado del host.
  • Protege las entradas del FPGA de picos de 5V, por ejemplo, con diodos Zener de 3.3V.
  • Para pruebas, usa placas como la STM32 BluePill para emular dispositivos HID.

Arquitectura de módulos del controlador USB

El controlador se divide en módulos interconectados, cada uno con una tarea específica en la pila del protocolo USB.

  • M_DATA — Módulo principal para enviar y recibir datos.
  • M_CRC16_USB — Calcula sumas de verificación CRC16 para paquetes de datos.
  • M_GET_PACKET — Maneja la recepción de datos: envía token IN, recibe paquete, verifica CRC y envía ACK.
  • M_RECEIVE_MODULE — Módulo de recepción de datos, que incluye:

* M_GET_DATA — Lee datos crudos del bus.

* M_MEMORY_BUF_CRC16 — Buffer que calcula CRC16 en tiempo real.

Google AdInline article slot

* M_BUF_RETRANSLATOR — Reenvía datos del buffer solo tras verificación exitosa de CRC.

  • M_SEND_PACKET — Gestiona la transmisión de datos: token OUT, envío de datos y espera de ACK.
  • M_TRANSMITE_MODULE — Transmite datos, tokens, paquetes Start of Frame (SOF) y paquetes de handshake.
  • M_CRC_5 — Calcula CRC5 para tokens, SOF y handshakes.
  • M_SEND_DATA — Envía paquetes de datos con CRC16.
  • M_SEND_TOKEN — Envía tokens, SOF y handshakes (todos con CRC5).
  • M_SEND_END_OF_PACKET — Genera la señal End of Packet (EOP).
  • M_DATA_TRANSFER — Capa física para transmisión de bits en el bus.
  • M_SOF_SENDER — Envía paquetes SOF periódicamente para sincronización de reloj.
  • M_MAIN_AUTOMATA — Máquina de estados principal que controla el controlador.
  • M_USB_INIT — Maneja la inicialización del dispositivo USB.
  • M_TRANSACTION — Procesa transferencias de control.
  • M_HID_ANALYZER — Analiza descriptores HID para entender formatos de datos.
  • MEMORY_BUF (4 instancias) — Buffers para datos de análisis HID.

Proceso de inicialización del dispositivo

La inicialización comienza detectando la velocidad a partir de los estados de D+ y D-. Luego, la máquina de estados principal (M_MAIN_AUTOMATA) ejecuta una secuencia precisa.

always @(posedge clk)
begin
    if (rst)
    begin
        State_main <= S_IDLE;
        SOF_En <= 1'b0;
        FullSpeedConnect <= 1'b0;
        // ... reset other signals
    end
    else
    begin
        case (State_main)
            S_IDLE:
                // Wait for connection and speed detection
                if (Dm & !Dp) // Low Speed
                begin
                    FullSpeedConnect <= 1'b0;
                    ResetTime <= 15_000; // 10 ms at 1.5 MHz
                    // ... set Low Speed timers
                    State_main <= S_POWER_RISE;
                end
                else if(Dp & !Dm) // Full Speed
                begin
                    FullSpeedConnect <= 1'b1;
                    ResetTime <= 120_000; // 10 ms at 12 MHz
                    // ... set Full Speed timers
                    State_main <= S_POWER_RISE;
                end
            S_POWER_RISE:
                // Wait 100 ms for power stabilization
                if (WaiteCount == PowerRiseTime || SKIP_POWER_RISE)
                begin
                    WaiteCount <= 0;
                    State_main <= S_USB_RESET;
                end
            S_USB_RESET:
                // Device reset (0 on D+ and D- for 10 ms)
                if (WaiteCount == ResetTime || SKIP_POWER_RISE)
                begin
                    WaiteCount <= 0;
                    State_main <= S_INIT_SOF_SENDER;
                end
            S_INIT_SOF_SENDER:
                // Start SOF packets right after reset
                if (Eof1) State_main <= S_WAITE_SOF;
                else SOF_En <= 1'b1;
            S_WAITE_SOF:
                if (!Eof1) State_main <= S_USB_RESET_RECOWERY;
            S_USB_RESET_RECOWERY:
                // Recovery after reset (10 ms)
                if (WaiteCount == ResetTime || SKIP_POWER_RISE)
                begin
                    WaiteCount <= 0;
                    State_main <= S_USB_INIT;
                end
            S_USB_INIT:
                // Run control transfer sequence
                if (InitComplite)
                begin
                    State_main <= S_REQUEST;
                end
                else if (InitFail)
                    State_main <= S_FAIL;
            // ... polling states (S_REQUEST, S_WAIT, etc.)
        endcase
    end
end

Consejo clave: Comienza a enviar paquetes SOF (Start of Frame) inmediatamente después de la fase de reset (S_USB_RESET), no durante la recuperación. Los SOF mantienen al dispositivo sincronizado con el host. Velocidad Plena usa un número de frame con CRC5; Baja Velocidad solo envía EOP (dos ciclos de cero en ambas líneas). Retrasar los SOF puede romper la compatibilidad con algunos dispositivos.

Google AdInline article slot

Análisis de descriptores HID y sondeo del dispositivo

Una vez finalizada la inicialización —incluyendo la asignación de una dirección no cero—, el controlador pasa al sondeo (S_REQUEST). El módulo M_HID_ANALYZER examina los descriptores obtenidos vía transferencias de control para determinar formatos y tamaños de paquetes de datos. Para un teclado, extrae conteo de reportes, tamaño e IDs.

Bucle típico de sondeo HID:

  • Envía token IN a la dirección del dispositivo y Endpoint 1.
  • Recibe paquete de datos.
  • Verifica CRC16.
  • Envía ACK si es correcto.
  • Extrae la carga útil (como códigos de escaneo de teclas), saltando bytes de cabecera iniciales.
  • Espera el intervalo de sondeo (p. ej., 24 ms) antes del siguiente sondeo.

Si el dispositivo envía NAK (No Aceptado), reintenta después. ¿Sin respuesta o errores? Vuelve a esperar o reinicia.

Lecciones clave

  • Paquetes SOF oportunos: Envía Start of Frame justo después del reset —esencial para un funcionamiento HID confiable.
  • Protección de entradas FPGA: Escuda las líneas de datos de 5V con circuitería adecuada, ya que USB mezcla niveles 3.3V/5V.
  • Análisis de descriptores: HID no funcionará sin desglosar descriptores para mapear estructuras de datos.
  • Manejo de errores: Gestiona NAK, STALL y timeouts con elegancia para enlaces robustos.
  • Diseño modular: Dividir en módulos facilita la depuración y reutilización en proyectos.

— Editorial Team

Advertisement 728x90

Leer después