Inventer la bibliothĂšque vusb

Présentation


AprÚs avoir lu le titre, une question logique peut se poser: pourquoi étudier aujourd'hui l'implémentation logicielle de l'USB bas débit alors qu'il existe un tas de contrÎleurs bon marché avec un module matériel? Le fait est que le module matériel, cachant le niveau d'échange des niveaux logiques, transforme le protocole USB en une sorte de magie. Pour ressentir le fonctionnement de cette «magie», rien de mieux que de la reproduire à partir de zéro, à partir du niveau le plus bas.


À cette fin, nous allons essayer de fabriquer un appareil prĂ©tendant ĂȘtre USB-HID basĂ© sur le contrĂŽleur ATmega8. Contrairement Ă  la littĂ©rature rĂ©pandue, nous n'irons pas de la thĂ©orie Ă  la pratique, du niveau le plus bas au plus Ă©levĂ©, des tensions logiques aux conclusions, et nous terminerons par l '«invention» de la mĂȘme vusb, aprĂšs chaque Ă©tape vĂ©rifiant si le code fonctionne comme prĂ©vu. SĂ©parĂ©ment, je note que je n'invente pas d'alternative Ă  cette bibliothĂšque, mais plutĂŽt, je reproduis constamment son code source, en prĂ©servant autant que possible la structure et les noms d'origine, expliquant pourquoi telle ou telle section sert. Cependant, mon style habituel d'Ă©criture de code est diffĂ©rent du style des auteurs vusb. ImmĂ©diatement, j'avoue honnĂȘtement qu'en plus de l'intĂ©rĂȘt altruiste (pour raconter un sujet difficile aux autres), j'ai aussi un intĂ©rĂȘt Ă©goĂŻste - Ă©tudier le sujet par moi-mĂȘme et attraper un maximum de points subtils pour moi-mĂȘme. Il s'ensuit Ă©galement qu'un point important peut ĂȘtre manquĂ© ou qu'un sujet n'est pas entiĂšrement divulguĂ©.


Pour une meilleure compréhension du code, j'ai essayé de mettre en évidence les sections modifiées avec des commentaires et de les supprimer des sections discutées précédemment. En fait, le code source sera la principale source d'information, et le texte expliquera ce qui a été fait et pourquoi, ainsi que le résultat attendu.


Je note Ă©galement que seul l'USB Ă  bas dĂ©bit est pris en compte, mĂȘme sans mentionner, ce qui distingue les variĂ©tĂ©s les plus rapides.


Étape 0. Fer et autres prĂ©parations


Comme test, prenons une carte de dĂ©bogage maison basĂ©e sur ATmega8 avec du quartz 12 MHz. Je ne donnerai pas le schĂ©ma, il est assez standard (voir le site officiel vusb), la seule chose qui mĂ©rite d'ĂȘtre mentionnĂ©e est les conclusions utilisĂ©es. Dans mon cas, la sortie D + correspond Ă  PD2, la sortie D-PD3, et la bretelle se bloque sur PD4. En principe, une rĂ©sistance de rappel pourrait ĂȘtre connectĂ©e Ă  l'alimentation, mais le contrĂŽle manuel semble un peu plus conforme Ă  la norme.


Une alimentation de 5 V est fournie par le connecteur USB, cependant, on ne s'attend pas à plus de 3,6 V sur les lignes de signal (pourquoi était-ce un mystÚre pour moi). Vous devez donc soit réduire la puissance du contrÎleur, soit placer les diodes zener sur les lignes de signal. J'ai choisi la deuxiÚme option, mais dans l'ensemble, cela n'a pas d'importance.


Puisque nous «inventons» l'implémentation, il serait agréable de voir ce qui se passe dans le cerveau du contrÎleur, c'est-à-dire qu'au moins une sorte d'informations de débogage est nécessaire. Dans mon cas, ce sont deux LED sur PD6, PD7 et, surtout, UART sur PD0, PD1, configurées sur 115200, afin que vous puissiez écouter le bavardage du contrÎleur via un écran normal ou un autre programme pour travailler avec le port COM:


$ screen /dev/ttyUSB0 115200 

De plus, un wirehark avec le module appropriĂ© se rĂ©vĂ©lera ĂȘtre un utilitaire utile pour le dĂ©bogage USB (il ne dĂ©marre pas toujours Ă  partir de la boĂźte, mais la rĂ©solution de ces problĂšmes se trouve assez bien sur Internet et ce n'est pas la tĂąche de cet article).


Ici, il serait possible de dĂ©penser un autre kilo-octet de texte pour la description du programmeur, des makefiles et d'autres choses, mais cela n'a guĂšre de sens. De mĂȘme, je ne me concentrerai pas sur les paramĂštres pĂ©riphĂ©riques qui ne sont pas liĂ©s Ă  l'USB. Si quelqu'un ne peut mĂȘme pas comprendre cela, est-il trop tĂŽt pour entrer dans les entrailles du logiciel USB?


Le code source de toutes les étapes est disponible sur Github.


Étape 1. Acceptez au moins quelque chose


Selon la documentation, l'USB prend en charge plusieurs vitesses fixes, dont l'AVR ne tirera que le plus bas: 1,5 mĂ©gabits par seconde. Elle est dĂ©terminĂ©e par la rĂ©sistance de rappel et la communication ultĂ©rieure. Pour notre frĂ©quence choisie, la rĂ©sistance doit connecter D- avec une alimentation de 3,3 V et avoir une valeur nominale de 1,5 kOhm, mais en pratique, elle peut ĂȘtre connectĂ©e avec +5 V et la valeur nominale peut varier lĂ©gĂšrement. Avec une frĂ©quence de contrĂŽleur de 12 MHz, seulement 8 cycles d'horloge par bit. Il est clair qu'une telle prĂ©cision et vitesse ne sont rĂ©alisables que dans l'assembleur, nous allons donc ouvrir le fichier drvasm.S. Cela implique Ă©galement la nĂ©cessitĂ© d'utiliser une interruption pour intercepter le dĂ©but d'un octet. Je suis heureux que le premier octet transmis via USB soit toujours le mĂȘme, SYNC, donc si vous arrivez au dĂ©but, ça va. Par consĂ©quent, du dĂ©but de l'octet Ă  sa fin, seuls 64 cycles de contrĂŽleur passent (en fait, la marge est encore plus petite), vous ne devez donc pas utiliser d'autres interruptions non USB.


Mettez immédiatement la configuration dans un fichier usbconfig.h distinct. C'est là que seront fixées les broches responsables de l'USB, ainsi que les bits, constantes et registres utilisés.


Encart théorique
Le transfert via USB s'effectue en paquets de plusieurs octets chacun. Le premier octet est toujours l'octet de synchronisation SYNC, Ă©gal Ă  0b10000000, le second est l'identifiant d'octet du paquet PID. Le transfert de chaque octet passe du bit le moins significatif au plus significatif (ce n'est pas tout Ă  fait vrai, mais en subtilitĂ© cette subtilitĂ© est ignorĂ©e, donnĂ©e ailleurs) en utilisant le codage NRZI. Cette mĂ©thode consiste dans le fait qu'un zĂ©ro logique est transmis en changeant le niveau logique Ă  l'opposĂ©, et une unitĂ© logique est transmise par non-changement. De plus, une protection est introduite contre la dĂ©synchronisation (que nous n'utiliserons pas, mais doit ĂȘtre prise en compte) de la source et du rĂ©cepteur du signal: s'il y a six unitĂ©s d'affilĂ©e dans la sĂ©quence transmise, c'est-Ă -dire que l'Ă©tat des terminaux ne change pas pendant six pĂ©riodes consĂ©cutives, une inversion forcĂ©e est ajoutĂ©e Ă  la transmission, comme si zĂ©ro est transmis. Ainsi, la taille des octets peut ĂȘtre de 8 ou 9 bits.
Il convient également de mentionner que les lignes de données en USB sont différentielles, c'est-à-dire que lorsque D + est élevé, D- il est faible (c'est ce qu'on appelle l'état K) et vice versa (état J). Ceci est fait pour une meilleure immunité au bruit à haute fréquence. Certes, il y a une exception: le signal à la fin du paquet (il s'appelle SE0) est transmis en tirant les deux lignes de signal vers le sol (D + = D- = 0). Il y a deux autres signaux transmis en maintenant une basse tension sur la ligne D + et une haute tension sur la ligne D + pendant des moments différents. Si le temps est petit (une longueur d'octet ou un peu plus long) alors c'est Idle, une pause entre les paquets, et s'il est grand, un signal de réinitialisation.

Ainsi, la transmission se fait sur une paire diffĂ©rentielle, sans compter le cas exotique de SE0, mais nous ne le considĂ©rerons pas encore. Ainsi, pour dĂ©terminer l'Ă©tat du bus USB, nous n'avons besoin que d'une seule ligne, D + ou D-. Dans l'ensemble, il n'y a aucune diffĂ©rence Ă  choisir, mais pour ĂȘtre prĂ©cis, laissez D- ĂȘtre.


Le dĂ©but du paquet peut ĂȘtre dĂ©terminĂ© en recevant l'octet SYNC aprĂšs un long repos. L'Ă©tat inactif correspond Ă  log.1 sur la ligne D (c'est Ă©galement l'Ă©tat J), et l'octet SYNC est 0b100000, mais il est transmis du bit le moins significatif au bit le plus significatif, de plus, il est codĂ© en NRZI, c'est-Ă -dire que chaque zĂ©ro signifie l'inversion du signal et un moyen maintenir le mĂȘme niveau. La sĂ©quence des Ă©tats D- sera donc la suivante:


octetInactifSYNCPID
USB1..100000001????????
D-1..101010100????????

Le dĂ©but du paquet est plus facile Ă  dĂ©tecter sur un front descendant, et nous allons configurer une interruption sur celui-ci. Mais que se passe-t-il si le contrĂŽleur est occupĂ© au dĂ©but de la rĂ©ception et ne peut pas entrer immĂ©diatement l'interruption? Afin d'Ă©viter de perdre le nombre de pistes dans une telle situation, nous utilisons l'octet SYNC pour sa destination. Il se compose entiĂšrement de fronts aux limites des bits, de sorte que nous pouvons attendre l'un d'eux, puis un autre demi-bit, et aller directement au milieu du suivant. Cependant, attendre un «certain» front n'est pas une bonne idĂ©e, car nous devons non seulement entrer au milieu du bit, mais aussi savoir quel bit nous avons obtenu dans le score. Et pour cela, SYNC convient Ă©galement: il a deux bits zĂ©ro consĂ©cutifs Ă  la fin (ce sont des Ă©tats K). Ici, nous les attraperons. Ainsi, dans le fichier drvasm.S, un morceau de code apparaĂźt Ă  partir de l'entrĂ©e d'interruption vers foundK. De plus, en raison du temps nĂ©cessaire pour vĂ©rifier l'Ă©tat du port, pour une transition inconditionnelle et ainsi de suite, nous arrivons Ă  la marque non pas au dĂ©but du bit, mais juste au milieu. Mais il est inutile de vĂ©rifier la mĂȘme chose, car nous connaissons dĂ©jĂ  sa signification. Par consĂ©quent, nous attendons 8 cycles d'horloge (jusqu'Ă  prĂ©sent nop'ami vide) et vĂ©rifions le bit suivant. S'il est Ă©galement nul, alors nous avons trouvĂ© la fin de SYNC et pouvons procĂ©der Ă  la rĂ©ception de bits significatifs.


En fait, tout le code supplémentaire est destiné à lire deux octets supplémentaires avec une sortie ultérieure vers UART. Eh bien, en attendant l'état de SE0 pour ne pas entrer accidentellement dans le prochain paquet.


Vous pouvez maintenant compiler le code résultant et voir quels octets notre appareil accepte. Personnellement, j'ai la séquence suivante:


 4E 55 00 00 4E 55 00 00 4E 55 00 00 4E 55 00 00 4E 55 00 00 

N'oubliez pas que nous générons des données brutes, à l'exclusion des zéros incrémentiels et du décodage NRZI. Essayons de décoder manuellement, en commençant par le bit bas:


4E
NRZI010011100 (bit précédent)
octet00101101
2D

55
NRZI010101010 (bit précédent)
octet00000000
00

Il n'est pas logique de dĂ©coder des zĂ©ros, car 16 valeurs identiques consĂ©cutives ne peuvent pas ĂȘtre incluses dans un paquet.


Ainsi, nous avons pu écrire un firmware qui accepte les deux premiers octets du paquet, mais jusqu'à présent sans décodage.


Étape 2. Version de dĂ©monstration de NRZI


Afin de ne pas recoder manuellement, vous pouvez confier cela au contrĂŽleur lui-mĂȘme: l'opĂ©ration XOR fait exactement ce dont vous avez besoin, bien que le rĂ©sultat soit inversĂ©, alors ajoutez une autre inversion aprĂšs:


 mov temp, shift lsl shift eor temp, shift com temp rcall uart_hex 

Le résultat est tout à fait attendu:


 2D 00 FF FF 2D 00 FF FF 2D 00 FF FF 2D 00 FF FF 2D 00 FF FF 

Étape 3. DĂ©barrassez-vous du cycle de rĂ©ception d'octets


Prenons un petit pas de plus et élargissons le cycle de réception du premier octet dans un code linéaire. Ainsi, il s'avÚre que beaucoup de nops, nécessaires uniquement pour attendre le début du bit suivant. Au lieu de certains d'entre eux, vous pouvez utiliser le décodeur NRZI, d'autres vous seront utiles plus tard.


Le résultat de l'option précédente n'est pas différent.


Étape 4. Lire dans le tampon


La lecture dans des registres séparés est, bien sûr, rapide et belle, mais quand il y a trop de données, il est préférable d'utiliser une entrée de tampon située quelque part dans la RAM. Pour ce faire, nous déclarerons un tableau de taille suffisante dans le main, et dans l'interruption nous y écrirons.
Encart théorique


La structure des paquets en USB est normalisée et se compose des parties suivantes: octet SYNC, octet PID + CHECK (2 champs de 4 bits chacun), champ de données (parfois 11 bits, mais le plus souvent un nombre arbitraire d'octets 8 bits) et une somme de contrÎle CRC de 5 ( pour un champ de données de 11 bits), ou 16 (pour le reste) bits. Enfin, l'indication de fin de paquet (EOP) est de deux bits de pause, mais ce ne sont plus des données.


Avant de travailler avec le tableau, vous devez toujours configurer les registres et libérer nop avant que le premier bit ne soit pas suffisant pour cela. Par conséquent, vous devrez mettre la lecture des deux premiers bits dans la section linéaire du code, entre les commandes dont nous insérerons le code d'initialisation, puis sauter au milieu du cycle de lecture, sur l'étiquette rxbit2. En parlant de taille de tampon. Selon la documentation, dans un paquet, il est impossible de transférer plus de 8 octets de données. Nous ajoutons les octets de service PID et CRC16, nous obtenons une taille de tampon de 11 octets. Les octets SYNC et l'état EOP ne seront pas écrits. Nous ne serons pas en mesure de contrÎler l'intervalle des demandes de l'hÎte, mais nous ne voulons pas non plus les perdre, nous allons donc prendre une double marge pour la lecture. Pour l'instant, nous n'utiliserons pas l'intégralité du buffer, mais afin de ne pas revenir dans le futur, il est préférable d'allouer immédiatement le volume requis.


Étape 5. Travailler humainement avec le tampon


Au lieu de lire directement les premiers octets du tableau, nous Ă©crivons un morceau de code qui lit exactement autant d'octets qu'il a Ă©tĂ© rĂ©ellement Ă©crit dans le tableau. Et en mĂȘme temps, ajoutez un sĂ©parateur entre les packages.
Maintenant, la sortie ressemble Ă  ceci:


 >03 2D 00 10 >01 FF >03 2D 00 10 >01 FF >03 2D 00 10 >01 FF >03 2D 00 10 >01 FF >03 2D 00 10 >01 FF 

Étape 6. Ajout d'un additif Zero Additive


Enfin, il est temps de terminer la lecture du flux binaire en standard. Le dernier Ă©lĂ©ment dont nous avons rĂ©ussi Ă  nous passer Ă©tait un faux zĂ©ro, ajoutĂ© toutes les six unitĂ©s consĂ©cutives. Étant donnĂ© que la rĂ©ception d'octets est dĂ©ployĂ©e sur le corps linĂ©aire de la boucle, vous devez vĂ©rifier aprĂšs chaque bit, aux huit endroits. ConsidĂ©rez les deux premiers bits comme exemple:


 unstuff0: ;1 (  breq) andi x3, ~(1<<0) ;1 [15]  0-  .     mov x1, x2 ;1 [16]      () in x2, USBIN ;1 [17] <-- 1-   .     ori shift, (1<<0) ;1 [18]  0-   .1      rjmp didUnstuff0 ;2 [20] ;<---//---> rxLoop: eor shift, x3 ;1 [0] in x1, USBIN ;1 [1] st y+, shift ;2 [3] ldi x3, 0xFF ;1 [4] nop ;1 [5] eor x2, x1 ;1 [6] bst x2, USBMINUS ;1 [7]     0-   shift bld shift, 0 ;1 [8] in x2, USBIN ;1 [9] <--  1- (, ) andi x2, USBMASK ;1 [10] breq se0 ;1 [11] andi shift, 0xF9 ;1 [12] didUnstuff0: breq unstuff0 ;1 [13] eor x1, x2 ;1 [14]; bst x1, USBMINUS ;1 [15]     1-   shift bld shift, 1 ;1 [16] rxbit2: in x1, USBIN ;1 [17] <--  2-  (, ) andi shift, 0xF3 ;1 [18] breq unstuff1 ;1 [19] didUnstuff1: 

Pour la commoditĂ© de la navigation, les adresses des commandes dĂ©crites seront comptĂ©es par les Ă©tiquettes Ă  droite. Veuillez noter qu'ils ont Ă©tĂ© introduits pour compter les cycles d'horloge du contrĂŽleur, donc ils ne sont pas en ordre. L'octet suivant est lu sur l'Ă©tiquette rxLoop, l'octet prĂ©cĂ©dent est inversĂ© et Ă©crit dans le tampon [0, 3]. Ensuite, sur l'Ă©tiquette [1], l'Ă©tat de la ligne D est lu, selon XOR avec l'Ă©tat acceptĂ© prĂ©cĂ©dent, nous dĂ©codons NRZI (je rappelle que le XOR ordinaire ajoute son inversion, pour corriger ce que nous entrons dans le registre de masque x3, initialisĂ© avec les unitĂ©s 0xFF) et Ă©crivons Ă  0- iĂšme bit du registre Ă  dĂ©calage [7,8]. Ensuite, le plaisir commence - nous vĂ©rifions si le bit reçu Ă©tait le sixiĂšme inchangĂ©. Le bit constant reçu avec D- correspond Ă  l'Ă©criture de zĂ©ro (pas un! Nous passerons Ă  un Ă  la fin, XOR) dans le registre. Par consĂ©quent, vous devez vĂ©rifier si les bits 0, 7, 6, 5, 4, 3 sont des zĂ©ros. Les deux bits restants n'ont pas d'importance, ils sont restĂ©s de l'octet prĂ©cĂ©dent et ont Ă©tĂ© vĂ©rifiĂ©s plus tĂŽt. Pour s'en dĂ©barrasser, nous coupons le registre par le masque [12], oĂč tous les bits qui nous intĂ©ressent sont mis Ă  1: 0b11111001 = 0xF9. Si aprĂšs avoir appliquĂ© le masque tous les bits se sont avĂ©rĂ©s ĂȘtre des zĂ©ros, la situation de l'ajout d'un bit est corrigĂ©e et il y a une transition vers l'Ă©tiquette unstuff0. Un bit de plus [17] y est lu au lieu de ce qui a Ă©tĂ© lu prĂ©cĂ©demment, dans l'intervalle entre les autres opĂ©rations, d'un excĂšs [9]. Nous Ă©changeons Ă©galement les registres des valeurs actuelles et prĂ©cĂ©dentes x1, x2. Le fait est que sur chaque bit, la valeur est lue dans un registre, puis XOR est dans un autre, aprĂšs quoi les registres sont Ă©changĂ©s. Par consĂ©quent, lors de la lecture du registre incrĂ©mental, cette opĂ©ration doit Ă©galement ĂȘtre effectuĂ©e. Mais la chose la plus intĂ©ressante est que dans le registre de donnĂ©es Ă  dĂ©calage, nous Ă©crivons non pas le zĂ©ro, que nous avons reçu honnĂȘtement, mais l'unitĂ© que l'hĂŽte a tentĂ© de transfĂ©rer [18]. Cela est dĂ» au fait que lors de la rĂ©ception des bits suivants, la valeur de zĂ©ro devra Ă©galement ĂȘtre prise en compte, et si nous avons enregistrĂ© zĂ©ro, la vĂ©rification du masque n'a pas pu dĂ©couvrir que le bit supplĂ©mentaire a dĂ©jĂ  Ă©tĂ© pris en compte. Ainsi, dans le registre Ă  dĂ©calage, tous les bits sont inversĂ©s (par rapport Ă  ceux transmis par l'hĂŽte), et le zĂ©ro ne l'est pas. Pour Ă©viter un tel gĂąchis dans le tampon, nous effectuerons une inversion inverse selon XOR non pas avec 0xFF [0], mais avec 0xFE, c'est-Ă -dire un registre dans lequel le bit correspondant sera rĂ©initialisĂ© Ă  0 et, par consĂ©quent, ne conduira pas Ă  une inversion. Pour ce faire, sur l'Ă©chantillon [15] et rĂ©initialisez le bit zĂ©ro.


Une situation similaire se produit avec les bits 1-5. Disons que le 1er bit correspond à la vérification 1, 0, 7, 6, 5, 4, tandis que les bits 2, 3 sont ignorés. Cela correspond au masque 0xF3.
Mais le traitement des 6 et 7 bits est différent:


 didUnstuff5: andi shift, 0x3F ;1 [45]   5-0 breq unstuff5 ;1 [46] ;<---//---> bld shift, 6 ;1 [52] didUnstuff6: cpi shift, 0x02 ;1 [53]   6-1 brlo unstuff6 ;1 [54] ;<---//---> bld shift, 7 ;1 [60] didUnstuff7: cpi shift, 0x04 ;1 [61]   7-2 brsh rxLoop ;3 [63] unstuff7: 

Le masque du 6e bit est le numĂ©ro 0b01111110 (0x7E), mais vous ne pouvez pas le superposer sur le registre Ă  dĂ©calage, car il rĂ©initialisera le 0e bit, qui doit ĂȘtre Ă©crit dans le tableau. De plus, au compte Ă  rebours [45], un masque Ă©tait dĂ©jĂ  superposĂ©, rĂ©initialisant 7 bits. Par consĂ©quent, il est nĂ©cessaire de traiter le bit supplĂ©mentaire si les bits 1-6 sont Ă©gaux Ă  zĂ©ro et que le 0Ăšme n'a pas d'importance. Autrement dit, la valeur du registre doit ĂȘtre 0 ou 1, ce qui est parfaitement vĂ©rifiĂ© en comparant "moins de 2" [53, 54].


Le mĂȘme principe a Ă©tĂ© utilisĂ© pour le 7e bit: au lieu d'appliquer le masque 0xFC, une vĂ©rification est effectuĂ©e pour «moins de 4» [61, 63].


Étape 7. Triez les packages


Puisque nous pouvons recevoir un paquet avec le premier octet (PID) Ă©gal Ă  0x2D (SETUP), nous essaierons de trier celui reçu. Au fait, pourquoi ai-je appelĂ© le package 0x2D SETUP alors qu'il semble ĂȘtre ACK? Le fait est que la transmission USB du bit le moins significatif au bit le plus significatif est effectuĂ©e dans chaque champ, et non octet, alors que nous acceptons octet par octet. Le premier champ significatif, PID, ne prend que 4 bits, suivi de 4 bits CHECK supplĂ©mentaires, reprĂ©sentant une inversion au niveau du bit du champ PID. Ainsi, le premier octet reçu ne sera pas PID + CHECK, mais plutĂŽt CHECK + PID. Cependant, il n'y a pas beaucoup de diffĂ©rence, car toutes les valeurs sont connues Ă  l'avance et il est facile de rĂ©organiser les grignotages par endroits. Tout de suite, nous Ă©crirons les principaux codes qui peuvent nous ĂȘtre utiles dans le fichier usbconfig.h.


Nous n'avons pas encore commencĂ© Ă  ajouter le code de traitement PID, notez qu'il devrait ĂȘtre rapide (c'est-Ă -dire dans l'assembleur), mais l'alignement par horloge n'est pas requis, car nous avons dĂ©jĂ  acceptĂ© le paquet. Par consĂ©quent, cette section sera par la suite transfĂ©rĂ©e dans le fichier asmcommon.inc, qui contiendra du code assembleur qui n'est pas liĂ© Ă  la frĂ©quence. En attendant, mettez simplement le commentaire en surbrillance.
Passons maintenant au tri des paquets reçus.


Encart théorique
Les paquets de donnĂ©es sur le bus USB sont combinĂ©s en transactions. Chaque transaction commence par l'envoi par l'hĂŽte d'un paquet marqueur spĂ©cial qui contient des informations sur ce que l'hĂŽte veut faire avec le pĂ©riphĂ©rique: configurer (SETUP), transmettre des donnĂ©es (OUT) ou les recevoir (IN). AprĂšs la transmission du paquet marqueur, une pause de deux bits s'ensuit. Ceci est suivi d'un paquet de donnĂ©es (DATA0 ou DATA1), qui peut ĂȘtre envoyĂ© Ă  la fois par l'hĂŽte et le pĂ©riphĂ©rique, en fonction du paquet marqueur. Ensuite, une autre pause de deux bits et la rĂ©ponse est HANDSHAKE, un paquet de confirmation (ACK, NAK, STALL, nous les considĂ©rerons une autre fois).
CONFIGURATIONDATA0Poignée de main
hĂŽte-> appareilpausehĂŽte-> appareilpauseappareil-> hĂŽte

OUTDATA0 / DATA1Poignée de main
hĂŽte-> appareilpausehĂŽte-> appareilpauseappareil-> hĂŽte

ENDATA0 / DATA1Poignée de main
hĂŽte-> appareilpauseappareil-> hĂŽtepausehĂŽte-> appareil


Étant donnĂ© que l'Ă©change va sur les mĂȘmes lignes, l'hĂŽte et l'appareil doivent constamment basculer entre la transmission et la rĂ©ception. De toute Ă©vidence, le dĂ©lai de deux bits est prĂ©cisĂ©ment Ă  cet effet et est fait pour qu'ils ne commencent pas Ă  jouer push-push, tout en essayant de transfĂ©rer simultanĂ©ment certaines donnĂ©es sur le bus.

Nous connaissons donc tous les types de packages nĂ©cessaires Ă  l'Ă©change. Nous ajoutons une vĂ©rification de l'octet PID reçu pour la conformitĂ© avec chacun. Pour le moment, le pĂ©riphĂ©rique n'est pas encore en mesure d'Ă©crire mĂȘme des paquets primitifs tels que ACK sur le bus, ce qui signifie qu'il est incapable de dire Ă  l'hĂŽte ce que c'est. Par consĂ©quent, des commandes comme IN ne peuvent pas ĂȘtre attendues. Nous ne vĂ©rifierons donc que la rĂ©ception des commandes SETUP et OUT, pour lesquelles nous indiquerons l'inclusion des LED correspondantes dans les branches correspondantes.


En outre, il vaut la peine de prendre l'envoi de journaux au-delĂ  de l'interruption, quelque part dans le principal.


Nous flashons l'appareil avec ce qui s'est passé aprÚs avoir effectué ces modifications et observons la séquence suivante d'octets reçus:


 2D|80|06|00|01|00|00|40|00 C3|80|06|00|01|00|00|40|00 2D|80|06|00|01|00|00|40|00 C3|80|06|00|01|00|00|40|00 

Et en plus - les deux LED allumées. Nous avons donc pris SETUP et OUT.


Étape 8. Lisez l'adresse sur l'enveloppe


Encart théorique
Les paquets de marqueurs (SETUP, IN, OUT) servent non seulement Ă  montrer au pĂ©riphĂ©rique ce qu'ils en veulent, mais Ă©galement Ă  adresser un pĂ©riphĂ©rique spĂ©cifique sur le bus et Ă  un point de terminaison spĂ©cifique Ă  l'intĂ©rieur. Des points de terminaison sont nĂ©cessaires pour mettre en Ă©vidence de maniĂšre fonctionnelle une sous-fonction particuliĂšre d'un appareil. Ils peuvent varier en frĂ©quence d'interrogation, en taux de change et en d'autres paramĂštres. Supposons que si l'appareil semble ĂȘtre un adaptateur USB-COM, sa tĂąche principale est de recevoir des donnĂ©es du bus et de les transfĂ©rer vers le port (premier point final) et de recevoir des donnĂ©es du port et de les envoyer vers le bus (deuxiĂšme). En termes de signification, ces points sont destinĂ©s Ă  un flux important de donnĂ©es non structurĂ©es. Mais en plus de cela, de temps en temps, l'appareil doit Ă©changer avec l'hĂŽte l'Ă©tat des lignes de contrĂŽle (toutes sortes de RTS, DTR, etc.) et Ă©changer des paramĂštres (vitesse, paritĂ©). Et ici, de grandes quantitĂ©s de donnĂ©es ne sont pas attendues. De plus, il est pratique lorsque les informations de service ne sont pas mĂ©langĂ©es avec des donnĂ©es. Il s'avĂšre donc qu'il est pratique d'utiliser au moins 3 points de terminaison pour l'adaptateur USB-COM. En pratique, bien sĂ»r, cela se passe de diffĂ©rentes maniĂšres ...
Une question tout aussi intĂ©ressante est de savoir pourquoi l'appareil reçoit son adresse, car en dehors de cela, vous ne pouvez toujours rien coller sur ce port particulier. Ceci est fait pour simplifier le dĂ©veloppement des concentrateurs USB. Ils peuvent ĂȘtre assez "stupides" et simplement diffuser des signaux de l'hĂŽte vers tous les appareils sans se soucier du tri. Et l'appareil lui-mĂȘme le dĂ©couvrira, traitera le paquet ou l'ignorera.
Ainsi, l'adresse du périphérique et l'adresse du point de terminaison sont contenues dans les paquets marqueurs. La structure de ces packages est donnée ci-dessous:
le terrain
le terrainSYNCaddrpoint finalCRCEop
Bits USB0-7012345601230123401
bits reçus0123456701234567


, - ( - PID = SETUP OUT) (IN) , .

, (-) (Handshake) :


  • : , , NAK
  • -: SETUP OUT, , IN — ,
  • . , , ,

« — » . PID', , . «PID» . usbCurrentTok. PID' (DATA0, DATA1) , . , ? : , ( 0 usbCurrentTok ), , . ( SE0) , - , D+, D- . , SYNC, . , , . «» , . .


, . x3, (, , , ).


, USB , , . , , , CRC ( ). , [21]. 0- . , [26]. , CRC, .


9.


, , « », ACK. NAK', ( cnt — ). USB , , SYNC PID. Y, cnt ( ). , — ACK. x3 — 1 , . x3 ( r20) 20.


( SETUP, ), ACK' , , , . , .


, D+, D- ( ), — . XOR , , , , - .


, , , , . , , , . . vusb : txBitloop 2 ([00], [08]). 3 , 6 . , . 1 3 : 171. ( 171, 11 , ), — , . cnt=4:


4 — 171 = -167 = ( ) 89 (+ )
89 — 171 = -82 = ( ) 174 (+ )
174 — 171 = 3. ,
, .


, 3 , 1. 6 , , x4. D+, D- , . .
:


 2D|80|06|00|01|00|00|40|00 69|00|10|00|01|00|00|40|00 

C3 . , , UART . , , IN , . , .


10. NAK


NAK , . , . , - .


, . , , - , . usbRxBuf, . , — , USB_BUFSIZE. usbInputBufOffset, . .


NAK handleData , [22]. (usbRxLen), - . ( — ), usbRxLen, , — usbRxToken, SETUP OUT - . : , , ACK .


. , , - , -, . ? , , , , - .


,


 2D|80|06|00|01|00|00|40|00 

, NAK`, , .


11.


, , . — . , , , , , . . . , USB, usbPoll. — , . — . SETUP , PID CRC, SETUP 5- , 16-. 3 «» . «» PID usbRxToken, CRC , , . usbProcessRx, , .


, , — , SE0. , USB .


. SETUP, . . SETUP usbRequest_t 8 . : ( USB-) , - . , . .
, , , .


12. SETUP'


, , . . usbDriverSetup, . , . , ( , , ) . , : ACK NAK, .


13.


, SETUP + DATAx, DATAx 8 . IN DATAx, . , . , ACK NAK. , . — usbTxBuf, , usbTxLen . low-speed USB 8 ( PID, CRC), usbTxLen 11. PID, , . , 16, , 0x0F, . PID , . IN, , (handshake , ).


:
SETUP + DATAx, ACK NAK . , , usbPoll, , ( PID=DATA1 ( DATA0 DATA1 , , DATA1). CRC . , , - . — 4 . , 3 , 4. , SYNC . « IN NAK?» NAK. , , DATA1 .


, — USBRQ_SET_ADDRESS ( , ). . (drvsdm.S, make SE0). , , , DATA1 , , . , , , , , . , , .


14.


, . , USBRQ_GET_DESCRIPTOR USBRQ_SET_ADDRESS, , . usbDriverDescriptor, . , USBRQ_GET_DESCRIPTOR. , , :


USBDESCR_DEVICE — : USB (1.1 ), , , . .
USBDESCR_CONFIG — , , . .
USBDESCR_STRING — , .
, , USBDESCR_DEVICE, , .


15.


. -, . , - - , , HID, , . Vendor ID Product ID, USB, . , vusb .


, , - . , , , (, ) usbMsgPtr, — len, usbMsgLen. ( ) 18 , 8. , , 3 . - , STALL.


usbDeviceRead. , memcpy_P, , , .


, , , . , , .


, , .



PID' DATA0 DATA1 . PID' , , - .

, DATA0 / DATA1 ( ), , , 3 , . XOR PID', . , , XOR' . PID DATA1, XOR PID , XOR DATA0 .


, , USBDESCR_CONFIG.


16. - !


USBDESCR_CONFIG USBDESCR_DEVICE. ( , ) . , - USB-, , D+, D-.


, : , , . , ( , ). , UTF-16, . USB UTF-8 .


vusb , lsusb . VID, PID , . , VID, PID, — .


, , ( ). SETUP: , , . 0, , — . , , , .
.


17. (HID)



HID — human interface device, , , . HID , . , , , , , . «» . HID ( low-speed 800 ), .

HID , USBDESCR_HID_REPORT. vusb, . , usbDriverSetup ( ) usbFunctionSetup ( ). , SETUP, OUT. , , , usbFunctionWrite.


, usbDeviceRead usbFunctionRead, . , , usbFunctionSetup ( , ) USB_FLG_USE_USER_RW, usbDriverSetup .


— — usbFunctionWrite usbFunctionRead. . — , .


usbDriverSetup.


18.


, , . HID, , , ( udev - ). , , . , , , .
UPD: ramzes2 , HIDAPI


.


19. vusb


vusb , .


drvasm.S - usbdrvasm.S asmcommon.inc, -, , usbdrvasm12.inc — usbdrvasm20.inc.


main.c main.c ( ) usbdrv.c ( vusb)
usbconfig.h ( ), , , usbconfig.h.


Conclusion


vusb, , , . , , . . , , , USB-HID. , , , vusb, , , , .



https://www.obdev.at/products/vusb/index.html ( vusb)
http://microsin.net/programming/arm-working-with-usb/usb-in-a-nutshell-part1.html
.. USB:
https://radiohlam.ru/tag/usb/
http://we.easyelectronics.ru/electro-and-pc/usb-dlya-avr-chast-1-vvodnaya.html
http://usb.fober.net/cat/teoriya/


PS - (, ) ,

Source: https://habr.com/ru/post/fr460815/


All Articles