SE2a4 USB 2023/2024

De wiki-se.plil.fr
Aller à la navigation Aller à la recherche

Cahier des charges

Vous devez réaliser un périphérique USB permettant de contrôler le démarrage et l'arrêt d'ecrans de stations de travail. L'idée est de ne pas imposer de modification sur l'écran. Le dispositif est sensé appuyer sur le bouton de l'écran avec un servo-moteur et contrôler l'état de l'écran avec un photo-récepteur.

Pour le tutorat USB il est juste demandé de simuler le dispositif avec un servo-moteur et un photo-récepteur, pour la partie électronique l'ATmega8u2 d'un Arduino Uno Rev3 va être utilisé et la partie mécanique n'est pas demandée. Vous poursuivrez cet exercice au prochain semestre en projet système et réseau. En projet vous aurez à concevoir et réaliser une carte et à réfléchir à la partie mécanique (il y aura bien entendu aussi des aspects système et réseau).

La difficulté avec l'Arduino Uno est que les entrées/sorties de l'ATmega8u2 ne sont pas accessibles hormis l'UART. Vous aurez donc aussi à programmer l'ATmega328p pour accéder au servo-moteur et au photo-récepteur. Il faut aussi prévoir une communication UART entre les deux puces.

Le périphérique USB sera implanté à l'aide de la bibliothèque LUFA sous la forme d'un périphérique spécifique. La gestion du périphérique doit se faire au travers d'une application sur PC utilisant la bibliothèque libusb-1.0.

Programmation de l'ATmega8u2

Récupérez le code de la bibliothèque LUFA. Créez un sous-répertoire SE2a4 dans cette bibliothèque et installez-y la démonstration minimale Média:2023-SE2a4-Minimal.zip.

Adaptez le makefile de la démonstration à votre microcontrôleur et carte cible. Ajustez aussi le chemin de la bibliothèque LUFA en restant en chemin relatif.

Définition du périphérique USB

Votre périphérique USB est un capteur et un actionneur : il faut donc prévoir un point d'accès entrant et un point d'accès sortant. Des points d'accès de type interruption sont parfaitement adaptés ici. Pour informer le contrôleur USB de la présence de ces points d'accès vous devez effectuer les actions ci-dessous.

  • Dans le fichier descripteur.h ajouter à la structure USB_Descriptor_Configuration_t un champ interface et deux champs points d'accès. Pour vous aidez dans le nom des types à employer pour les champs, regardez le même fichier dans la démonstration LUFA Demos/Device/LowLevel/Keyboard/.
  • Dans le fichier descripteur.h décommentez l'union pour donner un numéro à votre interface spécifique.
  • Dans le fichier descripteur.h ajoutez des macros pour indiquer les numéros de vos points d'accès ainsi que la taille des données à leur transmettre. Là encore la démonstration Keyboard vous indique comment faire.
  • Dans le fichier descripteur.c instanciez la structure définie dans le fichier d'inclusion. Pour vous aider sur les valeurs des champs, regardez le même fichier dans la démonstration Keyboard. La constante à utiliser pour la classe de l'interface spécifique est USB_CSCP_VendorSpecificClass. N'oubliez pas de mettre à jour le descripteur de configuration.

A ce niveau vous pouvez compiler la démonstration, passer l'ATmega8u2 en mode DFU USB en court-circuitant les broches reset et GND de son connecteur ISP, télécharger la démonstration sur l'ATmega8u2 en utilisant make dfu et vérifier que les descripteurs sont bien reconnus via l'utilitaire lsusb. Concernant l'utilitaire lsusb mettez à profit les options -d et -vvv.

Gestion des interruptions USB

La gestion des interruptions USB se fait dans le fichier Minimal.c. Passez par les étapes ci-dessous.

  • Initialisez vos points d'accès dans la fonction EVENT_USB_Device_ConfigurationChanged. Adaptez le code se trouvant dans la même fonction du fichier Keyboard.c de la démonstration Keyboard.
  • Ecrivez une fonction de gestion du point d'accès entrant, vous en trouvez un exemple dans le fichier Keyboard.c de la démonstration Keyboard. N'oubliez pas d'appeler cette fonction dans la boucle infinie du programme principal.
  • Ecrivez une fonction de gestion du point d'accès sortant, vous en trouvez un exemple dans le fichier Keyboard.c de la démonstration Keyboard. N'oubliez pas d'appeler cette fonction dans la boucle infinie du programme principal.

La gestion des capteurs et des actionneurs ne se fait pas sur l'ATmeaga8u2 mais sur l'ATmega328p. La gestion des interruptions USB consiste donc à communiquer avec l'ATmega328p via l'UART. Pour implanter les communications vous pouvez utiliser les fonctions séries de la bibliothèque LUFA : Serial_Init, Serial_IsCharReceived, Serial_ReceiveByte et Serial_SendByte.

Compilez et téléchargez. Pour pouvoir tester il vous faut un programme PC fonctionnel.

Programmation de l'ATmega328p

L'ATmega328p doit communiquer avec l'ATmega8u2 et gérer, à sa place, le photo-récepteur et le servo-moteur.

Communication série

Pour la communication série, les fonctions ci-dessous vous seront utiles.

void serial_init(long int speed){
UBRR0=F_CPU/(((unsigned long int)speed)<<4)-1;
UCSR0B=(1<<TXEN0 | 1<<RXEN0);
UCSR0C=(1<<UCSZ01 | 1<<UCSZ00);
UCSR0A &= ~(1 << U2X0);
}

void serial_send(unsigned char c){
loop_until_bit_is_set(UCSR0A,UDRE0);
UDR0=c;
}

unsigned char serial_get(void){
loop_until_bit_is_set(UCSR0A, RXC0);
return UDR0;
}

Gestion du photo-récepteur

La principale difficulté pour utiliser un photo-récepteur de type photo-résistance est de mesurer une tension via un canal du convertiseur analogique numérique. Les fonctions ci-après vous seront utiles.

void ad_init(unsigned char channel){
ADCSRA |= (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
ADCSRA &= ~(1<<ADFR);
ADMUX |= (1<<REFS0)|(1<<ADLAR);
ADMUX=(ADMUX&0xf0)|channel;
ADCSRA|=(1<<ADEN);
}

unsigned int ad_capture(void){
ADCSRA|=(1<<ADSC);
while(bit_is_set(ADCSRA, ADSC));
return ADCH;
}

Gestion du servo-moteur

Un servo-moteur se commande avec des implusions (en patoi Pulse Width Modulation ou PWM). Il est possible d'utiliser un minuteur (timer en patoi) pour générer ces implusions. L'initialisation du minuteur est donné ci-dessous, le minuteur 0 de l'ATmega328p peut générer des impulsions sur les ports PD5 et PD6.

#define PWM_DDR         DDRD
#define PWM1            5
#define PWM2            6
void PWM_init(void){
PWM_DDR |= (1<<PWM1)|(1<<PWM2);
TCCR0A |= (1<<COM0A1)|(1<<COM0B1);
TCCR0A |= (1<<WGM01)|(1<<WGM00);
TCCR0B |= (1<<CS00);
}

Pour changer les largeurs des impulsions, et donc la position des servo-moteurs, il faut initialiser les registres OCR0A pour la broche PD5 et OCR0B pour la broche PD6.

Application PC

L’objectif est d’utiliser la bibliothèque USB libusb-1.0 pour écrire un programme C permettant de récupérer des informations de votre périphérique USB.

Un manuel complet de la bibliothèque est disponible [1]. Vous pouvez aussi trouver un résumé de la bibliothèque [2]. Pour cette utilisation assez basique nous utiliserons les fonctions de communication bloquantes.

Enumération des périphériques USB

Ecrivez une fonction pour examiner tous les périphériques USB disponibles sur les bus USB de votre machine. Dès qu'un périphérique s'avère être le périphérique USB du type cherché, sauvez la "poignée" vers ce périphérique dans un tableau global dont les éléments sont de type libusb_device_handle.

Vous pouvez utiliser la commande lsusb pour trouver les identifiants USB de votre type de périphérique. Pour chaque périphérique sélectionné, faites afficher le numéro du bus sur lequel se trouve le périphérique ainsi que son adresse sur ce bus.

Configuration du périphérique USB

Une fois les périphériques détectés, il faut les configurer. Ecrivez une fonction de configuration.

Commencez par récupérer la première configuration du périphérique, c'est à dire la configuration d'indice 0. La fonction libusb_get_config_descriptor qui permet cela nécessite un pointeur sur périphérique de type libusb_device *. Vous pouvez retrouver ce pointeur en fonction de la "poignée" par la fonction libusb_get_device. Faites afficher la valeur de cette configuration.

Vous pouvez alors installer cette configuration comme configuration courante avec la fonction libusb_set_configuration.

Il vous reste ensuite, à réclamer les interfaces des périphériques USB pour votre usage. Attention, la fonction libusb_claim_interface nécessite le numéro de l'interface et pas son indice. Vous allez donc parcourir la structure arborescente de description de configuration du périphérique de la façon suivante :

  • itérer sur le nombre d'interfaces ;
  • explorer chaque sous-structure d'interface ;
  • pour chaque interface, prendre la première alternative d'interface ;
  • ne considérer que les interfaces de classe LIBUSB_CLASS_VENDOR_SPEC ;
  • trouver le numéro d'interface pour utilisation avec libusb_claim_interface ;
  • récupérer les adresses des points d'accès de l'interfaces spécifique, les stocker dans un tableau global.

Affichez l'indice et le numéro de chaque interface détectée et réclamée ainsi que les adresses des points d'accès stockés.

Fermeture des périphérique USB

Ecrivez une fonction qui libère les périphériques détectés. Pour cela, relachez toutes les interfaces réclamées puis fermez la "poignée". Soit vous avez sauvé les numéros des interfaces réclamés dans un tableau global soit vous allez devoir, à nouveau, explorer la structure arborescente de description de configuration du périphérique. Dans ce dernier cas utilisez la fonction libusb_get_active_config_descriptor.

Affichez l'indice et le numéro de chaque interface libérée.

Commande des dispositifs de contrôle des écrans

Votre fonction principale doit demander à l'utilisateur quelle action doit être réalisée. Vous pouvez implanter cela avec une interaction via le terminal (fonctions fprintf et fgets/sscanf) ou en utilisant les paramètres passés au programme (fonction getopt_long).

Les actions à permettre sont :

  • de lister l'état des écrans ;
  • changer l'état d'un écran (l'allumer ou l'éteindre).

Pour récupérer l'état d'un écran ou inverser son état vous aurez à utiliser la fonction libusb_control_transfer.