2 points par GN⁺ 2024-06-04 | 1 commentaires | Partager sur WhatsApp
  • Présentation structurée de tout le parcours nécessaire pour créer un premier appareil USB, du câblage physique au PCB, aux débits de l’USB 2.0, aux couches de protocole et à une mise en pratique sur STM32
  • La connexion de base de l’USB 2.0 repose sur quatre fils : +5V, GND, D+, D- ; D+ et D- forment une paire différentielle qui transporte ensemble 1 bit
  • Sur un PCB, l’égalisation des longueurs de la paire différentielle, son placement rapproché et l’impédance sont importants, mais un prototype full speed à 12 Mbit/s peut fonctionner avec une certaine tolérance
  • La mise en pratique sur NUCLEO-F103RB consiste à configurer PA12 en USB_DP et PA11 en USB_DM, à créer un périphérique USB série reconnu comme port COM virtuel CDC, puis à allumer une LED quand on envoie 1
  • L’approche STM32CubeIDE impose une génération de code pilotée par interface graphique et fortement liée au boilerplate, tandis qu’une implémentation de périphérique USB sous Linux peut offrir des API plus standardisées et une architecture plus découplée

Portée d’une première implémentation USB

  • L’objectif est de réaliser jusqu’au bout, dans sa forme la plus simple, un périphérique USB reconnu lorsqu’on le branche à un ordinateur
  • Ici, le périphérique désigne un accessoire qui étend les fonctions de l’ordinateur, et l’ordinateur est appelé host
  • Le périmètre couvre à la fois la connexion physique entre le périphérique USB et le host, et une application simple côté host pour interagir avec le périphérique
  • Il s’agit moins d’un commentaire faisant autorité sur la spécification que d’un projet E2E très simple de fabrication d’un périphérique USB, accompagné d’un index de ressources

Concepts de base de l’USB

  • L’USB est une norme industrielle qui permet l’échange de données et l’alimentation électrique entre plusieurs appareils électroniques
  • L’USB est un bus série, donc les bits sont transmis un par un sur le bus, et non en parallèle
  • Les bus modernes sont principalement série ; on peut comprendre l’USB comme un mécanisme d’échange série de bits entre un host et un device
  • L’USB ne se limite pas à une spécification physique pour relier deux appareils et échanger des bits, il inclut aussi un protocole de communication pour le transfert de données et d’énergie
  • L’explication se concentre sur l’USB 2.0

Câblage USB et paire différentielle

  • Une connexion USB 2.0 classique se décrit au minimum avec quatre fils
    • +5 V : ligne par laquelle le host alimente le device
    • D-, D+ : deux lignes qui fonctionnent ensemble comme une paire différentielle pour transmettre 1 bit
    • GND : masse
  • Certaines connexions peuvent avoir des broches supplémentaires comme ID, mais la mise en pratique ne traite que ces quatre fils
  • Remarque sur l’USB-C

    • L’USB-C utilise lui aussi des paires différentielles en interne, mais diffère d’une explication USB 2.0 classique notamment parce qu’il doit fonctionner quel que soit le sens d’insertion
    • Le fait d’utiliser un connecteur USB-C ne détermine ni le débit ni la version USB
    • Un appareil USB-C peut très bien être en USB 2.0, ou être un appareil plus moderne en USB 3.0
    • La suite de la mise en pratique n’aborde pas davantage l’USB-C
  • Comment la paire différentielle réduit le bruit

    • Une ligne unique représente un bit en comparant sa tension à GND, mais sur une liaison filaire longue, l’effet du bruit peut devenir important
    • Une paire différentielle utilise deux lignes : l’une transporte V, l’autre -V
    • Le récepteur observe la différence de tension entre les deux lignes
    • Si l’on simplifie en supposant que le même bruit Vn s’ajoute aux deux lignes, alors (V + Vn) - (-V + Vn) = 2V, ce qui signifie que le bruit s’annule
    • Ce modèle est très simplifié ; pour une compréhension plus approfondie des paires différentielles, on peut consulter la vidéo YouTube d’Altium de Zach Peterson
    • Video 15

Manipuler l’USB sur un PCB

  • Si l’on utilise une carte de développement sans concevoir directement le matériel, on peut sauter la section PCB, mais les principes de base restent utiles lorsqu’on place un connecteur USB sur un PCB
  • Les composants de bibliothèque de connecteurs USB comportent les broches mentionnées plus haut, et la paire différentielle doit être routée vers les broches adjacentes correspondantes du microcontrôleur ou du SoC
  • Il y a trois principes de base pour le routage d’une paire différentielle
    • Faire correspondre la longueur de la piste allant de D+ à la broche plus du composant avec celle de l’autre piste
    • Placer les deux pistes très près l’une de l’autre
    • Tenir compte de l’impédance adaptée au signal
  • Comme les deux lignes traversent pratiquement le même environnement, on peut supposer dans le modèle simplifié précédent qu’elles subissent le même bruit
  • Le calcul d’impédance peut se faire à l’aide du calculateur du fabricant, en entrant l’impédance cible, la distance au plan de masse, l’écartement de la paire différentielle, etc., afin d’obtenir la largeur de piste nécessaire
  • Des vidéos de Zach Peterson sur les paires différentielles et le routage USB sont proposées en ressources

Débits de l’USB 2.0

  • USB 2.0 ne signifie pas qu’il n’existe qu’un seul débit fixe
  • L’USB 2.0 peut par exemple proposer deux niveaux de vitesse
    • full speed : 12 Mbit/s
    • high speed : 480 Mbit/s
  • Le device et le host doivent décider, lors de la connexion, quelle vitesse utiliser
  • Pour atteindre de manière fiable le high speed sur un PCB, l’adaptation d’impédance est importante
  • Pour un prototype de base, le full speed à 12 Mbit/s peut suffire et, si les pistes entre le connecteur USB et la puce sont courtes, on peut être relativement tolérant sur la largeur des pistes et d’autres paramètres

Protocole et couches logicielles

  • L’USB ne concerne pas seulement le matériel ; il faut aussi prendre en compte les couches logicielles côté host et côté device
  • Une vidéo d’environ 45 minutes expliquant l’USB du point de vue Linux est proposée en ressource
  • Cette vidéo traite des trames USB, des endpoints, des configurations, et de la manière dont un même device peut remplir plusieurs fonctions USB
  • Il est important de voir l’USB comme un réseau d’appareils
  • Il serait irréaliste que le système d’exploitation du host implémente séparément un driver pour chaque périphérique USB ; le système reconnaît donc plusieurs classes de périphériques USB
    • périphérique de stockage de masse
    • périphérique série
    • et de nombreuses autres classes
  • Le périphérique de la mise en pratique fonctionne, du point de vue du host, comme un port série

Créer un périphérique USB serial port avec STM32

  • Le périphérique de démonstration est un simple périphérique USB qui allume une LED lorsqu’il reçoit une requête du host
  • Le host reconnaît ce périphérique comme un serial port device
  • Il existe globalement deux approches d’implémentation
    • utiliser un microcontrôleur avec prise en charge USB
    • utiliser un SoC capable d’exécuter Linux pour laisser le noyau prendre en charge une grande partie du travail
  • Pour des raisons de simplicité, la mise en pratique utilise l’approche microcontrôleur
  • Carte NUCLEO-F103RB

    • La carte de développement utilisée est la NUCLEO-F103RB
    • Aux États-Unis, on peut l’acheter un peu au-dessus de 10 dollars chez des distributeurs comme Digikey
    • Cette carte est composée de deux parties attachées l’une à l’autre
    • La petite partie correspond à la zone du programmer, la grande à celle du MCU principal réellement programmé
    • La connexion USB par défaut de la carte ne va pas vers le MCU principal, mais vers le MCU du programmer
    • Ce programmer utilise le protocole ST-LINK pour échanger des messages USB avec l’ordinateur, puis programmer le MCU principal
    • Si l’on fabriquait directement un PCB à base de STM32, on pourrait n’avoir qu’une seule puce sur la carte, la programmer via USB, puis réutiliser ce même port USB pour la logique du périphérique
    • Des vidéos de Phil’s Lab sur les PCB STM32 et la programmation par USB sont proposées en référence
    • Video 19
    • Video 20

Câblage du véritable port USB

  • Comme la connexion USB par défaut de la carte Nucleo n’est pas reliée au MCU principal, il faut connecter directement le port USB du MCU principal au host
  • Après avoir chargé le programme, on débranche le programmer ST-LINK de l’ordinateur, puis on alimente le MCU principal via le vrai port USB
  • STM32CubeIDE est utilisé pour écrire le logiciel, et STM32CubeProgrammer pour le flasher sur la carte
  • Broches et configuration nécessaires

    • Quatre connexions sont nécessaires pour créer un périphérique USB 2.0
    • l’alimentation 5V venant du host
    • GND
    • D+
    • D-
    • Sur la NUCLEO-F103RB, pour alimenter le MCU principal depuis son port USB, il faut modifier le cavalier JP5 afin que la carte accepte une alimentation external 5 V
    • Dans STM32CubeIDE, il faut configurer les broches ainsi
    • PA12USB_DP
    • PA11USB_DM
    • La puce de cette carte nécessite une résistance de pull-up de 1.5 kΩ externe pour la connexion USB
    • La résistance applique un pull-up de PA12 vers 3.3V
    • Le montage est réalisé sur breadboard
    • Pour relier un MacBook Pro aux broches individuelles, un câble breakout USB acheté sur Amazon a été utilisé ; son terminal block a été retiré pour exposer les broches USB, puis des jumper wires ont servi à relier le tout à la carte Nucleo et à la breadboard
    • Même sans respecter strictement l’égalisation des longueurs ni le contrôle d’impédance de la paire différentielle, il a été possible d’établir dans cette démonstration une liaison à 12 Mbit/s

Écriture du logiciel USB CDC

  • Quand on configure les broches USB dans CubeIDE, une alerte liée à la configuration de l’horloge apparaît, et il est possible de choisir l’option où CubeIDE s’en charge automatiquement
  • Dans Pinout & Configuration, il faut configurer USB_DEVICE sous Middleware and Software Packs
  • Le mode important dans cette mise en pratique est le suivant
Communication Device Class (Virtual Port Com)
  • Avec ce réglage, la carte Nucleo se comporte, du point de vue du host, comme un périphérique CDC serial port
  • Le host peut, à partir de cette information de classe, choisir le driver approprié pour communiquer avec le périphérique custom
  • CubeIDE génère le code C, et main.c contient notamment l’appel d’initialisation suivant
MX_USB_DEVICE_Init();
  • Pour allumer la LED, on ajoute le code suivant à la routine CDC_Receive_FS
/* USER CODE BEGIN 6 */
if (Buf[0] == '1') {
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, 1);
}
  • Cet appel HAL allume la LED embarquée connectée à la broche 5 du port A
  • La vidéo de référence pas à pas est la suivante

Flash et exécution

  • Une fois le fichier ELF compilé, on envoie le code sur la carte avec CubeProgrammer
  • On débranche ensuite le programmer, puis on câble la carte en utilisant l’alimentation external 5V comme décrit plus haut
  • Lorsque la carte démarre, elle doit apparaître dans le gestionnaire de périphériques du système d’exploitation sous une entrée de type port COM ou Serial port
  • Dans le menu middleware USB_DEVICE de CubeIDE, on peut modifier la valeur de Device Descriptor pour définir le nom du périphérique custom affiché dans le gestionnaire de périphériques du système
  • Sous Mac OS, le nouveau périphérique apparaît dans le système de fichiers /dev
    • Exemple de chemin : /dev/tty.usbmodem497A0F6739561
  • Sous Linux, il peut apparaître sous un nom comme /dev/ttyUSB0
  • Exemple de connexion au périphérique série avec Minicom
minicom --device /dev/tty.usbmodem497A0F6739561
  • Une fois connecté, si l’on tape 1 au clavier, la LED verte de la carte Nucleo s’allume

Différences logicielles entre l’approche STM32 et l’approche Linux

  • Le résultat de la mise en pratique est un périphérique USB serial port reconnu par les systèmes d’exploitation grand public
  • L’approche fondée sur STM32CubeIDE présente plusieurs inconvénients du point de vue de l’ingénierie logicielle
    • il faut cliquer dans des menus d’interface pour générer beaucoup de boilerplate
    • on n’utilise pas une bibliothèque paramétrable souplement en code comme InitUsbDevice(UsbClass.CDC)
    • le code généré peut être fortement couplé au code utilisateur, ce qui complique la revue de code
    • il n’est pas clair comment mettre à jour le boilerplate lorsqu’une nouvelle version sort
    • la configuration actuelle reste fortement liée à l’écosystème STM32
  • La façon dont Linux se comporte comme un périphérique USB est jugée plus propre
    • les API Linux sont plus robustes et standardisées
    • l’interaction peut s’appuyer sur des pseudo-fichiers et des appels système
    • l’espace utilisateur et l’espace noyau sont séparés
    • on peut voir Linux comme une couche HAL
  • En revanche, si l’on a besoin d’un périphérique USB léger, peu coûteux et facile à produire, un SoC Linux peut être trop lourd et excessif pour de nombreux cas d’usage
  • Cela conduit à la conclusion qu’un framework plus portable et moins opinionated serait souhaitable pour le développement de périphériques USB bare metal

1 commentaires

 
GN⁺ 2024-06-04
Commentaires sur Hacker News
  • C’est un très bon article d’introduction à l’USB, mais il est très orienté vers l’utilisation des microcontrôleurs ST, et comparé aux approches récentes de l’écosystème ESP32, où l’on peut connecter et utiliser des périphériques USB plus facilement, il semble y avoir bien plus d’étapes et une chaîne d’outils beaucoup plus lourde
    Par ailleurs, du point de vue de quelqu’un qui a déjà fabriqué plusieurs cartes USB, les paires différentielles ne sont pas vraiment un sujet dont les débutants doivent trop s’inquiéter, cela devient surtout important pour le haut débit
    Comme les circuits intégrés de contrôleur USB couramment utilisés avec Arduino/ESP gèrent déjà beaucoup de détails, faire faire en plus les calculs à quelqu’un qui construit son premier périphérique paraît un peu excessif

    • J’ai récemment fabriqué un macro pad câblé à la main avec un Arduino Pro Micro équipé d’un ATmega32U4, et cela semble assez populaire dans la communauté des amateurs de claviers personnalisés
      C’était un projet rapide et amusant pour un débutant, et la partie la plus pénible a été de tailler le boîtier en bois
    • Je pense qu’avoir une compréhension de base des paires différentielles et de l’impédance est utile
      En pratique, je n’ai presque jamais fait les calculs moi-même, et le logiciel ECAD que j’utilise fournit des outils de routage de paires différentielles et d’analyse de signal d’impédance
      Malgré tout, si l’on garde les pistes très courtes, cela ne pose généralement pas de gros problème
    • J’aimerais qu’on me recommande des circuits intégrés de contrôleur USB
      En général, je n’ai utilisé que des microcontrôleurs avec USB intégré
  • J’ai effectué des tests de conformité USB il y a longtemps, et l’un des problèmes que je voyais souvent concernait le test de courant d’appel
    En gros, c’était quand il y avait trop de condensateurs de découplage sur le 5V, et j’ai l’impression que l’article ne traite pas ce point
    Il est facile de se concentrer sur la conception numérique haut débit, mais en conformité, ce sont parfois les aspects les moins glamour qui posent problème
    Je ne sais pas comment cela fonctionne dans les normes les plus récentes, mais il semble que ce test existe toujours, et l’article lui-même était bon
    https://compliance.usb.org/index.asp?UpdateFile=Electrical#:...

    • Je me demande quels composants on utilise pour la limitation du courant d’appel
      On peut sûrement fabriquer un limiteur de courant avec quelques transistors, mais j’imagine qu’il existe de meilleures solutions intégrées avec des fonctions comme la protection thermique
  • Pour compléter l’article à propos de l’USB-C, il faut connecter les broches CC aux résistances appropriées
    Sinon, il y a de fortes chances que cela ne fonctionne pas
    Et le routage différentiel ainsi que l’impédance ne sont pas un si gros sujet que ça en USB 2.0
    Il suffit d’avoir des pistes de longueur similaire, de les router directement de manière raisonnable et, si possible, de les garder proches l’une de l’autre
    Pas besoin de se préoccuper excessivement de l’ajustement fin des longueurs et largeurs de piste, du contrôle d’impédance ou des bonnes pratiques RF : il suffit de connecter le net

    • 480Mbit/s est déjà suffisamment rapide pour qu’il faille tout de même faire attention aux bonnes pratiques et à un appariement d’impédance à environ 10 % près
      Beaucoup de MCU exigent aussi des résistances en série entre les broches USB PHY et le connecteur
      Il n’y aura probablement pas à refaire toute la carte parce qu’il fallait utiliser des pistes de 20mil au lieu de 24mil, mais même si la mise en page des paires différentielles USB 2.0 reste assez tolérante, c’est un bon exercice d’essayer de la faire correctement
  • Si des composants difficiles à souder comme les processeurs ARM vous inquiètent, il n’est pas nécessaire d’utiliser des composants aussi gros
    Les STM32 sont bien quand on a besoin de performances, mais pour de petites tâches, un contrôleur plus modeste peut être préférable
    Par exemple, VUSB est une bibliothèque de bit-banging USB sur de petits microcontrôleurs Atmel : https://www.obdev.at/products/vusb/index.html
    Il existe aussi le schéma d’une carte d’exemple utilisée pour enseigner à des étudiants la programmation de modules du noyau Linux : https://gitlab.cs.fau.de/i4/passt/passtboard-v2
    Le firmware est ici : http://www.poempelfox.de/ds1820tousb/ et https://gitlab.cs.fau.de/i4/passt/ds1820tousb
    Si vous préférez la programmation façon Arduino, il existe aussi de nombreuses cartes qui peuvent se comporter comme un périphérique USB avec seulement quelques lignes de bibliothèques incluses, par exemple https://www.az-delivery.de/en/products/digispark-board

    • Si la soudure vous inquiète, on peut acheter à un prix très raisonnable des cartes de développement où le port USB du microcontrôleur est déjà présent et fonctionnel
      Par exemple, la NUCLEO-F429ZI : https://www.st.com/en/evaluation-tools/nucleo-f429zi.html
      Elle est très proche de la NUCLEO-F103RB utilisée par l’auteur, mais en plus du connecteur USB supérieur pour le programmateur/débogueur intégré, elle possède en bas un connecteur USB relié directement au microcontrôleur
      On peut aussi télécharger le schéma de la carte si l’on veut voir des choix comme la protection ESD
    • Il y a aussi le CH32V203
      C’est un TSSOP-20 avec USB matériel intégré, pour un prix unitaire d’environ 0,81 $
      En revanche, le logiciel peut être un peu plus délicat
      https://www.wch-ic.com/products/CH32V203.html?
  • J’ai déjà essayé d’écrire du code USB bare metal sur un MCU, et comparé à un protocole numérique simple comme SPI ou I2C, c’était assez déroutant
    La couche physique et la couche liaison de données ne sont pas tellement plus complexes que CAN, mais juste après, on se retrouve face au mur des descripteurs, des endpoints et de la configuration des pilotes
    L’USB a été conçu dès le départ comme un écosystème plug-and-play pour PC, et cela se voit clairement
    Si possible, il vaut clairement mieux utiliser au maximum les logiciels fournis par le fabricant
    De mémoire, pour un débit élevé, il vaut mieux utiliser des transferts bulk et éviter complètement les transferts isochrones
    L’USB est un protocole maître/esclave, donc si vous n’obtenez pas le débit maximal, la cause vient souvent de l’hôte, c’est-à-dire le PC
    Si la licence LGPL correspond à vos contraintes, libusb est plutôt facile à utiliser
    Si vous n’utilisez pas de pilote constructeur, un analyseur matériel du protocole USB est vraiment très utile, et USB in a NutShell est une bonne ressource web pour comprendre le protocole : https://www.beyondlogic.org/usbnutshell/usb1.shtml

  • Puisqu’on parle d’USB sur STM32, je voudrais demander si quelqu’un sait comment prendre en charge la réception de plus de 64 octets dans une trame
    Pour l’instant, je traite simplement des trames de 64 octets en boucle dans le logiciel, mais je sais qu’il est possible de faire plus grand
    Il me semble que cela allait jusqu’à 1 Mo au maximum
    Le problème, c’est que dans le Reference Manual, ces réglages apparaissent comme une sorte de pseudo-registres plutôt que comme des registres ordinaires
    Je me demande s’il existe une solution de contournement simple
    Il s’agit du périphérique USB, pas de l’OTG

    • Le transfert bulk USB full-speed est limité à 64 octets
      Je ne sais pas de quel STM32 il s’agit, mais le stm32g4 ne prend en charge que le full-speed
  • J’utilise surtout l’ESP32, mais il existe une méthode de bidouille bon marché adaptée à plusieurs projets
    Si vous avez besoin d’un contrôleur personnalisé très rapide et facile à réaliser, cela peut valoir le coup de récupérer la carte de contrôle d’un clavier USB mis au rebut
    Au lieu de souder, on peut fixer des fils aux contacts avec de la colle conductrice, puis les maintenir solidement avec un pistolet à colle
    J’ai déjà fabriqué ainsi un contrôleur de jeu à un seul bouton, basé sur un bouton d’arcade qui envoie l’entrée de la barre d’espace ; c’était peu coûteux mais assez robuste, tout le debounce, etc., est déjà géré, et aucun code n’est nécessaire

  • Je me demande s’il existe des cartes de développement avec prise en charge de l’USB 3
    J’essaie de prototyper un sink pour moniteur USB-C, mais j’ai du mal à trouver une carte assez performante pour recevoir du DisplayPort via USB

    • Il ne faut pas énormément de performances pour recevoir du DisplayPort via USB
      À condition bien sûr de pouvoir déjà recevoir le signal DisplayPort ou de le router vers un moniteur externe
      Il suffit d’implémenter un périphérique USB Billboard et de signaler le bon mode alternatif
      Pour le fonctionnement lui-même, c’est optionnel, mais de mémoire la spécification l’exige
      Ensuite, le signal DisplayPort sortira sur la prise USB-C, et il suffira de relier les bonnes lignes AUX au connecteur DisplayPort
    • Il existe cette carte : https://octopart.com/cyusb3kit-003-cypress+semiconductor-494...
      Le prix semble aussi raisonnable
      Quand j’ai cherché quelque chose de similaire il y a deux ans, c’était un ordre de grandeur plus cher
    • C’est possible avec la plupart des FPGA qui disposent de transceivers
  • J’ai un Raspberry Pi branché sur un PC et j’utilise https://github.com/xairy/raw-gadget pour créer un périphérique USB virtuel
    En ce moment, je m’en sers pour émuler un appareil photo MTP afin de tromper un logiciel propriétaire

  • J’ai déjà prototypé plusieurs périphériques USB avec un Raspberry Pi Zero et l’USB composite du noyau Linux
    Au minimum, il a été assez facile de faire fonctionner le stockage de masse et le port série
    Il faut une sorte de script shell pour initialiser le module composite du noyau sur le Raspberry Pi, et on peut trouver le code passe-partout dans la documentation du noyau

    • J’aimerais essayer de créer avec un Pi un périphérique USB virtuel de type webcam
      pikvm est aussi un projet intéressant
      Une fois connecté à un PC, l’USB ne fonctionne pas seulement comme un clavier et une souris, mais peut aussi devenir un lecteur USB utilisable au démarrage du système
      C’est une approche assez intéressante pour les installations de logiciels