AVR – Chapitre 9 – Créer son périphérique USB

L’usine à gaz USB

L’Universal Serial Bus est une norme de transfert de données en série (c’est à dire que les données sont transmises les unes à la suite des autres ; à opposer au mode parallèle où les données sont transmises via plusieurs canaux). Créé en 1994 par un consortium de constructeurs, l’USB est maintenant incontournable depuis le début des années 2000. Tout fonctionne maintenant par USB : claviers, souris, stockages de données, caméras, imprimantes, protocoles sans fil,.. L’idée de pouvoir rejoindre cette communauté doit vous sembler excitante !

Il est important de saisir certains concepts avant de se jeter à l’eau. Par ailleurs, je ne traiterai ici que des périphériques de version USB 1.1. Cette limitation est due aux caractéristiques techniques des micro-contrôleurs.

Les caractéristiques du bus

L’USB 1 et 2, se déploit sur quatre fils :

  • L’alimentation +5V VCC en rouge (parfois orange)
  • Le câble « données » D- en blanc (parfois jaune)
  • Le câble « données » D+ en vert
  • et la masse GND en noir (parfois bleu)

Communément, un périphérique USB 1.x peut être alimenté par un courant de 150mA (500mA pour l’USB 2.0). Si vous avez besoin de plus de puissance, il sera nécessaire de prévoir votre propre source de courant (batteries, alimentation, …).

Les connecteurs à chaque bout peuvent avoir différentes formes, les plus courants étant les suivantes :

Broches Type A Type B Mini-A Mini-B Micro-A Micro-B
VCC 1 1 1 1 1 1
D- 2 2 2 2 2 2
D+ 3 3 3 3 3 3
GND 4 4 5 5 5 5

La topologie

Le bus se dessine suivant une topologie en arbre, l’hôte étant la racine. Il est possible de connecter jusqu’à 127 périphériques, sur 5 niveaux (derrière des hubs).

L’USB peut fonctionner jusqu’à 3 mètres et même au-delà en utilisant des répétiteurs alimentés (ceci est néanmoins déconseillé).

Les identifiants

Chaque périphérique s’identifie grâce à deux codes :

  • le code fournisseur (vendor ID) sur 2 octets (soit 65536 possibilités). Ce code est distribué par l’USB Implementers Forum. Il est nécessaire de payer entre 2500 et 5000 US$ pour disposer du sien de façon officielle. Heureusement, il existe des codes pour les « bricoleurs » comme nous.
  • le code produit (product ID) sur 2 octets  (soit 65536 possibilités). Ce code est défini par le fournisseur à sa guise. Généralement, tous les produits d’une même génération partagent le même code produit.

On les représente communément sous forme hexadécimale, sur 4 chiffres, en les séparant par un deux-points. Voici quelques exemples :

  • Appareil photo Sony NEX-5R : 054c:066e
  • Téléphone Nexus 4 : 18d1:4ee1

Ce couple ne permettent pas d’identifier de manière unique un individu, seulement sa nature. Pour l’identifier précisément, il sera nécessaire de le prévoir de manière logicielle, ou en utilisant son chemin de connexion dans le réseau (par exemple, hub 2 > hub 2.1> hub 2.1.4 > port 2.1.4.5).

Par ailleurs, il existe une identification textuelle représentée par une chaîne de caractères.

Les classes USB

Vue la myriade de possibilités qu’offre la norme USB, il est nécessaire de regrouper les périphériques par classes, afin de factoriser l’effort à fournir pour le support de nouveaux matériels.

Ainsi, les souris, claviers, joysticks, etc. sont syndiqués sous la classe « Human interface device » (HID): 0x03. Les périphériques de stockage sont sous la classe « Mass storage » : 0x08. Cette page (en anglais) résume les classes officiellement déclarées.

Suivant la classe, les systèmes d’exploitation ont bâti des pilotes (drivers) afin de rendre plus ou moins transparente l’intégration des périphériques USB. Ainsi, la plupart des périphériques de classe HID sont supportés sans la nécessité de fournir des pilotes. C’est cette classe qui va nous intéresser, car il s’agit de la plus simple à mettre en œuvre.

Le « Report Descriptor »

Dans le cas de la classe HID, il est nécessaire de fournir un « report descriptor ». Il s’agit d’une chaîne d’entiers qui décrit le format de données que le périphérique va retourner à l’hôte.

Prenons l’exemple d’une souris ; celle-ci doit retourner les informations suivantes :

  • l’état de ses boutons (gauche, droite, milieu, molette vers le haut et molette vers le bas)
  • un mouvement sur l’axe X
  • un mouvement sur l’axe Y

La classe HID « mouse »

Voici une façon de représenter ces données, sur 3 octets :

octet bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
Boutons -     -     -     molette bas molette haut clic droit clic milieu clic gauche
Axe X entier relatif entre -128 et 127
Axe Y entier relatif entre -128 et 127

Voici à quoi correspond le rapport :

 

Pour aider à la rédaction de ce rapport, il est possible d’utiliser HID Descriptor Tool disponible ici. Voici à quoi cela ressemble :

HIDtoolJe ne vais pas  vous faire un cours sur le contenu détaillé de ces rapports. Il suffit simplement de savoir qu’ils existent et à quoi ils servent. Pour plus de détails, voici une bonne introduction (en anglais).

La classe HID « Custom »

Dans mon tutoriel, je vais utiliser une version allégée qui n’utilise pas de donnée formatée :

L’utilisation d’un tel rapport permet de s’affranchir des limitations du format HID, tout en étant facile à « installer » sur l’hôte. Cependant, il devient nécessaire d’utiliser un logiciel sur l’hôte pour se servir du périphérique.

La bibliothèque V-USB

AVR-GCC ne fournit pas de méthode pour utiliser l’USB, et ce n’est pas son but.

Il est donc nécessaire de charger une bibliothèque (library) tierce. V-USB est une bibliothèque qui propose une implémentation logicielle de la norme USB 1.1 pour les micro-contrôleurs AVR.

Celle-ci est proposée sous double licence : GPL version 2 ou commerciale. Cela implique que vos projets qui incluent cette bibliothèque doivent être publiés en accord avec la GPL (et donc avec distribution des sources), ou nécessite l’achat d’une licence commerciale (9.90€ pour une licence « Hobbyiste », 500€ pour une licence professionnelle).

Il existe des alternatives, mais la plupart nécessitent des micro-contrôleurs « puissants » ou des contrôleurs USB matériel. V-USB est utilisable sur des micro-contrôleurs modestes, dont l’ATTiny85.

Que contient-elle ?

La version actuelle (20121206) contient le répertoire usbdrv, dans lequel vous trouverez (entre autres) :

usbdrv.h les définitions nécessaires
usbdrv.c le cœur du pilote
usbdrvasm.S code assembleur utilisé par le pilote
usbdrvasm*.inc fichier appelé depuis usbdrvasm.S
asmcommon.inc fichier appelé depuis usbdrvasm.S
oddebug.c fournit plusieurs fonction de débogage
oddebug.h Les définitions requises par oddebug.c
usbportability.h Des définitions utilisés par le compilateur
usbconfig-prototype.h un exemple de fichier de configuration

Pour le bon fonctionnement du projet, il est important de copier l’intégralité du répertoire dans vos sources.

Comment fonctionne la bibliothèque ?

La configuration

Toute la configuration se trouve dans le fichier usbconfig.h, que vous devez ajouter à vos sources. Pour cela, vous pouvez copier le fichier usbconfig-prototype.h. Voici les points importants à adapter à votre projet :

Macro Description Exemple
USB_CFG_IOPORTNAME Port du microcontrôleur utilisé pour la communication en USB B
USB_CFG_DMINUS_BIT Numéro de broche utilisé par D- 0
USB_CFG_DPLUS_BIT Numéro de broche utilisé par D+ 2
USB_CFG_IS_SELF_POWERED Indique si votre périphérique dispose de sa propre source (0 : Non/ 1 : Oui) 0
USB_CFG_MAX_BUS_POWER Courant maximum que consomme votre périphérique (en mA) 50
USB_CFG_IMPLEMENT_FN_WRITE Indique si vous souhaitez utiliser la fonction usbFunctionWrite() ou non 0
USB_CFG_IMPLEMENT_FN_READ Indique si vous souhaitez utiliser la fonction usbFunctionRead() ou non 0
USB_CFG_LONG_TRANSFERS Indique si vous souhaitez faire des transferts de plus de 254 octets 0
USB_CFG_VENDOR_ID Octet faible et octet fort qui définissent l’identifiant fournisseur 0xc0, 0x16
USB_CFG_PRODUCT_ID Octet faible et octet fort qui définissent l’identifiant produit 0xdc, 0x05
USB_CFG_VENDOR_NAME Nom du fournisseur (il est demandé d’y mettre un nom de domaine) ‘c’,’i’,’c’,’a’,’t’,’r’,’i’,’c’,’e’,’.’,’e’,’u’
USB_CFG_VENDOR_NAME_LEN Taille de la macro fournisseur 12
USB_CFG_DEVICE_NAME Nom du produit ’I’,’R’,’-‘,’L’,’e’,’d’
USB_CFG_DEVICE_NAME_LEN Taille de la macro produit 6

Bien entendu, il reste beaucoup de macros, je vous laisse découvrir leur utilité dans les commentaires du code.

Les callbacks

V-USB implémente plusieurs « callbacks » (fonctions de retour), qui sont appelées à la suite d’événements précis. Cela ressemble à des interruptions, sauf que celles-ci sont purement logicielles :

  • usbFunctionSetup  : appelée à l’arrivée d’une requête ; c’est ici que vous définirez ce qu’il doit se passer au niveau du micro-contrôleur.
  • usbFunctionWrite  (nécessite USB_CFG_IMPLEMENT_FN_WRITE à 1) Cette fonction est appelée quand la fonction usbFunctionSetup retourne USB_NO_MSG et si la requête est de type USBRQ_HID_SET_REPORT (0x09).
  • usbFunctionRead  (nécessite USB_CFG_IMPLEMENT_FN_READ à 1) Cette fonction est appelée quand la fonction usbFunctionSetup retourne USB_NO_MSG et si la requête est de type USBRQ_HID_GET_REPORT (0x01).

Il y en a d’autres mais ce sont les principales. Ce diagramme résume l’utilisation que vous pouvez faire de ces méthodes.

USBlibs

La bibliothèque LibUSB

Comme je l’ai indiqué lors du chapitre sur les « report descriptors », nous allons avoir besoin d’un programme sur la machine hôte qui se chargera de dialoguer avec le micro-contrôleur branché en USB. Je vais utiliser ici libusb.

Ne voulant pas m’embêter avec la signature des pilotes sous Windows (obligatoire depuis la version 8) j’ai décidé de tout faire sous Linux (j’utilise une Fedora 20).

LibUSB est une bibliothèque dont le but est de fournir des méthodes de gestion et d’accès aux périphériques branchés en USB.

Le programme se découpe en deux parties :

  • une partie d’initialisation, où l’on cherche le périphérique USB qui nous intéresse
  • une partie d’exécution, où l’on effectue les actions nécessaires, notamment grâce à la méthode  libusb_control_transfer()  que vous avez pu apercevoir dans mon diagramme plus haut.

La structure du projet

Pour mon exemple, je vais utiliser un petit projet qui permet à mon PC de servir de télécommande pour une bande de LED RGB.

Aperçu

Voici comment doit se présenter votre projet :

  \dirtree{% .1 projet. .2 commands. .3 send.c. .3 Makefile. .2 usbdrv. .3 .... .2 Makefile. .2 remote.c. .2 requests.h. .2 usbconfig.h. }

Vous verrez dans beaucoup d’exemples que les fichiers remote.c , requests.h ,  usbconfig.h  et Makefile (celui du micro-contrôleur) se trouvent dans un répertoire firmware. Vous pouvez ranger ces fichiers comme bon vous semble, l’important est que cette structure soit claire pour vous.

Le schéma

Avant de parler du code, voici un exemple de branchements :ir_rgb_schémaIl n’y a pas grand chose à dire sur ce schéma. Les deux diodes Zener doivent limiter la tension à 3.3V.

 

Les définitions

usbconfig.h

Comme décrit plus tôt, il s’agit de la configuration pilote V-USB. Celui est chargé automatiquement, à partir du moment où votre projet inclus le fichier usbdrv.h . Il sera également inclus lors de la compilation du programme hôte.

 

requests.h

Ce fichier contient les requêtes USB personnalisées. Comme pour le fichier précédent, il sera inclus lors de la compilation.

 

Le programme du micro-contrôleur

remote.c

Il s’agit du code principal, qui sera envoyé sur le micro-contrôleur.

 

Makefile

Ici il faut faire quelques ajouts, pour prendre en compte la bibliothèque. J’ai mis en surbrillance les lignes qui diffèrent par rapport à d’habitude.

 

Le programme hôte

Ici, tout se passe sur une machine sous Fedora 20.

commande-hote.c

Comme vous pouvez le voir, ce code à besoin d’inclure les fichiers  ../requests.h  et ../usbconfig.h .

Makefile

Je ne sais pas ce qu’il en ait quant à l’outil pkg-config en dehors de Fedora, mais celui est plutôt pratique puisqu’il génère les options tout seul.

Utiliser un ATTiny85

Le problème que pose l’utilisation d’un ATTiny, est qu’il faut utiliser une horloge cadencée au minimum à 12MHz. Or l’ATTiny ne fonctionne correctement à 8MHz maximum. On va utiliser une astuce pour le passer à 16.5MHz sans cristal externe : la PLL (Phase-Locked Loop : boucle à verrouillage de phase).

Sans entrer dans les détails, il s’agit d’un dispositif qui permet d’augmenter la fréquence d’un micro-contrôleur (basée sur un cristal), au prix de sa stabilité. Il est ainsi possible de faire fonctionner un ATTiny au delà de 80MHz ! Mais dans notre cas, 16.5MHz suffiront.

Pour cela il est nécessaire d’harmoniser la fréquence au début de l’exécution. Ceci se fait en définissant un « hook » dans le ficher usbconfig.h , en y ajoutant la fonction hadUsbReset() , qui s’occupera d’appeler une autre fonction ( calibrateOscillator() ), qui calibrera l’oscillateur PLL.

L’exemple donné dans cet article prend déjà en compte ces modifications.

Dans le cas d’un micro-contrôleur capable d’utiliser une horloge à 16.5MHz, il n’y a pas besoin de ce dispositif, et donc des fonctions hadUsbReset() ,  calibrateOscillator()  ainsi que des lignes suivantes dans usbconfig.h  :

 

 Et voilà !

Voici les sources du projet : RGB remote (fichier 7z).

Lorsque vous branchez votre périphérique, les messages suivants doivent apparaitre dans dmesg.

Une fois le programme hôte compilé, vous pouvez envoyer des commandes au micro-contrôleur avec les commandes suivantes :

 

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

*