IMA4 2019/2020 EC1 : Différence entre versions

De Wiki d'activités IMA
(Projet PAD)
(Optimisation et limites)
 
(7 révisions intermédiaires par le même utilisateur non affichées)
Ligne 23 : Ligne 23 :
 
= Cahier des charges =
 
= Cahier des charges =
  
Durant cette épreuve, je vais me servir de la board ''XMEGA-C3 Xplained'' comme un périphérique USB spécifique. Afin de réaliser toutes les tâches attendues, il va falloir procéder en plusieurs étapes :
+
Durant cette épreuve, je vais me servir de la board ''XMEGA-C3 Xplained'' comme un périphérique USB spécifique. Je vais devoir gérer les Endpoints pour que ma carte communique avec mon PC. Afin de réaliser toutes les tâches attendues, il va falloir procéder en plusieurs étapes :
  
 
* '''Lire l'état de mes I/O et sauver les points d'accès''' : Pour se faire, il va falloir implanter une configuration avec la librairie [http://libusb.sourceforge.net/api-1.0/modules.html libusb-1.0] :  
 
* '''Lire l'état de mes I/O et sauver les points d'accès''' : Pour se faire, il va falloir implanter une configuration avec la librairie [http://libusb.sourceforge.net/api-1.0/modules.html libusb-1.0] :  
  
 
Interface ''IN'' : un seul point d'accès (sens Hôte vers Périphérique) <br>
 
Interface ''IN'' : un seul point d'accès (sens Hôte vers Périphérique) <br>
Interface ''OUT'' : deux points d'accès (sens inverse)
 
  
* '''Créer le déclenchement et l'arrêt du chenillard en C''' : Pour programmer le microcontrôleur, la [http://www.fourwalledcubicle.com/LUFA.php LUFA] me permettra de gérer l'USB sans passer par les registres. Pour téléverser mon programme, je me servirai de l'utilitaire dfu-programmer.
+
Elle me permettra d'écrire une donnée sur l'Endpoint et la recevoir sur mon PC pour connaître l'état de ma carte.
  
= Travail réalisé =
+
Interface ''OUT'' : deux points d'accès (sens inverse)
 
 
==Périphérique USB==
 
 
 
===Reconnaissance USB===
 
Afin de se servir du ''XMEGA-C3 Xplained'' comme périphérique USB, il faut le reconnaître parmi tous les périphériques de la machine. Grâce à la commande suivante, je peux facilement reconnaître les caractéristiques de la carte :
 
<pre>lsusb</pre>
 
  
Je crée alors une fonction ''enum_usb'' qui reconnaîtra le périphérique pour la suite des opérations et qui ne se soucis pas des autres :
+
Cette interface servira à lire les données sur les Endpoints depuis l'Atxmega384c3. Ces données seront envoyées depuis le PC.
  
[[Fichier:DeviceF.PNG|center|x150px|Reconnaissance USB]]
+
* '''Gestion des LEDs et du chenillard en C''' : Pour programmer le microcontrôleur, la [http://www.fourwalledcubicle.com/LUFA.php LUFA] me permettra de gérer l'USB et les Endpoints sans passer par les registres. Pour téléverser mon programme, je me servirai de l'utilitaire dfu-programmer.
  
Je peux ensuite sauver la "poingée" dans une variable globale de type ''libusb_device_handle *''.
+
'''Je pourrai alors créer une communication bi-directionnelle via deux applications : HOST vers DEVICE ainsi que DEVICE vers HOST.'''
  
Il faut ensuite configurer notre périphérique USB. Je crée une fonction ''configuration'' qui permettra, dans un premier temps, de récupérer la première configuration de la carte via un pointeur sur périphérique ''libusb_device *''. Dans un second temps, je peux afficher cette configuration et la déclarer comme courante. Cependant, le périphérique ou la ressource semblent occupés. Je tente de résoudre ce problème au plus vite.
+
= Travail réalisé =
 
 
Il semblerait que cela soit dû au méchant noyau passé avant moi. Il faut détacher le "''driver''". Je crée alors une fonction ''detach'' qui inclura ''libusb_detach_kernel_driver''. Je peux maintenant récupérer et sauver les endpoints.
 
 
 
===Récupération des Endpoints===
 
 
 
Grâce à la commande suivante, je peux retrouver le parcours de l'arbre arborescent et les paramètres qui m'intéressent :
 
 
 
<pre>lsusb -D /dev/bus/usb/001/00x </pre>
 
 
 
Avec x le numéro correspondant à l'adresse du périphérique (retrouvé avec ''lsusb'') qui change lorsqu'on détache le driver. Pour éviter des manipulations pénibles, je crée une fonction ''attach'' qui inclura ''libusb_attach_kernel_driver'' et qui fixera x. Le but est de re-attacher le driver après l'avoir détaché dans la fonction ''configuration''.
 
 
 
Grâce à cette commande, nous pouvons retrouver les 3 interfaces de notre périphérique. L'interface 0 possède un seul Endpoint IN tandis que les deux autres ont un Endpoint IN ainsi que OUT. Voici un résumé dans le tableau ci-dessous :
 
  
{| class="wikitable" style="margin: auto;"
+
==Programme de l'ATXMEGA384C3==
! Numéro Interface !! Adresse EP_IN !! Type de Transfert EP_IN !! Adresse EP_OUT !! Type de Transfert EP_OUT
 
|-
 
| 0 || 0x81 || Interrupt || NONE || NONE
 
|-
 
| 1 || 0x82 ||BULK || 0x02 || BULK
 
|-
 
| 2 || 0x83 || BULK || 0x03 ||BULK
 
|}
 
  
On peut remarquer que tous les Endpoints ne sont pas de type Interrupt. Pour notre application, je vais m'intéresser à l'Endpoint IN de type Interrupt de l'interface 0, ainsi qu'aux Endpoints OUT des interfaces suivantes. Cependant, vous pouvez voir ci-dessous que j'ai sauvé tous les endpoints au début, pour éviter certains problèmes. Puis j'ai sélectionné ceux qui m'intéressaient.
+
===Implantation USB===
  
Enfin, il faut libérer le périphérique USB, grâce à une fonction ''libere'' . Cela sert à relâcher toutes les interfaces et fermer la "poignée". Voici le résultat final de mon programme :
+
Pour commencer, il faut implanter un périphérique USB. Tout le projet sera inspiré de la démonstration KeyBoard dans le répertoire Device/LowLevel/Keyboard de la LUFA.
  
[[Fichier:Configend.PNG|center|x500px|Exécution finale du programme]]
+
Pour que le programme fonctionne avec la carte, je peux adapter le fichier makefile de la façon suivante :
  
===Intéractions avec la carte===
+
[[Fichier:Mmakefile.PNG|center|x150px|Makefile]]
  
Suite à cela, je test ma board en récupérant les valeurs de mes boutons et en allumant mes LEDs. Pour communiquer avec la carte, j'utilise les fonctions ''libusb_interrupt_transfer'' et ''libusb_bulk_transfer'' ([http://libusb.sourceforge.net/api-1.0/group__libusb__syncio.html#gac412bda21b7ecf57e4c76877d78e6486 détails ici]) selon les types de transferts de mes points d'accès.
+
Les fréquences choisies sont propres à la [http://www.farnell.com/datasheets/1853183.pdf?_ga=2.135649527.711003060.1587031958-701760493.1587031958&_gac=1.192153176.1587031958.CjwKCAjwhOD0BRAQEiwAK7JHmL1ECX70Im_xdIpQne1WMBDu1J15ltd9ksDnAFcec6xCvAP2dJSlfhoCmHUQAvD_BwE board].
 
 
Je crée alors une fonction ''board'' qui, selon le choix de l'utilisateur, test les LEDs ou les boutons. Puis, je récupère les valeurs ''data'' dans une boucle infini (attendant l'appui d'un bouton par exemple), mais elle reste à 0. Je ne parviens pas à tester ma board pour l'instant.
 
 
 
==Programmation avec LUFA==
 
 
 
Comme dans le Tutorat, j'ai crée un projet ''PAD'' inspiré de ''RelayBoard'' dans un répertoire ''PolytechLille'' situé au même niveau que ''Demos'' et ''Projects''. Dans le chemin suivant, je commente l'option ''CONTROL_ONLY_DEVICE'' :
 
 
 
<pre>PolytechLille/PAD/Config/LUFAConfig.h</pre>
 
 
 
Puis, je peux configurer mon ''makefile'' comme ci-dessous :
 
 
 
[[Fichier:makefile.PNG|center|x150px|Makefile]]
 
 
 
Les fréquences choisies sont propres à la [http://www.farnell.com/datasheets/1853183.pdf?_ga=2.135649527.711003060.1587031958-701760493.1587031958&_gac=1.192153176.1587031958.CjwKCAjwhOD0BRAQEiwAK7JHmL1ECX70Im_xdIpQne1WMBDu1J15ltd9ksDnAFcec6xCvAP2dJSlfhoCmHUQAvD_BwE board]. Tout le programme sera inspiré par le projet ''Keyboard'' situé dans le chemin ci-dessous :
 
 
 
<pre>Demos/Device/LowLevel/Keyboard</pre>
 
 
 
===Descriptors===
 
  
 
Je peux maintenant adapter ''Descriptors.h'' à mon application. Je déclare, comme ci-dessous, mes trois interfaces dans une structure :
 
Je peux maintenant adapter ''Descriptors.h'' à mon application. Je déclare, comme ci-dessous, mes trois interfaces dans une structure :
Ligne 121 : Ligne 76 :
 
</pre>
 
</pre>
  
Enfin, il faut définir l'adresse de chacun de mes endpoints ainsi que leur taille (en bits):
+
Enfin, il faut définir l'adresse et la direction de chacun de mes endpoints ainsi que leur taille (en bits):
  
 
<pre>
 
<pre>
 
#define BOUTONS_EPADDR  (ENDPOINT_DIR_IN | 1)
 
#define BOUTONS_EPADDR  (ENDPOINT_DIR_IN | 1)
#define LEDS1_EPADDR    (ENDPOINT_DIR_OUT | 2)
+
#define LEDS1_EPADDR    (ENDPOINT_DIR_OUT | 1)
#define LEDS2_EPADDR    (ENDPOINT_DIR_OUT | 3)
+
#define LEDS2_EPADDR    (ENDPOINT_DIR_OUT | 2)
 
#define EPSIZE          8
 
#define EPSIZE          8
 
</pre>
 
</pre>
  
Dans mon fichier ''Descriptors.c '', j'inclus ''Descriptors.h''. Je peux alors renseigner les VendorID et ProductID de ma board ainsi que déclarer mes interfaces et points d'accès.
+
Comme vous pouvez le voir, nous allons utiliser 1 Endpoint IN pour les boutons et 2 Endpoints OUT pour les LEDs.
 +
 
 +
Dans mon fichier ''Descriptors.c '', j'inclus ''Descriptors.h''. Je peux alors renseigner les VendorID et ProductID de ma board ainsi que déclarer mes interfaces et points d'accès. La XMEGAC3 XPLAINED sera finalement reconnue comme un périphérique USB avec les interfaces définies grâce à la fonction USB_USBTask() de la boucle principale de mon programme chenillard.c.  
  
===Projet PAD===
+
===Gestion des Endpoints===
  
Dans mon fichier ''PAD.h'', je définis tous les mask correspondant aux LEDs qui m'intéressent. Dans le chemin suivant, je retrouve la déclaration de chacune des LEDs :
+
Dans mon fichier ''chenillard.h'', je définis tous les mask correspondant aux LEDs qui m'intéressent. Dans le chemin suivant, je retrouve la déclaration de chacune des LEDs :
  
 
<pre>LUFA/Drivers/USB/XMEGA/C3_XPLAINED/LEDS.h</pre>
 
<pre>LUFA/Drivers/USB/XMEGA/C3_XPLAINED/LEDS.h</pre>
  
Puis je déclare ma fonction ''SetupHardware()'' qui me permettra de configurer la partie Hardware du projet. Voici mon ''PAD.h'' final :
+
Puis je déclare mes fonctions prototypes qui me serviront pendant ce projet. Voici mon chenillard.h final :
  
[[Fichier:padh.PNG|500px|center|PAD.h]]
+
[[Fichier:Chenh.PNG|700px|center|chenillard.h]]
  
Dans mon ''PAD.c'', la fonction ''main()'' me permettra de configurer la partie hardware de la carte correspondante à mon application. Puis, dans une boucle infini, la fonction [http://www.fourwalledcubicle.com/files/LUFA/Doc/170418/html/group___group___u_s_b_management.html#gac4059f84a2fc0b926c31868c744f5853 ''USB_USBTask()''] me permet d'exécuter mon programme de manière continue.
+
Dans mon ''chenillard.c'', ma fonction principale ''main()'' configure ma XMEGAC3 XPLAINED et appelle mes fonctions dans une boucle infini pour gérer continuellement les Endpoints.
  
Dans ma fonction ''SetupHardware'' je prends en compte uniquement la condition suivante pour que le programme soit adapté à ma carte :
+
Dans ''SetupHardware()'', je prends uniquement en compte la condition suivante pour que le programme soit adapté à ma carte :
 
<pre>
 
<pre>
 
#if (ARCH == ARCH_XMEGA)
 
#if (ARCH == ARCH_XMEGA)
Ligne 151 : Ligne 108 :
 
</pre>
 
</pre>
  
Puis j'initialise toutes mes I/O avec les fonctions ''LEDs_Init()'', ''USB_Init()'' et ''Buttons_Init()''
+
Puis j'initialise toutes mes I/O avec les fonctions ''LEDs_Init()'', ''USB_Init()'' et ''Buttons_Init()''. Je peux maintenant créer mes applications et gérer mes Endpoints :
  
Enfin, je fais appel à la fonction ''EVENT_USB_Device_Connect()'' qui s'exécute dès que la carte est connectée. C'est dans celle-ci que mon programme principal se trouve. Pour la gestion des boutons, le chemin suivant me permet de récupérer la fonction ''Buttons_GetStatus()'' et les masks correspondants :  
+
* Mes fonctions ''LEDS_RV()'' et ''LEDS_OR()'' me permettent de gérer les Endpoints OUT de mes LEDs. Ces fonctions sélectionnent le point d'accès avec la fonction ''Endpoint_SelectEndpoint()''. Lorsqu'il est prêt, nous pouvons lire la donnée ''data'' qui lui est attribuée. Selon sa valeur, les LEDs peuvent s'allumer ou s'éteindre. Il ne faut pas oublier la fonction ''Endpoint_ClearOUT()'' en fin d'action pour libérer le point d'accès et le rendre prêt lors les futurs échanges de données.
 +
 
 +
* Ma fonction ''BOUT_IN()'' sélectionne l'Endpoint correspondant et attend la pression d'un bouton lorsqu'il est prêt; avec la fonction ''Buttons_GetStatus()''. Les boutons correspondants à ma XMEGAC3 XPLAINED peuvent être retrouvés dans le répertoire suivant :
  
 
<pre>LUFA/Drivers/Board/XMEGA/C3_XPLAINED/Buttons.h</pre>
 
<pre>LUFA/Drivers/Board/XMEGA/C3_XPLAINED/Buttons.h</pre>
  
Mon programme consiste à rentrer dans une boucle infini lors de l'appui du bouton 1 (''Buttons_GetStatus()==BUTTONS_BUTTON1''). Toutes les LEDs sont alors allumées puis éteintes à tour de rôle avec un délai de 500ms. Ce sont les fonctions ''LEDs_TurnOnLEDs'' et ''LEDs_TurnOffLEDs'' ainsi que mes mask déclarés qui me permettent de gérer les LEDs. Lors de l'appui du second bouton, je sors de la boucle (''break'').
+
Je crée une donnée ''data'' qui prend la valeur 1 lorsque le bouton 1 (SW0) est appuyé. Grâce à la fonction ''Endpoint_Write_8()'' je peux finalement écrire la donnée dans le point d'accès, qui va être lue par le PC. Après avoir libéré le point d'accès avec ''Endpoint_ClearIN()'', le chenillard se lance.
  
===dfu-programmer===
+
* Ma fonction ''chenillard()'' lance un chenillard dans une boucle infini, avec les LEDs de la XMEGAC3 XPLAINED. Lors de l'appui du bouton 2 (SW1) de la board, l'endpoint des boutons est sélectionné. Le programme attend que le point d'accès soit libre et prêt puis crée une donnée ''data'' qui prend la valeur 2. Je peux également l'écrire sur l'endpoint avec la même fonction ''Endpoint_Write_8()'', sans oublier de le libérer avec ''Endpoint_ClearIN()'' juste après.
  
Pour téléverser mon programme, il faut débrancher la carte. Puis, j'appuie longuement sur le bouton SW0 pendant que je connecte la board à la machine ([http://ww1.microchip.com/downloads/en/AppNotes/Atmel-42053-XMEGA-C3-Xplained-Hardware-Users-Guide_Application-Note_AVR1925.pdf Explications : Section 2.4 Programming the Kit]). Avec la commande suivante, je peux alors créer tous les fichiers nécessaires à la téléversation du programme, notamment ''PAD.hex'' :
+
==Programme du PC==
  
<pre>make all</pre>
+
===Reconnaissance USB===
 +
Pour utiliser la XMEGAC3 XPLAINED comme un périphérique USB, il faut le reconnaître parmi tous les périphériques de la machine. Grâce à la commande suivante, je peux facilement reconnaître les caractéristiques (VendorID et ProductID) de la carte :
 +
<pre>lsusb</pre>
  
Je peux ensuite utiliser ''dfu-programmer'' pour appliquer mon programme :
+
Je crée alors une fonction ''enum_usb()'' qui reconnaîtra le périphérique pour la suite des opérations et qui ne se soucis pas des autres :
 +
 
 +
[[Fichier:reconnaissance.PNG|center|x500px|Reconnaissance USB]]
 +
 
 +
Je peux ensuite sauver la "poingée" dans une variable globale de type ''libusb_device_handle *'', et sauver le périphérique dans un variable globale ''my_device'' grâce à la fonction ''libusb_get_device()''.
 +
 
 +
Il faut ensuite configurer notre périphérique USB. Je crée une fonction ''configuration()'' qui permettra, dans un premier temps, de récupérer la première configuration de la carte via un pointeur sur périphérique ''libusb_device *''. Dans un second temps, je peux afficher cette configuration et la déclarer comme courante.
 +
 
 +
Pour éviter que le méchant noyau passe avant moi, il faut détacher le "''driver''". Je crée alors une fonction ''detach'' qui inclura ''libusb_detach_kernel_driver''. Je peux maintenant récupérer et sauver les endpoints.
 +
 
 +
===Gestion des Endpoints===
 +
 
 +
Grâce à la commande suivante, je peux retrouver le parcours de l'arbre arborescent et les paramètres qui m'intéressent :
 +
 
 +
<pre>lsusb -D /dev/bus/usb/001/00x </pre>
 +
 
 +
Avec x le numéro correspondant à l'adresse du périphérique (retrouvé avec ''lsusb'').
 +
 
 +
Ainsi je peux retrouver les 3 interfaces du périphérique avec les endpoints correspondants, que j'ai déclaré dans le programe de l'Atxmega384c3. Toujours dans ma fonction ''configuration()'', je peux réclamer les interfaces et parcourrir tous les Descriptors. Connaissant les addresses de mes Endpoints, je peux facilement les sélectionner pour les enregistrer dans des tableaux ''ep_in'' et ''ep_out''. Voici le résultat final de ma configuration :
 +
 
 +
[[Fichier:config.PNG|center|x500px|Résultat de Configuration()]]
 +
 
 +
Maintenant que tout est en place, nous pouvons créer nos 2 applications qui correspondent à deux types de communication :
 +
 
 +
* HOST vers DEVICE : L'utilisateur peut gérer les LEDs de la XMEGAC3 XPLAINED grâce à son clavier.
 +
 
 +
* DEVICE vers HOST : L'utilisateur peut activer ou désactiver un chenillard via les boutons SW1 et SW0 et vérifier son état directement sur son PC.
 +
 
 +
Le programme demande alors quelle communication l'utilisateur souhaite appliquer. Lorsque l'utilisation de ces applications est terminée, je peux libérer les interfaces, re-attacher le driver, fermer le périphérique et désinitialiser la librairie avec les fonctions :
 +
<pre>
 +
libere();
 +
attach();
 +
libusb_close(handle);
 +
libusb_exit(context);
 +
</pre>
 +
 
 +
====Communication HOST vers DEVICE====
 +
 
 +
[[Fichier:IHM1.PNG|x500px|IHM HOST-DEVICE]]
 +
 
 +
Pour ce premier choix, le programme renvoi à la fonction ''keyboard()''. Ici, je vais m'intéresser aux Endpoints OUT des LEDs. La fonction ''libusb_interrupt_transfer()'' me permettra alors d'écrire une donnée ''data'' sur le point d'accès. Pour cela, j'utilise la fonction ''getchar()'' qui attend l'entrée de caractères sur le terminal et qui sera lue par le programme de l'Atxmega384c3 lorsqu'il correspond.
 +
 
 +
Le modèle d'écriture sur point d'accès avec la librairie libusb-1.0 est la suivante :
 +
 
 +
<pre>
 +
if((data[0] == 'x')){
 +
  int status_interrupt = libusb_interrupt_transfer(handle,endpoint,data,length,transferred,timeout);
 +
  if(status_interrupt!=0){perror("libusb_interrupt_transfer");}
 +
}
 +
</pre>
 +
 
 +
====Communication DEVICE vers HOST====
 +
 
 +
[[Fichier:IHM2.PNG|x500px|IHM DEVICE-HOST]]
 +
 
 +
Ici, le programme renvoi à la fonction ''lecture()''. En effet, je m'intéresse à l'Endpoint IN des boutons qui écrivent des données sur les points d'accès. L'objectif sera alors de récupérer ces données pour les transmettre à la machine. J'utilise toujours la fonction ''libusb_interrupt_transfer()'', mais cette fois pour lire la donnée ''data'' reçu par l'Atxmega384c3. Cette fonction est capable de lire ou d'écrire grâce à la direction déterminée du point d'accès ''endpoint'' en paramètre. Voici le modèle de lecture d'Endpoint avec la librairie libusb-1.0 :
 +
 
 +
<pre>
 +
int status_interrupt = libusb_interrupt_transfer(handle,endpoint,data,length,transferred,timeout);
 +
if(status_interrupt!=0){perror("libusb_interrupt_transfer");}
 +
if((data[0] == 'x')){printf("donnée reçu");}
 +
}
 +
</pre>
 +
 
 +
Je vais donc m'en servir pour connaître l'état du chenillard directement sur le PC. Nous avons vu précédemment que ''data'' prend la valeur 1 lors de l'appui de SW0 et 2 pour SW1. Donc lorsque ''data'' vaut 1, le chenillard est actif mais désactivé si elle vaut 2.
 +
 
 +
==Optimisation et limites==
 +
 
 +
Pour améliorer mes programmes, certaines modifications ont été effectuées :
 +
 
 +
* Tous les codes en commentaires ont été supprimés
 +
* Dans le '''programme du PC''', je me passe de ''libusb_open_device_with_vid_pid()'' et ''libusb_get_device()''. Je peux donc optimiser le code dans ''enum_usb'' avec :
  
 
<pre>
 
<pre>
dfu-programmer atxmega384c3 erase
+
int status_open = libusb_open(device,&handle);
dfu-programmer atxmega384c3 flash PAD.hex
+
if(status_open!=0){
dfu-programmer atxmega384c3 reset
+
perror("libusb_open");
 +
exit(-1);
 +
}
 +
 
 +
my_device = device;
 
</pre>
 
</pre>
  
En déconnectant puis reconnectant la carte, mon programme s'applique immédiatement.
+
* Dans le '''programme de l'Atxmega384c3''', l'implantation de mon chenillard via la fonction ''chenillard()'' bloquait la gestion USB de la carte. En effet, l'utilisation de ''_delay_ms()'' rend le micro-contrôleur indisponible pendant un temps donné. J'ai donc décidé d'appeler des variables globales pour la gestion d'état de mes LEDs et de mes boutons, dans la boucle principale du programme.
 +
 
 +
En ce qui concerne le chenillard, je crée un compteur de type ''long int'' pour éviter un "overflow". Tant que la valeur finale du compteur n'est pas atteinte, la LED reste allumée. Sinon elle s'éteint et la LED suivante s'allume. Lorsque le bouton 2 est appuyé, le chenillard s'arrête.
 +
 
 +
'''Limites :'''
 +
 
 +
* Le mouvement du chenillard semble plus saccadé (on dirait que pour allumer une LED, elle attend que la précédente s'éteigne). Sans en être sûr, je pense que c'est dû à la valeur finale de mon compteur que j'ai choisi par tâtonnement. Il y a sûrement un lien avec la fréquence du CPU. Lorsque je change mon compteur, le chenillard devient invisible à l'oeil nu ou "constant" (toutes les LEDs allumées) selon sa valeur. Comment trouver une valeur optimale ?
 +
 
 +
* L'état -1 de ma variable globale ''etat'' indique aux LEDs de s'éteindre. Cette valeur est appelée lorsque le deuxième bouton est appuyé. Cependant, il semblerait qu'à certains moments, une LED reste allumée avec l'appui de ce bouton. En revanche, lorsque j'utilise directement la fonction LEDs_TurnOffLEDs() pour éteindre toutes les LEDs, cela fonctionne très bien. Je n'ai pas réussit à expliquer cette différence.
  
 
=Résultat Final=
 
=Résultat Final=
  
Voici le résultat final. Un chenillard avec les 4 LEDs s'active lors de l'appui du premier bouton. Avec le second bouton, le chenillard s'arrête.
+
* Communication HOST vers DEVICE
 +
 
 +
Ci-dessous vous pouvez retrouver un exemple d'utilisation de la gestion des LEDs via le clavier d'une machine :
 +
 
 +
[[Fichier:hostdev.PNG|center|x500px|HOST vers DEVICE]]
 +
 
 +
Comme vous pouvez remarquer, il est possible d'entrer plusieurs commande à la fois. L'état des LEDs sera toujours indiqué.
 +
 
 +
* Communication DEVICE vers HOST
 +
 
 +
Ci-dessous vous pouvez retrouver l'enregistrement des états du chenillard directement sur la machine :
 +
 
 +
[[Fichier:devhost.PNG|center|x350px|DEVICE vers HOST]]
 +
 
 +
Vous pourrez remarquer que son état est donné de manière périodique, pour permettre de le récupérer à n'importe quel moment.
 +
 
 +
=Documents Rendus=
  
[[Fichier:final2.mp4|600px|Vidéo]]
+
[https://archives.plil.fr/sandriam/UE72.git Accès au GIT]

Version actuelle datée du 22 juin 2020 à 10:52

Objectif

Vous allez travailler sur une carte de développement ATXMEGAC3-XPLD. L'idée est de refaire le tutorat système sur cette carte.

Vous aurez donc à écrire un programme C sur PC avec la libusb-1.0 et à écrire un programme pour le micro-contrôleur ATXmega384C3 avec la LUFA.

Coté périphérique USB vous devez implanter une configuration avec une interface IN pour les deux boutons mécanique de la carte, une interface OUT pour les deux LED orange et une interface OUT pour les LED verte et rouge.

Coté PC vous devez écrire un programme qui attend une pression sur un bouton mécanique et qui déclenche un chenillard sur les 4 LED. Le chenillard s'arrête sur pression du second bouton mécanique.

Environnement de travail

Le matériel a été reçu le 09/04/2020 : il s'agit bien d'une carte de développement ATXMEGAC3-XPLD ainsi qu'une carte micro-SD de 8GB et le câble USB qui va avec.

Matériel à disposition


Tout le travail sera effectué sous Linux, plus précisément avec la distribution Ubuntu. Ci-dessous, vous pourrez retrouver un aperçu des fonctionnalités de la carte :

XMEGA-C3 Xplained


Cahier des charges

Durant cette épreuve, je vais me servir de la board XMEGA-C3 Xplained comme un périphérique USB spécifique. Je vais devoir gérer les Endpoints pour que ma carte communique avec mon PC. Afin de réaliser toutes les tâches attendues, il va falloir procéder en plusieurs étapes :

  • Lire l'état de mes I/O et sauver les points d'accès : Pour se faire, il va falloir implanter une configuration avec la librairie libusb-1.0 :

Interface IN : un seul point d'accès (sens Hôte vers Périphérique)

Elle me permettra d'écrire une donnée sur l'Endpoint et la recevoir sur mon PC pour connaître l'état de ma carte.

Interface OUT : deux points d'accès (sens inverse)

Cette interface servira à lire les données sur les Endpoints depuis l'Atxmega384c3. Ces données seront envoyées depuis le PC.

  • Gestion des LEDs et du chenillard en C : Pour programmer le microcontrôleur, la LUFA me permettra de gérer l'USB et les Endpoints sans passer par les registres. Pour téléverser mon programme, je me servirai de l'utilitaire dfu-programmer.

Je pourrai alors créer une communication bi-directionnelle via deux applications : HOST vers DEVICE ainsi que DEVICE vers HOST.

Travail réalisé

Programme de l'ATXMEGA384C3

Implantation USB

Pour commencer, il faut implanter un périphérique USB. Tout le projet sera inspiré de la démonstration KeyBoard dans le répertoire Device/LowLevel/Keyboard de la LUFA.

Pour que le programme fonctionne avec la carte, je peux adapter le fichier makefile de la façon suivante :

Makefile

Les fréquences choisies sont propres à la board.

Je peux maintenant adapter Descriptors.h à mon application. Je déclare, comme ci-dessous, mes trois interfaces dans une structure :

USB_Descriptor_Interface_t     Binterface;
USB_Descriptor_Endpoint_t      EPIN;

USB_Descriptor_Interface_t     L1interface;
USB_Descriptor_Endpoint_t      EPOUT1;

USB_Descriptor_Interface_t     L2interface;
USB_Descriptor_Endpoint_t      EPOUT2;

Puis l'ID de chaque interface :

enum InterfaceDescriptors_t{
      BOUTONS_ID = 0,
      LEDS1_ID = 1,
      LEDS2_ID = 2,
}

Enfin, il faut définir l'adresse et la direction de chacun de mes endpoints ainsi que leur taille (en bits):

#define BOUTONS_EPADDR   (ENDPOINT_DIR_IN | 1)
#define LEDS1_EPADDR     (ENDPOINT_DIR_OUT | 1)
#define LEDS2_EPADDR     (ENDPOINT_DIR_OUT | 2)
#define EPSIZE           8

Comme vous pouvez le voir, nous allons utiliser 1 Endpoint IN pour les boutons et 2 Endpoints OUT pour les LEDs.

Dans mon fichier Descriptors.c , j'inclus Descriptors.h. Je peux alors renseigner les VendorID et ProductID de ma board ainsi que déclarer mes interfaces et points d'accès. La XMEGAC3 XPLAINED sera finalement reconnue comme un périphérique USB avec les interfaces définies grâce à la fonction USB_USBTask() de la boucle principale de mon programme chenillard.c.

Gestion des Endpoints

Dans mon fichier chenillard.h, je définis tous les mask correspondant aux LEDs qui m'intéressent. Dans le chemin suivant, je retrouve la déclaration de chacune des LEDs :

LUFA/Drivers/USB/XMEGA/C3_XPLAINED/LEDS.h

Puis je déclare mes fonctions prototypes qui me serviront pendant ce projet. Voici mon chenillard.h final :

chenillard.h

Dans mon chenillard.c, ma fonction principale main() configure ma XMEGAC3 XPLAINED et appelle mes fonctions dans une boucle infini pour gérer continuellement les Endpoints.

Dans SetupHardware(), je prends uniquement en compte la condition suivante pour que le programme soit adapté à ma carte :

#if (ARCH == ARCH_XMEGA)
...
#endif

Puis j'initialise toutes mes I/O avec les fonctions LEDs_Init(), USB_Init() et Buttons_Init(). Je peux maintenant créer mes applications et gérer mes Endpoints :

  • Mes fonctions LEDS_RV() et LEDS_OR() me permettent de gérer les Endpoints OUT de mes LEDs. Ces fonctions sélectionnent le point d'accès avec la fonction Endpoint_SelectEndpoint(). Lorsqu'il est prêt, nous pouvons lire la donnée data qui lui est attribuée. Selon sa valeur, les LEDs peuvent s'allumer ou s'éteindre. Il ne faut pas oublier la fonction Endpoint_ClearOUT() en fin d'action pour libérer le point d'accès et le rendre prêt lors les futurs échanges de données.
  • Ma fonction BOUT_IN() sélectionne l'Endpoint correspondant et attend la pression d'un bouton lorsqu'il est prêt; avec la fonction Buttons_GetStatus(). Les boutons correspondants à ma XMEGAC3 XPLAINED peuvent être retrouvés dans le répertoire suivant :
LUFA/Drivers/Board/XMEGA/C3_XPLAINED/Buttons.h

Je crée une donnée data qui prend la valeur 1 lorsque le bouton 1 (SW0) est appuyé. Grâce à la fonction Endpoint_Write_8() je peux finalement écrire la donnée dans le point d'accès, qui va être lue par le PC. Après avoir libéré le point d'accès avec Endpoint_ClearIN(), le chenillard se lance.

  • Ma fonction chenillard() lance un chenillard dans une boucle infini, avec les LEDs de la XMEGAC3 XPLAINED. Lors de l'appui du bouton 2 (SW1) de la board, l'endpoint des boutons est sélectionné. Le programme attend que le point d'accès soit libre et prêt puis crée une donnée data qui prend la valeur 2. Je peux également l'écrire sur l'endpoint avec la même fonction Endpoint_Write_8(), sans oublier de le libérer avec Endpoint_ClearIN() juste après.

Programme du PC

Reconnaissance USB

Pour utiliser la XMEGAC3 XPLAINED comme un périphérique USB, il faut le reconnaître parmi tous les périphériques de la machine. Grâce à la commande suivante, je peux facilement reconnaître les caractéristiques (VendorID et ProductID) de la carte :

lsusb

Je crée alors une fonction enum_usb() qui reconnaîtra le périphérique pour la suite des opérations et qui ne se soucis pas des autres :

Reconnaissance USB

Je peux ensuite sauver la "poingée" dans une variable globale de type libusb_device_handle *, et sauver le périphérique dans un variable globale my_device grâce à la fonction libusb_get_device().

Il faut ensuite configurer notre périphérique USB. Je crée une fonction configuration() qui permettra, dans un premier temps, de récupérer la première configuration de la carte via un pointeur sur périphérique libusb_device *. Dans un second temps, je peux afficher cette configuration et la déclarer comme courante.

Pour éviter que le méchant noyau passe avant moi, il faut détacher le "driver". Je crée alors une fonction detach qui inclura libusb_detach_kernel_driver. Je peux maintenant récupérer et sauver les endpoints.

Gestion des Endpoints

Grâce à la commande suivante, je peux retrouver le parcours de l'arbre arborescent et les paramètres qui m'intéressent :

lsusb -D /dev/bus/usb/001/00x 

Avec x le numéro correspondant à l'adresse du périphérique (retrouvé avec lsusb).

Ainsi je peux retrouver les 3 interfaces du périphérique avec les endpoints correspondants, que j'ai déclaré dans le programe de l'Atxmega384c3. Toujours dans ma fonction configuration(), je peux réclamer les interfaces et parcourrir tous les Descriptors. Connaissant les addresses de mes Endpoints, je peux facilement les sélectionner pour les enregistrer dans des tableaux ep_in et ep_out. Voici le résultat final de ma configuration :

Résultat de Configuration()

Maintenant que tout est en place, nous pouvons créer nos 2 applications qui correspondent à deux types de communication :

  • HOST vers DEVICE : L'utilisateur peut gérer les LEDs de la XMEGAC3 XPLAINED grâce à son clavier.
  • DEVICE vers HOST : L'utilisateur peut activer ou désactiver un chenillard via les boutons SW1 et SW0 et vérifier son état directement sur son PC.

Le programme demande alors quelle communication l'utilisateur souhaite appliquer. Lorsque l'utilisation de ces applications est terminée, je peux libérer les interfaces, re-attacher le driver, fermer le périphérique et désinitialiser la librairie avec les fonctions :

libere();
attach();
libusb_close(handle);
libusb_exit(context);

Communication HOST vers DEVICE

IHM HOST-DEVICE

Pour ce premier choix, le programme renvoi à la fonction keyboard(). Ici, je vais m'intéresser aux Endpoints OUT des LEDs. La fonction libusb_interrupt_transfer() me permettra alors d'écrire une donnée data sur le point d'accès. Pour cela, j'utilise la fonction getchar() qui attend l'entrée de caractères sur le terminal et qui sera lue par le programme de l'Atxmega384c3 lorsqu'il correspond.

Le modèle d'écriture sur point d'accès avec la librairie libusb-1.0 est la suivante :

if((data[0] == 'x')){
   int status_interrupt = libusb_interrupt_transfer(handle,endpoint,data,length,transferred,timeout);
   if(status_interrupt!=0){perror("libusb_interrupt_transfer");}
} 

Communication DEVICE vers HOST

IHM DEVICE-HOST

Ici, le programme renvoi à la fonction lecture(). En effet, je m'intéresse à l'Endpoint IN des boutons qui écrivent des données sur les points d'accès. L'objectif sera alors de récupérer ces données pour les transmettre à la machine. J'utilise toujours la fonction libusb_interrupt_transfer(), mais cette fois pour lire la donnée data reçu par l'Atxmega384c3. Cette fonction est capable de lire ou d'écrire grâce à la direction déterminée du point d'accès endpoint en paramètre. Voici le modèle de lecture d'Endpoint avec la librairie libusb-1.0 :

int status_interrupt = libusb_interrupt_transfer(handle,endpoint,data,length,transferred,timeout);
if(status_interrupt!=0){perror("libusb_interrupt_transfer");}
if((data[0] == 'x')){printf("donnée reçu");}
} 

Je vais donc m'en servir pour connaître l'état du chenillard directement sur le PC. Nous avons vu précédemment que data prend la valeur 1 lors de l'appui de SW0 et 2 pour SW1. Donc lorsque data vaut 1, le chenillard est actif mais désactivé si elle vaut 2.

Optimisation et limites

Pour améliorer mes programmes, certaines modifications ont été effectuées :

  • Tous les codes en commentaires ont été supprimés
  • Dans le programme du PC, je me passe de libusb_open_device_with_vid_pid() et libusb_get_device(). Je peux donc optimiser le code dans enum_usb avec :
int status_open = libusb_open(device,&handle);
			if(status_open!=0){
				perror("libusb_open");
				exit(-1);
			}

my_device = device;
  • Dans le programme de l'Atxmega384c3, l'implantation de mon chenillard via la fonction chenillard() bloquait la gestion USB de la carte. En effet, l'utilisation de _delay_ms() rend le micro-contrôleur indisponible pendant un temps donné. J'ai donc décidé d'appeler des variables globales pour la gestion d'état de mes LEDs et de mes boutons, dans la boucle principale du programme.

En ce qui concerne le chenillard, je crée un compteur de type long int pour éviter un "overflow". Tant que la valeur finale du compteur n'est pas atteinte, la LED reste allumée. Sinon elle s'éteint et la LED suivante s'allume. Lorsque le bouton 2 est appuyé, le chenillard s'arrête.

Limites :

  • Le mouvement du chenillard semble plus saccadé (on dirait que pour allumer une LED, elle attend que la précédente s'éteigne). Sans en être sûr, je pense que c'est dû à la valeur finale de mon compteur que j'ai choisi par tâtonnement. Il y a sûrement un lien avec la fréquence du CPU. Lorsque je change mon compteur, le chenillard devient invisible à l'oeil nu ou "constant" (toutes les LEDs allumées) selon sa valeur. Comment trouver une valeur optimale ?
  • L'état -1 de ma variable globale etat indique aux LEDs de s'éteindre. Cette valeur est appelée lorsque le deuxième bouton est appuyé. Cependant, il semblerait qu'à certains moments, une LED reste allumée avec l'appui de ce bouton. En revanche, lorsque j'utilise directement la fonction LEDs_TurnOffLEDs() pour éteindre toutes les LEDs, cela fonctionne très bien. Je n'ai pas réussit à expliquer cette différence.

Résultat Final

  • Communication HOST vers DEVICE

Ci-dessous vous pouvez retrouver un exemple d'utilisation de la gestion des LEDs via le clavier d'une machine :

HOST vers DEVICE

Comme vous pouvez remarquer, il est possible d'entrer plusieurs commande à la fois. L'état des LEDs sera toujours indiqué.

  • Communication DEVICE vers HOST

Ci-dessous vous pouvez retrouver l'enregistrement des états du chenillard directement sur la machine :

DEVICE vers HOST

Vous pourrez remarquer que son état est donné de manière périodique, pour permettre de le récupérer à n'importe quel moment.

Documents Rendus

Accès au GIT