Retour à l'accueil

Contrôleur USB sur FPGA : développement pour dispositifs HID en utilisant l'exemple d'un clavier

L'article est dédié au développement d'un contrôleur USB 1.1 basé sur FPGA pour connecter des dispositifs HID tels qu'un clavier. Il couvre l'architecture modulaire en Verilog, le processus d'initialisation du dispositif, l'importance de la transmission opportune des paquets SOF et l'analyse des descripteurs HID.

Création d'un contrôleur USB sur FPGA : de la théorie à un clavier fonctionnel
Advertisement 728x90

# Réaliser un contrôleur USB 1.1 sur FPGA : Guide pratique pour intégrer un clavier HID

Développer son propre contrôleur USB sur un FPGA exige une bonne maîtrise du protocole et des particularités matérielles. Ce guide détaille la mise en œuvre d’un contrôleur USB 1.1 connecté à un clavier, en couvrant les modules essentiels, l’initialisation du périphérique et la gestion des descripteurs HID.

Caractéristiques matérielles de l’USB 1.1

Contrairement à l’USB 2.0, l’USB 1.1 repose sur seulement deux lignes numériques (D+ et D-) fonctionnant en mode différentiel, que l’on peut relier directement aux broches du FPGA. Il propose deux vitesses : Low Speed (1,5 Mbps) et Full Speed (12 Mbps). Le périphérique signale sa vitesse en tirant D- au haut pour Low Speed ou D+ au haut pour Full Speed lors de la connexion.

Points matériels clés :

Google AdInline article slot
  • Les lignes de données nécessitent des résistances de rappel à la masse côté hôte.
  • Protéger les entrées FPGA contre les pics à 5 V, par exemple avec des diodes Zener 3,3 V.
  • Pour les tests, optez pour des cartes comme la STM32 BluePill pour émuler des périphériques HID.

Architecture modulaire du contrôleur USB

Le contrôleur se décompose en modules interconnectés, chacun gérant une tâche spécifique de la pile protocolaire USB.

  • M_DATA — Module central pour l’envoi et la réception des données.
  • M_CRC16_USB — Calcule les sommes de contrôle CRC16 pour les paquets de données.
  • M_GET_PACKET — Gère la réception : envoie un jeton IN, reçoit le paquet, vérifie le CRC et renvoie un ACK.
  • M_RECEIVE_MODULE — Module de réception des données, incluant :

* M_GET_DATA — Lit les données brutes sur le bus.

* M_MEMORY_BUF_CRC16 — Tampon qui calcule le CRC16 en temps réel.

Google AdInline article slot

* M_BUF_RETRANSLATOR — Transfère les données du tampon uniquement après vérification CRC réussie.

  • M_SEND_PACKET — Gère l’émission : jeton OUT, envoi des données et attente d’ACK.
  • M_TRANSMIT_MODULE — Émet les données, jetons, paquets Start of Frame (SOF) et paquets de poignée de main.
  • M_CRC_5 — Calcule le CRC5 pour les jetons, SOF et poignées de main.
  • M_SEND_DATA — Envoie les paquets de données avec CRC16.
  • M_SEND_TOKEN — Envoie les jetons, SOF et poignées de main (tous avec CRC5).
  • M_SEND_END_OF_PACKET — Génère le signal End of Packet (EOP).
  • M_DATA_TRANSFER — Couche physique pour la transmission bit à bit sur le bus.
  • M_SOF_SENDER — Envoie périodiquement les paquets SOF pour la synchronisation horloge.
  • M_MAIN_AUTOMATE — Automate d’états principal contrôlant le contrôleur.
  • M_USB_INIT — Gère l’initialisation du périphérique USB.
  • M_TRANSACTION — Traite les transferts de contrôle.
  • M_HID_ANALYZER — Analyse les descripteurs HID pour comprendre les formats de données.
  • MEMORY_BUF (4 instances) — Tampons pour les données d’analyse HID.

Processus d’initialisation du périphérique

L’initialisation commence par la détection de la vitesse via les états de D+ et D-. Puis l’automate principal (M_MAIN_AUTOMAT) exécute une séquence précise.

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

Astuce cruciale : Commencez à envoyer les paquets SOF (Start of Frame) immédiatement après la phase de reset (S_USB_RESET), et non pendant la récupération. Les SOF maintiennent la synchronisation avec l’hôte. Full Speed utilise un numéro de trame avec CRC5 ; Low Speed se contente d’envoyer un EOP (deux cycles à zéro sur les deux lignes). Retarder les SOF peut rompre la compatibilité avec certains périphériques.

Google AdInline article slot

Analyse des descripteurs HID et polling du périphérique

Une fois l’initialisation terminée — y compris l’attribution d’une adresse non nulle —, le contrôleur passe en mode polling (S_REQUEST). Le module M_HID_ANALYZER examine les descripteurs récupérés via des transferts de contrôle pour déterminer les formats et tailles des paquets de données. Pour un clavier, il extrait le nombre de rapports, leur taille et leurs ID.

Boucle de polling HID typique :

  • Envoyer un jeton IN à l’adresse du périphérique et à l’Endpoint 1.
  • Recevoir le paquet de données.
  • Vérifier le CRC16.
  • Envoyer un ACK si tout est bon.
  • Extraire la charge utile (comme les codes de scan des touches), en sautant les octets d’en-tête initiaux.
  • Attendre l’intervalle de polling (ex. : 24 ms) avant le prochain sondage.

Si le périphérique renvoie un NAK (Not Acknowledged), réessayez plus tard. Pas de réponse ou erreurs ? Revenez en attente ou réinitialisez.

Points clés à retenir

  • Paquets SOF à temps : Lancez les Start of Frame juste après le reset — essentiel pour un fonctionnement HID fiable.
  • Protection des entrées FPGA : Protégez les lignes de données contre le 5 V avec une circuiterie adaptée, car l’USB mélange les niveaux 3,3 V/5 V.
  • Analyse des descripteurs : Le HID ne fonctionne pas sans décortiquer les descripteurs pour mapper les structures de données.
  • Gestion des erreurs : Traitez gracieusement les NAK, STALL et timeouts pour des liaisons robustes.
  • Conception modulaire : La décomposition en modules facilite le débogage et la réutilisation dans d’autres projets.

— Editorial Team

Advertisement 728x90

Lire ensuite