« I2L 2024 Groupe1 » : différence entre les versions

De wiki-se.plil.fr
Aller à la navigation Aller à la recherche
 
(9 versions intermédiaires par 3 utilisateurs non affichées)
Ligne 15 : Ligne 15 :


= Proposition définitive =
= Proposition définitive =
Un Memory Game (jeu de Simon), avec des boutons colorés où l'on doit appuyer pour reproduire une séquence lumineuse et sonore de plus en plus longue, chaque tour rajoutant une nouvelle couleur.
Branché sur un ordinateur via un port USB le Memory Game utilisera l'écran via une application spécialisée qui affichera aussi le nombre de tours déjà passé ainsi que le maximum atteint.
On utilisera la classe USB "vendeur spécifique" (Minimal) avec des points d'accès propres à votre application.
Un soin particulier est demandé au niveau du son : des notes avec enveloppe ADSR sont attendues.


= Répartition du travail =
= Répartition du travail =
Nous avons travaillé à part égale, ensemble, sur toutes les fonctionnalités.


= Carte =
= Carte =
Ligne 78 : Ligne 86 :
[[File:ISL-2024-G1-Realisation-C.jpg|thumb|center|400px|Réalisation finale]]
[[File:ISL-2024-G1-Realisation-C.jpg|thumb|center|400px|Réalisation finale]]


Non encore réalisé :
LED remplacées, un mini haut-parleur fixé sur la carte. Un programme de démonstration est fourni (voir en bas de page Wiki). Comme exercice, il vous est demandé de voir pourquoi les notes hautes sont fausses.
* ajouter la perle de ferrite ;
 
* changer 3 LED rouges par des LED verte, jaune et bleue.
= Travaux =
 
=== Du 14/02/2025 au 23/02/2025 ===
Nous avons testé la carte et tenté de réaliser le jeu Simon sans utiliser LUFA.
 
=== Du 24/02/2025 au 26/02/2025 ===
Nous avons configuré le projet "Minimal" de LUFA et essayé d'utiliser la carte avec cette configuration.
 
=== Du 27/02/2025 au 12/03/2025 ===
Nous avons tenté de faire fonctionner le son sur la carte, mais sans succès. Nous obtenions uniquement un très léger grésillement.
 
Après investigation, nous avons constaté qu'un problème matériel affectait la carte.
 
=== Du 13/03/2025 au 30/03/2025 ===
Le problème de son lié à la carte a été corrigé, et le son fonctionne désormais correctement.
 
Nous avons alors commencé à développer le jeu Simon directement sur la carte.
 
Pour continuer le projet, nous avons emporté la carte chez nous : une première version du jeu Simon a été réalisée. Cependant, nous avons rencontré un problème : la carte ne communiquait pas correctement avec un PC.
 
=== Du 31/03/2025 au 27/04/2025 ===
Nous avons identifié et corrigé le problème de liaison entre la carte et le PC : il s'agissait d'une erreur de configuration dans le projet "Minimal" de LUFA.


Un condensateur de 470uF de taille adapté remplace le précedent condensateur C10. Le son faible provenait de la mauvaise utilisation de J1.
Nous avons adapté notre jeu Simon pour qu'il fonctionne correctement avec la carte connectée au PC.


= Montage =
Enfin, nous avons tenté d'ajouter le support du son dans le projet Minimal, mais cela n'a pas abouti : l'horloge nécessaire au fonctionnement du son n'était pas compatible avec celle du Simon.


= Code =
= Code =
=== 1 - Initialisation ===
<syntaxhighlight lang="c" line="1">
void SetupHardware(void)
{
    #if (ARCH == ARCH_AVR8)
        MCUSR &= ~(1 << WDRF);
        wdt_disable();
        clock_prescale_set(clock_div_1);
    #endif
    USB_Init();
}
void initiliaze_simon(void)
{
    MCUCR |= (1<<JTD);
    DDRC &= ~(1 << BTNGREEN);
    PORTC |= (1 << BTNGREEN);
    DDRB &= ~(1 << BTNRED);
    PORTB |= (1 << BTNRED);
    DDRD &= ~(1 << BTNORANGE);
    PORTD |= (1 << BTNORANGE);
    DDRD &= ~(1 << BTNBLUE);
    PORTD |= (1 << BTNBLUE);
    DDRC |= (1 << LEDGREEN);
    DDRB |= (1 << LEDRED);
    DDRD |= (1 << LEDORANGE) | (1 << LEDBLUE);
}
</syntaxhighlight>Sert à configurer notre matériel au démarrage, on y configure les broches pour les LED et les boutons et on y initialise l'USB pour la communication avec l'ordinateur
=== 2 - Gestion du jeu de Simon ===
==== Boucle principale ====
<syntaxhighlight lang="c" line="1">
int main(void)
{
    SetupHardware();
    GlobalInterruptEnable();
    init_sound();
    initiliaze_timer(DIVIDER, COUNTER_PERIOD);
    initiliaze_simon();
    srand(1234);
    while (1)
    {
        USB_USBTask();
        if (defaite_en_cours)
        {
            if (afficher_defaite())
            {
                melody_index = 0;
                melody_playing = true;
                melody_last_time = time_counter * COUNTER_PERIOD;
                play_note(melody_defaite[melody_index]);
           
                defaite_en_cours = false;
                add_color = true;
            }
        }
        else
        {
            if (add_color)
            {
                if (longueur < SEQUENCE_LENGTH)
                {
                    sequence[longueur] = rand() % 4;
                    longueur++;
                }
                add_color = false;
                sequence_affichee = true;
                user_play = false;
            }
            if (sequence_affichee)
                afficher_sequence(sequence);
            if (user_play)
                if (play_user())
                    defaite_en_cours = true;
        }
    }
}
</syntaxhighlight>
C'est le cœur du jeu, a chaque tour de boucle elle regarde l'état dans lequel elle est.
==== La gestion des boutons ====
<syntaxhighlight lang="c" line="1">
bool play_user(void)
{
    static unsigned long last_time = 0;
    if (last_time == 0)
        last_time = time_counter * COUNTER_PERIOD;
    unsigned long elapsed = time_counter * COUNTER_PERIOD - last_time;
    if (elapsed > 400)
    {
        if (bouton_en_cours == -1)
        {
            if (!(PINC & (1 << BTNGREEN))) {
                bouton_en_cours = 0;
                allumer_led(0);
            } else if (!(PINB & (1 << BTNRED))) {
                bouton_en_cours = 1;
                allumer_led(1);
            } else if (!(PIND & (1 << BTNORANGE))) {
                bouton_en_cours = 2;
                allumer_led(2);
            } else if (!(PIND & (1 << BTNBLUE))) {
                bouton_en_cours = 3;
                allumer_led(3);
            }
        }
        else
        {
            bool relache = false;
            switch (bouton_en_cours) {
                case 0: relache = (PINC & (1 << BTNGREEN)); break;
                case 1: relache = (PINB & (1 << BTNRED)); break;
                case 2: relache = (PIND & (1 << BTNORANGE)); break;
                case 3: relache = (PIND & (1 << BTNBLUE)); break;
            }
            if (relache)
            {
                eteindre_led(bouton_en_cours);
                if (bouton_en_cours == sequence[index_user])
                {
                    // Bon bouton
                    user_input[index_user] = bouton_en_cours;
                    index_user++;
                    if (index_user == longueur)
                    {
                        index_user = 0;
                        add_color = true;
                        user_play = false;
                        sequence_affichee = false;
                    }
                }
                else
                {
                    // Mauvais bouton
                    defaite_en_cours = true;
                    index_user = 0;
                    longueur = 0;
                    add_color = true;
                    user_play = false;
                    sequence_affichee = false;
                    bouton_en_cours = -1;
                    return true;  // retourne défaite
                }
                bouton_en_cours = -1;
                last_time = time_counter * COUNTER_PERIOD;
            }
        }
    }
    return false;
}
</syntaxhighlight>
'''play_user''' regarde à intervalle réguliers l'activation des boutons pour éviter de compter à de multiples reprises une unique pression,
s'il détecte la pression d'un bouton alors il va le comparer avec la séquence en cours et changer le statut de la boucle principal en fonction du résultat
=== 3 - Affichage des LED ===
<syntaxhighlight lang="c" line="1">
void allumer_led(uint8_t led) {
    switch (led) {
        case 0:
            PORTC |= (1 << LEDGREEN);
            play_note(261); // Do
            break;
        case 1:
            PORTB |= (1 << LEDRED);
            play_note(293); // Ré
            break;
        case 2:
            PORTD |= (1 << LEDORANGE);
            play_note(329); // Mi
            break;
        case 3:
            PORTD |= (1 << LEDBLUE);
            play_note(349); // Fa
            break;
    }
}
void eteindre_led(uint8_t led) {
    switch (led) {
        case 0:
            PORTC &= ~(1 << LEDGREEN);
            break;
        case 1:
            PORTB &= ~(1 << LEDRED);
            break;
        case 2:
            PORTD &= ~(1 << LEDORANGE);
            break;
        case 3:
            PORTD &= ~(1 << LEDBLUE);
            break;
    }
    stop_note();
}
void afficher_sequence(uint8_t *sequence) {
    static uint8_t index = 0;
    static unsigned long last_time = 0;
    static bool led_on = false;
    if (last_time == 0)
    {
        last_time = time_counter * COUNTER_PERIOD;
    }
    unsigned long elapsed = time_counter * COUNTER_PERIOD - last_time;
    if (elapsed > 250) {
        last_time = time_counter * COUNTER_PERIOD;
        if (index >= longueur) {
            index = 0;
            add_color = false;
            sequence_affichee = false;
            user_play = true;
            return;
        }
        if (led_on) {
            eteindre_led(sequence[index]);
            led_on = false;
            index++;
        } else {
            allumer_led(sequence[index]);
            led_on = true;
        }
    }
bool afficher_defaite(void)
{
    static int etat = 0;
    static unsigned long last_time = 0;
    static int clignotements = 0;
    if (last_time == 0)
        last_time = time_counter;
    if ((time_counter - last_time) > 25)
    {
        last_time = time_counter;
        if (etat == 0)
        {
            // Allume toutes les LED
            PORTC |= (1 << LEDGREEN);
            PORTB |= (1 << LEDRED);
            PORTD |= (1 << LEDORANGE) | (1 << LEDBLUE);
            etat = 1;
        }
        else
        {
            // Eteint toutes les LED
            PORTC &= ~(1 << LEDGREEN);
            PORTB &= ~(1 << LEDRED);
            PORTD &= ~(1 << LEDORANGE) & ~(1 << LEDBLUE);
            etat = 0;
            clignotements++;
            if (clignotements >= 3)
            {
                clignotements = 0;
                last_time = 0;
                return;  // Fin de l'animation
            }
        }
    }
    if (clignotements >= 3) {
        clignotements = 0;
        last_time = 0;
        return true;  // Animation terminée
    }
    return false;    // Animation pas finie
   
}
</syntaxhighlight>
'''afficher_sequence''' va faire clignoter une à une les LED qui composent la séquence à reproduire à intervalle réguliers (250ms) en utilisant les fonctions '''allumer_led''' et '''eteindre_led''', chaque couleur est représenté par un numéro et a un son associé.
'''afficher_defaite''' sert à afficher une petite animation en cas de défaite, elle est conçue de manière à ne pas bloquer le programme grâce à un timer dédié (voir partie 4)
=== 4 - Gestion du son ===
Le son est la partie qui a été la plus difficile et encore maintenant il n'est pas fonctionnel, au départ nous jouons des notes avec des _delay_ms() pour définir leurs durées et il n'y avait pas de problème mais quand on a implémenté la logique de notre Simon les deux n'étaient pas compatible, étant donné que _delay_ms() est une fonction bloquant le jeu ne peut pas correctement s'exécuté et la gestion du son ainsi que du jeu ne pouvaient donc pas être fait en parallèle, pour palier ce problème nous avons conçu les fonctions avec des timers dédiés de manière à n'avoir aucune fonction bloquantes, ce qui permit d'avoir le code du son et celui du jeu exécuté en même temps, malheureusement cela n'a pas suffit à régler tous les problèmes.
Le fichier '''sound.c''' contient des fonctions permettant de générer les différents sons qui sont appelés dans notre programme principal
=== 5 - Communication USB ===
Notre jeu du Simon peut également être joué depuis un PC sur lequel est branché la carte via un script python qui communique avec celle-ci, cela est rendu possible grâce à la bibliothèque LUFA qui nous a permit de créer deux endpoints: un pour l'input et un pour l'output.
==== Côté carte ====
<syntaxhighlight lang="c" line="1">
void EVENT_USB_Device_ControlRequest(void)
{
    if ((USB_ControlRequest.bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_VENDOR | REQREC_DEVICE)) &&
        (USB_ControlRequest.bRequest == 0x01))
    {
        Endpoint_ClearSETUP();
        while (!(Endpoint_IsOUTReceived()));
        uint8_t bouton = Endpoint_Read_8();
        Endpoint_ClearOUT();
        while (!(Endpoint_IsINReady()));
        Endpoint_ClearIN();
        if (user_play)
        {
            if (bouton == sequence[index_user])
            {
                index_user++;
                if (index_user == longueur)
                {
                    SendResultToHost(1);  // Séquence réussie
                    index_user = 0;
                    add_color = true;
                    user_play = false;
                }
                else
                {
                    SendResultToHost(2);  // Bouton correct mais séquence pas finie
                }
            }
            else
            {
                SendResultToHost(0);      // Mauvais bouton
                index_user = 0;
                longueur = 0;
                add_color = true;
                user_play = false;
                defaite_en_cours = true;
            }
        }
        else
        {
            SendResultToHost(0xAA); // Pas en phase de jeu
        }
    }
}
</syntaxhighlight>
Cette fonction va lire une entrée reçue (le bouton sur lequel l'utilisateur a appuyé) et le "jouer", en retour on envoie à l'ordinateur le résultat.
==== Côté ordinateur ====
<syntaxhighlight lang="python3" line="1">
import usb.core
import usb.util
import tkinter as tk
# Recherche du périphérique USB
dev = usb.core.find(idVendor=0x9999, idProduct=0x2048)
if dev is None:
    raise ValueError('Device not found')
if dev.is_kernel_driver_active(0):
    dev.detach_kernel_driver(0)
dev.set_configuration()
# Cherche l'endpoint Bulk IN
cfg = dev.get_active_configuration()
interface = cfg[(0, 0)]
endpoint_in = None
for ep in interface:
    if usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_IN:
        endpoint_in = ep
        break
if endpoint_in is None:
    raise ValueError("Endpoint IN non trouvé")
# Initialisation de Tkinter
root = tk.Tk()
root.title("Simon Game")
colors = ['green', 'red', 'orange', 'blue']
buttons = []
frame = tk.Frame(root)
frame.pack(padx=20, pady=20)
# Variables de score
current_score = 0
high_score = 0
# Création des labels pour afficher les scores
score_label = tk.Label(root, text=f"Score: {current_score}", font=("Helvetica", 14))
score_label.pack(pady=10)
high_score_label = tk.Label(root, text=f"High Score: {high_score}", font=("Helvetica", 14))
high_score_label.pack(pady=10)
# Fonction appelée quand on clique un bouton
def on_button_click(index):
    global current_score, high_score
    print(f"[Python] Bouton {colors[index]} appuyé !")
    try:
        # Envoyer l'index du bouton appuyé au périphérique USB
        dev.ctrl_transfer(
            bmRequestType=0x40,
            bRequest=0x01,
            wValue=0,
            wIndex=0,
            data_or_wLength=[index]
        )
        print("[Python] Envoyé au device.")
        # Lire la réponse du périphérique
        data = dev.read(endpoint_in.bEndpointAddress, endpoint_in.wMaxPacketSize, timeout=1000)
        print(f"[Python] Réponse du device: {data[0]:02X}")
       
        if data[0] == 1:
            # Bonne réponse
            current_score += 1
            if current_score > high_score:
                high_score = current_score
            score_label.config(text=f"Score: {current_score}")
            high_score_label.config(text=f"High Score: {high_score}")
            print(f"[Python] Correct! Score actuel: {current_score}, Score max: {high_score}")
        elif data[0] == 0:
            # Mauvaise réponse
            print(f"[Python] Mauvais! Ton score final était {current_score}.")
            current_score = 0  # Réinitialiser le score
            score_label.config(text=f"Score: {current_score}")
            print("[Python] Réinitialisation du score.")
        else:
            print("[Python] Réponse inconnue")
    except usb.core.USBTimeoutError:
        print("[Python] Timeout de lecture USB")
    except Exception as e:
        print(f"[Python] Erreur USB: {e}")
# Création des 4 boutons
for i, color in enumerate(colors):
    btn = tk.Button(frame, bg=color, width=10, height=5, command=lambda i=i: on_button_click(i))
    btn.grid(row=i//2, column=i%2, padx=10, pady=10)
    buttons.append(btn)
root.mainloop()
</syntaxhighlight>Ce script python va se connecter à notre carte via la librairie '''pyusb''' en prenant ses identifiants, va localiser ses endpoints et va pouvoir communiquer avec notre carte, il crée ensuite une application via tkinter représentant les 4 boutons de notre jeu du Simon


= Démonstrations =
= Démonstrations =
<div style="text-align: center;">
[[Fichier:ISL2024Groupe1Demo1.mp4|300x300px]]
<br />
'''Démonstration sans connexion PC'''
</div>
<div style="text-align: center;">
[[Fichier:ISL2024Groupe1Demo2.mp4|300x300px]]
<br />
'''Démonstration avec connexion PC'''
</div>


= Rendus =
= Rendus =
Ligne 97 : Ligne 614 :
* démonstration : [[File:I2L-2024-Programmes-G1-rex.zip]]
* démonstration : [[File:I2L-2024-Programmes-G1-rex.zip]]
* SIMON : [[File:I2L-2024-Programmes-G1.zip]]
* SIMON : [[File:I2L-2024-Programmes-G1.zip]]
* SIMON_TEST : [[FILE:I2L-2024-Programmes_TEST-G1.zip]]
* Script python [[FILE:I2L-2024-Programmes-Python-G1.zip]]

Version actuelle datée du 27 avril 2025 à 17:45

Proposition de système

Un Memory Game (jeu de Simon), avec des boutons colorés où l'on doit appuyer pour reproduire une séquence lumineuse et sonore de plus en plus longue, chaque tour rajoutant une nouvelle couleur.

Branché sur un ordinateur via un port USB le Memory Game utilisera l'écran via une application spécialisée.

Contre-proposition

OK pour la proposition. En mode connecté en USB sur un PC, faire en sorte d'afficher aussi le nombre de tours déjà passé ainsi que le maximum atteint.

Vous utiliserez la classe USB "vendeur spécifique" avec des points d'accès propres à votre application. Plus exactement vous prévoierez un point d'accès entrant pour récupérer la couleur à afficher, le score et le score max. Une scrutation du point d'accès tous les dixièmes de seconde va être nécessaire au niveau de votre application PC.

Un soin particulier est demandé au niveau du son : des notes avec enveloppe ADSR sont attendues.

Pour l'application sur PC vous utiliserez la bibliothèque C libusb-1.0.

Proposition définitive

Un Memory Game (jeu de Simon), avec des boutons colorés où l'on doit appuyer pour reproduire une séquence lumineuse et sonore de plus en plus longue, chaque tour rajoutant une nouvelle couleur.

Branché sur un ordinateur via un port USB le Memory Game utilisera l'écran via une application spécialisée qui affichera aussi le nombre de tours déjà passé ainsi que le maximum atteint.

On utilisera la classe USB "vendeur spécifique" (Minimal) avec des points d'accès propres à votre application.

Un soin particulier est demandé au niveau du son : des notes avec enveloppe ADSR sont attendues.

Répartition du travail

Nous avons travaillé à part égale, ensemble, sur toutes les fonctionnalités.

Carte

Schéma initial

Schéma de la carte
Position des boutons et des LED

Carte routée

Schéma de la carte
Vue carte

Composants

  • ATmega32u4 : disponible
  • quartz GND24 : disponible
  • connecteur jack : disponible
  • buzzer : disponible
  • perle ferrite MH2029-300Y : commandée
  • chargeur MAX1811 : disponible
  • amplificateur LM386 : disponible
  • potentiomètre : disponible

Carte réalisée

Carte au 23/02/2025

Première réalisation

Non encore réalisé :

  • ajouter la perle de ferrite ;
  • changer 3 LED rouges par des LED verte, jaune et bleue  ;
  • ajouter les connecteurs J5, J6, J7 et J9 pour la charge ;
  • ajouter le condensateur de 2,2uF pour la charge ;
  • ajouter le circuit LM386 pour l'amplification ;
  • ajouter les connecteurs J1, J3 et J4 pour l'amplification ;
  • ajouter le potentiomètre pour l'amplification ;
  • ajouter le condensateur c10 pour l'amplification.

Carte au 27/02/2025

Non encore réalisé :

  • ajouter la perle de ferrite ;
  • changer 3 LED rouges par des LED verte, jaune et bleue  ;
  • ajouter le condensateur C10 pour l'amplification (un condensateur peu adapté de 1000uF installé).

Regarder pourquoi le son est faible.

Carte au 27/02/2025

Réalisation finale

LED remplacées, un mini haut-parleur fixé sur la carte. Un programme de démonstration est fourni (voir en bas de page Wiki). Comme exercice, il vous est demandé de voir pourquoi les notes hautes sont fausses.

Travaux

Du 14/02/2025 au 23/02/2025

Nous avons testé la carte et tenté de réaliser le jeu Simon sans utiliser LUFA.

Du 24/02/2025 au 26/02/2025

Nous avons configuré le projet "Minimal" de LUFA et essayé d'utiliser la carte avec cette configuration.

Du 27/02/2025 au 12/03/2025

Nous avons tenté de faire fonctionner le son sur la carte, mais sans succès. Nous obtenions uniquement un très léger grésillement.

Après investigation, nous avons constaté qu'un problème matériel affectait la carte.

Du 13/03/2025 au 30/03/2025

Le problème de son lié à la carte a été corrigé, et le son fonctionne désormais correctement.

Nous avons alors commencé à développer le jeu Simon directement sur la carte.

Pour continuer le projet, nous avons emporté la carte chez nous : une première version du jeu Simon a été réalisée. Cependant, nous avons rencontré un problème : la carte ne communiquait pas correctement avec un PC.

Du 31/03/2025 au 27/04/2025

Nous avons identifié et corrigé le problème de liaison entre la carte et le PC : il s'agissait d'une erreur de configuration dans le projet "Minimal" de LUFA.

Nous avons adapté notre jeu Simon pour qu'il fonctionne correctement avec la carte connectée au PC.

Enfin, nous avons tenté d'ajouter le support du son dans le projet Minimal, mais cela n'a pas abouti : l'horloge nécessaire au fonctionnement du son n'était pas compatible avec celle du Simon.

Code

1 - Initialisation

void SetupHardware(void)
{
    #if (ARCH == ARCH_AVR8)
        MCUSR &= ~(1 << WDRF);
        wdt_disable();
        clock_prescale_set(clock_div_1);
    #endif

    USB_Init();
}

void initiliaze_simon(void)
{
    MCUCR |= (1<<JTD);

    DDRC &= ~(1 << BTNGREEN);
    PORTC |= (1 << BTNGREEN);

    DDRB &= ~(1 << BTNRED);
    PORTB |= (1 << BTNRED);

    DDRD &= ~(1 << BTNORANGE);
    PORTD |= (1 << BTNORANGE);

    DDRD &= ~(1 << BTNBLUE);
    PORTD |= (1 << BTNBLUE);

    DDRC |= (1 << LEDGREEN);
    DDRB |= (1 << LEDRED);
    DDRD |= (1 << LEDORANGE) | (1 << LEDBLUE);
}

Sert à configurer notre matériel au démarrage, on y configure les broches pour les LED et les boutons et on y initialise l'USB pour la communication avec l'ordinateur

2 - Gestion du jeu de Simon

Boucle principale

int main(void)
{
    SetupHardware();
    GlobalInterruptEnable();
    init_sound();
    initiliaze_timer(DIVIDER, COUNTER_PERIOD);
    initiliaze_simon();

    srand(1234);

    while (1)
    {
        USB_USBTask();

        if (defaite_en_cours)
        {
            if (afficher_defaite())
            {
                melody_index = 0;
                melody_playing = true;
                melody_last_time = time_counter * COUNTER_PERIOD;
                play_note(melody_defaite[melody_index]);
            
                defaite_en_cours = false;
                add_color = true;
            }
        }
        else
        {
            if (add_color)
            {
                if (longueur < SEQUENCE_LENGTH)
                {
                    sequence[longueur] = rand() % 4;
                    longueur++;
                }
                add_color = false;
                sequence_affichee = true;
                user_play = false;
            }

            if (sequence_affichee)
                afficher_sequence(sequence);

            if (user_play)
                if (play_user())
                    defaite_en_cours = true;
        }
    }
}


C'est le cœur du jeu, a chaque tour de boucle elle regarde l'état dans lequel elle est.

La gestion des boutons

bool play_user(void)
{
    static unsigned long last_time = 0;

    if (last_time == 0)
        last_time = time_counter * COUNTER_PERIOD;

    unsigned long elapsed = time_counter * COUNTER_PERIOD - last_time;

    if (elapsed > 400) 
    {
        if (bouton_en_cours == -1) 
        {
            if (!(PINC & (1 << BTNGREEN))) {
                bouton_en_cours = 0;
                allumer_led(0);
            } else if (!(PINB & (1 << BTNRED))) {
                bouton_en_cours = 1;
                allumer_led(1);
            } else if (!(PIND & (1 << BTNORANGE))) {
                bouton_en_cours = 2;
                allumer_led(2);
            } else if (!(PIND & (1 << BTNBLUE))) {
                bouton_en_cours = 3;
                allumer_led(3);
            }
        }
        else 
        {
            bool relache = false;
            switch (bouton_en_cours) {
                case 0: relache = (PINC & (1 << BTNGREEN)); break;
                case 1: relache = (PINB & (1 << BTNRED)); break;
                case 2: relache = (PIND & (1 << BTNORANGE)); break;
                case 3: relache = (PIND & (1 << BTNBLUE)); break;
            }

            if (relache) 
            {
                eteindre_led(bouton_en_cours);

                if (bouton_en_cours == sequence[index_user]) 
                {
                    // Bon bouton
                    user_input[index_user] = bouton_en_cours;
                    index_user++;

                    if (index_user == longueur) 
                    {
                        index_user = 0;
                        add_color = true;
                        user_play = false;
                        sequence_affichee = false;
                    }
                } 
                else 
                {
                    // Mauvais bouton
                    defaite_en_cours = true;
                    index_user = 0;
                    longueur = 0;
                    add_color = true;
                    user_play = false;
                    sequence_affichee = false;
                    bouton_en_cours = -1;
                    return true;  // retourne défaite
                }

                bouton_en_cours = -1;
                last_time = time_counter * COUNTER_PERIOD;
            }
        }
    }

    return false;
}


play_user regarde à intervalle réguliers l'activation des boutons pour éviter de compter à de multiples reprises une unique pression,

s'il détecte la pression d'un bouton alors il va le comparer avec la séquence en cours et changer le statut de la boucle principal en fonction du résultat

3 - Affichage des LED

void allumer_led(uint8_t led) {
    switch (led) {
        case 0: 
            PORTC |= (1 << LEDGREEN);
            play_note(261); // Do
            break;
        case 1: 
            PORTB |= (1 << LEDRED);
            play_note(293); // Ré
            break;
        case 2: 
            PORTD |= (1 << LEDORANGE);
            play_note(329); // Mi
            break;
        case 3: 
            PORTD |= (1 << LEDBLUE);
            play_note(349); // Fa
            break;
    }
}

void eteindre_led(uint8_t led) {
    switch (led) {
        case 0: 
            PORTC &= ~(1 << LEDGREEN);
            break;
        case 1: 
            PORTB &= ~(1 << LEDRED); 
            break;
        case 2: 
            PORTD &= ~(1 << LEDORANGE); 
            break;
        case 3: 
            PORTD &= ~(1 << LEDBLUE); 
            break;
    }
    stop_note();

}

void afficher_sequence(uint8_t *sequence) {
    static uint8_t index = 0;
    static unsigned long last_time = 0;
    static bool led_on = false;

    if (last_time == 0)
    {
        last_time = time_counter * COUNTER_PERIOD;
    }

    unsigned long elapsed = time_counter * COUNTER_PERIOD - last_time;

    if (elapsed > 250) {
        last_time = time_counter * COUNTER_PERIOD;

        if (index >= longueur) {
            index = 0;
            add_color = false;
            sequence_affichee = false;
            user_play = true;
            return;
        }

        if (led_on) {
            eteindre_led(sequence[index]);
            led_on = false;
            index++;
        } else {
            allumer_led(sequence[index]);
            led_on = true;
        }
    }
}  

bool afficher_defaite(void)
{
    static int etat = 0;
    static unsigned long last_time = 0;
    static int clignotements = 0;

    if (last_time == 0)
        last_time = time_counter;

    if ((time_counter - last_time) > 25)
    {
        last_time = time_counter;

        if (etat == 0)
        {
            // Allume toutes les LED
            PORTC |= (1 << LEDGREEN);
            PORTB |= (1 << LEDRED);
            PORTD |= (1 << LEDORANGE) | (1 << LEDBLUE);
            etat = 1;
        }
        else
        {
            // Eteint toutes les LED
            PORTC &= ~(1 << LEDGREEN);
            PORTB &= ~(1 << LEDRED);
            PORTD &= ~(1 << LEDORANGE) & ~(1 << LEDBLUE);
            etat = 0;
            clignotements++;

            if (clignotements >= 3)
            {
                clignotements = 0;
                last_time = 0;
                return;  // Fin de l'animation
            }
        }
    }
    if (clignotements >= 3) {
        clignotements = 0;
        last_time = 0;
        return true;  // Animation terminée
    }
    return false;     // Animation pas finie
    
}


afficher_sequence va faire clignoter une à une les LED qui composent la séquence à reproduire à intervalle réguliers (250ms) en utilisant les fonctions allumer_led et eteindre_led, chaque couleur est représenté par un numéro et a un son associé.

afficher_defaite sert à afficher une petite animation en cas de défaite, elle est conçue de manière à ne pas bloquer le programme grâce à un timer dédié (voir partie 4)

4 - Gestion du son

Le son est la partie qui a été la plus difficile et encore maintenant il n'est pas fonctionnel, au départ nous jouons des notes avec des _delay_ms() pour définir leurs durées et il n'y avait pas de problème mais quand on a implémenté la logique de notre Simon les deux n'étaient pas compatible, étant donné que _delay_ms() est une fonction bloquant le jeu ne peut pas correctement s'exécuté et la gestion du son ainsi que du jeu ne pouvaient donc pas être fait en parallèle, pour palier ce problème nous avons conçu les fonctions avec des timers dédiés de manière à n'avoir aucune fonction bloquantes, ce qui permit d'avoir le code du son et celui du jeu exécuté en même temps, malheureusement cela n'a pas suffit à régler tous les problèmes.


Le fichier sound.c contient des fonctions permettant de générer les différents sons qui sont appelés dans notre programme principal

5 - Communication USB

Notre jeu du Simon peut également être joué depuis un PC sur lequel est branché la carte via un script python qui communique avec celle-ci, cela est rendu possible grâce à la bibliothèque LUFA qui nous a permit de créer deux endpoints: un pour l'input et un pour l'output.

Côté carte

void EVENT_USB_Device_ControlRequest(void)
{
    if ((USB_ControlRequest.bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_VENDOR | REQREC_DEVICE)) &&
        (USB_ControlRequest.bRequest == 0x01))
    {
        Endpoint_ClearSETUP();

        while (!(Endpoint_IsOUTReceived()));
        uint8_t bouton = Endpoint_Read_8();
        Endpoint_ClearOUT();

        while (!(Endpoint_IsINReady()));
        Endpoint_ClearIN();

        if (user_play)
        {
            if (bouton == sequence[index_user])
            {
                index_user++;

                if (index_user == longueur)
                {
                    SendResultToHost(1);  // Séquence réussie
                    index_user = 0;
                    add_color = true;
                    user_play = false;
                }
                else
                {
                    SendResultToHost(2);  // Bouton correct mais séquence pas finie
                }
            }
            else
            {
                SendResultToHost(0);      // Mauvais bouton
                index_user = 0;
                longueur = 0;
                add_color = true;
                user_play = false;
                defaite_en_cours = true;
            }
        }
        else
        {
            SendResultToHost(0xAA); // Pas en phase de jeu
        }
    }
}


Cette fonction va lire une entrée reçue (le bouton sur lequel l'utilisateur a appuyé) et le "jouer", en retour on envoie à l'ordinateur le résultat.

Côté ordinateur

import usb.core
import usb.util
import tkinter as tk

# Recherche du périphérique USB
dev = usb.core.find(idVendor=0x9999, idProduct=0x2048)
if dev is None:
    raise ValueError('Device not found')

if dev.is_kernel_driver_active(0):
    dev.detach_kernel_driver(0)

dev.set_configuration()

# Cherche l'endpoint Bulk IN
cfg = dev.get_active_configuration()
interface = cfg[(0, 0)]

endpoint_in = None
for ep in interface:
    if usb.util.endpoint_direction(ep.bEndpointAddress) == usb.util.ENDPOINT_IN:
        endpoint_in = ep
        break

if endpoint_in is None:
    raise ValueError("Endpoint IN non trouvé")

# Initialisation de Tkinter
root = tk.Tk()
root.title("Simon Game")

colors = ['green', 'red', 'orange', 'blue']
buttons = []

frame = tk.Frame(root)
frame.pack(padx=20, pady=20)

# Variables de score
current_score = 0
high_score = 0

# Création des labels pour afficher les scores
score_label = tk.Label(root, text=f"Score: {current_score}", font=("Helvetica", 14))
score_label.pack(pady=10)

high_score_label = tk.Label(root, text=f"High Score: {high_score}", font=("Helvetica", 14))
high_score_label.pack(pady=10)


# Fonction appelée quand on clique un bouton
def on_button_click(index):
    global current_score, high_score
    print(f"[Python] Bouton {colors[index]} appuyé !")

    try:
        # Envoyer l'index du bouton appuyé au périphérique USB
        dev.ctrl_transfer(
            bmRequestType=0x40,
            bRequest=0x01,
            wValue=0,
            wIndex=0,
            data_or_wLength=[index]
        )
        print("[Python] Envoyé au device.")

        # Lire la réponse du périphérique
        data = dev.read(endpoint_in.bEndpointAddress, endpoint_in.wMaxPacketSize, timeout=1000)
        print(f"[Python] Réponse du device: {data[0]:02X}")
        
        if data[0] == 1:
            # Bonne réponse
            current_score += 1
            if current_score > high_score:
                high_score = current_score
            score_label.config(text=f"Score: {current_score}")
            high_score_label.config(text=f"High Score: {high_score}")
            print(f"[Python] Correct! Score actuel: {current_score}, Score max: {high_score}")
        elif data[0] == 0:
            # Mauvaise réponse
            print(f"[Python] Mauvais! Ton score final était {current_score}.")
            current_score = 0  # Réinitialiser le score
            score_label.config(text=f"Score: {current_score}")
            print("[Python] Réinitialisation du score.")
        else:
            print("[Python] Réponse inconnue")

    except usb.core.USBTimeoutError:
        print("[Python] Timeout de lecture USB")
    except Exception as e:
        print(f"[Python] Erreur USB: {e}")

# Création des 4 boutons
for i, color in enumerate(colors):
    btn = tk.Button(frame, bg=color, width=10, height=5, command=lambda i=i: on_button_click(i))
    btn.grid(row=i//2, column=i%2, padx=10, pady=10)
    buttons.append(btn)

root.mainloop()

Ce script python va se connecter à notre carte via la librairie pyusb en prenant ses identifiants, va localiser ses endpoints et va pouvoir communiquer avec notre carte, il crée ensuite une application via tkinter représentant les 4 boutons de notre jeu du Simon

Démonstrations


Démonstration sans connexion PC


Démonstration avec connexion PC

Rendus

Projet KiCAD : Fichier:I2L-2024-Carte-G1.zip

Programmes :