# 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:
- 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.
* 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.
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
Aún no hay comentarios.