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:
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 :
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.
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()) { // blocking
HIDTxReport(Buffer, PacketSize ); // transmit packet
pressed=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()) { // blocking
HIDTxReport(Buffer, PacketSize ); // transmit packet
pressed=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.