I. Introduction

Commencer de nouveau avec l'USB peut être intimidant. Avec la spécification de l'USB 2.0 de 650 pages, on pourrait facilement être découragé par la dimension absolue du standard. C'est seulement le commencement d'une longue liste de standards associés pour l'USB. Il y a des standards de Classes USB tels que la Spécification Classe HID qui détaille l'opération commune des appareils (claviers, souris, etc.) tombant sous la Classe HID (Interface Homme Machine) - seulement 97 autres pages. Si vous concevez un hôte (Host) USB, alors vous avez trois standards d'Interfaces Contrôleurs d'hôte à choisir. Aucun de ceux-ci n'est détaillé dans la spécification de l'USB 2.0.

La bonne nouvelle est que vous n'avez pas besoin de vous ennuyer à lire le standard USB en entier. Quelques chapitres ont été pondus par le marketing, d'autres parlent de couches bas niveau pris en charge par le circuit intégré contrôleur d'USB et une partie concerne les développeurs de host et de hub. Parcourons les différents chapitres de la spécification 2.0 et introduisons les points-clés.

Image non disponible
Image non disponible
Image non disponible
Image non disponible

Donc maintenant nous pouvons commencer à lire les chapitres relatifs à nos besoins. Si vous développez des pilotes (logiciels) pour les périphériques USB alors vous pouvez avoir besoin de lire seulement les chapitres :

o 4 - Vue d'ensemble, architecture ;

o 5 - Modèle de flux de données de l'USB ;

o 9 - Cadre de travail du circuit USB ;

o 10 - USB Host Matériel et Logiciel.

Les concepteurs de périphériques électroniques peuvent seulement avoir besoin de lire les chapitres :

o 4 - Vue d'ensemble, architecture ;

o 5 - Modèle de flux de données de l'USB ;

o 6 - La mécanique ;

o 7 - L'électrique.

I-A. L'USB en bref pour les concepteurs de périphériques

Maintenant voyons les choses en face (1) la plupart d'entre nous sommes ici pour développer des périphériques USB et (2) il est courant de lire la documentation d'un standard sans avoir une idée de la manière dont il faut se servir de l'appareil. Aussi dans les sept prochains chapitres, nous nous concentrons sur les parties pertinentes nécessaires au développement d'un appareil USB. Ceci vous permet de comprendre l'USB et ces problèmes et vous guidera dans une recherche approfondie des problèmes spécifiques relatifs à votre application.

Le standard USB 1.1 était suffisamment complexe avant que la haute vitesse soit introduite dans l'USB 2.0. Afin d'aider à la compréhension des principes fondamentaux de l'USB, nous omettons volontairement beaucoup de domaines spécifiques aux appareils hautes vitesses.

I-B. Présentation du bus série universel

L'USB version 1.1 comprenait deux vitesses, un mode vitesse rapide de 12 Mbit/s et un mode vitesse lente de 1,5 Mbit/s. Le mode de 1,5 Mbit/s est plus lent et moins sujet aux perturbations électromagnétiques (EMI) réduisant ainsi le coût de perles de ferrites et des composants de qualité. Par exemple les quartz peuvent être remplacés par des résonateurs meilleur marché.

L'USB 2.0 qui est encore à la veille de voir le jour sur les ordinateurs grand public a fait monter les enchères jusqu'à 480 Mbit/s. Le 480 Mbit/s est connu sous le nom de mode haute vitesse et a été créé pour entrer en compétition avec le bus Série Firewire.

I-C. Les vitesses USB

· Vitesse haute - 480 Mbit/s.....High Speed ;

· Vitesse pleine - 12 Mbit/s.....Full Speed ;

· Vitesse basse - 1,5Mbit/s.....Low Speed.

Le bus série universel est contrôlé par l'hôte. Il ne peut y avoir qu'un hôte par bus. La spécification en elle-même ne comporte aucune forme d'arrangement multimaître. Cependant la spécification "au pied levé" (On-The-Go specification) qui a été rajoutée à l'USB 2.0 a introduit un protocole de négociation de l'hôte qui permet à deux appareils de négocier pour le rôle d'hôte. Ceci est uniquement réservé à des connexions simples point par point telles qu'un téléphone mobile ou un organisateur personnel et non à un hub multiple ou à des configurations d'appareils de bureau multiples. L'hôte USB a la charge de mener à bien toutes les transactions et de programmer la bande passante. Les données peuvent être envoyées par différentes méthodes de transactions en utilisant un protocole basé sur un système de jetons (Token).

À mon avis la topologie du bus de l'USB limite quelque peu. L'une des intentions originales de l'USB était de réduire la quantité de câbles à l'arrière de votre PC. Les gens d'Apple diront que l'idée est venue du bus pour appareil de bureau de chez Apple, où le clavier, la souris et d'autres périphériques pouvaient être connectés ensemble (daisy chained ou connexions en guirlandes) en utilisant le même câble.

Cependant USB utilise une topologie en étoile à étages, qui ressemble à celle d'Ethernet base10.

Ceci impose l'utilisation d'un hub quelque part, ce qui sous-entend une plus grande dépense, davantage de boîtes sur votre bureau et davantage de câbles. Cependant la situation n'est pas aussi mauvaise qu'on le croit. Beaucoup d'appareils comprennent des hubs USB. Par exemple votre clavier peut contenir un hub qui est connecté à votre ordinateur. Votre souris et d'autres appareils tels qu'un caméscope numérique peuvent être branchés facilement au dos de votre clavier. Les moniteurs ne sont que d'autres périphériques sur une longue liste d'appareils qui comporte communément des hubs intégrés.

Cette topologie en étoile à étages, comparée à des appareils à simple connexion en guirlandes (daisy chaining) comporte tout de même des avantages. D'abord l'alimentation de chaque appareil peut être contrôlée et même coupée si des conditions de surintensité se produisent sans perturber d'autres appareils USB. Les appareils à haute vitesse ainsi que ceux à vitesse pleine et basse peuvent être maintenus alors que le hub filtre les transactions de vitesse haute et pleine de façon à ce que les appareils à vitesse lente ne les reçoivent pas.

On peut connecter jusqu'à 127 appareils à un bus USB à un temps donné. Vous avez besoin de davantage d'appareils ? Ajouter simplement un autre port ou un autre hôte. Alors que la plupart des hôtes USB précédents n'avaient que deux ports, la plupart des constructeurs ont considéré ceci comme limitatif et commencent à introduire quatre et cinq ports hôtes sur carte avec un port interne pour les disques durs, etc. Les hôtes précédents n'avaient qu'un contrôleur USB et ainsi les deux ports partageaient la même bande passante USB disponible. Comme les exigences des bandes passantes ont augmenté, nous commençons à voir des cartes multiports avec deux contrôleurs ou plus qui permettent des canaux individuels.

Les contrôleurs d'hôte USB ont leurs propres caractéristiques. L'USB 1.1 comprenait deux spécifications de contrôleur d'interface hôte, UHCI (Contrôleur d'Interface d'hôte universel) développé par Intel qui met davantage le poids sur le logiciel Microsoft par exemple et qui permet d'avoir des appareils meilleur marché et le OHCI (Contrôleur d'Interface d'hôte ouvert) développé par Compaq, Microsoft et National Semiconductor qui met davantage la charge de travail sur l'appareil (Intel) et se contente de logiciels plus simples. Ce sont les relations typiques entre les ingénieurs qui fabriquent le matériel et ceux qui font les logiciels…

Avec l'apparition de l'USB 2.0, on a eu besoin d'une nouvelle caractéristique de contrôleur d'interface d'hôte pour décrire les détails du niveau d'enregistrement spécifique à l'USB 2.0.

Le EHCI (Contrôleur d'Interface d'hôte Accrue) était né. Les collaborateurs les plus importants comprennent Intel, Compaq, Nec, Lucent et Microsoft, il semble donc comme nous l'espérons qu'ils se seraient regroupés pour nous fournir une interface standard et ainsi un seul nouveau driver (pilote) à exécuter dans notre O.S. (Operating System ou système d'exploitation). Il était temps.

L'USB comme son nom l'indique est un bus Série. Il utilise quatre fils isolés dont deux sont l'alimentation (+5V et GND). Les deux restants forment une paire torsadée qui véhicule les signaux de données différentiels. Il utilise un schéma d'encodage NRZI (pas de retour à zéro inversé) pour envoyer des données avec un champ sync de manière à synchroniser les horloges de l'hôte et du récepteur.

L'USB supporte le système "plug'n play" branchement à chaud avec des drivers qui sont directement chargeables et déchargeables. L'utilisateur branche simplement l'appareil sur le bus.

L'hôte détectera cet ajout, interrogera l'appareil nouvellement inséré et chargera le driver approprié pendant le temps qu'il faut au sablier pour clignoter sur votre écran assurant qu'un driver est installé pour votre appareil. L'utilisateur final n'a pas besoin de se soucier des terminaisons, de termes tels que IRQ et adresses de ports, ou de la réinitialisation de l'ordinateur. Une fois que l'utilisateur a terminé, on peut simplement retirer le câble, l'hôte détectera cette absence et déchargera automatiquement le driver.

Le chargement du driver approprié sera réalisé en utilisant une combinaison PID/VID (Interface Produit Machine/Vendeur Machine). On peut se procurer le VID au forum des fournisseurs USB en payant, ce qui est considéré comme un autre point de blocage par USB. On peut trouver le catalogue des tarifs réactualisés sur le site web des fournisseurs USB.

D'autres organisations fournissent un VID supplémentaire pour des activités non commerciales telles que l'enseignement, la recherche ou le bricolage. Le forum des fournisseurs USB ne peut pas encore vous procurer ce service. Dans ce cas vous voudrez peut-être utiliser un appareil distribué chez le constructeur de votre système de développement. Par exemple la plupart des constructeurs de puces auront une combinaison VID/PID que vous pouvez utiliser pour vos puces et qui n'est pas connue comme appareil commercialisé. D'autres constructeurs de puces peuvent même vous vendre un PID à utiliser avec leur VID pour votre appareil commercial.

Une autre caractéristique intéressante de l'USB réside dans ses modes de transferts. L'USB soutient des transferts de contrôles, d'interruptions, en bloc et isochrones. Lorsque nous examinerons les autres modes de transferts ultérieurement, nous nous rendrons compte que l'isochrone permet à un appareil de réserver une approximation définie de la bande passante avec un temps d'attente garanti. Ce système se révèle idéal dans les applications audio et vidéo ou l'encombrement peut susciter une perte de données ou une chute de trames. Chaque mode de transfert fournit au concepteur des compromis dans les domaines de la détection d'erreur et de la reprise, du temps d'attente garanti et de la bande passante.

II. Le matériel

II-A. Les connecteurs

Tous les appareils ont une connexion amont vers l'hôte et tous les hôtes ont une connexion aval vers l'appareil. Les connecteurs amont et aval ne sont pas interchangeables mécaniquement, éliminant ainsi les connexions de rebouclage interdites aux hubs comme pour un port aval connecté à un port aval. Il y a généralement deux types de connecteurs, appelés type A et type B présentés ci-dessous.

Image non disponible

Les prises mâles de type A sont toujours tournées vers l'amont. Les prises femelles de type A se trouveront généralement sur les hôtes et les hubs. Par exemple, les prises femelles de type A sont courantes sur les cartes mères des ordinateurs et les hubs. Les prises mâles de type B sont toujours connectées vers l'aval et par conséquent les prises femelles de type B se trouvent sur les appareils.

Il peut être intéressant de trouver des câbles de type A vers type A avec un câblage direct et une matrice de changeur de genre USB dans certains magasins d'ordinateurs. C'est en contradiction avec la spécification USB. Les seuls appareils avec prise type A vers prise type A sont des ponts que l'on utilise pour brancher deux ordinateurs entre eux. D'autres câbles prohibés sont les extensions USB qui ont une prise mâle à une extrémité (soit de type A ou de type B) et une prise femelle à l'autre. Ces câbles ne respectent pas les exigences de longueur de câble de l'USB.

L'USB 2.0 comprenait un errata qui présente les miniconnecteurs USB de type B. On peut trouver les détails de ces connecteurs dans la notice changement technique des miniconnecteurs B. La raison d'être des miniconnecteurs est issue de la gamme d'appareils électroniques miniatures comme les téléphones mobiles et les agendas électroniques. Le connecteur ordinaire de type B est trop grand pour être facilement intégré à ces appareils.

Il vient de paraître une spécificité On-the-Go qui ajoute la fonctionnalité pair-à-pair (peer to peer) à l'USB. D'où l'introduction des hôtes USB dans les téléphones mobiles et les agendas électroniques, de même qu'une particularité pour les prises mâles mini A, les prises femelles mini A et les prises femelles mini A-B. Tout porte à croire que nous devrions être inondés de câbles mini USB et d'une gamme de câbles convertisseurs de la taille mini à standard.

Image non disponible

On utilise des couleurs standards pour les fils intérieurs des câbles USB de façon à faciliter l'identification des fils d'un constructeur à un autre. La normalisation précise les différents paramètres électriques pour les câbles. Il peut être intéressant de lire en détail la documentation sur l'USB 1.0 d'origine qui est incluse. Vous comprendrez qu'elle précise les caractéristiques électriques, mais le paragraphe 6.3.1.2 suggérait que la couleur recommandée pour le surmoulage sur les câbles devrait être blanc comme neige. Quelle monotonie ! Quand on sait que l'USB 1.1 et USB 2.0 ont été assouplis pour recommander le noir, le gris ou le naturel.

Les concepteurs PCB voudront se référer au chapitre 6 pour des empreintes de pas standards et des affectations des broches.

II-B. L'électrique

À moins que vous ne conceviez le silicium pour un appareil ou émetteur/récepteur USB ou bien pour un hôte ou hub USB, il n'y a pas grand-chose à retenir des particularités électriques du chapitre 7. Nous vous précisons brièvement les points essentiels ici même.

Comme nous en avons déjà discuté, l'USB utilise une paire de transmissions différentielle pour les données. Celle-ci étant codée en utilisant le NRZI et est garnie de bits pour assurer les transitions adéquates dans le flot de données. Sur les appareils à vitesse basse et pleine un '1' différentiel est transmis en mettant D+ au-dessus de 2,8 V grâce à une résistance de 15 kOhms relié à la masse et D- en dessous de 0,3 V avec une résistance de 1,5 kOhms relié à 3,6 V. D'autre part un différentiel '0' correspond à D- plus grand que 2,8 V et D+ inférieur à 0,3 V avec les mêmes résistances de rappel état haut/bas adéquates.

Le récepteur définit un différentiel '1' avec D+ plus grand de 200 mV que D- et un différentiel '0' avec D+ plus petit de 200 mV que D-. La polarité du signal est inversée en fonction de la vitesse du bus. En conséquence les états référencés par les termes 'J' et 'K' sont utilisés pour signifier les niveaux logiques. En vitesse basse, un état 'J' est un différentiel '0'. En vitesse haute, un état 'J' est un différentiel '1'.

Les émetteurs/récepteurs USB comprendront à la fois des sorties différentielles et uniques (non complémentaires). Certains états de bus USB sont indiqués par des signaux à sorties uniques (single ended zero) sur D+, D- ou les deux. Par exemple un zéro à sorties uniques ou SE0 peut être utilisé pour signifier la réinitialisation d'un appareil s'il est maintenu plus de 10 ms. On génère un SE0 en maintenant D+ et D- en position basse (inférieur à 0,3 V). Les sorties uniques et différentielles sont importantes d'être notées si vous utilisez un émetteur/récepteur et un FPGA comme appareil USB. Vous ne pouvez pas vous contenter simplement d'échantillonner la sortie différentielle.

Le bus basse et pleine vitesse a une impédance caractéristique de 90 Ohms +/-15 %. Il est donc important d'observer la documentation technique lorsque vous sélectionnez les résistances des caractéristiques électriques séries pour D+ et D- afin d'équilibrer l'impédance. Toute documentation technique devrait spécifier ces valeurs et tolérances.

Le mode vitesse haute (480 Mbit/s) utilise un courant constant de 17,78 mA pour demander de réduire le bruit.

II-C. Identification de la vitesse

Un appareil USB doit indiquer sa vitesse en mettant soit D+ ou D- à 3,3 V. Un appareil pleine vitesse, représenté plus bas utilisera une résistance de rappel rattaché à D+ pour se signaler comme tel. Ces résistances de rappel à l'extrémité de l'appareil seraient aussi utilisées par l'hôte ou hub pour détecter la présence d'un appareil connecté à son port. Sans résistance de rappel, l'USB suppose qu'il n'y a rien de connecté au bus. Certains appareils possèdent cette résistance intégrée sur le silicium, pouvant être connectée ou non sous commande microprogrammée, d'autres exigent une résistance externe.

Par exemple Philips Semiconducteur a une technologie Soft connectTM. Lors d'une première connexion au bus, elle permet au microcontrôleur d'initialiser la fonction USB de l'appareil avant de valider la résistance de rappel d'identification de vitesse, indiquant ainsi qu'un appareil est relié au bus. Si la résistance de rappel était connectée à VBUS, alors celle-ci indiquerait qu'un appareil est branché au bus dès que la prise est insérée. L'hôte peut alors tenter de réinitialiser l'appareil et demander un descripteur au moment où le microprocesseur n'a pas encore commencé à initialiser la fonction USB de l'appareil.

D'autres fournisseurs tels que Cypress Semiconducteur utilisent aussi une résistance programmable à des fins de renumérationTM dans leurs appareils EzUSB ou l'appareil peut être énuméré par une fonction telle que la programmation sur champ puis être déconnecté du bus sous commande microprogrammée, et énuméré en tant qu'appareil différent, tout cela, en un clin d'œil. La plupart des appareils EzUSB n'ont pas de Flash ou de ROM OTP pour stocker le code. Ils sont amorcés dès le branchement.

Image non disponible
Image non disponible

Vous noterez que nous n'avons pas inclus d'identification de vitesse pour le mode haute vitesse.

Les appareils haute vitesse démarreront dès qu'ils seront connectés en tant qu'appareils pleine vitesse (1,5 kOhms à 3,3 V). Une fois qu'il sera attaché, il émettra un Chirp à haute vitesse pendant la réinitialisation et établira une connexion à grande vitesse si le hub le supporte. Si l'appareil fonctionne en mode haute vitesse, alors la résistance de rappel est retirée pour équilibrer la ligne.

Un appareil de détection d'anomalie pour l'USB 2.0 n'est pas utile pour supporter le mode haute vitesse. Cela permet d'utiliser des appareils meilleur marché si la vitesse n'est pas critique. C'est aussi le cas des appareils USB1.1 basse vitesse qui n'est pas nécessaire pour supporter la pleine vitesse.

Toutefois un appareil haute vitesse ne peut pas supporter le mode basse vitesse. Il devrait seulement supporter le mode pleine vitesse nécessaire en début de connexion, ensuite le mode haute vitesse s'il réussit par être négocié. Un appareil face aval à détection d'anomalies pour l'USB 2.0 (hub ou hôte) doit supporter les trois modes, haute, pleine et basse vitesse.

II-D. Alimentation (VBUS)

Un des avantages de l'USB réside dans ces appareils alimentés par le bus. Ceux-ci obtiennent leur alimentation à partir du bus et ne demandent aucune prise externe et câble additionnel. Cependant beaucoup de gens se focalisent sur cette option sans prendre en compte au préalable tous les critères nécessaires.

Un appareil USB précise sa consommation électrique exprimée en unité de 2 mA dans le descripteur de la configuration que nous examinerons plus tard. Un appareil ne peut pas augmenter sa consommation électrique plus qu'il n'est précisé pendant l'énumération, même s'il perd de la puissance externe. Il existe trois classes de fonctions USB :

· les fonctions alimentées par le bus à basse puissance (Low-Power) ;

· les fonctions alimentées par le bus à haute puissance (High-Power) ;

· les fonctions autoalimentées (Self-powered).

Les fonctions alimentées par le bus à basse puissance tirent toute leur puissance de VBus et ne peuvent en tirer que la charge d'une unité. La spécification USB définit une charge d'unité à 100mA.

Les fonctions alimentées par le bus à basse puissance peuvent aussi être conçues pour travailler à une tension de VBUS tombant à 4,4 V et montant à un maximum de 5,25 V mesurés à la prise amont de l'appareil. Pour beaucoup d'appareils 3,3 V, des régulateurs LDO sont obligatoires.

Les fonctions alimentées par le bus à haute puissance tireront toute leur puissance du bus et ne pourront tirer plus d'une unité de charge jusqu'à ce qu'elles aient été configurées, après quoi elles pourront tirer cinq unités de charge (500 mA max) pourvu que cela soit demandé dans son descripteur. Les fonctions du bus à haute puissance doivent être capables d'être détectées et énumérées à un minimum de 4,40 V. Lorsqu'elles fonctionnent à pleine charge, un VBUS minimum de 4,75 V est précisé avec un maximum de 5,25 V. Une fois de plus, ces mesures sont prises à la prise mâle amont.

Les fonctions autoalimentées peuvent tirer jusqu'à une unité et faire dériver le reste de leur alimentation d'une source extérieure. Si cette source extérieure venait à manquer, il doit y avoir des réserves en place de manière à ne pas tirer plus d'une unité de charge du bus. Les fonctions autoalimentées sont plus faciles à concevoir au niveau de la spécification, car il ne peut guère y avoir de problèmes en ce qui concerne l'alimentation électrique. La charge alimentée parle bus à une seule unité permet la détection de l'énumération d'appareil sans avoir besoin d'une alimentation principale/secondaire du secteur.

Aucun appareil USB, qu'il soit alimenté par le bus ou bien autoalimenté ne peut piloter VBus sur son port face amont. Si VBus est perdu, l'appareil a une durée de dix secondes pour retirer l'alimentation des résistances de rappel de D+/D- utilisées pour l'identification de la vitesse.

Les autres considérations de VBUS sont l'appel de courant qui doit être limité. Ceci est souligné dans le paragraphe 7.2.4.1 des spécifications USB et est souvent laissé de côté. L'appel de courant est soutenu au niveau de la capacité de tête de votre appareil entre VBUS et la masse. Les spécifications précisent par conséquent que la capacité de découplage maximum que vous pouvez avoir sur votre appareil est de 10 μF. Quand vous déconnectez l'appareil après que le courant est passé par le câble USB inductif, une grande tension de retour peut se produire sur l'extrémité ouverte du câble.

Pour éviter ceci, on recommande une capacité de découplage VBUS minimum de 1 μF.

Pour l'appareil habituel alimenté sur un bus, celui-ci ne peut drainer plus de 500 mA, ce qui est fort raisonnable. Où est donc la complication, me demandez-vous ? Peut-être dans le mode veille (suspend) ?

II-E. Courant de veille

Le mode Veille est obligatoire sur tous les appareils. Pendant son temps d'action, d'autres contraintes surviennent. Le courant maximum de veille est proportionnel à l'unité de charge. Pour un appareil d'une unité de charge (par défaut) le courant de veille maximum est de 500 μA. Ceci comprend le courant dû aux résistances de rappel sur le bus. Au niveau du hub, D- et D+ possèdent des résistances de rappel niveau bas de 15 kOhm. Pour des raisons de consommation électrique, la résistance de rappel niveau bas de l'appareil est montée en série avec la résistance de rappel niveau haut de 1,5 kOhm, totalisant ainsi une charge de 16,5 kOhm sur VTERM habituel de 3,3 V. Par conséquent cette résistance draine un courant de 200 μA avant même que l'on commence.

Une autre considération pour bon nombre d'appareils est le régulateur de 3,3 V. Un grand nombre d'appareils USB fonctionnent sur 3,3 V. Le PDIUSBD11 en est un exemple. Les régulateurs linéaires sont habituellement tout à fait inefficaces avec des courants de repos moyen de l'ordre de 600 μA, par conséquent on aura besoin de régulateurs plus efficaces et donc plus chers. Dans la majorité des cas, vous devez ralentir ou arrêter les horloges pour que la consommation du microcontrôleur tombe en dessous de la limite de 500 μA.

Beaucoup de développeurs demandent au forum des fournisseurs USB, quelles sont les complications si l'on dépasse cette limite ? On comprend bien, que la plupart des hôtes et des hubs n'ont pas la possibilité de détecter de surcharge de cette importance et ainsi si vous drainez peut-être 5 mA ou même 10 mA cela devrait aller, tout en gardant à l'esprit qu'à la fin de la journée, votre appareil a enfreint la spécification USB. Toutefois en fonctionnement normal, si vous essayez de dépasser les 100 mA ou la charge permise qui vous est indiquée, alors attendez-vous à ce que le hub ou hôte le détecte et déconnecte votre appareil, dans l'intérêt de l'intégrité du bus.

Bien sûr, ces problèmes de conception peuvent être évités si vous choisissez de concevoir un appareil autoalimenté. Les courants de veille peuvent ne pas être d'un grand intérêt pour les ordinateurs de bureau, mais avec l'introduction de la spécification On-The-Go, nous allons commencer à voir des hôtes USB construits dans les téléphones mobiles et agendas électroniques.

La consommation électrique utilisée à partir de ces appareils affectera défavorablement la durée de vie de la batterie.

II-F. Accès au mode veille

Un appareil USB entrera en veille lorsqu'il n'y a aucune activité sur le bus pendant plus de 3 ms. Il dispose ensuite de 7 ms de plus pour éteindre l'appareil et ne prendre que le courant de veille désigné, ne prenant ainsi que le courant de veille nominal à partir du bus 10 ms après que l'activité du bus se soit arrêtée. Afin de le maintenir connecté à un hub ou à un hôte mis en veille, l'appareil doit encore fournir de l'alimentation à ses résistances de rappel de sélection de vitesse pendant le mode veille.

L'USB possède un démarrage de trames de bits (frame packet) ou bien d'entretien qui sont envoyés périodiquement sur le bus. Ceci empêche un bus inutilisé d'entrer dans le mode veille en l'absence de données.

· Un bus haute vitesse enverra une trame toutes les 125.0 μs ± 62.5 ns.

· Un bus pleine vitesse enverra une trame toutes les 1.000 ms ± 500 ns.

· Un bus basse vitesse aura un dispositif d'entretien qui est un EOP (End Of Packet ou Fin De

Paquet) toutes les 1 ms simplement en l'absence de données basse vitesse.

Le terme veille globale (Global Suspend) est utilisé lorsque le bus USB entier entre collectivement dans le mode veille. Cependant les appareils sélectionnés peuvent être mis en veille en ordonnant au hub sur lequel l'appareil est aussi connecté. On fait référence à cette opération comme mode "veille sélective".

L'appareil reprendra son fonctionnement quand il recevra tout signal qui n'est pas en attente. Si un appareil possède une mise en service de réveil retardé, alors il devra signaler à l'hôte de reprendre à partir du mode veille.

II-G. Taux de transfert de données

Un autre domaine qui est souvent négligé est la tolérance de l'horloge USB. Elle est précisée dans la spécification USB 7.1.11 :

· les données haute vitesse sont cadencées à 480.00 Mb/s avec une tolérance de transmission

de données de ± 500 ppm ;

· les données pleine vitesse sont cadencés à 12.000 Mb/s avec une tolérance de transmission

de données de ± 0.25 % ou 2,500 ppm ;

· les données basse vitesse sont cadencées à 1.50 Mb/s avec une tolérance de transmission de

données de ±1.5 % ou 15,000 ppm.

Cela permet aux résonateurs d'être utilisés pour des appareils basse vitesse et faible coût, mais d'être exclus pour des appareils pleine et haute vitesse.

III. Le protocole USB

III-A. Les protocoles USB

Contrairement à la RS232 et des interfaces sérielles similaires où le format des données envoyées n'est pas défini, l'USB est composé de plusieurs couches de protocoles. Bien que cela semble compliqué, n'abandonnez pas pour l'instant. Une fois que vous aurez compris ce qui se passe, vous devrez uniquement vous inquiéter des couches supérieures. En fait la plupart des circuits intégrés (C.Int.) contrôleur d'USB s'occuperont de la couche inférieure, la rendant ainsi presque invisible au regard du concepteur final.

Chaque transaction USB consiste en

· un paquet Jeton (Token) (en tête définissant ce qu'il attend par la suite) :

· un paquet DATA optionnel (contenant la "charge utile" (payload)) ;

· un paquet d'état (utilisé pour valider les transactions et pour fournir des moyens de correction d'erreurs).

Comme nous en avons déjà discuté, l'USB est un bus géré par l'hôte. L'hôte initie toutes les transactions. Le premier paquet, aussi appelé Jeton est produit par l'hôte pour décrire ce qui va suivre et si la transaction de données sera en lecture ou écriture et ce que sera l'adresse de l'appareil et la terminaison désignée. Le paquet suivant est généralement un paquet de données transportant la "charge utile" et est suivi par un paquet "poignée de main" (handShaking), signalant si les données ou le jeton ont été reçus correctement ou si la terminaison est bloquée, ou n'est pas disponible pour accepter de données.

III-B. Les champs de paquets USB ordinaires

Les données sur le bus USB sont transmises avec le bit LSB en premier. Les paquets USB se composent des champs suivants :

· Sync : tous les paquets doivent commencer avec un champ Sync. Le champ Sync fait de 8 bits de long pour la basse et pleine vitesse ou 32 bits pour la haute vitesse est utilisé pour synchroniser l'horloge du récepteur avec celle de l'émetteur/récepteur. Les deux derniers bits indiquent l'endroit ou le champ PID commence ;

· PID : PID signifie Paquet ID. Ce champ est utilisé pour identifier le type de paquet qui est envoyé. Le tableau suivant montre les valeurs possibles ;

Image non disponible
Image non disponible

Remarque :

SOF = Start Of Frame ; début de trame ;

SETUP = Installation, configuration ;

ACK = ACKnowledge ; validation ;

NAK = No AcKnowledge ; pas de validation ;

STALL = bloqué ;

PREamble = Synchroniseur initial ;

Split = partager, fractionner ;

Ping = s'assurer d'une bonne connexion.

Il y a quatre bits pour le PID, toutefois pour s'assurer qu'il a été reçu correctement, les quatre bits sont complémentés et répétés faisant un PID de huit bits au total. Le format résultant figure ci-dessous.

Image non disponible

· ADDR : le champ adresse détermine à quel appareil le paquet est destiné. Sa longueur de 7 bits, lui permet de supporter 127 appareils. L'adresse 0 n'est pas valide, tant qu'un appareil qui n'a pas encore d'adresse attribuée doit répondre aux paquets envoyés d'adresse 0 ;

· ENDP : le champ de terminaison est composé de 4 bits, autorisant 16 terminaisons possibles. Les appareils basse vitesse toutefois peuvent seulement avoir deux terminaisons additionnelles au-dessus du canal de communication par défaut (quatre terminaisons maximum) ;

· CRC : les Contrôles à redondance cyclique sont exécutés sur les données à l'intérieur du paquet de charge utile. Tous les paquets jetons ont un CRC de 5 bits tandis que les paquets de données ont un CRC de 16 bits ;

· EOP : fin de Paquet. Signalé par une sortie unique zéro (SE0) pendant une durée approximative de 2 bits suivie par un " J " d'une durée de 1 bit.

III-C. Les types de paquets USB

L'USB a quatre types différents de paquet. Les paquets jetons indiquent le type de la transaction qui va suivre, les paquets de données contiennent la charge utile, les paquets « poignée de main » sont utilisés pour valider les données ou rapporter les erreurs et les paquets début de trame (SOF) indiquent le commencement d'une nouvelle trame.

III-C-1. Les paquets jetons

Il y a trois sortes de paquets Jetons :

o In -informe l'appareil USB que l'hôte veut lire des informations ;

o Out - : informe l'appareil USB que l'hôte veut envoyer des informations ;

o Setup - utilisé pour commencer les transferts de commande.

Les paquets jetons doivent se conformer au format suivant :

Image non disponible

III-C-2. Les paquets de données

Il y a deux sortes de paquets de données, chacun étant capable de transmettre plus de 1024 octets de données.

o Data0 ;

o Data1.

Le mode haute vitesse définit deux autres PID de données, DATA2 et MDATA. Les paquets de données ont le format suivant :

Image non disponible

III-C-3. Les paquets début de trame (SOF)

Le paquet SOF composé d'une trame de 11 bits est envoyé par l'hôte toutes les 1 ms ± 500ns sur un bus pleine vitesse ou bien toutes les 125 μs ±  0,0625 μs sur un bus haute vitesse.

Image non disponible

III-D. Les fonctions USB

Quand nous pensons à un appareil USB, nous pensons à un périphérique USB, mais un appareil USB peut signifier un appareil émetteur/récepteur USB sur l'hôte ou périphérique, un hub USB ou un circuit intégré contrôleur d'hôte ou un appareil périphérique USB. Par conséquent le standard fait référence aux fonctions USB qui peuvent être considérées comme appareils USB qui fournissent une possibilité ou une fonction comme une imprimante, un lecteur Zip, un scanner, un modem ou un autre périphérique.

Donc à l'heure qu'il est nous devrions savoir de quoi est composé un paquet USB. Ce n'est pas le cas ? Vous avez déjà oublié combien de bits composent un champ PID ? Bon, ne vous alarmez pas trop. Heureusement la plupart des fonctions USB manipulent les protocoles USB bas niveau jusqu'à la couche transaction (que nous traiterons au chapitre prochain) dans le silicium. La raison pour laquelle nous traiterons cette information est que la plupart des contrôleurs de fonction USB signaleront des erreurs comme l'Erreur d'Encodage PID. Sans traiter rapidement ceci, l'on pourrait se demander ce qu'est l'erreur d'encodage PID ?

Si vous avez proposé que les quatre derniers bits du PID ne correspondent pas au complément des quatre premiers bits alors vous auriez raison.

Image non disponible

La plupart des fonctions auront des séries de tampons (buffers), généralement de 8 octets de long. Chaque tampon appartiendra à une terminaison EP0 IN, EP0 OUT, etc. Supposons par exemple que l'hôte envoie une demande de descripteur d'appareil. La fonction matérielle lira le paquet d'installation et déterminera à partir du champ adresse si le paquet est pour lui-même, et si c'est le cas, il copiera la "charge utile" du paquet de données suivant au tampon de la terminaison appropriée, dictée par la valeur dans le champ de la terminaison du jeton d'installation. Il enverra ensuite un paquet "poignée de main" pour valider la réception de l'octet et génèrera une interruption interne à l'intérieur du semi-conducteur/microcontrôleur pour la terminaison appropriée signalant qu'il a reçu un paquet. C'est en principe déjà intégré dans la matière (silicium).

Le logiciel a maintenant une interruption, et doit lire le contenu du tampon de terminaison et analyser la demande de descripteur d'appareil.

III-E. Les terminaisons

Les terminaisons peuvent être décrites comme émetteurs ou récepteurs de données. Du fait que le bus est régi par l'hôte, les terminaisons se présentent à la fin de la chaîne de communications sur la fonction USB. Au niveau de la couche logicielle, le pilote (driver) logiciel de votre appareil va envoyer, par exemple, un paquet à vos appareils EP1. À la sortie de l'hôte, la donnée aboutira au tampon EP1 OUT. Votre microprogramme pourra alors lire à loisir cette donnée. S'il veut retourner la donnée, la fonction ne peut pas simplement écrire sur le bus comme celui-ci est contrôlé par l'hôte. Par conséquent il écrit la donnée dans EP1 IN qui s'installe dans le tampon jusqu'à ce que l'hôte envoie un paquet IN à cette terminaison demandant la donnée. Les terminaisons peuvent être aussi considérées comme l'interface entre le matériel de l'appareil de fonction et le microprogramme s'exécutant sur ce même appareil.

Tous les appareils doivent prendre en charge la terminaison zéro. C'est la terminaison qui reçoit la totalité de la commande des appareils et des demandes d'états pendant l'énumération et tant que l'appareil est opérationnel sur le bus.

III-F. Canaux de communications (Pipes)

Tandis que l'appareil envoie et reçoit des données sur une succession de terminaisons, le logiciel client transfère des données à travers des canaux de communications. Un canal de communication (Pipe) est une connexion logique entre l'hôte et les terminaisons. Les canaux de communications auront aussi un ensemble de paramètres qui leur seront associés tels que : combien de bande passante leur est allouée, quel type de transfert (Commande, Bloc, Iso ou Interruption) ils utilisent, la direction du flux de données et les tailles maximales du paquet/tampons. Par exemple le canal de communication par défaut est un canal bidirectionnel composé d'une terminaison zéro IN et d'une terminaison zéro OUT avec un type de transfert de commande.

L'USB définit deux types de canaux de communications :

· les flux de données (Stream Pipes) n'ont pas de format USB défini. Cela veut dire que vous pouvez envoyer n'importe quelle sorte de données par un flux de données et que vous pouvez rapporter les données de sorties à l'autre extrémité. Les données circulent séquentiellement et ont une direction prédéfinie, soit en entrée soit en sortie.Les flux de données supporteront les types de transferts en bloc, isochrone et d'interruptions. Les flux de données peuvent soit être contrôlés par l'hôte ou l'appareil ;

· Les canaux de messages (Message Pipes) ont un format USB défini. Ils sont contrôlés par l'hôte et sont initiés par une demande émanant de l'hôte. Les données sont ensuite transférées dans la direction voulue, dictées par la demande. Par conséquent les canaux de messages permettent aux données de circuler dans les deux directions, mais ne prendront seulement en charge que les transferts de commande.

IV. Les types de terminaisons

La spécification du bus Série Universel définit quatre types de transferts ou de terminaisons :

· transferts de commande ;

· transferts d'interruption ;

· transferts isochrones ;

· transferts en bloc (BULK).

IV-A. Gestion de la bande passante

IV-A-1. Transferts de commande

Les transferts de commande sont régulièrement utilisés pour les opérations de commande et d'état. Ils sont essentiels pour installer un appareil USB avec toutes les fonctions d'énumération qui seront exécutées en utilisant les transferts de commande. Ils surviennent généralement en paquets directs et par rafales qui sont initiés par l'hôte et utilisent le meilleur rendement de livraison. La longueur du paquet du transfert de commande pour appareil basse vitesse doit être de 8 octets, les appareils pleine vitesse autorisent une taille de paquet de 8, 16, 32 ou 64 octets et les appareils haute vitesse doivent avoir une taille de paquet de 64 octets.

Un transfert de commande peut avoir plus de trois étapes.

· L'étape d'installation se passe lorsqu'une demande est envoyée. Elle est composée de trois paquets. Le jeton (token) d'installation envoyé le premier est celui qui contient l'adresse et le numéro de la terminaison. Le paquet de données est envoyé après et a toujours un type PID de Data0 et inclut un paquet d'installation qui détaille le type de la demande.

Nous détaillerons le paquet d'installation plus tard. Le dernier paquet est une poignée de main et est utilisé pour valider la bonne réception ou pour indiquer une erreur. Si la fonction reçoit correctement la donnée d'installation (CRC et PID, etc. OK) elle répond avec ACK, sinon elle ignore la donnée et n'envoie pas un paquet de poignée de main. Les fonctions ne peuvent pas émettre un paquet STALL ou NAK en réponse à un paquet d'installation.

Image non disponible

· L'étape de données facultative consiste en un ou plusieurs transferts IN (entrée) ou OUT (sortie). La demande d'installation indique la quantité de données qui doit être envoyée dans cette étape. Si elle dépasse la taille maximale du paquet, les données seront envoyées en plusieurs transferts, chacune ayant la longueur maximale du paquet à l'exception du dernier paquet.

L'étape de données comporte deux scénarios différents selon la direction du transfert de données :

o IN (entrée) : quand l'hôte est prêt à recevoir les données de commande, il émet un jeton (token) IN. Si la fonction reçoit le jeton IN avec une erreur, c'est-à-dire que le PID ne correspond pas avec les bits inversés du PID, il ignore donc le paquet. Si le jeton est reçu correctement, l'appareil peut soit répondre avec un paquet de données contenant les données de commande à envoyer, soit un paquet "d'arrêt" signalant que la terminaison a eu une erreur soit un paquet NAK signalant à l'hôte que la terminaison fonctionne, mais provisoirement n'a pas de données à envoyer.

Image non disponible

o OUT (sortie) : quand l'hôte a besoin d'envoyer à l'appareil un paquet de données de commande, il émet un jeton OUT suivi par un paquet de données contenant les données de commande comme "charge utile" (payload). Si une partie du jeton OUT ou du paquet de données est altérée alors la fonction ignore le paquet. Si le buffer de terminaison de la fonction était vide et qu'il a cadencé les données dans le buffer de terminaison, il produit un ACK avisant l'hôte qu'il a bien reçu les données. Si le buffer de terminaison n'est pas vide à cause du traitement du paquet précédent, alors la fonction retourne un NAK. Toutefois si la terminaison comporte une erreur et que son bit "halt" a été positionné, elle retourne un STALL (bloqué).

· L'étape d'état rend compte des états de l'ensemble de demandes et cette fois encore change selon la direction du transfert. Le rapport d'état est toujours réalisé par la fonction.

o IN (entrée) : si l'hôte envoie un (ou des) jeton(s) IN pendant l'étape de données pour recevoir des données, alors l'hôte doit valider la bonne réception de ces données. Ceci est réalisé par l'hôte qui envoie un jeton OUT suivi par un paquet de données de longueur nulle. La fonction peut maintenant rendre compte de son état dans l'étape poignée de main. Un ACK indique que la fonction a achevé la commande et qu'elle est maintenant prête à accepter une autre commande. Si une erreur s'est produite pendant l'exécution de cette commande, alors la fonction émettra un STALL (bloqué).

Toutefois si la fonction continue l'exécution, elle retourne un NAK indiquant à l'hôte la nécessité de répéter l'étape d'état ultérieurement.

Image non disponible

o OUT (sortie) : si l'hôte envoie un (ou des) jeton(s) OUT pendant l'étape de données pour transmettre des données, la fonction validera la bonne réception des données en envoyant un paquet de longueur nulle en réponse à un jeton IN.

Toutefois si une erreur intervenait, cela déboucherait sur un STALL ou si la fonction était encore occupée à traiter les données, cela déboucherait sur un NAK demandant à l'hôte de retenter l'étape d'état ultérieurement.

Image non disponible

IV-B. Le cœur du problème

Maintenant, voyons comment tout cela s'accorde. Supposons par exemple que l'hôte veuille demander un descripteur d'appareil pendant l'énumération. Les paquets qui sont envoyés sont les suivants :

l'hôte enverra un jeton d'installation disant à la fonction que le paquet suivant est un paquet d'installation. Le champ adresse fixera l'adresse de l'appareil à qui l'hôte a demandé le descripteur. Le numéro de la terminaison devrait être un zéro, indiquant une ligne défectueuse ;

l'hôte enverra alors un paquet DATA0. Celui-ci aura 8 octets de "charge utile" (payload) correspondant à la Demande de Descripteur d'Appareil comme souligné au chapitre 9 de la spécification USB. La fonction USB annoncera alors que le paquet d'installation a été lu correctement et sans aucune erreur. Si le paquet était reçu altéré, l'appareil ignorerait tout simplement ce paquet. L'hôte renverra alors le paquet après un court délai.

Image non disponible

Les trois paquets ci-dessus représentent la première transaction USB. L'appareil USB décodera maintenant les 8 octets reçus, et déterminera que c'était une demande de Descripteur d'Appareil. L'Appareil tentera d'envoyer le Descripteur d'Appareil, qui sera la transaction USB suivante.

Image non disponible

Dans ce cas nous supposons que la taille maximale de "charge utile" (payload) est de 8 octets. L'hôte envoie le jeton IN, disant à l'Appareil qu'il peut maintenant envoyer des données pour cette terminaison. Comme la taille maximale du paquet est de 8 octets, nous devons scinder les 12 octets du Descripteur d'Appareil en morceaux avant de pouvoir les envoyer. Chaque morceau doit être de 8 octets excepté la dernière transaction. L'hôte validera chaque paquet de données que nous lui enverrons.

Une fois que le Descripteur d'Appareil est envoyé, il s'ensuit une transaction d'état. Pour le cas où les transactions seraient réussies, l'hôte enverra un paquet de longueur nulle indiquant que l'ensemble de transactions était correct. La fonction répond alors à ce paquet de longueur nulle indiquant son état.

Image non disponible

IV-C. Transferts d'interruption

Celui qui a eu une expérience de demandes d'interruption sur microcontrôleurs saura que les interruptions sont générées par l'appareil. Toutefois sous USB, si un appareil demande l'attention de l'hôte, il doit attendre que l'hôte l'interroge avant de signaler qu'il a besoin d'une attention urgente !

IV-C-1. Caractéristiques

· Temps de retard (ou latence) garanti.

· Ligne de flux - Unidirectionnel.

· Détection d'erreur et nouvel essai sur période suivante.

Les transferts d'interruptions sont communément non périodiques, les petits appareils qui ont "initié" la communication ont besoin de temps de retard limité.

· La taille maximale de "charge utile" (payload) pour des appareils basse vitesse est de 8 octets.

· La taille maximale de "charge utile" (payload) pour des appareils pleine vitesse est de 64.

· La taille maximale de "charge utile" (payload) pour des appareils haute vitesse est de 1024 octets.

Image non disponible

Le diagramme ci-dessus montre le format d'une transaction d'interruption d'entrée IN et d'interruption de sortie OUT.

IN : l'hôte interrogera périodiquement la terminaison d'interruption. Le taux d'interrogation est spécifié dans le descripteur de terminaison qui sera examiné plus tard. Chaque interrogation obligera l'hôte à envoyer un jeton IN. Si le jeton IN est altéré, la fonction ignore le paquet et continue la surveillance du bus pour de nouveaux jetons.

Si une interruption a été mise en attente par l'appareil, la fonction enverra un paquet Data contenant des données ayant rapport à l'interruption quand il recevra le jeton IN. Sur des réceptions au niveau de l'hôte, celui-ci retournera un ACK. Toutefois si les données sont altérées, l'hôte ne mentionnera aucun état. Si, d'autre part, une condition d'interruption n'était pas présente quand l'hôte interrogeait la terminaison d'interruption avec un jeton IN, alors la fonction signale cet état en envoyant un NAK. Si une erreur se produisait sur cette terminaison, un STALL (Bloqué) serait envoyé en réponse à un jeton IN.

OUT : quand l'hôte veut envoyer à l'appareil les données d'interruptions, il émet un jeton OUT suivi par un paquet Data contenant les données d'interruption. Si une partie du jeton OUT ou du paquet Data est altérée alors la fonction ignore le paquet. Si le tampon de terminaison de la fonction était vide et qu'il a cadencé les données dans le tampon de terminaison, il émettrait un ACK prévenant l'hôte qu'il a reçu correctement les données. Si le tampon de terminaison n'est pas vide à cause du traitement d'un paquet précédent, alors la fonction retourne un NAK.

Toutefois si une erreur se produisait à cause de la terminaison et que son bit d'arrêt (Halt) a été positionné, elle renverrait un STALL (bloqué).

IV-D. Transferts isochrones

Les transferts isochrones se produisent continuellement et périodiquement. Ils contiennent généralement des informations à durée de vie critique, telles des trains de données audio ou vidéo.

S'il y avait un retard ou une reprise de données dans un flot de données audio, alors on pourrait s'attendre à de l'audio par intermittence contenant des signaux transitoires. Le battement (rythme) ne serait plus synchronisé. Toutefois si un paquet ou une trame se perdait, il est vraisemblable que l'auditeur ne le remarquerait même pas.

Les transferts isochrones fournissent :

· un accès garanti à la bande passante USB ;

· un temps d'attente limité ;

· des flux de données - unidirectionnel ;

· la détection d'erreur via le CRC, mais sans reprise ou garantie de livraison ;

· seulement des modes pleine et haute vitesse ;

· pas de données de basculement (basculage, cachées, de commutation) toggling?????.

La taille maximale de données en "charge utile" (payload) est spécifiée dans le descripteur de terminaison d'une terminaison isochrone. Cela peut être un maximum de 1023 octets pour un appareil pleine vitesse et 1024 octets pour un appareil haute vitesse. Comme la taille maximale de données en "charge utile" va effectuer des exigences de bande passante du bus, il est prudent de spécifier une taille de "charge utile" modérée. Si vous utilisez de grandes "charges utiles" il peut être aussi plus intéressant de spécifier une série d'interfaces alternatives avec des tailles de charges utiles isochrones variables. Si pendant l'énumération l'hôte ne peut pas valider votre terminaison isochrone nommée à cause des restrictions de bande passante, il reste une solution de repli préférable à un échec complet. Les données qui ont été envoyées sur une terminaison isochrone peuvent être inférieures à la taille prénégociée et peuvent varier en longueur de transaction en transaction.

Image non disponible

Le diagramme ci-dessus montre le format d'une transaction isochrone IN et OUT. Les transactions isochrones n'ont pas d'étape de poignée de main et ne peuvent pas rendre compte des erreurs ou des conditions STALL/HALT ((Bloqué)/Arrêt).

IV-E. Transferts en bloc

Les Transferts en bloc peuvent être utilisés pour de grandes quantités de données sporadiques. De tels exemples pourraient inclure un travail d'impression envoyé à une imprimante ou une image provenant d'un scanner. Les transferts en bloc se prémunissent de correction d'erreurs sous la forme d'un champ CRC16 sur les données "charge utile" et sur les mécanismes de détection et de retransmission d'erreurs qui assure la transmission et la réception de données de manière infaillible.

Les transferts en bloc utiliseront une bande passante de réserve non attribuée sur le bus après que toutes les autres transactions aient été allouées. Si le bus est occupé avec de l'isochrone et/ou de l'interruption, les données en bloc peuvent alors s'écouler doucement sur le bus. En conséquence, les transferts en bloc devraient seulement être utilisés pour des communications insensibles au temps du fait de la non-garantie du temps d'attente.

IV-E-1. Caractéristiques

· Utilisés pour de grandes quantités de données sporadiques.

· Détection d'erreurs via le CRC, avec la garantie de livraison.

· Pas de garantie de bande passante ou du temps d'attente minimum.

· Des flux de données - unidirectionnel.

· Seulement des modes pleine et haute vitesse.

Les Transferts en bloc sont seulement supportés par des appareils pleine et haute vitesse. Pour des terminaisons pleine vitesse, la longueur maximale du paquet en bloc est soit 8, 16, 32 ou 64 octets. Pour des terminaisons haute vitesse, la longueur maximale du paquet peut aller jusqu'à 512 octets. Si la charge utile des données est inférieure à la taille maximale du paquet, elle n'a pas besoin d'être remplie avec des zéros. Un transfert en bloc est considéré comme complet quand il a transféré la quantité exacte de données demandées, ou un paquet inférieur à la taille maximale de la terminaison, ou un paquet de longueur zéro.

Image non disponible

Le diagramme ci-dessus montre le format d'une transaction en bloc IN et OUT.

IN : quand l'hôte est prêt à recevoir des données en bloc, il émet un jeton IN. Si la fonction reçoit le jeton IN avec une erreur, il ignore le paquet. Si le jeton est reçu correctement, la fonction peut soit répondre avec un paquet DATA contenant les données en bloc à envoyer ou bien un paquet Stall signalant que la terminaison a eu une erreur ou un paquet NACK signalant à l'hôte que la terminaison travaille, mais provisoirement n'a pas de données à envoyer.

OUT : quand l'hôte veut envoyer à la fonction un paquet de données en bloc, il émet un jeton OUT suivi par un paquet DATA contenant les données en bloc. Si une partie du jeton OUT ou du paquet DATA est altérée, alors la fonction ignore le paquet. Si le tampon de terminaison de la fonction est vide et qu'il a cadencé les données dans le tampon de terminaison, il émet un ACK prévenant l'hôte qu'il a reçu correctement les données. Si le tampon de terminaison n'est pas vide à cause du traitement d'un paquet précédent, alors la fonction retourne un NAK. Toutefois si la terminaison a eu une erreur et que son bit d'arrêt a été positionné, elle retourne un Stall (bloqué).

IV-F. Gestion de la bande passante

L'hôte est responsable de la bande passante du bus. Elle est faite par l'énumération (dénombrement) lors de la configuration des terminaisons isochrones et d'interruptions et durant le fonctionnement du bus. La spécification place des limites sur le bus, l'autorisant à allouer plus de 90 % pour des transferts périodiques (interruption et isochrone) sur un bus pleine vitesse. Sur des bus hautes vitesses, cette limitation se réduit à moins de 80 % d'une microtrame qui peut être allouée pour des transferts périodiques.

Aussi vous pouvez assez rapidement voir que si vous avez un bus hautement saturé avec des transferts périodiques, les 10 % restants sont laissés pour les transferts de contrôle et une fois qu'ils ont été attribués, les transferts en bloc prendront ce qui reste.

V. Les descripteurs USB

Tous les appareils USB ont une hiérarchie de descripteurs qui détaillent pour le compte de l'hôte des informations l'instruisant sur la nature de l'appareil, qui l'a réalisé, quelle version USB il supporte, de combien de manières il peut être configuré, le nombre de terminaisons et leurs types, etc.

Les descripteurs les plus courants sont :

· descripteurs d'appareils ;

· descripteurs de configurations ;

· descripteurs d'interfaces ;

· descripteurs de terminaisons ;

· descripteurs de chaînes.

Les appareils USB ne peuvent avoir qu'un seul descripteur d'appareil. Le descripteur d'appareil inclut des informations qui précisent la révision USB à laquelle l'appareil se soumet, les ID (Identificateurs d'Appareils) du produit et du constructeur utilisés pour charger les pilotes logiciels appropriés et le nombre possible de configurations que l'appareil peut avoir. Le nombre de configurations indique combien de ramifications de descripteurs de configurations sont appelées à suivre.

Le descripteur de configuration précise des valeurs comme la quantité de puissance qu'utilise cette configuration particulière, si l'appareil est autoalimenté ou alimenté par le bus et le nombre d'interfaces qu'il possède. Quand un appareil est énuméré, l'hôte lit les descripteurs d'appareils et peut décider de la configuration à valider. Il peut seulement valider une configuration à la fois.

Par exemple, il est possible d'avoir une configuration d'alimentation de bus de grande puissance et une configuration autoalimentée. Si l'appareil est branché à un hôte possédant une alimentation électrique secteur, le pilote logiciel de l'appareil choisira peut-être de permettre la configuration d'alimentation du bus de grande puissance tolérant ainsi que l'appareil soit alimenté sans être relié au secteur, cependant s'il est connecté à un laptop (ordinateur portatif) ou à un organiseur personnel, il pourra valider la seconde configuration (autoalimentée) exigeant de l'utilisateur de brancher son appareil sur un point d'alimentation (secteur).

Les paramètres de configurations ne sont pas limités aux différences d'alimentations. Chaque configuration pourrait être alimentée de la même façon et drainer le même courant, et avoir cependant des combinaisons de terminaisons et d'interfaces différentes. Toutefois il faut tenir compte que la modification de la configuration exige l'arrêt de toute activité sur chaque terminaison. Tandis que l'USB propose cette flexibilité, très peu d'appareils possèdent plus d'une configuration.

Image non disponible

Le descripteur d'interface peut être vu comme un "entête" ou un regroupement de terminaison à l'intérieur d'un groupe fonctionnel accomplissant une seule fonctionnalité de l'appareil. Par exemple, vous pouvez avoir un appareil multifonction : fax/scanner/imprimante. Le descripteur d'interface 1 pourra décrire les terminaisons de la fonction fax, le descripteur d'interface 2, la fonction scanner et le descripteur d'interface 3 la fonction imprimante. Contrairement au descripteur de configuration, il n'y a pas de limitations à avoir une seule interface validée à la fois.

Un appareil peut avoir un ou plusieurs descripteurs d'interfaces validés en même temps.

Les descripteurs d'interfaces ont un champ bInterfaceNumber précisant le numéro de l'interface et un bAlternateSetting qui autorise l'interface à modifier ses paramètres au vol. Par exemple, on peut avoir un appareil avec deux interfaces, interface 1 et interface 2. Interface 1 a bInterfaceNombre mis à 0 indiquant qu'il est le premier descripteur d'interface et un bAlternativeSetting de 0.

L'interface 2 aura un bInterfaceNumber mis à 1 indiquant qu'il est la seconde interface et un bAlternativeSetting de 0 (par défaut). On pourra donc y faire entrer un autre descripteur, comprenant lui aussi un bInterfaceNumber mis à 1 indiquant qu'il est la seconde interface, mais cette fois le bAlternativeSetting mis à 1, indiquant que ce descripteur d'interface peut représenter un paramètre alternatif à celui de l'autre descripteur d'interface 2.

Quand cette configuration est validée, les deux premiers descripteurs d'interfaces avec bAlternativeSettings égal à 0 sont utilisés. Toutefois pendant le fonctionnement, l'hôte peut envoyer une demande imposée SetInterface (Sélection d'Interface) à l'interface 1 avec un alternative setting (paramètre alternatif) à 1 pour valider l'autre descripteur d'interface.

Image non disponible

Cela est plus avantageux que d'avoir deux configurations, dans le sens où l'on peut transmettre des données via l'interface 0 tandis que l'on change les paramètres de terminaisons associés à l'interface 1 sans affecter l'interface 0.

Chaque descripteur de terminaison est utilisé pour spécifier le type de transfert, la direction, l'intervalle d'interrogation et la taille maximale de paquet pour chaque terminaison. La terminaison 0, la terminaison de commandes par défaut est toujours supposée être une terminaison de commandes et en tant que tel ne possède jamais de descripteur.

V-A. Composition des descripteurs USB

Tous les descripteurs relèvent d'un schéma commun. Le premier octet précise la longueur du descripteur, tandis que le second octet indique le type de descripteur. Si la longueur du descripteur est plus petite que ce que définit la spécification, alors l'hôte doit l'ignorer. Toutefois si la taille est plus grande que prévue, l'hôte ignorera les octets supplémentaires et ne commencera à rechercher le prochain descripteur qu'à la fin d celui-ci.

Image non disponible

V-B. Descripteurs d'appareils

Le descripteur d'appareil d'un appareil USB représente l'appareil en entier. En conséquence un appareil USB ne peut avoir qu'un seul descripteur d'appareil. Il donne des informations élémentaires et pourtant fondamentales sur l'appareil tels la version USB supportée, la taille maximale de paquet, les ID constructeur et produits et le nombre de configurations possibles que peut avoir l'appareil. Le format du descripteur d'appareil est montré ci-dessous :

Image non disponible
Image non disponible

Le champ bcdUSB rapporte la version USB la plus haute que peut supporter l'appareil. La valeur est en binaire codé décimal avec un format de 0xJJMM ou JJ est le numéro de version de poids fort, M le numéro de version de poids faible et N correspond au numéro de sous-version c'est-à-dire USB 2.0 est inscrit comme 0x0200, USB 1.1 comme 0x0110 et USB 1.0 comme 0x0100.

Les bDeviceClass, bDeviceSubClass et bDeviceProtocol sont utilisés par le système d'exploitation pour trouver un pilote logiciel de classe pour votre appareil. Habituellement seul le bdeviceclass est positionné au niveau de l'appareil. La plupart des spécifications de classe choisissent de s'identifier au niveau de l'interface et en conséquence positionnent le bdeviceclass à 0x00. Cela permet à un appareil de supporter plusieurs classes.

Le champ bMaxPacketSize rapporte la taille maximale pour la terminaison zéro. Tous les appareils doivent supporter la terminaison zéro.

Le champ idVendor et idProduct sont utilisés par le système d'exploitation pour trouver un pilote logiciel pour votre appareil. L'identification constructeur (vendor ID) est assignée par USBIF.

Le champ bcdDevice a le même format que bcdUSB et est utilisé pour fournir un numéro de version d'appareil. Cette valeur est assignée par le developpeur.

Trois descripteurs de chaines de caractères existent pour fournir des détails du fabricant, du produit et du numéro de série. Il n'y a pas d'obligation à avoir des descripteurs de chaines de caractères. Si aucun descripteur de chaines de caractères n'est présent, il faudrait utiliser un index de zéro.

bNumConfigurations définit le nombre de configurations que l'appareil peut supporter et sa vitesse normale d'exécution.

V-C. Les descripteurs de configuration

Un appareil USB peut avoir plusieurs configurations différentes alors que la majorité des appareils sont simples et n'en ont qu'une. Le descripteur de configuration précise la façon dont l'appareil est alimenté, quelle est sa consommation électrique maximale, le nombre d'interfaces qu'il possède. Il est donc possible d'avoir deux configurations, quand il est alimenté par le bus et une autre quand il est alimenté par le secteur. Comme ceci est un "entête " de descripteur d'interface, il est aussi possible d'avoir une configuration utilisant un mode de transfert différent de celui d'une autre configuration.

Une fois que toutes les configurations ont été examinées par l'hôte, celui-ci enverra une instruction SetConfiguration avec une valeur différente de zéro qui correspondra à la valeur bConfiguration de l'une des configurations. Elle est utilisée pour sélectionner la configuration voulue.

Image non disponible
Image non disponible

Lors de la lecture du descripteur de configuration, il renvoie la hiérarchie ou l'arborescence complète de configuration qui inclut toute interface apparentée et les descripteurs de terminaisons. Le champ wTotalLength indique le nombre d'octets dans la hiérarchie.

Image non disponible

Le champ bNumInterfaces indique le nombre d'interfaces présentes pour cette configuration.

Le champ bConfigurationValue est utilisé par la demande SetConfiguration pour sélectionner cette configuration.

Le champ iConfiguration est un index de descripteur de chaine de caractères décrivant la configuration dans un format lisible par l'homme.

Le champ bmAttributes précise les paramètres d'alimentation pour la configuration. Si un appareil est autoalimenté, il positionne D6. Le bit D7 était autrefois utilisé par l'USB 1.0 pour indiquer un appareil alimenté par le bus, ceci est maintenant réalisé par le champ bMaxPower.

Si un appareil utilise l'énergie du bus, que ce soit un appareil alimenté par le bus ou un appareil autoalimenté, il doit rapporter sa consommation électrique dans le champ bMaxPower. Les appareils peuvent aussi prendre en charge l'activation d'une station à distance qui permettra à l'appareil de réveiller l'hôte quand celui-ci est en mode veille.

Le champ bMaxPower définit la consommation électrique maximale que l'appareil peut prendre du bus. Elle est donnée en unités de 2 mA, jusqu'au chiffre maximum de 500 mA environ. La spécification permet à un appareil alimenté par un bus de forte puissance de ponctionner jusqu'à 500 mA à partir de Vbus. Dans le cas où un appareil perd son alimentation externe, il ne doit pas ponctionner plus que ce qui est indiqué dans bMaxPower. Il devrait échouer sur toutes opérations nécessitant l'alimentation externe.

V-D. Les descripteurs d'interfaces

Le descripteur d'interface peut être vu comme un "entête" ou un regroupement de terminaisons dans un groupe fonctionnel exécutant une simple fonction pour l'appareil. Le descripteur d'interface correspond au format suivant :

Image non disponible

Le champ bInterfaceNumber indique l'index du descripteur d'interface. Il devait être pointé à zéro, et incrémenté une fois pour chaque nouveau descripteur d'interface.

Le champ bAlternativeSetting peut être utilisé pour préciser les interfaces de remplacement.

Ces interfaces alternatives peuvent être sélectionnées par la demande SetInterface.

bNumEndpoints indique le nombre de terminaisons utilisé par l'interface. Cette valeur devrait exclure la terminaison zéro et est utilisée pour indiquer le nombre de descripteurs de terminaisons à suivre.

bInterfaceClass, bInterfaceSubClass et bInterfaceProtocol peuvent être utilisés pour préciser les classes prises en compte (par exemple : HID, Communications, mémoire de masse, etc.). Ceci permet à plusieurs appareils d'utiliser des "drivers" (pilote logiciel) de classe évitant le besoin d'écrire des "drivers" spécifiques pour votre appareil.

iInterface permet d'avoir une description textuelle de l'interface.

V-E. Les descripteurs de terminaisons

Les descripteurs de terminaison sont utilisés pour décrire les terminaisons autres que la terminaison zéro. La terminaison zéro est toujours censée être une terminaison de commande et est configurée avant que n'importe quel autre descripteur ne soit sollicité. L'Hôte utilisera l'information renvoyée par ces descripteurs pour déterminer les besoins de bande passante du bus.

Image non disponible
Image non disponible

bEndpointAddress indique quelle terminaison ce descripteur décrit.

bmAttributes précise le type de transfert. Cela peut être soit des transferts de type commande, interruption, isochrone ou par blocs. Si une terminaison isochrone est précisée, des attributs supplémentaires peuvent être sélectionnés tels que la synchronisation et les types d'utilisations.

wMaxPacketSize indique la taille maximale de charge utile pour cette terminaison.

bInterval est utilisée pour préciser cet intervalle d'interrogation de certains transferts. L'unité est exprimée en termes équivalents ainsi à 1 ms pour des appareils basse/pleine vitesse et 125 μs pour des appareils haute vitesse.

V-F. Les descripteurs de chaînes de caractères

Les descripteurs de chaînes fournissent une information lisible pour l'homme et sont optionnels.

S'ils ne sont pas utilisés, tout champ d'index de descripteurs de chaînes doit être mis à zéro indiquant qu'il n'y a pas de descripteur de chaîne disponible.

Les chaînes de caractères sont codées au format Unicode et les produits peuvent être prévus pour comprendre les diverses langues. L'index de chaîne zéro devra retourner une liste de langues acceptées. On peut trouver une liste USB d'identification de langue dans Universal Serial bus Language Identifiers (LANGID) version 1.0 (Identificateurs de langue sur bus Série Universel version 1.0)

Image non disponible

Le descripteur de chaînes ci-dessus montre le format du descripteur de chaîne zéro. L'Hôte devra lire ce descripteur pour déterminer quelles langues sont disponibles. Si une langue est acceptée, elle peut être référencée en envoyant l'Identification de la langue dans le champ wIndex à la demande de Get Descriptor(String).

Toutes les chaînes de caractères à venir tiennent dans le format ci-dessous :

Image non disponible

VI. Les requêtes USB

VI-A. Le paquet d'installation

Tout appareil USB doit répondre aux paquets d'installation sur le canal de communication par défaut. Les paquets d'installation sont utilisés pour la détection et configuration de l'appareil et véhiculent des fonctions courantes telles que la mise en place de l'adresse de l'appareil USB, la demande d'un descripteur d'appareil ou la vérification de l'état d'une terminaison.

Un hôte USB conforme s'attend à ce que toutes les requêtes soient traitées dans une période maximale de cinq secondes. Il précise aussi des temps plus stricts pour des requêtes particulières :

les requêtes standards d'appareils sans étage de données doivent être accomplies en 50 ms ;

les requêtes standards d'appareils avec un étage de données doivent commencer à renvoyer les données 500 ms après la requête.

· Chaque paquet de données doit être envoyé dans les 500 ms de la transmission réussie du paquet précédent.

· L'étage d'état doit être accompli dans les 50 ms après la transmission du dernier paquet de données.

L'instruction SetAdress (qui contient une phase de données) doit traiter l'instruction et retourner l'état dans ces 50 ms. L'appareil a donc 2 ms pour changer d'adresse avant que la prochaine requête ne soit envoyée.

Ces temporisations sont très acceptables même pour les appareils les plus lents, mais peuvent être restrictives pendant les mises au point. 50 ms ne sont pas suffisantes pour l'envoi de caractères de mise au point à 9600bds vers un port série asynchrone ou bien pour un émulateur/débogueur de faire du pas à pas ou d'arrêter l'exécution du programme pour examiner les registres internes. En conséquence, l'USB demande des méthodes différentes de mises au point des autres projets à base de microcontrôleur.

En lisant le DDK XP (Kit Développement), vous noterez que le driver contrôleur d'hôte a maintenant une instruction USBUSER_OP_SEND_ONE_PACKET qui comporte le commentaire suivant : "Cette API (Application Programme Interface) est utilisée pour appliquer le 'pas-à-pas' en tant qu'outil de développement pour une transaction USB". Alors qu'un tel outil n'a pas encore été produit, nous ne pouvons qu'espérer en voir bientôt un sur le marché.

Chaque requête commence avec un paquet d'installation de 8 octets qui a le format suivant :

Image non disponible

Le champ bmRequestType déterminera le sens de la requête, le type de la requête et le destinataire désigné. Le champ bRequest indique la requête formulée.

bmRequestType est généralement analysé et l'exécution est raccordée à un numéro d'identificateurs comme un gestionnaire de requête d'un appareil standard, un gestionnaire de requête d'une interface standard, un gestionnaire de requête de terminaison standard, un gestionnaire de requête d'un appareil de classe, etc. La façon d'exécuter le paquet d'installation vous appartient. D'autres pourraient choisir d'exécuter en premier bRequest puis de déterminer le type et le destinataire basé sur chaque requête.

Les requêtes standards sont communes à tous les appareils USB et seront détaillées dans les prochaines pages. Les requêtes de classe sont communes aux classes de drivers (pilote logiciel).

Par exemple, tout appareil étant conforme à la classe HID aura un ensemble commun de requêtes spécifiques de classe. Ils différeront d'un appareil conforme à la classe communication et différeront encore d'un appareil conforme à la mémoire de masse (de grande capacité de stockage).

Et pour finir, les requêtes définies par le constructeur. Ce sont des requêtes que vous pouvez attribuer en tant que concepteur d'appareil USB. Elles sont normalement différentes d'un appareil à un autre, mais dépendent de votre réalisation et de votre imagination.

Une requête commune peut être dirigée vers des destinataires différents et basée sur les fonctions différentes exécutées par le destinataire. Par exemple une requête standard GetStatus peut être dirigée sur l'appareil, l'interface ou la terminaison. Quand elle est dirigée sur un appareil, ce dernier retourne des drapeaux (flags) indiquant l'état de la station d'activation et si l'appareil est autoalimenté.

Toutefois si la même requête est dirigée sur l'interface, il renvoie toujours zéro, ou bien si elle était dirigée sur une terminaison, l'appareil retournerait le drapeau Halt pour la terminaison.

Les champs wValue et wIndex permettent le passage de paramètres avec la requête.

wLength est utilisé pour préciser le nombre d'octets à transférer au cas où il existerait une phase de donnée.

VI-B. Les requêtes standards

La section 9.4 de la spécification USB détaille les requêtes "d'appareils standards" exigées qui doivent être appliquées pour chaque appareil USB. Le standard fournit un tableau unique regroupant des articles par requête. Considérant que la plupart des (micro)programmes analyseront le paquet d'installation par destinataire, nous choisirons de séparer les requêtes en fonction du destinataire pour une étude et une application plus simple.

Les requêtes d'appareils standards

Il y a actuellement huit requêtes d'appareils standards, chacune étant détaillée dans le tableau ci-dessous :

Image non disponible

La requête GetStatus dirigée vers l'appareil retournera 2 octets pendant l'étage de données suivant le format :

Image non disponible

Si D0 est à 1, ceci indique que l'appareil est autoalimenté. S'il est à 0, l'appareil est alimenté par le bus. Si D1 est à 1, l'activation à distance de l'appareil est validée et ce dernier peut réveiller l'hôte pendant le mode veille. Le bit d'activation à distance peut être positionné par les requêtes SetFeature et ClearFeature grâce à un sélecteur de fonction de DEVICE_REMOTE_WAKEUP(0x01).

Les requêtes Clear Feature et Set Feature peuvent être utilisées pour positionner des fonctions booléennes. Quand le destinataire désigné est l'appareil, les deux seuls sélecteurs de fonctions disponibles sont DEVICE_REMOTE_WAKEUP et TEST_MODE. Test mode permet à l'appareil de présenter diverses conditions. À leur égard, des précisions supplémentaires sont fournies dans la spécification USB, version 2.0.

Set Address est utilisé pendant l'énumération pour attribuer une adresse unique à l'appareil USB. L'adresse est précisée dans wValue et peut valoir au maximum 127. Cette requête est unique dans le sens ou l'appareil ne positionnera pas son adresse tant que la phase d'état ne sera pas achevée (voir transfert de commande). Toutes les autres requêtes doivent être terminées avant la phase d'état.

Set Descriptor/Get Descriptor est utilisé pour renvoyer le descripteur indiqué dans wValue.

Une requête pour le descripteur de configuration retournera le descripteur d'appareil et tous les descripteurs d'interfaces et de terminaisons dans la même requête.

· Les descripteurs de terminaisons ne peuvent pas être accessibles directement par une requête de GetDescriptor / SetDescriptor.

· Les descripteurs d'interfaces ne peuvent pas être accessibles directement par une requête de GetDescriptor / SetDescriptor.

· Les descripteurs de chaînes incluent une IDentification de langues dans wIndex pour autoriser le support de plusieurs langues.

Get Configuration/Set Configuration est utilisé pour demander ou positionner la configuration de l'appareil actuel. Dans le cas d'une requête GetConfiguration, un octet sera renvoyé pendant la phase de donnée indiquant l'état de l'appareil. Une valeur zéro signifie que l'appareil n'est pas configuré et une valeur différente de zéro indique que l'appareil est configuré.

SetConfiguration est utilisé pour valider un appareil. Il doit contenir la valeur de bConfigurationValue du descripteur de configuration voulu dans l'octet de poids faible de wValue pour sélectionner quelle configuration valider.

VI-C. Les requêtes d'interfaces standards

La spécification actuelle définit cinq requêtes d'interface standards qui sont détaillées dans le tableau ci-dessous : il est intéressant de noter que, seules deux requêtes donnent quelque chose de compréhensible.

Image non disponible

wIndex est normalement utilisé pour préciser l'interface de référence pour des requêtes liées à l'interface. Voir son format dans le schéma ci-dessous.

Image non disponible

Get Status est utilisé pour retourner l'état de l'interface. Une telle requête à l'interface doit renvoyer deux octets de valeur 0x00, 0x00. (Les deux octets sont réservés pour une utilisation future.)

Les requêtes ClearFeature et SetFeature peuvent être utilisées pour positionner des fonctions booléennes. Quand le destinataire désigné est l'interface, la spécification USB actuelle révision 2 précise qu'il n'y a pas de fonctions d'interface.

Get Interface et Set Interface règle le positionnement de l'interface de remplacement qui est décrit plus en détail dans le descripteur d'interface.

VI-D. Les requêtes de terminaisons standards

Les requêtes de terminaisons standards sont au nombre de quatre, listées ci-dessous :

Image non disponible

Le champ wIndex est normalement utilisé pour préciser la terminaison de référence et la direction pour les requêtes liées à la terminaison. Voir son format dans le schéma ci-dessous.

-----------------------------------------

Image non disponible

GetStatus renvoie 2 octets indiquant l'état (Arrêté/Bloqué) d'une terminaison. Le format des 2

octets renvoyés est illustré ci-dessous.

Image non disponible

ClearFeature et SetFeature sont utilisés pour positionner les fonctions de la terminaison. Le standard définit actuellement un sélecteur de fonction de terminaison : ENDPOINT_HALT (0x00) qui permet à l'hôte de bloquer et d'effacer une terminaison. Seules les terminaisons autres que la terminaison par défaut sont conseillées pour avoir cette fonctionnalité.

Une requête SynchFrame est utilisée pour rapporter une trame de synchronisation de terminaison.

VII. Exemple avec un PDIUSBD11 et un PIC16F87x

VII-A. L'énumération

L'énumération est la manière de déterminer l'appareil qui vient juste d'être branché au bus et les paramètres dont il a besoin, comme la consommation électrique, le nombre et le type de terminaison, la classe du produit, etc. L'hôte attribuera donc à l'appareil une adresse et validera une configuration lui permettant de transférer des données sur le bus. Un assez bon processus d'énumération générique est détaillé en section 9.1.2 de la spécification USB.

Toutefois lorsque l'on écrit un microprogramme USB pour la première fois, il est plus pratique de connaître la manière dont l'hôte répond pendant l'énumération, plutôt que le processus d'énumération général détaillé dans la spécification.

Une énumération sous Windows ordinaire implique les étapes suivantes :

· l'hôte ou hub détecte la connexion d'un nouvel appareil via les résistances de rappel de l'appareil reliées sur les deux fils de données. L'hôte attend au moins 100 ms, le temps que la prise soit insérée complètement et que l'alimentation de l'appareil soit stabilisée ;

· l'hôte émet un "reset" mettant l'appareil dans l'état par défaut. L'appareil peut maintenant répondre à l'adresse zéro par défaut ;

· l'hôte (sous MS Windows) demande les 64 premiers octets du descripteur d'appareil ;

· après avoir reçu les huit premiers octets du descripteur d'appareil, l'hôte émet immédiatement un autre reset sur le bus ;

· l'hôte émet maintenant une commande SetAdress, mettant l'appareil dans l'état adressable ;

· l'hôte demande la totalité des 18 octets du descripteur d'appareil ;

· puis il demande les neuf octets du descripteur de configuration pour déterminer la taille totale ;

· l'hôte demande les 255 octets du descripteur de configuration ;

· l'hôte demande l'un des descripteurs de chaînes s'ils étaient indiqués.

À la fin de l'étape 9, Windows demandera un driver (pilote logiciel) pour votre appareil. Il est alors courant de le voir redemander tous les descripteurs avant d'émettre une requête SetConfiguration.

Le processus d'énumération ci-dessus est courant dans Windows 2000, Windows XP et Windows 98 SE.

L'étape 4 embarrasse souvent les gens qui écrivent des microprogrammes pour la première fois.

L'hôte demande les 64 premiers octets du descripteur d'appareil, aussi lorsque l'hôte met à zéro votre appareil après avoir reçu les huit premiers octets, il est tout à fait naturel de penser qu'il y a un problème soit au niveau du descripteur d'appareil soit dans la façon dont votre microprogramme manipule la requête. Cependant, comme vous le diront beaucoup de gens, si vous insistez sur la mise en œuvre de la commande SetAdress, elle sera récompensée par la demande suivante de 18 octets pleins du descripteur d'appareil.

Généralement quand il y a un problème avec le descripteur ou sur la façon dont il est envoyé, l'hôte tentera de le lire trois fois avec de longues pauses entre les requêtes. Après la troisième tentative, l'hôte abandonne signalant une erreur au niveau de l'appareil.

VII-B. Microprogramme : le PIC 16F876 pilotant le PDIUSBD11

Nous commençons nos exemples avec le circuit USB PDIUSBD11 à liaison série I2C de Philips relié au microcontrôleur de Microchip, le PIC 16F876 représenté ici ou bien le PIC 16F877 (circuit à 40 broches). Tandis que Microchip a deux circuits USB basse vitesse sur le marché actuellement, le PIC 16C745 et le PIC 16C765, ils sont seulement OTP (One Time Programmable= programmable une seule fois) sans le soutien du circuit interne de débogage (ICD) qui n'apporte pas vraiment d'aide au débit de la réalisation. Ils possèdent quatre nouveaux circuits " Flash " pleine vitesse avec le support de l'ICD qui devraient sortir. En attendant, on trouve le PDIUSBD11 de Philips relié au PIC 16F876 qui a l'avantage d'être Flash et d'avoir l'ICD.

Un schéma électronique de base est représenté ci-dessous.

Image non disponible

L'exemple énumère et permet à des tensions analogiques d'être mesurées à partir des cinq entrées ADC multiplexées du microcontrôleur (MCU) PIC 16F876. Le code est compatible avec le PIC 16F877 permettant un maximum de huit voies analogiques. Une LED reliée à la broche RB3 s'allume quand le circuit est configuré. Un régulateur de 3,3 V n'est pas dessiné, mais il est nécessaire au PDIUSBD11. Si vous alimentez le circuit exemple d'une alimentation externe, alors vous pouvez utiliser un vulgaire régulateur de tension 78L033 de 3,3 V, cependant si vous voulez faire fonctionner le circuit en tant qu'appareil alimenté par le bus, un régulateur à faible chute de tension se révèlera obligatoire.

Le débogage peut être fait en connectant TXD (broche 17) à un câble RS232 et branché à un PC configuré à 115 200 bauds. Des formulations "printf" ont été incorporées permettant l'affichage de la progression de l'énumération.

Le code a été écrit en C et compilé avec le compilateur Hi-Tech PICC. Ils ont une version de démonstration (7.86 PL4) de PICC à télécharger utilisable pendant 30 jours. Un fichier Hex précompilé inclus en archive a été compilé pour être utilisé avec (ou sans) l'ICD.

 
Sélectionnez
#include <pic.h>

#include <stdio.h>

#include <string.h>

#include "usbfull.h"

const USB_DEVICE_DESCRIPTOR DeviceDescriptor = {

sizeof(USB_DEVICE_DESCRIPTOR), /* bLength */

TYPE_DEVICE_DESCRIPTOR, /* bDescriptorType */

0x0110, /* bcdUSB USB Version 1.1 */

0, /* bDeviceClass */

0, /* bDeviceSubclass */

0, /* bDeviceProtocol */

8, /* bMaxPacketSize 8 Bytes */

0x04B4, /* idVendor (Cypress Semi) */

0x0002, /* idProduct (USB Thermometer Example) */

0x0000, /* bcdDevice */

1, /* iManufacturer String Index */

0, /* iProduct String Index */

0, /* iSerialNumber String Index */

1 /* bNumberConfigurations */

};

Les "structures" sont toutes définies dans les fichiers d'entête. Nous avons basé cet exemple sur l'exemple du thermomètre USB de Cypress pour que vous puissiez utiliser le driver USB du kit de démarrage de Cypress. Un nouveau driver générique qui prendrait ceci en charge est en phase d'écriture et d'autres exemples seront bientôt disponibles. Une seule chaîne de caractères est fournie pour afficher le nom du fabricant. Cela donne suffisamment d'informations sur la façon de mettre en œuvre des descripteurs de chaînes sans remplir le composant dans sa totalité avec du code. On peut trouver ici une description de descripteur d'appareil et de ses champs.

 
Sélectionnez
const USB_CONFIG_DATA ConfigurationDescriptor = {

{ /* configuration descriptor */

sizeof(USB_CONFIGURATION_DESCRIPTOR), /* bLength */

TYPE_CONFIGURATION_DESCRIPTOR, /* bDescriptorType */

sizeof(USB_CONFIG_DATA), /* wTotalLength */

1, /* bNumInterfaces */

1, /* bConfigurationValue */

0, /* iConfiguration String Index */

0x80, /* bmAttributes bus Powered, No Remote Wakeup */

0x32 /* bMaxPower, 100mA */

},

{ /* interface descriptor */

sizeof(USB_INTERFACE_DESCRIPTOR), /* bLength */

TYPE_INTERFACE_DESCRIPTOR, /* bDescriptorType */

0, /* bInterface Number */

0, /* bAlternateSetting */

2, /* bNumEndpoints */

0xFF, /* bInterfaceClass (Vendor specific) */

0xFF, /* bInterfaceSubClass */

0xFF, /* bInterfaceProtocol */

0 /* iInterface String Index */

},

{ /* endpoint descriptor */

sizeof(USB_ENDPOINT_DESCRIPTOR), /* bLength */

TYPE_ENDPOINT_DESCRIPTOR, /* bDescriptorType */

0x01, /* bEndpoint Address EP1 OUT */

0x02, /* bmAttributes - Interrupt */

0x0008, /* wMaxPacketSize */

0x00 /* bInterval */

},

{ /* endpoint descriptor */

sizeof(USB_ENDPOINT_DESCRIPTOR), /* bLength */

TYPE_ENDPOINT_DESCRIPTOR, /* bDescriptorType */

0x81, /* bEndpoint Address EP1 IN */

0x02, /* bmAttributes - Interrupt */

0x0008, /* wMaxPacketSize */

0x00 /* bInterval */

}

};

Une description du descripteur de configuration et de ses champs figure ici. Nous fournissons deux descripteurs de terminaison en plus du canal de communication par défaut. EP1 OUT est une terminaison OUT de Bloc de huit octets maximum et EP1 IN OUT est une terminaison IN de Bloc de huit octets maximum. Notre exemple lit les données de la terminaison OUT de bloc et les met dans un tampon mémoire circulaire de 80 octets. L'envoi d'un paquet IN à EP1 permettra de lire un morceau de huit octets du tampon mémoire circulaire.

 
Sélectionnez
LANGID_DESCRIPTOR LANGID_Descriptor = { /* LANGID String Descriptor Zero */

sizeof(LANGID_DESCRIPTOR), /* bLenght */

TYPE_STRING_DESCRIPTOR, /* bDescriptorType */

0x0409 /* LANGID US English */

};

const MANUFACTURER_DESCRIPTOR Manufacturer_Descriptor = { /* ManufacturerString 1 */

sizeof(MANUFACTURER_DESCRIPTOR), /* bLenght */

TYPE_STRING_DESCRIPTOR, /* bDescriptorType */

"B\0e\0y\0o\0n\0d\0 \0L\0o\0g\0i\0c\0" /* ManufacturerString in

UNICODE */

};

Un descripteur de chaîne d'index zéro est formé pour supporter les besoins de LANGID des descripteurs de chaînes USB. Celui-ci indique que tous les descripteurs sont en anglais US. Le descripteur de constructeur peut être un peu décevant parce que la taille du tableau de caractères est fixée dans l'entête et n'est pas dynamique.

 
Sélectionnez
#define MAX_BUFFER_SIZE 80

bank1 unsigned char circularbuffer[MAX_BUFFER_SIZE];

unsigned char inpointer;

unsigned char outpointer;

unsigned char *pSendBuffer;

unsigned char BytesToSend;

unsigned char CtlTransferInProgress;

unsigned char DeviceAddress;

unsigned char DeviceConfigured;

#define PROGRESS_IDLE 0

#define PROGRESS_ADDRESS 3

void main (void)

{

TRISB = 0x03; /* Int & Suspend Inputs */

RB3 = 1; /* Device Not Configured (LED) */

RB2 = 0; /* Reset PDIUSBD11 */

InitUART();

printf("Initialising\n\r");

I2C_Init();

RB2 = 1; /* Bring PDIUSBD11 out of reset */

ADCON1 = 0x80; /* ADC Control - All 8 Channels Enabled, */

/* supporting upgrade to 16F877 */

USB_Init();

printf("PDIUSBD11 Ready for connection\n\r");

while(1) D11GetIRQ();

}

La fonction principale dépend de l'exemple. Elle est responsable de l'initialisation de la direction des E/S des ports, de l'initialisation de l'interface I2C, des convertisseurs analogiques digitaux et du PDIUSBD11. Une fois que tout ceci est configuré, elle continue d'interroger D11GetIRQ qui traite les requêtes d'interruption du PDIUSBD11.

 
Sélectionnez
void USB_Init(void)

{

unsigned char Buffer[2];

/* Disable hub Function in PDIUSBD11 */

Buffer[0] = 0x00;

D11CmdDataWrite(D11_SET_hub_ADDRESS, Buffer, 1);

/* Set Address to zero (default) and enable function */

Buffer[0] = 0x80;

D11CmdDataWrite(D11_SET_ADDRESS_ENABLE, Buffer, 1);

/* Enable function generic endpoints */

Buffer[0] = 0x02;

D11CmdDataWrite(D11_SET_ENDPOINT_ENABLE, Buffer, 1);

/* Set Mode - Enable SoftConnect */

Buffer[0] = 0x97; /* Embedded Function, SoftConnect, Clk Run, No LazyClk, Remote

Wakeup */

Buffer[1] = 0x0B; /* CLKOut = 4MHz */

D11CmdDataWrite(D11_SET_MODE, Buffer, 2);

}

La fonction USB " init " initialise le PDIUSBD11. Cette procédure d'initialisation a été omise de la documentation technique du PDIUSBD11 de Philips, mais elle est disponible de leur FAQ (Foire Aux Questions). La dernière commande valide la connexion logicielle de la résistance de rappel sur D+ signalant que c'est un appareil pleine vitesse, mais aussi prévient de sa présence sur le bus Série Universel.

 
Sélectionnez
void D11GetIRQ(void)

{

unsigned short Irq;

unsigned char Buffer[1];

do {

/* Read Interrupt Register to determine source of interrupt */

D11CmdDataRead(D11_READ_INTERRUPT_REGISTER, (unsigned char *)&Irq, 2);

if (Irq) printf("Irq = 0x%X: ",Irq);

La fonction main() continue d'appeler D11GetIRQ en boucle. Cette fonction lit les registres d'interruption du PDIUSBD11 pour établir si des interruptions sont en suspens. Si c'est le cas, elle les traitera, sinon elle continuera sa boucle. D'autres appareils peuvent avoir plusieurs vecteurs d'interruptions relatif à chaque terminaison. Dans ce cas, chaque ISR (Interrupt Service Routine je pense !) traitera l'interruption appropriée enlevant les formulations "if".

 
Sélectionnez
if (Irq & D11_INT_bus_RESET) {

printf("bus Reset\n\r");

USB_Init();

}

if (Irq & D11_INT_EP0_OUT) {

printf("EP0_Out: ");

Process_EP0_OUT_Interrupt();

}

if (Irq & D11_INT_EP0_IN) {

printf("EP0_In: \n\r");

if (CtlTransferInProgress == PROGRESS_ADDRESS) {

D11CmdDataWrite(D11_SET_ADDRESS_ENABLE,&DeviceAddress,1);

D11CmdDataRead(D11_READ_LAST_TRANSACTION + D11_ENDPOINT_EP0_IN, Buffer,

1);

CtlTransferInProgress = PROGRESS_IDLE;

}

else {

D11CmdDataRead(D11_READ_LAST_TRANSACTION + D11_ENDPOINT_EP0_IN, Buffer,

1);

WriteBufferToEndPoint();

}

}

Les formulations "if" apparaissent par ordre de priorité. L'interruption prioritaire la plus haute est le reset du bus. Elle appelle simplement USB_Init qui réinitialise la fonction USB. La priorité suivante est le canal de communication par défaut constitué de EP0 OUT et de EP1 IN. C'est ici que sont envoyées l'énumération et toutes les requêtes de commandes. Nous bifurquons sur une autre fonction pour manipuler les requêtes de EP0 OUT.

Quand une requête est faite par l'hôte et qu'il veut recevoir des données, le PIC 16F876 enverra au PDIUSBD11 un paquet de huit octets. Comme le bus USB est contrôlé par l'hôte, le PDIUSBD11 ne peut écrire les données quand il le désire, aussi il les stocke et attend un jeton IN provenant de l'hôte. Quand le PDIUSBD11 reçoit le jeton IN, il provoque une interruption. Cela laisse un temps suffisant pour recharger le prochain paquet de données à envoyer. Ceci est réalisé par une fonction supplémentaire : WriteBufferToEndpoint().

La section incluse dans CtlTransferInProgress == PROGRESS_ADDRESS maintient le positionnement de l'adresse de l'appareil. Nous détaillerons ceci plus tard.

 
Sélectionnez
if (Irq & D11_INT_EP1_OUT) {

printf("EP1_OUT\n\r");

D11CmdDataRead(D11_READ_LAST_TRANSACTION + D11_ENDPOINT_EP1_OUT, Buffer, 1);

bytes = D11ReadEndpoint(D11_ENDPOINT_EP1_OUT, Buffer);

for (count = 0; count < bytes; count++) {

circularbuffer[inpointer++] = Buffer[count];

if (inpointer >= MAX_BUFFER_SIZE) inpointer = 0;

}

loadfromcircularbuffer(); //Kick Start

}

if (Irq & D11_INT_EP1_IN) {

printf("EP1_IN\n\r");

D11CmdDataRead(D11_READ_LAST_TRANSACTION + D11_ENDPOINT_EP1_IN, Buffer, 1);

loadfromcircularbuffer();

}

EP1 OUT et EP1 In sont mis en place pour lire et écrire des données par Blocs à destination et en provenance d'un tampon mémoire circulaire. Cette configuration permet au code d'être utilisé en adéquation avec l'exemple BulkUSB provenant du DDK de Windows. Le tampon mémoire circulaire a été défini plus haut dans le code comme comportant 80 octets de long et prenant toute la banque1 de la RAM du 16F876.

 
Sélectionnez
if (Irq & D11_INT_EP2_OUT) {

printf("EP2_OUT\n\r");

D11CmdDataRead(D11_READ_LAST_TRANSACTION + D11_ENDPOINT_EP2_OUT, Buffer, 1);

Buffer[0] = 0x01; /* Stall Endpoint */

D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + D11_ENDPOINT_EP2_OUT, Buffer, 1);

}

if (Irq & D11_INT_EP2_IN) {

printf("EP2_IN\n\r");

D11CmdDataRead(D11_READ_LAST_TRANSACTION + D11_ENDPOINT_EP2_IN, Buffer, 1);

Buffer[0] = 0x01; /* Stall Endpoint */

D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + D11_ENDPOINT_EP2_IN, Buffer, 1);

}

if (Irq & D11_INT_EP3_OUT) {

printf("EP3_OUT\n\r");

D11CmdDataRead(D11_READ_LAST_TRANSACTION + D11_ENDPOINT_EP3_OUT, Buffer, 1);

Buffer[0] = 0x01; /* Stall Endpoint */

D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + D11_ENDPOINT_EP3_OUT, Buffer, 1);

}

if (Irq & D11_INT_EP3_IN) {

printf("EP3_IN\n\r");

D11CmdDataRead(D11_READ_LAST_TRANSACTION + D11_ENDPOINT_EP3_IN, Buffer, 1);

Buffer[0] = 0x01; /* Stall Endpoint */

D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + D11_ENDPOINT_EP3_IN, Buffer, 1);

}

} while (Irq);

}

Les terminaisons 2 et 3 ne sont pas utilisées pour l'instant, aussi nous les bloquons si elles reçoivent une donnée quelconque. Le PDIUSBD11 a une commande SetEndpointEnable qui peut être utilisée pour valider ou invalider la fonction de terminaison générique (n'importe quelle terminaison autre que le canal de communication par défaut). Nous pourrions utiliser cette commande pour invalider les terminaisons de base, si nous envisageons de ne pas nous en servir plus tard. Toutefois pour l'instant, ce code fournit une base de travail.

 
Sélectionnez
void Process_EP0_OUT_Interrupt(void)

{

unsigned long a;

unsigned char Buffer[2];

USB_SETUP_REQUEST SetupPacket;

/* Check if packet received is Setup or Data - Also clears IRQ */

D11CmdDataRead(D11_READ_LAST_TRANSACTION + D11_ENDPOINT_EP0_OUT, &SetupPacket, 1);

if (SetupPacket.bmRequestType & D11_LAST_TRAN_SETUP) {

La première chose à faire est de déterminer si le paquet reçu sur EP0 est un paquet de données ou un paquet d'installation. Un paquet d'installation contient une requête telle que GetDescriptor, tandis qu'un paquet de données contient des données d'une requête précédente. Nous avons de la chance que la plupart des requêtes n'envoient pas des paquets de données provenant de l'hôte vers l'appareil. La seule requête qui le réalise est SET_DESCRIPTOR, mais elle est rarement mise en œuvre.

 
Sélectionnez
/* This is a setup Packet - Read Packet */

D11ReadEndpoint(D11_ENDPOINT_EP0_OUT, &SetupPacket);

/* Acknowlegde Setup Packet to EP0_OUT & Clear Buffer*/

D11CmdDataWrite(D11_ACK_SETUP, NULL, 0);

D11CmdDataWrite(D11_CLEAR_BUFFER, NULL, 0);

/* Acknowlegde Setup Packet to EP0_IN */

D11CmdDataWrite(D11_ENDPOINT_EP0_IN, NULL, 0);

D11CmdDataWrite(D11_ACK_SETUP, NULL, 0);

/* Parse bmRequestType */

switch (SetupPacket.bmRequestType & 0x7F) {

Comme nous l'avons vu dans notre description de transferts de commande, un paquet d'installation ne peut pas être non ACQuité(NAKed) ou bloqué (STALLed). Quand le PDIUSBD11 reçoit un paquet d'installation, il vide le tampon EP0 IN et invalide les commandes du tampon mémoire de validation et celles du tampon mémoire d'effacement. Cela garantit que le paquet d'installation est acquitté par le microcontrôleur en envoyant un acquittement de commande d'installation à EP0 IN et EP0 OUT avant qu'une commande de validation ou d'effacement de tampon mémoire ne soit effective. La réception d'un paquet d'installation débloquera aussi une terminaison de commande bloquée (STALLed).

Une fois le paquet installé dans la mémoire et le paquet d'installation validé, nous allons analyser la requête en commençant par son type. À ce moment nous ne nous intéressons pas à la direction, aussi nous masquons ce bit. Les trois requêtes que tout appareil doit traiter sont la requête standard d'appareil, la requête standard d'interface et les requêtes standard de terminaisons. Nous fournissons notre fonctionnalité (lecture des entrées analogiques) par la requête constructeur, aussi nous ajoutons un type d'instruction pour les requêtes standards de constructeur. Si votre appareil supporte une spécification de classe USB, alors vous pouvez aussi avoir besoin d'ajouter des cas pour la requête de classe d'appareil, la requête de classe d'interface et/ou de la requête de classe de terminaison.

 
Sélectionnez
case STANDARD_DEVICE_REQUEST:

printf("Standard Device Request ");

switch (SetupPacket.bRequest) {

case GET_STATUS:

/* Get Status Request to Device should return */

/* Remote Wakeup and Self Powered Status */

Buffer[0] = 0x01;

Buffer[1] = 0x00;

D11WriteEndpoint(D11_ENDPOINT_EP0_IN, Buffer, 2);

break;

case CLEAR_FEATURE:

case SET_FEATURE:

/* We don't support DEVICE_REMOTE_WAKEUP or TEST_MODE */

ErrorStallControlEndPoint();

break;

La requête GetStatus est utilisée pour rapporter l'état de l'appareil et dire par exemple si l'appareil est alimenté par le bus ou autoalimenté et s'il supporte le réveil à distance. Dans notre appareil, nous rapportons qu'il est autoalimenté et qu'il ne prend pas en charge le réveil à distance.

D'après la requête caractéristique d'appareil, l'appareil ne supporte ni DEVICE_REMOTE_WAKEUP ni TEST_MODE et retourne comme résultat une erreur de requête USB.

 
Sélectionnez
case SET_ADDRESS:

printf("Set Address\n\r");

DeviceAddress = SetupPacket.wValue | 0x80;

D11WriteEndpoint(D11_ENDPOINT_EP0_IN, NULL, 0);

CtlTransferInProgress = PROGRESS_ADDRESS;

break;

La commande SetAddress est la seule commande qui continue d'être traitée après l'étape d'état.

Toutes les autres commandes doivent finir le traitement avant l'étape d'état. L'adresse de l'appareil est lue puis combinée par un OU logique avec 0x80 et ensuite stockée dans une variable DeviceAddress. La combinaison par un OU logique avec 0x80 avec le bit le plus significatif indiquant si l'appareil est validé ou non est spécifique au PDIUSBD11. Un paquet de longueur nulle est renvoyé comme état à l'hôte, indiquant que la commande est achevée.

Toutefois l'hôte doit envoyer un jeton IN, récupérer le paquet de longueur nulle et délivrer un ACK avant que l'on puisse changer d'adresse. Autrement l'appareil ne constatera peut-être jamais que le jeton IN a été envoyé à l'adresse par défaut.

L'achèvement de l'étape d'état est signalé par une interruption sur EP0 IN. Afin de différencier une réponse à SetAddress et une interruption normale EP0_IN, nous positionnons une variable, CtlTransferInProgress à PROGRESS_ADDRESS. Le programme de traitement de EP0 IN prévoit une vérification de CtlTransfertInProgress. Si elle équivaut à PROGRESS_ADDRESS la commande Set Address Enable est alors délivrée au PDIUSBD11 et CtlTransferInProgress est positionné à PROGRESS_IDLE. L'hôte donne 2 ms à l'appareil pour changer d'adresse avant que la prochaine commande ne soit envoyée.

 
Sélectionnez
case GET_DESCRIPTOR:

GetDescriptor(&SetupPacket);

break;

case GET_CONFIGURATION:

D11WriteEndpoint(D11_ENDPOINT_EP0_IN, &DeviceConfigured, 1);

break;

case SET_CONFIGURATION:

printf("Set Configuration\n\r");

DeviceConfigured = SetupPacket.wValue & 0xFF;

D11WriteEndpoint(D11_ENDPOINT_EP0_IN, NULL, 0);

if (DeviceConfigured) {

RB3 = 0;

printf("\n\r *** Device Configured *** \n\r");

}

else {

RB3 = 1; /* Device Not Configured */

printf("\n\r ** Device Not Configured *** \n\r");

}

break;

//case SET_DESCRIPTOR:

default:

/* Unsupported - Request Error - Stall */

ErrorStallControlEndPoint();

break;

}

break;

GetConfiguration et SetConfiguration sont utilisés pour " valider " l'appareil USB permettant aux données d'être transférées sur les terminaisons autres que la terminaison zéro. SetConfiguration devrait être délivré avec wValue égal à bConfigurationValue de la configuration que vous voulez valider. Dans notre cas, nous n'avons qu'une configuration, la configuration 1. Une configuration de valeur zéro signifie que l'appareil n'est pas configuré tandis qu'une configuration de valeur différente de zéro indique que l'appareil est configuré. Le code ne recopie pas complètement la valeur de configuration, il la recopie seulement dans une variable locale (de stockage) appelée DeviceConfigured. Si la valeur dans wValue ne correspond pas à bConfigurationValue d'une configuration, elle devrait renvoyer le message : Erreur de Requête USB.

 
Sélectionnez
case STANDARD_INTERFACE_REQUEST:

printf("Standard Interface Request\n\r");

switch (SetupPacket.bRequest) {

case GET_STATUS:

/* Get Status Request to Interface should return */

/* Zero, Zero (Reserved for future use) */

Buffer[0] = 0x00;

Buffer[1] = 0x00;

D11WriteEndpoint(D11_ENDPOINT_EP0_IN, Buffer, 2);

break;

case SET_INTERFACE:

/* Device Only supports default setting, Stall may be */

/* returned in the status stage of the request */

if (SetupPacket.wIndex == 0 && SetupPacket.wValue == 0)

/* Interface Zero, Alternative Setting = 0 */

D11WriteEndpoint(D11_ENDPOINT_EP0_IN, NULL, 0);

else ErrorStallControlEndPoint();

break;

case GET_INTERFACE:

if (SetupPacket.wIndex == 0) { /* Interface Zero */

Buffer[0] = 0; /* Alternative Setting */

D11WriteEndpoint(D11_ENDPOINT_EP0_IN, Buffer, 1);

break;

} /* else fall through as RequestError */

//case CLEAR_FEATURE:

//case SET_FEATURE:

/* Interface has no defined features. Return RequestError */

default:

ErrorStallControlEndPoint();

break;

}

break;

Parmi les requêtes standards d'interface, aucune n'exécute vraiment de fonction. La requête GetStatus doit retourner un message composé de zéros et est réservée pour un usage ultérieur.

Les requêtes SetInterface et GetInterface sont utilisées avec les descripteurs alternatifs d'interfaces. Jusqu'ici nous n'avons défini aucun descripteur alternatif d'interface, donc GetInterface renvoie un zéro et toutes les requêtes pour mettre à 1 une interface autre que l'interface zéro avec un positionnement alternatif de zéro se traduiront par une erreur de Requête.

 
Sélectionnez
case STANDARD_ENDPOINT_REQUEST:

printf("Standard Endpoint Request\n\r");

switch (SetupPacket.bRequest) {

case CLEAR_FEATURE:

case SET_FEATURE:

/* Halt(Stall) feature required to be implemented on all

Interrupt and */

/* Bulk Endpoints. It is not required nor recommended on the

Default Pipe */

if (SetupPacket.wValue == ENDPOINT_HALT)

{

if (SetupPacket.bRequest == CLEAR_FEATURE) Buffer[0] = 0x00;

else Buffer[0] = 0x01;

switch (SetupPacket.wIndex & 0xFF) {

case 0x01 : D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + \

D11_ENDPOINT_EP1_OUT, Buffer, 1);

break;

case 0x81 : D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + \

D11_ENDPOINT_EP1_IN, Buffer, 1);

break;

case 0x02 : D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + \

D11_ENDPOINT_EP2_OUT, Buffer, 1);

break;

case 0x82 : D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + \

D11_ENDPOINT_EP2_IN, Buffer, 1);

break;

case 0x03 : D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + \

D11_ENDPOINT_EP3_OUT, Buffer, 1);

break;

case 0x83 : D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + \

D11_ENDPOINT_EP3_IN, Buffer, 1);

break;

default : /* Invalid Endpoint - RequestError */

ErrorStallControlEndPoint();

break;

}

D11WriteEndpoint(D11_ENDPOINT_EP0_IN, NULL, 0);

} else {

/* No other Features for Endpoint - Request Error */

ErrorStallControlEndPoint();

}

break;

Les requêtes SetFeature et ClearFeature sont utilisées pour positionner des fonctions spécifiques de terminaison. Le standard définit un sélecteur de fonction de terminaison : ENDPOINT_HALT.

Nous vérifierons vers quelle terminaison la requête est dirigée et positionnerons/effacerons le bit STALL en conséquence. La fonction HALT n'est pas exigée sur les terminaisons par défaut.

 
Sélectionnez
case GET_STATUS:

/* Get Status Request to Endpoint should return */

/* Halt Status in D0 for Interrupt and Bulk */

switch (SetupPacket.wIndex & 0xFF) {

case 0x01 : D11CmdDataRead(D11_READ_ENDPOINT_STATUS + \

D11_ENDPOINT_EP1_OUT, Buffer, 1);

break;

case 0x81 : D11CmdDataRead(D11_READ_ENDPOINT_STATUS + \

D11_ENDPOINT_EP1_IN, Buffer, 1);

break;

case 0x02 : D11CmdDataRead(D11_READ_ENDPOINT_STATUS + \

D11_ENDPOINT_EP2_OUT, Buffer, 1);

break;

case 0x82 : D11CmdDataRead(D11_READ_ENDPOINT_STATUS + \

D11_ENDPOINT_EP2_IN, Buffer, 1);

break;

case 0x03 : D11CmdDataRead(D11_READ_ENDPOINT_STATUS + \

D11_ENDPOINT_EP3_OUT, Buffer, 1);

break;

case 0x83 : D11CmdDataRead(D11_READ_ENDPOINT_STATUS + \

D11_ENDPOINT_EP3_IN, Buffer, 1);

break;

default : /* Invalid Endpoint - RequestError */

ErrorStallControlEndPoint();

break;

}

if (Buffer[0] & 0x08) Buffer[0] = 0x01;

else Buffer[0] = 0x00;

Buffer[1] = 0x00;

D11WriteEndpoint(D11_ENDPOINT_EP0_IN, Buffer, 2);

break;

default:

/* Unsupported - Request Error - Stall */

ErrorStallControlEndPoint();

break;

}

break;

Quand la requête GetStatus est dirigée sur la terminaison, elle retourne l'état de terminaison, c'est-à-dire, si elle est arrêtée ou non. De même que pour la requête de fonction de positionnement/effacement ENDPOINT_HALT, nous avons juste besoin de rapporter l'état des terminaisons génériques (standards).

Toutes les requêtes standards de terminaison non définies peuvent être manipulées par une erreur de requête USB.

 
Sélectionnez
case VENDOR_DEVICE_REQUEST:

case VENDOR_ENDPOINT_REQUEST:

printf("Vendor Device bRequest = 0x%X, wValue = 0x%X, wIndex = 0x%X\n\r",

\

SetupPacket.bRequest, SetupPacket.wValue, SetupPacket.wIndex);

switch (SetupPacket.bRequest) {

case VENDOR_GET_ANALOG_VALUE:

printf("Get Analog Value, Channel %x :",SetupPacket.wIndex &

0x07);

ADCON0 = 0xC1 | (SetupPacket.wIndex & 0x07) << 3;

/* Wait Acquistion time of Sample and Hold */

for (a = 0; a <= 255; a++);

ADGO = 1;

while(ADGO);

Buffer[0] = ADRESL;

Buffer[1] = ADRESH;

a = (Buffer[1] << 8) + Buffer[0];

a = (a * 500) / 1024;

printf(" Value = %d.%02d\n\r",(unsigned int)a/100,(unsigned

int)a%100);

D11WriteEndpoint(D11_ENDPOINT_EP0_IN, Buffer, 2);

break;

Venons-en maintenant aux parties fonctionnelles de l'appareil USB. Les requêtes constructeur peuvent être imaginées par le concepteur. Nous avons imaginé deux requêtes :

VENDOR_GET_ANALOG_VALUE et VENDOR_SET_RB_HIGH_NIBBLE.

VENDOR_GET_ANALOG_VALUE lit la valeur analogique de 10 bits du canal x imposé par wIndex.

Elle est masquée avec 0x07 pour permettre huit canaux possibles, supportant le PIC 16F877 plus grand si nécessaire. La valeur analogique est renvoyée dans un paquet de données de 2 octets.

 
Sélectionnez
case VENDOR_SET_RB_HIGH_NIBBLE:

printf("Write High Nibble of PORTB\n\r");

PORTB = (PORTB & 0x0F) | (SetupPacket.wIndex & 0xF0);

D11WriteEndpoint(D11_ENDPOINT_EP0_IN, NULL, 0);

break;

default:

ErrorStallControlEndPoint();

break;

}

break;

VENDOR_SET_RB_HIGH_NIBBLE peut être utilisé pour positionner les bits factices de poids forts du PORTB[3:7].

 
Sélectionnez
default:


printf("UnSupported Request Type 0x%X\n\r",SetupPacket.bmRequestType);


ErrorStallControlEndPoint();


break;


}


} else {


printf("Data Packet?\n\r");


/* This is a Data Packet */


}


}

Tous les types de requêtes non supportés tels que la requête de classe d'appareil, la requête de classe d'interface, etc. se traduiront par une erreur de requête USB.

 
Sélectionnez
void GetDescriptor(PUSB_SETUP_REQUEST SetupPacket)

{

switch((SetupPacket->wValue & 0xFF00) >> 8) {

case TYPE_DEVICE_DESCRIPTOR:

printf("\n\rDevice Descriptor: Bytes Asked For %d, Size of Descriptor

%d\n\r", \

SetupPacket->wLength,DeviceDescriptor.bLength);

pSendBuffer = (const unsigned char *)&DeviceDescriptor;

BytesToSend = DeviceDescriptor.bLength;

if (BytesToSend > SetupPacket->wLength)

BytesToSend = SetupPacket->wLength;

WriteBufferToEndPoint();

break;

case TYPE_CONFIGURATION_DESCRIPTOR:

printf("\n\rConfiguration Descriptor: Bytes Asked For %d, Size of Descriptor

%d\n\r", \

SetupPacket->wLength, sizeof(ConfigurationDescriptor));

pSendBuffer = (const unsigned char *)&ConfigurationDescriptor;

BytesToSend = sizeof(ConfigurationDescriptor);

if (BytesToSend > SetupPacket->wLength)

BytesToSend = SetupPacket->wLength;

WriteBufferToEndPoint();

break;

Les requêtes GetDescriptor impliquent des réponses plus grandes que la taille maximale limite du paquet de huit octets de la terminaison. Par conséquent elles doivent être scindées en morceaux de huit octets. Les deux requêtes d'appareil et de configuration chargent l'adresse des descripteurs pertinents dans pSendBuffer et positionnent BytesToSend à la longueur du descripteur. La requête précisera aussi une longueur de descripteur dans wLength indiquant le maximum de données à envoyer. Dans chaque cas nous vérifions la longueur réelle par rapport à celle demandée par l'hôte et ajustons la taille si nécessaire. Ensuite nous appellerons WriteBufferToEndpoint qui charge les huit premiers octets dans le tampon de terminaison et incrémenterons le pointeur qui sera prêt pour le prochain paquet de huit octets.

 
Sélectionnez
case TYPE_STRING_DESCRIPTOR:

printf("\n\rString Descriptor: LANGID = 0x%04x, Index %d\n\r", \

SetupPacket->wIndex, SetupPacket->wValue & 0xFF);

switch (SetupPacket->wValue & 0xFF){

case 0 : pSendBuffer = (const unsigned char *)&LANGID_Descriptor;

BytesToSend = sizeof(LANGID_Descriptor);

break;

case 1 : pSendBuffer = (const unsigned char *)&Manufacturer_Descriptor;

BytesToSend = sizeof(Manufacturer_Descriptor);

break;

default : pSendBuffer = NULL;

BytesToSend = 0;

}

if (BytesToSend > SetupPacket->wLength)

BytesToSend = SetupPacket->wLength;

WriteBufferToEndPoint();

break;

Si des descripteurs de chaînes sont inclus, il doit y avoir la présence d'un descripteur de chaîne zéro qui détaille quelles langues sont supportées par l'appareil. N'importe quelle requête de chaînes différente de zéro a un language ID spécifié dans wIndex indiquant quelle langue est supportée. Dans notre cas nous trichons quelque peu et ignorons la valeur de wIndex (LANGID) en renvoyant la chaîne, quelle que soit la langue demandée.

 
Sélectionnez
default:

ErrorStallControlEndPoint();

break;

}

}

void ErrorStallControlEndPoint(void)

{

unsigned char Buffer[] = { 0x01 };

/* 9.2.7 RequestError - return STALL PID in response to next DATA Stage Transaction

*/

D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + D11_ENDPOINT_EP0_IN, Buffer, 1);

/* or in the status stage of the message. */

D11CmdDataWrite(D11_SET_ENDPOINT_STATUS + D11_ENDPOINT_EP0_OUT, Buffer, 1);

}

Quand nous rencontrons une requête invalide, un paramètre invalide ou une requête que l'appareil ne prend pas en charge, nous devons rendre compte d'une erreur de requête. Ceci est défini dans la spécification 9.2.7. Une erreur de requête renverra un STALL PID en réponse à la prochaine transaction d'étape de données ou au cours de l'étape d'état du message. Toutefois elle note que pour empêcher un trafic inutile sur le bus, l'erreur doit être reportée à la prochaine étape de données plutôt que d'attendre l'étape d'état.

 
Sélectionnez
unsigned char D11ReadEndpoint(unsigned char Endpoint, unsigned char *Buffer)

{

unsigned char D11Header[2];

unsigned char BufferStatus = 0;

/* Select Endpoint */

D11CmdDataRead(Endpoint, &BufferStatus, 1);

/* Check if Buffer is Full */

if(BufferStatus & 0x01)

{

/* Read dummy header - D11 buffer pointer is incremented on each read */

/* and is only reset by a Select Endpoint Command */

D11CmdDataRead(D11_READ_BUFFER, D11Header, 2);

if(D11Header[1]) D11CmdDataRead(D11_READ_BUFFER, Buffer, D11Header[1]);

/* Allow new packets to be accepted */

D11CmdDataWrite(D11_CLEAR_BUFFER, NULL, 0);

}

return D11Header[1];

}

void D11WriteEndpoint(unsigned char Endpoint, const unsigned char *Buffer, unsigned char

Bytes)

{

unsigned char D11Header[2];

unsigned char BufferStatus = 0;

D11Header[0] = 0x00;

D11Header[1] = Bytes;

/* Select Endpoint */

D11CmdDataRead(Endpoint, &BufferStatus, 1);

/* Write Header */

D11CmdDataWrite(D11_WRITE_BUFFER, D11Header, 2);

/* Write Packet */

if (Bytes) D11CmdDataWrite(D11_WRITE_BUFFER, Buffer, Bytes);

/* Validate Buffer */

D11CmdDataWrite(D11_VALIDATE_BUFFER, NULL, 0);

}

D11ReadEndpoint et D11WriteEndpoint sont des fonctions spécifiques au PDIUSBD11. Le PDIUSBD11 a deux octets factices préfixant toute opération de lecture ou écriture de données. Le premier octet est réservé, tandis que le second octet indique le nombre d'octets reçus ou à transmettre. Ces deux fonctions tiennent compte de cet entête.

 
Sélectionnez
void WriteBufferToEndPoint(void)

{

if (BytesToSend == 0) {

/* If BytesToSend is Zero and we get called again, assume buffer is smaller */

/* than Setup Request Size and indicate end by sending Zero Lenght packet */

D11WriteEndpoint(D11_ENDPOINT_EP0_IN, NULL, 0);

} else if (BytesToSend >= 8) {

/* Write another 8 Bytes to buffer and send */

D11WriteEndpoint(D11_ENDPOINT_EP0_IN, pSendBuffer, 8);

pSendBuffer += 8;

BytesToSend -= 8;

} else {

/* Buffer must have less than 8 bytes left */

D11WriteEndpoint(D11_ENDPOINT_EP0_IN, pSendBuffer, BytesToSend);

BytesToSend = 0;

}

}

Comme nous l'avons mentionné précédemment, WriteBufferToEndpoint est responsable du chargement des données dans PDIUSBD11 en morceaux de huit octets et de l'ajustement de la préparation des pointeurs pour le prochain paquet. Elle est appelée une fois par le programme de gestion de la requête pour charger les huit premiers octets dans le tampon mémoire de la terminaison. L'hôte enverra donc un jeton IN, lira cette donnée et le PDIUSBD11 génèrera une interruption. Le programme de gestion de EP0 IN appellera donc WriteBufferToEndpoint pour charger le prochain paquet qui sera prêt pour le prochain jeton IN provenant de l'hôte.

Un transfert est considéré comme complet si tous les octets requis ont été lus, si le paquet est reçu avec une charge utile inférieure à bMaxPacketSize ou si un paquet de longueur nulle est renvoyé. Par conséquent si le compteur BytesToSend atteint zéro, nous pouvons présumer que les données à envoyer représentaient un multiple de huit octets et que nous enverrons un paquet de longueur nulle pour indiquer les dernières données. Toutefois, s'il nous reste moins de huit octets à envoyer, nous enverrons seulement les octets restants. Il n'est pas nécessaire de rembourrer les données avec des zéros.

 
Sélectionnez
void loadfromcircularbuffer(void)

{

unsigned char Buffer[10];

unsigned char count;

// Read Buffer Full Status

D11CmdDataRead(D11_ENDPOINT_EP1_IN, Buffer, 1);

if (Buffer[0] == 0){

// Buffer Empty

if (inpointer != outpointer){

// We have bytes to send

count = 0;

do {

Buffer[count++] = circularbuffer[outpointer++];

if (outpointer >= MAX_BUFFER_SIZE) outpointer = 0;

if (outpointer == inpointer) break; // No more data

} while (count < 8); // Maximum Buffer Size

// Now load it into EP1_In

D11WriteEndpoint(D11_ENDPOINT_EP1_IN, Buffer, count);

}

}

}

La routine loadfromcircularbuffer() gère le chargement de données dans le tampon mémoire de la terminaison EP0 IN. On y fait normalement appel après une interruption EP0 IN pour recharger le tampon mémoire afin d'être prêt pour le prochain jeton IN sur EP1. Cependant afin d'envoyer le premier paquet, nous avons besoin de charger les données avant la réception de l'interruption EP1 IN. Par conséquent on fait aussi appel à la routine après que les données ont été reçues sur EP1 OUT.

En appelant aussi la routine du programme de gestion de EP0 OUT, nous pouvons facilement écraser les données dans le tampon IN sans se préoccuper si elles ont été envoyées ou pas. Pour empêcher ceci, nous déterminons si le tampon mémoire EP1 IN est vide, avant de tenter de le recharger avec de nouvelles données.

 
Sélectionnez
void D11CmdDataWrite(unsigned char Command, const unsigned char *Buffer, unsigned char

Count)

{

I2C_Write(D11_CMD_ADDR, &Command, 1);

if(Count) I2C_Write(D11_DATA_ADDR_WRITE, Buffer, Count);

}

void D11CmdDataRead(unsigned char Command, unsigned char Buffer[], unsigned char Count)

{

I2C_Write(D11_CMD_ADDR, &Command, 1);

if(Count) I2C_Read(D11_DATA_ADDR_READ, Buffer, Count);

}

D11CmdDataWrite et D11CmdDataRead sont deux fonctions spécifiques au PDIUSBD11 qui sont responsables de l'envoi de l'adresse/commande I2C en premier puis de l'envoi ou de la réception des données sur le bus I2C. Des fonctions bas niveau supplémentaires sont incluses dans le code source, mais ne sont pas reproduites ici du fait de l'intention de se focaliser sur les détails spécifiques de l'USB.

Cet exemple peut être utilisé avec l'exemple bulkUSB.sys comme faisant partie de la DDK de Windows. Pour charger le driver bulkUSB.sys, soit vous changez le code pour l'identifier avec un VID de 0x045E et un PID de 0x930A ou bien vous changez le fichier bulkUSB.inf accompagnant bulkUSB.sys pour correspondre à la combinaison VID/PID que vous utilisez dans cet exemple.

Il est alors possible d'utiliser le programme utilisateur en mode console, rwbulk.exe pour envoyer et recevoir des paquets du tampon mémoire circulaire. Utilisez :

 
Sélectionnez
rwbulk -r 80 -w 80 -c 1 -i 1 -o 0

pour envoyer des morceaux de données de 80 octets au PIC16F876. L'utilisation de charges utiles supérieures à 80 octets fera déborder le tampon mémoire circulaire de banque1 du PIC.

Cet exemple a été codé pour une lisibilité au détriment de la taille du code. La compilation vaut 3250 mots de la Flash (39 % de la capacité du pIC 16F876).

VIII. Décharger le code source

Version 1.2, 15ko.

IX. Historique des révisions

· 6 avril 2002 - Version 1.2 - Accroissement de la vitesse I2C pour correspondre au commentaire. Amélioration du traitement de l'IRQ PDIUSBD11.

· Le programme de gestion des routines en bloc de EP1 IN et EP1 OUT et la possibilité de charger des descripteurs à partir de la Flash. révision… a été ajouté.

· 31 décembre 2001 - Version 1.0.

X. Reconnaissance

Une reconnaissance spéciale à Michael DeVault de DeVaSys Embedded Systems. Cet exemple a été basé sur le code de Michael et développé sans effort sur la carte de développement USB de DeVaSys: USBLPT-PD11 avant d'être porté sur le PIC.