# 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 :
- 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.
* 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.
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
Aucun commentaire pour le moment.