Une Carte d'interface sur un port USB, protocole HID

L'idée dans cet article est d'expliquer comment mettre en place une carte électronique pouvant recevoir des ordres d'un ordinateur via le port USB tel que : allume une sortie, fait une conversion analogique,.. et que la carte transmette à l'ordinateur des réponses (la valeur de la conversion analogique, l'état d'une entrée, ...). On associe souvent au terme USB : la vitesse, l'utilisation dans des applications de stockage de données tel que les clés ou les disques dur externes. Ces dernières applications font partie d'un ensemble particulier « Mass storage » qui ne nous intéresse pas dans cet article. La gamme qui nous intéresse est le périphérique HID, qui est les initiales de « Human Interface Device » c'est-à-dire dans notre bon vieux français périphérique d'interface avec un humain. Cette gamme regroupe les outils les plus utilisés comme les souris, claviers et joysticks. La vitesse de ces périphériques est aussi limitée à 64ko/s… Ce qui entre nous n'est déjà pas mal et permet de résoudre pas mal d'application que ce soit en domotique ou en robotique.

Pourquoi utiliser le protocole HID

Comme il est indiqué au paragraphe un, les périphériques HID permettent la communication entre l'homme et la machine, on les retrouve sous la forme de clavier, souris, tablette graphique… Le but de cet article est de vous permettre de créer un périphérique qui vous offrira la possibilité de faire communiquer n'importe quoi avec l'ordinateur au travers du port USB, d'en recevoir des ordres et d'y renvoyer des réponses. Cette carte aura l'avantage de ne nécessiter aucun driver particulier, Windows possède en effet tous les pilotes nécessaires pour communiquer. Il ne restera plus, pour créer la communication, qu'à vous servir de votre langage de programmation préféré et d'une Dll qui s'occupera du reste.

L'application que nous allons décrire est très simple, elle utilisera une led, un bouton poussoir et une entrée analogique. Le but est dans un premier temps, lorsque l'utilisateur appuie sur le bouton, d'envoyer la valeur présente sur l'entrée analogique à l'ordinateur. Deuxièmement de pouvoir envoyer depuis l'ordinateur une requête pour effectuer une acquisition analogique. Et troisièmement depuis l'ordinateur de demander à la carte de faire clignoter la led un certain nombre de fois à une certaine fréquence. L'application d'exemple est simple, et certain n'y verront peut-être pas l'utilité, elle possède néanmoins plusieurs fonctions qui peuvent nous intéresser. Premièrement de tout simplement créer une communication depuis le port USB via un logiciel informatique, deuxièmement de pouvoir depuis la carte envoyer des informations à l'ordinateur, sans avoir pour cela besoins de programmer nous même une routine qui interroge continuellement la carte pour savoir si elle à quelque chose à dire.

Le protocole HID limite l'application à un débit de 64ko/s, c'est-à-dire d'envoyer un paquet de 64octets toutes les millisecondes. Pour information le temps de cycle d'un automate programmable destiné à contrôler une chaîne de production se compte souvent en plusieurs millisecondes. Nous pouvons donc estimer que pour allumer une lampe, mesurer une température, écrire sur un écran LCD ou même contrôler un robot, … C'est bien suffisant. Nous pouvons sans crainte nous y aventurer sans forcément se sentir limité. De plus est, le lecteur passionné d'électronique aura très vite compris qu'il peut sans problème interfacer d'autre circuit logique avec la carte s'il a besoins de travailler sur des bases de temps plus courtes. Pour finir dans les bonnes nouvelles les pilotes HID sont tels que si l'ordinateur envoie trop d'information à la carte de telle manière que celle-ci soit saturée, Windows les placera dans un buffer en attendant de les traiter. Ce qui évitera de faire planter la machine.

Mise en place de l'application

L'USB a le désavantage d'obliger l'utilisation de composants particuliers. Dans notre application, nous aurons recours au microcontrôleur 18f4550 de Microchip, une puce simple à programmer (en C) et munie de tous les composants hardware nécessaire à la communication sur port USB. Le schéma de la mise en place du composant reste très simple:
schema18f4550
Au centre du schéma, nous retrouvons le PIC18F4550, celui-ci est cadencé par un quartz à 20MHz. Nous avons aussi câblé sur ce schéma 4 entrées analogiques: AN0, AN1, AN2 et AN3. Sur les broches RC0 et RC1 nous plaçons deux leds qui nous servirons pour notre application. Sur la broche RC2 un switch qui servira aussi pour cet exemple. Le port B et D restent inutilisés pour notre article mais nous plaçons la possibilité d'étendre les fonctionnalités de la carte en installant des borniers.
Les capacités C1 et C2 sont de 22pf. Pour R5, R3, R4, R7 nous placerons simplement des résistances de 1k. Attention de ne pas se tromper pour le connecteur USB. Il s'agit d'un connecteur femelle de Type B. Pour la connecter au PC, j'utilise un cordon d'imprimante A vers B, le même que les cordons HP.

Le Firmware :

Sur le site du constructeur (http://www.microchip.com) on peut trouver une multitude de d'exemples liés au 18f4550. Pour l'application que nous construisons, nous nous baserons sur le l'exemple de la simulation d'une souris qui tourne en rond auquel nous aurons modifié le firmware.

Pour se faire, le firmware diffusé sur ce site a du subir une première modification au point de vue du descripteur. Ce dernier est une partie du code source qui reprend l'usage et les différents paramètres de la carte USB, tel que son PID et VID qui permettent d'identifier le périphérique usb et d'ouvrir la communication avec, mais aussi toutes les données relative au type d'application, au volume des paquets transmis, au débit maximum… Nous n'entrerons pas au sein de cet article dans cette procédure qui demande beaucoup de connaissance de la liaison USB, mais vous trouverez au bas de l'article des liens qui pourront vous renseigner sur ces sujets. Sachez néanmoins que c'est dans cette partie du code que la carte sera différenciée d'une souris, d'un clavier, d'un joystick ou d'un autre type d'application.

Nous ne nous intéresserons qu'à une seule et unique partie du code source qui est le fichier « user.c » situé dans le répertoire « user ». Ce fichier contient la procédure « ProcessIO » qui est exécuté cycliquement et qui permet à l'utilisateur de traiter les données reçues et d'en renvoyer de nouvelle. Pour ça nous possédons quelques fonctions.
HIDRxReport(Buffer,PacketSize)
La fonction renverra une valeur supérieur à 0 si la carte à reçu un paquet dans « Buffer » de la taille de PacketSize.
mHIDTxIsBusy()
Permet de voir si la liaison usb est actuellement occupée (dans ce cas la fonction renvoie 1)
HIDTxReport(Buffer, PacketSize)Permet de renvoyer un paquet contenu dans Buffer de la taille de PacketSize.

La vitesse de la communication ne dépend que de deux paramètres: la taille du paquet envoyé (maximum 64 bytes) et la période séparant chaque demande du pic (« pooling » minimum 1 ms). Soit le pic peut recevoir 64 bytes toutes les 1 ms dans les meilleures conditions. C'est-à-dire 1000 paquets de 64 octets. Donc 64 ko/s. Ces paramètres sont réglables dans le descripteur (« usbdsc.c »). Nous aurons donc défini « PacketSize » comme valant « 64 » afin d'atteindre la vitesse la plus haute au cas du besoins.

Le firmware - ProcessIO

C'est cette partie ci du programme la plus intéressante pour celui qui désire créer facilement une application USB. Comme il a été dit au chapitre précédent, le PIC va exécuter en boucle cette procédure. C'est via ces lignes de codes que l'on va traiter les informations reçues et les informations à envoyer.
graphprocessio
Première étape, nous interrogeons la fonction HIDRxReport afin de savoir si l'ordinateur à envoyé un paquet de données à la carte. Nous remarquons directement que c'est à chaque cycle que l'on pourra être mis au courant de nouvelles informations. Les cycles sont très rapide, c'est pourquoi nous pouvons considérer que lorsque la carte ne fait rien, le temps de réaction est immédiat (à l'échelle de perception humaine).
Si la carte à reçu quelque chose, on entre alors dans une sous procédure qui va analyser le paquet reçu, celui-ci se trouve dans un tableau Buffer où le premier élément est : Buffer[0] et le dernier élément Buffer[63]. Dans notre application, la carte pourra recevoir deux ordres différents, celui de demander une acquisition sur l'entrée analogique et celui de faire clignoter la led.
Premier cas: acquisition :
if (Buffer[0]==0){Buffer[0]=255;getacd(0, 1);Buffer[1] = ADRESH ;Buffer[2] = ADRESL ;}  Dans ce cas, l'instruction sera représentée par une valeur 0 dans Buffer[0], donc lorsque la carte lit cette valeur, elle sait qu'elle doit effectuer l'acquisition. Pour montrer à l'utilisateur, que la carte est bien entrée dans cette procédure, elle va mettre dans Buffer[0] la valeur 255. En effet, ce sera le même tableau où l'on a reçu les données qui sera envoyé à l'ordinateur avec des valeurs modifiées bien évidemment. La fonction « getacd » va ensuite être exécutée, celle-ci demande deux paramètres, le premier est le Channel sur lequel on veut effectuer la conversion, le second précise que l'on veut utiliser les paramètres d'acquisition inscrits dans le firmware (configuration des registres ADCONx). Le résultat de l'acquisition se trouve dans les registre du pic ADRESH (les bits de poids fort) et ADRESL (bits de poids faible) que nous copierons dans Buffer[1] et Buffer[2].
Deuxième cas: clignotant :
if (Buffer[0]==1){for (i=0; i==[Buffer[1]; i++) {PORTCbits.RC1 = 1;Delay10KTCYx(Buffer[2]);PORTCbits.RC1 = 0; Delay10KTCYx(Buffer[2]); } }   
Cette fois ci nous entrerons dans cette procédure en plaçant 1 dans Buffer[0]. La procédure allumera et éteindra un nombre de fois déterminé dans Buffer[1] (premier paramètre de l'instruction 1) la led placée sur la sortie RC1. Entre chaque état on marquera une pause via la fonction Delay10KTCYx qui génère un délai de x fois 10 000 cycle, un cycle valant 200ns, dix milles cycle équivaudront à 2ms. Nous déterminerons ce temps de chaque état via un deuxième paramètre placé dans Buffer[2] qui sera le nombre de pause de 2ms que la led doit restée allumée puis éteinte. Nous remarquerons que le tableau Buffer n'est pas modifié au cours de la procédure, il sera envoyé tel quel à l'ordinateur pour indiquer que le message à bien été reçu.
Troisième cas: L'envoie direct depuis la carte.
if (PORTCbits.RC2 == 0) {if (!pressed) {Buffer[0]=255;getacd(0, 1);Buffer[1] = ADRESH ;Buffer[2] = ADRESL ;if(!mHIDTxIsBusy()) { // blockingHIDTxReport(Buffer, PacketSize ); // transmit packetpressed=1;}}if (PORTCbits.RC2 == 1) {pressed=0;}
Ce troisième cas est très intéressant, à la différence des deux premiers, l'ordinateur n'envoie aucune requête à la carte, c'est celle-ci qui envoie d'elle-même à l'ordinateur une information. On entre dans cette procédure lorsque le switch placé sur RC2 est appuyé, et donc que RC2 passe au niveau 0 (le switch crée un court circuit entre l'entrée et la masse). On a vu précédemment que la procédure processIO est exécutée en boucle, donc si on laisse le bouton appuyé, le pic va continuellement revenir dans la partie du code intéressée et envoyé en continu de l'information. Pour éviter cela et n'envoyer qu'une seule information au moment où l'on appuie sur le bouton, nous nous servirons d'une variable « pressed » déclarée comme static*** qui prendra la valeur 1 quand l'information aura été envoyée et qui ne reviendra à 0 que lorsque le bouton aura été relâché. Nous placerons ensuite 255 dans Buffer[0] pour indiquer à l'ordinateur que nous envoyons une acquisition. Nous appellerons la fonction « getacd » vue précédemment. Le temps de cycle de la fonction ProcessIO suffit pour servir d'anti rebond au switch que nous avons installé.
Nous arrivons maintenant à la partie la plus importante du code :
if(!mHIDTxIsBusy()) { // blockingHIDTxReport(Buffer, PacketSize ); // transmit packetpressed=1; } 
Nous testons avant tout si la ligne est disponible pour l'envoie de donnée, si ce n'est pas le cas, nous reviendrons plus tard, dans le cas où la ligne est libre, nous envoyons le tableau Buffer et nous faisons passer la variable statique pressed à 1 afin de ne plus ré exécuté le code jusqu'à ce que le bouton soit relaché.
Dans la suite de l'article, découvrez la programmation sur l'ordinateur de la carte et accedez aux fichiers.
*** Pour rappel une variable statique est une variable interne à une fonction mais qui conserve sa valeur jusqu'au prochain appel de la fonction.

Le logiciel windows :

L'exemple de client que nous allons écrire sera en Delphi. Il existe des versions gratuite de Delphi disponible sur http://www.codegear.com.

Pour nous simplifier la tâche, nous allons utiliser une DLL écrite par Mecanique (http://www.mecanique.co.uk). Dans le dossier téléchargeable à la fin de l'article, vous trouverez cette DLL. Celle-ci était fournir avec le logiciel EasyHID, les copyright de ce dernier nous empêchant de le distribuer, nous ne vous transmettrons que la DLL et le code source du programme que nous avons réalisé.

Le logiciel - Exploration du code

Premièrement nous avons quatre constantes importantes :
BufferInSize  = 64;BufferOutSize = 64;VENDOR_ID = 1240;PRODUCT_ID = 0005;
Nous nous rappellerons que lorsque nous avons travaillé le firmware de la carte, nous avons brièvement parlé du descripteur, dans lequel nous configurons le VID (Vendor ID) PID (product ID). Ces deux valeurs identifiant auprès de l'ordinateur le périphérique branché. Dans le client qui va communiquer avec la carte, ces deux valeurs doivent se retrouver. Nous avons aussi la taille du buffer d'entrée et de sortie que nous avons configuré à 64 octets.
Nous sautons volontairement dans le code de nombreuses lignes de codes propres à la création des objets et la déclaration des procédures qui est automatique générée par delphi pour en arriver à l'envoie des informations. Pour ça nous avons un petit commentaire bien sympathique que nous allons analyser :
(*if you want to write some data to the USB device, thentry using something like this... 
// first element is report ID, set to 0...FBufferOut[0] := $0; 
// fill the buffer with some data...for index :
= 1 to BufferOutSize doFBufferOut[index] := index; DevHandle :
= GetHandle(VendorID, ProductID);Write(DevHandle,@FBufferOut);*) 
Le programme nous trouvons un tableau utilisable dans toute l'application de 65 éléments qui s'appelle FBufferOut. C'est dans ce tableau que l'on place les données à envoyer. Pourquoi 65 ??? Parce que l'élément 0 du tableau c'est le reportID, il devra toujours est mis à 0 comme indiqué dans le commentaire. Nous pouvons ensuite mettre dans les case 1 à 64 toutes nos données.
Une fois les données prêtes, nous devons créer la communication avec la carte. La première étape est de dire au programme à quelle adresse il doit envoyer son paquet. Pour ça DevHandler, variable de type cardinal, se voit attribuer une valeur, et cette valeur sera donnée par la fonction GetHandle (VendorID, ProductID). C'est là que l'on remarque l'importance de ces deux nombres. En effet lorsque l'on connecte la carte à l'ordinateur, celui-ci lui octroit un « Handle » et pour retrouver cet « Handle » et pouvoir communiquer avec la carte, il faut demander à l'ordinateur quel valeur il a attribué à la carte possédant tel et tel VID et PID. Il nous suffit plus que d'envoyer le tout par la fonction Write(DevHandle, @FBufferOut) . « @ » Car nous envoyons à la fonction la référence du tableau contenant les informations afin que celle-ci récupére chaque élément en mémoire.
La lecture de donnée se passe de façon différente. Nous avons dans le code du programme, une fonction préécrite :
function TForm1.USBEvent(var Msg: TMessage): Boolean;
Cette fonction réagit à chaque événement qui se produit sur le port USB. Si l'on connecte ou déconnecte une carte… Si elle envoie des données… C'est-à-dire que chaque fois qu'il va y avoir un échange entre une carte et l'ordinateur, cette fonction va réagir automatiquement. Et dans cette fonction nous avons justement :
NOTIFY_READ :beginDevHandle := Msg.LParam; // handle of HID device in this messageif (GetVendorID(DevHandle) = VENDOR_ID) and (GetProductID(DevHandle) = PRODUCT_ID) then begin// read the data - remember that first byte is report ID...Read(DevHandle,@FBufferIn);// process data here...end; 
Une fois que la carte va créer un évènement en voulant envoyer quelque chose, le logiciel va détecter qu'il s'agit de donnée à recevoir, il va alors entrer dans cette partie de procédure NOTIFY_READ.
Première étape il va regardé le « Handle » de la carte qui envoie une donnée, et va comparé celui-ci au « Handle » de notre carte, s'il s'agit du bon, il va lire les données envoyées par la carte. Nous pouvons alors placer notre code à la suite de cette instruction Read(DevHandle, @FBufferIn). Toutes les données reçues se trouveront dans le tableau FBufferIn.

Le Logiciel - Réalisation de notre interface

interface
Pour cette opération, nous aurons besoins de :
  • 3 Labels
  • 1 Gauge
  • 2 Boutons
  • 2 SpinEdits
  • 1 Memo
Placé comme sur l'image ci-dessus.
La gauge nous donnera en pourcent la valeur de la tension par rapport 5V, le bouton "mesurer" demandera une mesure, le premier label nous affichera la valeur mesurée, le bouton "Clignoter" enverra un paquet avec le nombre de clignotement et la période de ceux-ci. Et les deux spinedits permettront de configurer ces deux derniers paramètres.
Pour demander une mesure, nous devons simplement envoyer un paquet où l'élément 1 du paquet vaut 0 :
// mise à 0 du bufferFBufferOut[1] := $0; // on met le buffer[0] a 0
// Attribution de la carte usbDevHandle := GetHandle(VENDOR_ID , PRODUCT_ID);Write(DevHandle,@FBufferOut);
Pour faire clignoter la led, nous devons placer 1 dans l'élément 1 du buffer, placer le nombre de fois dans l'élément 2 et la pause entre chaque changement d'état dans l'élément 3
// mise à 0 du bufferFBufferOut [1]:= 1 ;FBufferOut [2]:= spinedit1.value ; // N° de foisFBufferOut [3]:= spinedit2.value ; // la pause
// Attribution de la carte usbDevHandle := GetHandle(VENDOR_ID , PRODUCT_ID);Write(DevHandle,@FBufferOut);
Petit conseil pour éviter les erreurs, dans les paramètres du spinedit, placez que celui-ci ne peut pas prendre de valeur supérieur à 255.
Maintenant la réception des données, comme nous l'avons vu précédemment, lorsque la carte va nous envoyer quelque chose, le programme va directement réagir, nous allons donc créer une nouvelle fonction « Refresh » qui sera appelée par la fonction USBEvent lorsque celle-ci réagira pour un envoie de donnée :
NOTIFY_READ :beginDevHandle := Msg.LParam; // handle of HID device in this messageif (GetVendorID(DevHandle) = VENDOR_ID) and (GetProductID(DevHandle) = PRODUCT_ID) thenbegin// read the data - remember that first byte is report ID...Read(DevHandle,@FBufferIn);Refresh ; // On appelle alors la fonction refresh !// process data here...end;  
La fonction "refresh" va premièrement regarder ce qui lui est envoyé, la carte enverra en élément 1 du paquet '255' s'il s'agit d'une mesure, le seul cas qui nous intéresse. S'il s'agit bien de 255, l'élément 2 possédera les 8 bits de poids fort et l'élément 3 les deux bits de poids faible restant de la conversion analogique (qui s'effectue sur 10 bits, ouf on a assez de doigts). Par une petite opération mathématique, nous reconstituons une seule valeur binaire regroupant les deux parties du message. Et nous affichons le résultat :
function TForm1.Refresh(): integer;var i : integer;var valeur : integer;beginif (FBufferIn[1]=255) then beginvaleur := FBufferIn[2]*4 + round(FBufferIn[3]/64);gauge1.Progress :=round(100*valeur/1024);label3.caption :='La valeur est : '+floattostrf(5 * valeur/1024,ffFixed,3,2)+' Volt';end;result := 0;end; 

Installation du tout :

proginaction
Il nous reste plus qu'à monter le tout, nous laisserons le soin à l'utilisateur de monter la carte par ses propres moyens, sachant que schéma et pcb sont mis à disposition. Pour la programmation du 18f4550, le programmateur de pic disponible sur ce site fonctionne très bien avec le 18f4550 en utilisant le logiciel WinPic800. Il ne suffit plus que de charger le firmware avec ce programme, toutes les options se configureront automatiquement.

Pour le logiciel, voici un screen de l'application en fonctionnement, vous remarquerez que la fonction « mesurer » est identique à l'action de pousser sur le bouton. Et que la gauge évolue en fonction de ce qui se trouve sur l'entrée analogique 0. Ceux muni d'un chronomètre pourront aussi vérifié le bon fonctionnement du clignotement.
detect

Autres informations et liens

Dès à présent, il vous est possible de réaliser vos propres interface usb, celle-ci est compatible avec toutes les versions de Windows supérieur où égale à 98. N'oubliez par de munir votre programme du fichier mcHID.dll.
Pour ceux qui voudrait en savoir plus sur les PID/VID et tout le reste :
Pour ceux qui voudrait utiliser autre chose que la DLL de mencanique, voici un composant pour Delphi permettant de communiquer avec les interface HID :
Pour ceux qui veulent en savoir plus sur le 18f4550 :
L'ensemble des fichiers nécessaire et suffisant à la réalisation de l'exemple de cet article est disponible directement ici:

Aucun commentaire:

Enregistrer un commentaire