SE3 PSE Binome2023-6
Conception de la manette
DESIGN, LIMITES, FONCTIONNALITÉS ET DESCRIPTION DE LA MANETTE
Ce projet a pour but de développer une manette capable jouer et interagir avec le jeu "Space Invaders" réalisé lors du projet informatique avec Monsieur DEQUIDT et Monsieur FORGET.
- Taille de la carte : 10x10 cm maximum
- Fonctionne en 5V
- Utilisation de LEDs pour visualiser les vies restantes sur la manette
- 6 boutons : 4 de déplacement mais que deux utilisables (gauche et droite) et les deux autres (haut et bas) pour d'autres jeux à l'avenir. Les deux boutons à droite de la carte, en bas pour tirer, en haut pour quitter.
- Le design de la forme de la manette est inspiré d'une vieille manette de SNES
SCHÉMATIQUE KiCAD
PCB et BRASURE
Programmation
CODE C
Pour pouvoir avoir nos boutons fonctionnel on s'est basé sur la démo du joystick pour créer "Manette", et ensuite on est partie d'un code minimal pour pouvoir rajouté la description de notre manette, et créer deux interfaces, une pour nos boutons, et une autre pour nos LEDs.
Fonctions importantes et descriptions des codes
Descriptor de notre code Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 1.10
bDeviceClass 0
bDeviceSubClass 0
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x054c Sony Corp.
idProduct 0x05c4 DualShock 4 Demo Application
bcdDevice 0.01
iManufacturer 1 LUFA Library
iProduct 2 LUFA Joystick Demo
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0032
bNumInterfaces 2
bConfigurationValue 1
iConfiguration 0
bmAttributes 0xc0
Self Powered
MaxPower 100mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0
bInterfaceProtocol 0
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.11
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 52
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 5
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 1
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 255 Vendor Specific Subclass
bInterfaceProtocol 255 Vendor Specific Protocol
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 5 |
Manette et minimal trouvable dans TPI_LUFA_LibUSB/manette_LUFA/Polytech/ | |
Code définissant les conditions de nos boutons. Quels boutons fait quoi et à quel moment. bool GetNextReport(USB_JoystickReport_Data_t* const ReportData)
{
bool InputChanged = false;
memset(ReportData, 0, sizeof(USB_JoystickReport_Data_t));
if (~(PINF>>PIN7) & 1) ReportData->Button |= (1 << 1);
if (~(PINF>>PIN6) & 1) ReportData->Button |= (1 << 0);
if (~(PIND>>PIN1) & 1) ReportData->Y = -100;
if (~(PIND>>PIN2) & 1) ReportData->X = 100;
if (~(PIND>>PIN3) & 1) ReportData->X = -100;
if (~(PIND>>PIN5) & 1) ReportData->Y = 100;
if ((~(PIND>>PIN5) & 1) & (~(PIND>>PIN1) & 1)) ReportData->Y = 0;
if ((~(PIND>>PIN3) & 1) & (~(PIND>>PIN2) & 1)) ReportData->X = 0;
InputChanged = 1;
return InputChanged;
}
Nous avons vérifié ces réglages avec jstest-gtk, un outil permettant de visualiser les directions et les pressions d'un joystick. Tous nos boutons sont détectés et configurés conformément à nos attentes. |
Les PIN6 et PIN7 contrôlent les deux boutons sur la droite de la manette. Vous pouvez voir sur la vidéo dans deux sections avant celle-ci quand nous appuyons dessus les cases "0" et "1" deviennent noires quand nous appuyons sur les boutons | |
Code définissant Les PORTx et DDRx de notre manette. /* Hardware Initialization */
Joystick_Init();
LEDs_Init();
Buttons_Init();
USB_Init();
MCUCR |= (1<<JTD);
MCUCR |= (1<<JTD);
CLKSEL0 = 0b00010101; // sélection de l'horloge externe
CLKSEL1 = 0b00001111; // minimum de 8Mhz
CLKPR = 0b10000000; // modification du diviseur d'horloge (CLKPCE=1)
CLKPR = 0; // 0 pour pas de diviseur (diviseur de 1)
//Boutons A et B
DDRF &= 0b00111111;
PORTF |= 0b11000000;
//Boutons directionnels
DDRD &= 0b11010001;
PORTD |= 0b00101110;
// Allumer les leds
PORTF |= 0b00100011;
PORTE |= 0b01000000;
|
En modifiant le code LUFA donné par le prof, on peut allumer les LEDs, pour celles sur les pins PF0, PF1 et PF5 il faut désactiver le JTAG en copiant deux fois cette ligne au niveau de la déclaration des pins : MCUCR |= (1<<JTD);
En ce qui concerne les LEDs sur PORTE et PORTD, il n'y a pas de problème dessus. |
Code permettant de contrôler les LEDs en entrant des chiffres dans le terminal |
void USB_OUT_PointManagement(void)
{
uint8_t c = 0;
Endpoint_SelectEndpoint(LED_OUT_EPADDR);
if (Endpoint_IsOUTReceived())
{
if (Endpoint_IsReadWriteAllowed())
{
c = Endpoint_Read_8();
if (c != 0) {
PORTF |= 0b00100011;
PORTE |= 0b01000000;
}
else{
PORTF &= 0b11011100;
PORTE &= 0b10111111;
}
if (c & 0b00000001)
PORTF |= 0b00000001; // LED 1 ON
else
PORTF &= ~0b00000001; // LED 1 OFF
if (c & 0b00000010)
PORTF |= 0b00000010; // LED 2 ON
else
PORTF &= ~0b00000010; // LED 2 OFF
if (c & 0b00000100)
PORTF |= 0b00010000; // LED 3 ON
else
PORTF &= ~0b00100000; // LED 3 OFF
if (c & 0b00001000)
PORTE |= 0b01000000; // LED 4 ON
else
PORTE &= ~0b01000000; // LED 4 OFF
}
Endpoint_ClearOUT();
}
}
|
Codes complets
Le code ci-dessous présente le code libusb.c permettant de faire la communication entre le PC et la manette. C'est ce qui nous permet d'envoyer des bits d'informations par le biais du terminal CMD qui allumeront certaines LEDs. Vous pouvez voir cet exemple sur la vidéo dans la section VIDÉOS DE PRÉSENTATIONS. |
Ceci est le Decriptor.c de notre code Fusion qui permet d'utiliser les boutons pour jouer et que le PC puisse envoyer les informations à la manette. Il crée les interfaces nécessaires pour la bonne utilisation des LEDs et des boutons de jeu. |
#include <stdio.h>
#include <stdlib.h>
#include <libusb-1.0/libusb.h>
#define VENDOR_ID 0x054c
#define PRODUCT_ID 0x05c4
int main(void) {
// Initialisation de la bibliothèque
libusb_context *context;
int status = libusb_init(&context);
if (status != 0) {
perror("libusb_init");
exit(-1);
}
// Énumération des périphériques USB
libusb_device **list;
ssize_t count = libusb_get_device_list(context, &list);
if (count < 0) {
perror("libusb_get_device_list");
libusb_exit(context);
exit(-1);
}
// Recherche du périphérique avec le VENDOR_ID et PRODUCT_ID spécifiés
libusb_device *device = NULL;
for (ssize_t i = 0; i < count; i++) {
libusb_device *dev = list[i];
struct libusb_device_descriptor desc;
int status = libusb_get_device_descriptor(dev, &desc);
if (status != 0) continue;
if (desc.idVendor == VENDOR_ID && desc.idProduct == PRODUCT_ID) {
device = dev;
break;
}
}
if (device == NULL) {
printf("Device not found\n");
libusb_free_device_list(list, 1);
libusb_exit(context);
exit(-1);
}
// Ouverture du périphérique
libusb_device_handle *handle;
status = libusb_open(device, &handle);
if (status != 0) {
perror("libusb_open");
libusb_free_device_list(list, 1);
libusb_exit(context);
exit(-1);
}
// Détacher les pilotes du noyau pour toutes les interfaces
struct libusb_config_descriptor *configdesc;
int interface_count, i;
status = libusb_get_active_config_descriptor(device, &configdesc);
if (status != 0) {
perror("libusb_get_active_config_descriptor");
libusb_close(handle);
libusb_free_device_list(list, 1);
libusb_exit(context);
exit(-1);
}
interface_count = configdesc->bNumInterfaces;
for (i = 0; i < interface_count; i++) {
if (libusb_kernel_driver_active(handle, i)) {
status = libusb_detach_kernel_driver(handle, i);
if (status != 0) {
perror("libusb_detach_kernel_driver");
libusb_free_config_descriptor(configdesc);
libusb_close(handle);
libusb_free_device_list(list, 1);
libusb_exit(context);
exit(-1);
}
}
}
libusb_free_config_descriptor(configdesc);
// Définir la configuration
status = libusb_set_configuration(handle, 1);
if (status != 0) {
perror("libusb_set_configuration");
libusb_close(handle);
libusb_free_device_list(list, 1);
libusb_exit(context);
exit(-1);
}
// Revendiquer toutes les interfaces
for (i = 0; i < interface_count; i++) {
status = libusb_claim_interface(handle, i);
if (status != 0) {
perror("libusb_claim_interface");
libusb_close(handle);
libusb_free_device_list(list, 1);
libusb_exit(context);
exit(-1);
}
}
// Envoi de données au périphérique
int endpoint_out = 0x02;
int bytes_out;
unsigned char message;
for (int j = 0; j < 10; j++) {
scanf("%hhu", &message);
printf("Vous avez envoyé %hhu\n", message);
status = libusb_interrupt_transfer(handle, endpoint_out, &message, sizeof(message), &bytes_out, 0);
if (status == 0) {
printf("%d octets envoyés avec succès\n", bytes_out);
} else {
perror("libusb_interrupt_transfer");
for (i = 0; i < interface_count; i++) {
libusb_release_interface(handle, i);
}
libusb_close(handle);
libusb_free_device_list(list, 1);
libusb_exit(context);
exit(-1);
}
}
// Libération de toutes les interfaces et fermeture du périphérique
for (i = 0; i < interface_count; i++) {
libusb_release_interface(handle, i);
}
libusb_close(handle);
libusb_free_device_list(list, 1);
libusb_exit(context);
return 0;
}
|
#include "Descriptors.h"
const USB_Descriptor_HIDReport_Datatype_t PROGMEM JoystickReport[] =
{
HID_RI_USAGE_PAGE(8, 0x01), /* Generic Desktop */
HID_RI_USAGE(8, 0x04), /* Joystick */
HID_RI_COLLECTION(8, 0x01), /* Application */
HID_RI_USAGE(8, 0x01), /* Pointer */
HID_RI_COLLECTION(8, 0x00), /* Physical */
HID_RI_USAGE(8, 0x30), /* Usage X */
HID_RI_USAGE(8, 0x31), /* Usage Y */
//HID_RI_USAGE(8, 0x32), /* Usage Z */ EN COMMENTAIRE POUR NE PAS AVOIR D'AXE Z
HID_RI_LOGICAL_MINIMUM(8, -100),
HID_RI_LOGICAL_MAXIMUM(8, 100),
HID_RI_PHYSICAL_MINIMUM(8, -1),
HID_RI_PHYSICAL_MAXIMUM(8, 1),
HID_RI_REPORT_COUNT(8, 0x03),
HID_RI_REPORT_SIZE(8, 0x08),
HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
HID_RI_END_COLLECTION(0),
HID_RI_USAGE_PAGE(8, 0x09), /* Button */
HID_RI_USAGE_MINIMUM(8, 0x01),
HID_RI_USAGE_MAXIMUM(8, 0x02),
HID_RI_LOGICAL_MINIMUM(8, 0x00),
HID_RI_LOGICAL_MAXIMUM(8, 0x01),
HID_RI_REPORT_SIZE(8, 0x01),
HID_RI_REPORT_COUNT(8, 0x02),
HID_RI_INPUT(8, HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE),
HID_RI_REPORT_SIZE(8, 0x06),
HID_RI_REPORT_COUNT(8, 0x01),
HID_RI_INPUT(8, HID_IOF_CONSTANT),
HID_RI_END_COLLECTION(0),
};
const USB_Descriptor_Device_t PROGMEM DeviceDescriptor =
{
.Header = {.Size = sizeof(USB_Descriptor_Device_t), .Type = DTYPE_Device},
.USBSpecification = VERSION_BCD(1,1,0),
.Class = USB_CSCP_NoDeviceClass,
.SubClass = USB_CSCP_NoDeviceSubclass,
.Protocol = USB_CSCP_NoDeviceProtocol,
.Endpoint0Size = FIXED_CONTROL_ENDPOINT_SIZE,
.VendorID = 0x054c,
.ProductID = 0x05c4,
.ReleaseNumber = VERSION_BCD(0,0,1),
.ManufacturerStrIndex = STRING_ID_Manufacturer,
.ProductStrIndex = STRING_ID_Product,
.SerialNumStrIndex = NO_DESCRIPTOR,
.NumberOfConfigurations = FIXED_NUM_CONFIGURATIONS
};
const USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor =
{
.Config =
{
.Header = {.Size = sizeof(USB_Descriptor_Configuration_Header_t), .Type = DTYPE_Configuration},
.TotalConfigurationSize = sizeof(USB_Descriptor_Configuration_t),
.TotalInterfaces = 2,
.ConfigurationNumber = 1,
.ConfigurationStrIndex = NO_DESCRIPTOR,
.ConfigAttributes = (USB_CONFIG_ATTR_RESERVED | USB_CONFIG_ATTR_SELFPOWERED),
.MaxPowerConsumption = USB_CONFIG_POWER_MA(100)
},
.HID_Interface =
{
.Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface},
.InterfaceNumber = INTERFACE_ID_Joystick,
.AlternateSetting = 0x00,
.TotalEndpoints = 1,
.Class = HID_CSCP_HIDClass,
.SubClass = HID_CSCP_NonBootSubclass,
.Protocol = HID_CSCP_NonBootProtocol,
.InterfaceStrIndex = NO_DESCRIPTOR
},
.HID_JoystickHID =
{
.Header = {.Size = sizeof(USB_HID_Descriptor_HID_t), .Type = HID_DTYPE_HID},
.HIDSpec = VERSION_BCD(1,1,1),
.CountryCode = 0x00,
.TotalReportDescriptors = 1,
.HIDReportType = HID_DTYPE_Report,
.HIDReportLength = sizeof(JoystickReport)
},
.HID_ReportINEndpoint =
{
.Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint},
.EndpointAddress = JOYSTICK_EPADDR,
.Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA),
.EndpointSize = JOYSTICK_EPSIZE,
.PollingIntervalMS = 0x05
},
.LED_Interface =
{
.Header = {.Size = sizeof(USB_Descriptor_Interface_t), .Type = DTYPE_Interface},
.InterfaceNumber = INTERFACE_ID_LED,
.AlternateSetting = 0x00,
.TotalEndpoints = 1,
.Class = USB_CSCP_VendorSpecificClass,
.SubClass = USB_CSCP_VendorSpecificSubclass,
.Protocol = USB_CSCP_VendorSpecificProtocol,
.InterfaceStrIndex = NO_DESCRIPTOR
},
.LED_OUTEndpoint =
{
.Header = {.Size = sizeof(USB_Descriptor_Endpoint_t), .Type = DTYPE_Endpoint},
.EndpointAddress = LED_OUT_EPADDR,
.Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA),
.EndpointSize = EPSIZE,
.PollingIntervalMS = 0x05
},
};
uint16_t CALLBACK_USB_GetDescriptor(const uint16_t wValue,
const uint16_t wIndex,
const void** const DescriptorAddress)
{
const uint8_t DescriptorType = (wValue >> 8);
const uint8_t DescriptorNumber = (wValue & 0xFF);
const void* Address = NULL;
uint16_t Size = NO_DESCRIPTOR;
switch (DescriptorType)
{
case DTYPE_Device:
Address = &DeviceDescriptor;
Size = sizeof(USB_Descriptor_Device_t);
break;
case DTYPE_Configuration:
Address = &ConfigurationDescriptor;
Size = sizeof(USB_Descriptor_Configuration_t);
break;
case DTYPE_String:
switch (DescriptorNumber)
{
case STRING_ID_Language:
Address = &LanguageString;
Size = pgm_read_byte(&LanguageString.Header.Size);
break;
case STRING_ID_Manufacturer:
Address = &ManufacturerString;
Size = pgm_read_byte(&ManufacturerString.Header.Size);
break;
case STRING_ID_Product:
Address = &ProductString;
Size = pgm_read_byte(&ProductString.Header.Size);
break;
}
break;
case DTYPE_HID:
Address = &ConfigurationDescriptor.HID_JoystickHID;
Size = sizeof(USB_HID_Descriptor_HID_t);
break;
case DTYPE_Report:
Address = &JoystickReport;
Size = sizeof(JoystickReport);
break;
}
*DescriptorAddress = Address;
return Size;
}
|
Code pour le jeu (théorique)
Malgré quelques problèmes rencontrés, nous souhaitons quand même offrir quelques codes auxquels nous avons pensé pour l'intégration dans le jeu
Initialisation et Check des vies pour l'allumage des LEDs. Nous avons 4 vies dans notre jeu. La fonction LEDStates allume ou éteint (0 ou 1 en paramètres) la LEDs i en paramètre.
void displayVieLEDS(libusb_device *controleur, libusb_device_handle *handle,
struct libusb_config_descriptor *configdesc, int vies)
{
for(int i = 0; i<vie;i++)
{
LEDStates(1,i,manette,handle,configdesc);
}
for(int i = vies; i<4;i++)
{
LEDStates(0,i,manette,handle,configdesc);
}
}
Initialisation des boutons et des axes de déplacements in-game
void ControlDeVaisseauManette(SDL_Joystick* p_Joytsick, int *button1, int *button2, int *axe_x, int *axe_y)
{
SDL_JoystickUpdate();
*axe_x = SDL_JoystickGetAxis(p_Joytsick,0);
*axe_y = SDL_JoystickGetAxis(p_Joytsick,1);
*button1 = SDL_JoystickGetBUtton(p_Joytsick,0);
*button2 = SDL_JoystickGetBUtton(p_Joytsick,1);
}
Gestion des évènements claviers et manette
void gestion_event(evenement* ev1,
char* touch1,
entite_t* vaisseau,
entite_t* Missile,
listentite* List2Missile,
int* compteurMissile,
int axe_x,
int bouton1) //gère les évènements claviers et fais une action en conséquence
{
if((((*ev1==toucheBas) &&
(*touch1 == 'q')) || (axe_x <-100)) &&
(vaisseau->x >= 0)) // déplacement gauche du vaisseau par l'utilisateur et vérification que l'utlisateur ne dépasse pas l'écran
{
deplacement_entite(vaisseau,-VITESSE_VAISSEAU,0);
}
if((((*ev1==toucheBas) &&
(*touch1 == 'd')) || (axe_x > 100)) &&
(vaisseau->taillex <= SCREEN_WIDTH)) //déplacement droite du vaisseau par l'utilisateur et vérification que l'utlisateur ne dépasse pas l'écran
{
deplacement_entite(vaisseau,VITESSE_VAISSEAU,0);
}
if((((*ev1==toucheBas)
&& ((*touch1 == 'z') || (*touch1 == 's'))) || (bouton2 == 1))
&& (*compteurMissile==0)) //tir du vaisseau quand la touche s ou z est pressé et que le canon du vaisseau est eu le temps de refroidir du tir précédent (cooldown_blast)
{
Missile->x = vaisseau->x+18;
Missile->y = vaisseau->y-18;
Missile->vie = 1;
int largeurMissile,hauteurMissile;
tailleLutin(Missile->numsprite,&largeurMissile,&hauteurMissile);
Missile->taillex = Missile->x + largeurMissile;
Missile->tailley = Missile->y + hauteurMissile;
addhead(*Missile,List2Missile);
*compteurMissile=30;
}
}
VIDÉOS PRÉSENTATIONS
Communication entre Ordinateur et les LEDs sur la manette. On rentre des chiffres dans le terminal et certaines LEDs D2, D3, D4 et D5 s'allument ou s'éteignent en fonction.
On mettra de côté le fait que la luminosité des LED est très faible. (:
Jeu avec Manette sans les LEDs de vie (pour adapter le jeu on se base sur ce tutoriel: https://alexandre-laurent.developpez.com/tutoriels/sdl/joysticks/)
Jeu avec Manette complète. Sur la croix directionnelle, le bouton de gauche déplace à gauche le vaisseau, le droit déplace à droite. Les boutons haut et bas ne servent à rien pour notre projet. Les boutons sur la droite, celui en bas permet de tirer et celui du haut de quitter.
PROBLÈMES RENCONTRÉS
Bien que nous ayons réussi à simuler notre joystick via les 4 boutons directionnels et les 2 boutons d'action, et que nous ayons réussi en parallèle à communiquer avec la manette grâce à libusb et à allumer des LEDs sur commande, l'étape de la fusion de ces deux programmes LUFA ne s'est pas bien passée ...
CONCLUSION ?
Nous avons réussi à concevoir et jouer avec la manette au SpaceInvaders et nous avons réussi à contrôler les LEDs grâce à l'ordinateur. Malheureusement, dû à un bug/problèmes que nous n'arrivons pas à corriger à temps, il n'est pas possible de jouer au SpaceInvaders avec les LEDs contrôlées par libusb mais il est possible de jouer au jeu avec les contrôles de tir et de déplacement sans les LEDs.
Archive
FICHIERS
Fichiers: Fichier:ManetteUSBLilianPierre.zip
gerber: Fichiers: Fichier:SE3-pad-job.gbrjob.zip gerber: Fichiers: Fichier:SE3-pad-LGPC.zip
GIT
GiT Manette : https://archives.plil.fr/lgrevin/TPI_LUFA_LibUSB.git