IMA4 2019/2020 EC1
Sommaire
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.
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 :
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 :
- 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)
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 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.
Travail réalisé
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 :
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 :
Je peux ensuite sauver la "poingée" dans une variable globale de type libusb_device_handle *.
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.
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 :
lsusb -D /dev/bus/usb/001/00x
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 :
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 cas où.
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 :
Intéractions avec la carte
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 (détails ici) selon les types de transferts de mes points d'accès.
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 :
PolytechLille/PAD/Config/LUFAConfig.h
Puis, je peux configurer mon makefile comme ci-dessous :
Les fréquences choisies sont propres à la board. Tout le programme sera inspiré par le projet Keyboard situé dans le chemin ci-dessous :
Demos/Device/LowLevel/Keyboard
Descriptors
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 de chacun de mes endpoints ainsi que leur taille (en bits):
#define BOUTONS_EPADDR (ENDPOINT_DIR_IN | 1) #define LEDS1_EPADDR (ENDPOINT_DIR_OUT | 2) #define LEDS2_EPADDR (ENDPOINT_DIR_OUT | 3) #define EPSIZE 8
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.
Projet PAD
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 :
LUFA/Drivers/USB/XMEGA/C3_XPLAINED/LEDS.h
Puis je déclare ma fonction SetupHardware() qui me permettra de configurer la partie Hardware du projet. Voici mon PAD.h final :
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 USB_USBTask() me permet d'exécuter mon programme de manière continue.
Dans ma fonction SetupHardware je prends en compte uniquement 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()
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 :
LUFA/Drivers/Board/XMEGA/C3_XPLAINED/Buttons.h
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).
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.