« I2L 2022 Groupe5 » : différence entre les versions
Aucun résumé des modifications |
|||
(19 versions intermédiaires par 3 utilisateurs non affichées) | |||
Ligne 41 : | Ligne 41 : | ||
[[Fichier:I2L-2022-G5-carte-soudée.jpg|thumb|500px|center]] | [[Fichier:I2L-2022-G5-carte-soudée.jpg|thumb|500px|center]] | ||
= Éléments notables du code = | |||
Au lancement de l'application on vient définir les sorties et les entrées pour les LEDs et les boutons. | |||
[[Fichier:Screenshot CodeSetupHardware.png|vignette|Setup LED et bouton|center]] | |||
On crée une méthode pour simplifier la modification des couleurs des LEDs de manière à pouvoir attribuer les mêmes valeurs pour les mêmes couleurs qu'importe les LEDs en ignorant leur positionnement sur la carte. | |||
Dans le cas où le joueur gagne nous jouons une musique. Ici le code va venir altérer le rythme et la tonalité du haut parleur de manière à pouvoir reproduire une mélodie de jeu vidéo. | |||
[[Fichier:ScreenshotZeldaMusicChest.png|vignette|Fonctions permettant de jouer la musique de victoire|center]] | |||
Ensuite nous venons développer le jeu en lui-même. Il va donc s'agir de générer une suite de chiffres aléatoire. Chaque chiffre va correspondre à une couleur. | |||
[[Fichier:ScreenshotJeu1.png|vignette|développement du jeu|center]] | |||
Enfin nous venons récupérer les valeurs d'entrée donné par l'utilisateur. Nous vérifions dans un premier temps leur validité puis nous les traduisons en un chiffre à avec le code aléatoire généré précédemment. En cas de victoire la musique est jouée. | |||
= Détails d’éléments du code = | |||
=== Main === | |||
La fonction main() correspond aux instructions suivantes :<syntaxhighlight lang="c"> | |||
int main(void) | |||
{ | |||
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) | |||
SetupHardware(); | |||
LEDs_SetAllLEDs(LEDMASK_USB_NOTREADY); | |||
GlobalInterruptEnable(); | |||
for (;;) | |||
{ | |||
CDC_Task(); | |||
USB_USBTask(); | |||
} | |||
} | |||
</syntaxhighlight> | |||
* Paramétrage de l’horloge ; | |||
* Configuration du matériel ; | |||
* Définition des LED devant d'allumer ou non pour indiquer que l'USB n'est pas encore prêt ; | |||
* Autorisation des interruptions du code ; | |||
* Jusqu’au reset / à l'interruption du dispositif : | |||
** Traitement des événements du jeu (exp : validation des touches saisies par l’utilisateur en mode connecté à un PC) ; | |||
** Traitement des événements USB propres à LUFA. | |||
=== Changement de couleur d’une LED === | |||
Le changement de couleur d’une LED est réalisé grâce à la fonction switchLedColor(). | |||
Elle reçoit trois paramètres : ce sont les valeurs hexadécimales des couleurs à paramétrer pour chaque LED. | |||
Voici les instructions qui la composent :<syntaxhighlight lang="c"> | |||
void switchLedColor(int L1, int L2, int L3) | |||
{ | |||
if (!(L1 == -1)) | |||
{ | |||
PORTD = (PORTD & 0xf8) | L1; | |||
} | |||
if (!(L2 == -1)) | |||
{ | |||
PORTD = (PORTD & 0xc7) | L2 << 3; | |||
} | |||
if (!(L3 == -1)) | |||
{ | |||
PORTC = (PORTC & 0x1f) | L3 << 5; | |||
} | |||
} | |||
</syntaxhighlight> | |||
Celles-ci décrivent le processus suivant : | |||
Pour chaque LED, si la valeur reçue en paramètre vaut -1 : aucune modification n’est apportée. | |||
Sinon, on sélectionne les bits correspondants et on les modifie en fonction de la valeur hexadécimale de la couleur voulue. | |||
Seuls les bits à modifier sont mis à jour grâce à l’utilisation du “&”. | |||
L’utilisation de “<< n” permet de décaler le bit de départ de n. | |||
Donc pour la LED 2, celle-ci correspondant aux bits 3 à 5 du port D, on décale de 3 bits. | |||
Pour rappel, une LED est composée de 3 diodes, chaque diode est reliée à un bit. Quand la valeur de ce dernier est à 1, la diode s’allume. | |||
Le jeu se base sur un ensemble de 8 couleurs listées dans la section “Proposition définitive”. | |||
=== Mélodie de victoire === | |||
Lorsque le joueur a trouvé la solution, une mélodie de jeu vidéo est jouée (mélodie de résolution d’une énigme dans les jeux de la saga The Legend of Zelda de Nintendo). | |||
La partition est la suivante : | |||
Pour jouer une note, il faut que la membrane d’un haut-parleur bouge à une certaine vitesse (plus ou moins vite pour une note plus ou moins aigüe). Cela revient donc à faire varier l’ensemble des bits du haut-parleur entre 0 et 1 pour faire vibrer sa membrane, ce, sur un laps de temps prédéfini. | |||
La fonction play_note() permet de jouer une note en prenant en paramètres la fréquence en Hertz de la note et le nombre de périodes où la note sera jouée. | |||
Une période correspond à l’étendue/la durée de la courbe représentant les oscillations entre 0 et 1 des bits du haut-parleur. | |||
Formule de calcul d’une période (avec une durée en seconde) :<syntaxhighlight lang="text"> | |||
(Fréquence en Hz de la note * (60 / Durée de la note)) / Tempo | |||
</syntaxhighlight> | |||
En musique, le tempo est la vitesse d'exécution d’un mouvement. Son unité est en nombre de battements par minute. | |||
La durée de la note correspond à 2 par exemple si la note est une croche car un temps vaut 1 noire et 2 croches valent 1 noire. | |||
Cette fonction appelle make_note_delay() qui est chargée d’attendre la moitié de la période (car la moitié est en valeur de bit 0 et l’autre 1). Nous avons dû mettre en place un switch car la fonction _delay_us() de la bibliothèque AVR ne reçoit en paramètre que des constantes connues à la compilation. | |||
En paramètre de _delay_us(), nous avons appliqué pour chaque note la formule :<syntaxhighlight lang="text"> | |||
1000000 / fréquence de la note / 2 | |||
</syntaxhighlight> | |||
Voici le tableau que nous avons mis en place pour déterminer la fréquence et la période de chaque note : | |||
{| class="wikitable" | |||
|Note | |||
|Calcul de la fréquence | |||
|Fréquence (Hz) | |||
|Calcul période | |||
|Période | |||
|- | |||
|sol | |||
|396*2 | |||
|792 | |||
|(792*(60/3))/80 | |||
|198 | |||
|- | |||
|fa # | |||
|371.25*2 | |||
|742.5 | |||
|(742,5*(60/3))/80 | |||
|185,625 | |||
|- | |||
|mi ♭ | |||
|316.8*2 | |||
|633.6 | |||
|(633,6*(60/3))/80 | |||
|158,4 | |||
|- | |||
|la | |||
|440 | |||
|440 | |||
|(440*(60/3))/80 | |||
|110 | |||
|- | |||
|sol # | |||
|412.50 | |||
|412.50 | |||
|(412,5*(60/3))/80 | |||
|103,125 | |||
|- | |||
|mi | |||
|330*2 | |||
|660 | |||
|(660*(60/3))/80 | |||
|165 | |||
|- | |||
|sol # | |||
|412.5*2 | |||
|825 | |||
|(825*(60/2))/80 | |||
|309,375 | |||
|- | |||
|do | |||
|528*2 | |||
|1056 | |||
|(1056*60)/80 | |||
|792 | |||
|- | |||
|do | |||
|528*2 | |||
|1056 | |||
|(1056*60)/80 | |||
|792 | |||
|} | |||
Le calcul de la fréquence d’une note se base sur la fréquence de la note dans la gamme d’un La 440Hz * 2 ^ n, où n représente le nombre d’octaves à monter ou descendre (ici 1 octave plus haute. Si l’on avait voulu une octave plus bas, il aurait fallu prendre la valeur -1). | |||
= Démonstration (connecté à un PC) = | |||
Avec Putty, on commence par sélectionner le dispositif USB : | |||
[[Fichier:Configuration_de_Putty.png|415x415px]] | |||
Au départ, toutes les diodes sont éteintes, ce qui équivaut à avoir pour couleur "Noir" : | |||
[[Fichier:Dispositif connecté à un PC (avant essais de combinaisons).jpg|644x644px]] | |||
On saisit 3 caractères correspondant à la combinaison des 3 couleurs de LEDs que l'on veut tester : | |||
[[Fichier:Premier essai de combinaison de couleurs.png|648x648px]] | |||
En dessous des caractères envoyés, la combinaison de +, - et/ou = indique les modifications à apporter. Ici, il faut essayer une valeur de couleur plus haute que le Rouge, une autre plus basse que le Cyan et une dernière plus basse que le Magenta. | |||
Les diodes s'allument aux couleurs correspondantes : | |||
[[Fichier:Essai combinaison Rouge Cyan Magenta.jpg|645x645px]] | |||
Lorsque l'on a trouvé la bonne combinaison, la mélodie de victoire se joue, puis un message s'affiche dans le terminal, indiquant la réussite et le nombre d'essais : | |||
[[Fichier:Terminal Putty après résolution de l'énigme.png|645x645px]] | |||
Les LEDs sont ici colorées en Blanc (W), Rouge et Rouge : | |||
[[Fichier:Combinaison finale.jpg|644x644px]] | |||
= Fichiers = | = Fichiers = | ||
Ligne 50 : | Ligne 267 : | ||
Projet LUFA "nombre mystère" : [[File:I2L-2022-G5-mystere.zip]] | Projet LUFA "nombre mystère" : [[File:I2L-2022-G5-mystere.zip]] | ||
Ceci est le code final écrit en C | Ceci est le code final écrit en C. | ||
[[Fichier:MysteryColor.zip|vignette]] | [[Fichier:MysteryColor.zip|vignette]] | ||
Pour compiler le projet LUFA, il faut commencer par récupérer la bibliothèque <code>lufa-LUFA-210130</code>. Il faut ensuite placer le répertoire de l'archive dans un nouveau répertoire I2L créé dans le répertoire de la bibliothèque : <code>lufa-LUFA-210130/I2L/Serial</code>. Un simple <code>make</code> compile le projet et un <code>make dfu</code> télécharge le binaire dans le périphérique USB. Pour jouer, utilisez par exemple l'utilitaire <code>minicom</code>. La commande <code>minicom -b 9600 -D /dev/ttyACM0</code> fonctionne très bien sous Linux. | Pour compiler le projet LUFA, il faut commencer par récupérer la bibliothèque <code>lufa-LUFA-210130</code>. Il faut ensuite placer le répertoire de l'archive dans un nouveau répertoire I2L créé dans le répertoire de la bibliothèque : <code>lufa-LUFA-210130/I2L/Serial</code>. Un simple <code>make</code> compile le projet et un <code>make dfu</code> télécharge le binaire dans le périphérique USB. Pour jouer, utilisez par exemple l'utilitaire <code>minicom</code>. La commande <code>minicom -b 9600 -D /dev/ttyACM0</code> fonctionne très bien sous Linux. |
Version actuelle datée du 4 juillet 2023 à 11:54
Proposition de système
Notre projet est de faire un système de jeu type "juste prix" où le but est de trouver une valeur numérique. Au plus on se rapproche de la valeur recherchée, au plus le son émis par le système est fort.
Contre-proposition
Il est demandé à ce que le périphérique puisse fonctionner en autonomie ou connecté à un PC. Si le périphérique est connecté à un PC, le périphérique se comporte comme un port série et communique avec le joueur via un terminal série (e.g. minicom ou putty). Si le périphérique est simplement alimenté par une batterie USB, le joueur utilise uniquement les deux touches et le retour se fait par les 3 LED multicolores et des effets sonores. En mode autonome, peut être serait-il plus pratique de trouver 3 couleurs qu'un nombre ?
Pour la programmation du périphèrique USB vous utilisez, comme base, la démonstration LUFA Demos/Device/LowLevel/VirtualSerial
.
Proposition définitive
Le but de ce projet est de concevoir un dispositif USB sur lequel tourne un jeu mélangeant "Juste prix" et "Mastermind".
En effet, le joueur devra trouver une combinaison de couleurs en paramétrant les trois LED colorées du dispositif.
À chaque couleur est associée une lettre et une valeur numérique :
- Noir (la LED est éteinte) → N - 0
- Rouge → R - 1
- Jaune (diodes rouge et verte allumées) → J - 2
- Vert → V - 3
- Cyan (diodes verte et bleue allumées) → C - 4
- Bleu → B - 5
- Magenta (diodes bleue et rouge allumées) → M - 6
- Blanc (toutes les diodes de la LED sont allumées) → W - 7
Lorsque le joueur valide une combinaison, le dispositif émet une mélodie via un haut-parleur intégré (différente en fonction de "perdu"/"gagné").
Il est possible de jouer de deux façons :
- Le dispositif est branché à un ordinateur. Pour paramétrer la combinaison à tester, le joueur saisit via un clavier les 3 lettres correspondant aux 3 couleurs souhaitées. Si la combinaison envoyée est erronée, une indication est affichée au joueur, à savoir, une combinaison de "+", "-" et ".". Le caractère "." signifie que la couleur envoyée est correcte, le "+" signifie que le joueur doit essayer une couleur avec une valeur numérique associée plus grande et le "-" signifie que la valeur numérique associée attendue est inférieure à celle proposée.
- Le dispositif est en mode autonome (branché sur une batterie). Pour paramétrer la combinaison à tester, le joueur utilise les deux boutons intégrés au dispositif. Le premier permet de sélectionner la LED à configurer, le second permet de choisir sa couleur. Les couleurs défilent alors selon l'ordre mentionné ci-dessus (du noir au blanc et ainsi de suite). Pour valider une combinaison, le joueur fait un appui long sur le bouton de sélection de LED. En cas de combinaison fausse, après mélodie "perdu", trois signaux sonores sont joués, à savoir, un LA 440Hz si la couleur est juste, un DO (264Hz) si la valeur numérique de la couleur est trop haute et un DO (528Hz) si la valeur numérique de la couleur est trop basse (en d'autres termes, un son aigu indique qu'il faut tester une couleur avec une valeur numérique plus haute et vice-versa).
Carte
Carte soudée :
Éléments notables du code
Au lancement de l'application on vient définir les sorties et les entrées pour les LEDs et les boutons.
On crée une méthode pour simplifier la modification des couleurs des LEDs de manière à pouvoir attribuer les mêmes valeurs pour les mêmes couleurs qu'importe les LEDs en ignorant leur positionnement sur la carte.
Dans le cas où le joueur gagne nous jouons une musique. Ici le code va venir altérer le rythme et la tonalité du haut parleur de manière à pouvoir reproduire une mélodie de jeu vidéo.
Ensuite nous venons développer le jeu en lui-même. Il va donc s'agir de générer une suite de chiffres aléatoire. Chaque chiffre va correspondre à une couleur.
Enfin nous venons récupérer les valeurs d'entrée donné par l'utilisateur. Nous vérifions dans un premier temps leur validité puis nous les traduisons en un chiffre à avec le code aléatoire généré précédemment. En cas de victoire la musique est jouée.
Détails d’éléments du code
Main
La fonction main() correspond aux instructions suivantes :int main(void)
{
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)
SetupHardware();
LEDs_SetAllLEDs(LEDMASK_USB_NOTREADY);
GlobalInterruptEnable();
for (;;)
{
CDC_Task();
USB_USBTask();
}
}
- Paramétrage de l’horloge ;
- Configuration du matériel ;
- Définition des LED devant d'allumer ou non pour indiquer que l'USB n'est pas encore prêt ;
- Autorisation des interruptions du code ;
- Jusqu’au reset / à l'interruption du dispositif :
- Traitement des événements du jeu (exp : validation des touches saisies par l’utilisateur en mode connecté à un PC) ;
- Traitement des événements USB propres à LUFA.
Changement de couleur d’une LED
Le changement de couleur d’une LED est réalisé grâce à la fonction switchLedColor().
Elle reçoit trois paramètres : ce sont les valeurs hexadécimales des couleurs à paramétrer pour chaque LED.
Voici les instructions qui la composent :void switchLedColor(int L1, int L2, int L3)
{
if (!(L1 == -1))
{
PORTD = (PORTD & 0xf8) | L1;
}
if (!(L2 == -1))
{
PORTD = (PORTD & 0xc7) | L2 << 3;
}
if (!(L3 == -1))
{
PORTC = (PORTC & 0x1f) | L3 << 5;
}
}
Celles-ci décrivent le processus suivant :
Pour chaque LED, si la valeur reçue en paramètre vaut -1 : aucune modification n’est apportée.
Sinon, on sélectionne les bits correspondants et on les modifie en fonction de la valeur hexadécimale de la couleur voulue.
Seuls les bits à modifier sont mis à jour grâce à l’utilisation du “&”.
L’utilisation de “<< n” permet de décaler le bit de départ de n.
Donc pour la LED 2, celle-ci correspondant aux bits 3 à 5 du port D, on décale de 3 bits.
Pour rappel, une LED est composée de 3 diodes, chaque diode est reliée à un bit. Quand la valeur de ce dernier est à 1, la diode s’allume.
Le jeu se base sur un ensemble de 8 couleurs listées dans la section “Proposition définitive”.
Mélodie de victoire
Lorsque le joueur a trouvé la solution, une mélodie de jeu vidéo est jouée (mélodie de résolution d’une énigme dans les jeux de la saga The Legend of Zelda de Nintendo).
La partition est la suivante :
Pour jouer une note, il faut que la membrane d’un haut-parleur bouge à une certaine vitesse (plus ou moins vite pour une note plus ou moins aigüe). Cela revient donc à faire varier l’ensemble des bits du haut-parleur entre 0 et 1 pour faire vibrer sa membrane, ce, sur un laps de temps prédéfini.
La fonction play_note() permet de jouer une note en prenant en paramètres la fréquence en Hertz de la note et le nombre de périodes où la note sera jouée.
Une période correspond à l’étendue/la durée de la courbe représentant les oscillations entre 0 et 1 des bits du haut-parleur.
Formule de calcul d’une période (avec une durée en seconde) :(Fréquence en Hz de la note * (60 / Durée de la note)) / Tempo
En musique, le tempo est la vitesse d'exécution d’un mouvement. Son unité est en nombre de battements par minute.
La durée de la note correspond à 2 par exemple si la note est une croche car un temps vaut 1 noire et 2 croches valent 1 noire.
Cette fonction appelle make_note_delay() qui est chargée d’attendre la moitié de la période (car la moitié est en valeur de bit 0 et l’autre 1). Nous avons dû mettre en place un switch car la fonction _delay_us() de la bibliothèque AVR ne reçoit en paramètre que des constantes connues à la compilation.
En paramètre de _delay_us(), nous avons appliqué pour chaque note la formule :1000000 / fréquence de la note / 2
Voici le tableau que nous avons mis en place pour déterminer la fréquence et la période de chaque note :
Note | Calcul de la fréquence | Fréquence (Hz) | Calcul période | Période |
sol | 396*2 | 792 | (792*(60/3))/80 | 198 |
fa # | 371.25*2 | 742.5 | (742,5*(60/3))/80 | 185,625 |
mi ♭ | 316.8*2 | 633.6 | (633,6*(60/3))/80 | 158,4 |
la | 440 | 440 | (440*(60/3))/80 | 110 |
sol # | 412.50 | 412.50 | (412,5*(60/3))/80 | 103,125 |
mi | 330*2 | 660 | (660*(60/3))/80 | 165 |
sol # | 412.5*2 | 825 | (825*(60/2))/80 | 309,375 |
do | 528*2 | 1056 | (1056*60)/80 | 792 |
do | 528*2 | 1056 | (1056*60)/80 | 792 |
Le calcul de la fréquence d’une note se base sur la fréquence de la note dans la gamme d’un La 440Hz * 2 ^ n, où n représente le nombre d’octaves à monter ou descendre (ici 1 octave plus haute. Si l’on avait voulu une octave plus bas, il aurait fallu prendre la valeur -1).
Démonstration (connecté à un PC)
Avec Putty, on commence par sélectionner le dispositif USB :
Au départ, toutes les diodes sont éteintes, ce qui équivaut à avoir pour couleur "Noir" :
On saisit 3 caractères correspondant à la combinaison des 3 couleurs de LEDs que l'on veut tester :
En dessous des caractères envoyés, la combinaison de +, - et/ou = indique les modifications à apporter. Ici, il faut essayer une valeur de couleur plus haute que le Rouge, une autre plus basse que le Cyan et une dernière plus basse que le Magenta.
Les diodes s'allument aux couleurs correspondantes :
Lorsque l'on a trouvé la bonne combinaison, la mélodie de victoire se joue, puis un message s'affiche dans le terminal, indiquant la réussite et le nombre d'essais :
Les LEDs sont ici colorées en Blanc (W), Rouge et Rouge :
Fichiers
Projet KiCAD : Fichier:I2L-2022-GENESTE-DECLOITRE.zip
Programme sonore : Fichier:I2L-2022-G5-Music.zip
Projet LUFA "nombre mystère" : Fichier:I2L-2022-G5-mystere.zip
Ceci est le code final écrit en C. Fichier:MysteryColor.zip
Pour compiler le projet LUFA, il faut commencer par récupérer la bibliothèque lufa-LUFA-210130
. Il faut ensuite placer le répertoire de l'archive dans un nouveau répertoire I2L créé dans le répertoire de la bibliothèque : lufa-LUFA-210130/I2L/Serial
. Un simple make
compile le projet et un make dfu
télécharge le binaire dans le périphérique USB. Pour jouer, utilisez par exemple l'utilitaire minicom
. La commande minicom -b 9600 -D /dev/ttyACM0
fonctionne très bien sous Linux.