« I2L 2023 Groupe2 » : différence entre les versions

De wiki-se.plil.fr
Aller à la navigation Aller à la recherche
 
(3 versions intermédiaires par le même utilisateur non affichées)
Ligne 365 : Ligne 365 :


=== Fonctionnement général de l'appareil ===
=== Fonctionnement général de l'appareil ===
[[Fichier:Fonctionnement du détecteur.mov|vignette|Fonctionnement du détecteur de bruit|néant|0x0px]]
[[Fichier:Fonctionnement du détecteur.mov|vignette|Fonctionnement du détecteur de bruit|néant|alt=Fonctionnement du détecteur de bruit]]
[[Fichier:Fonctionnement de l'appareil sur batterie.mov|néant|vignette|Fonctionnement de l'appareil sur batterie]]


=== Fonctionnement du programme PC ===
=== Fonctionnement du programme PC ===
TODO
[[Fichier:Fonctionnement programme PC - ILL 2023 Groupe 2.mp4|néant|vignette|Fonctionnement du programme PC]]


= Rendus =
= Rendus =

Version actuelle datée du 19 octobre 2023 à 09:32

Proposition de système

Fonctionnement

Nous proposons un appareil de détection de bruit en fonction du volume sonore. L'appareil allumera un certain nombre de LED selon plusieurs paliers pour un maximum de 7 LEDs. Si toutes les LEDs sont allumées, c'est-à-dire si le seuil maximal (en décibels) est atteint, un son est émis tant que le bruit ne redescend pas sous ce seuil.

L'appareil est basé sur 3 seuils (en décibels) enregistrés au niveau de la mémoire interne du micro-controlleur, et sont configurables via un programme sur ordinateur. Ce système doit, en outre, pouvoir fonctionner sur batterie pour une utilisation sans connexion à un ordinateur.

Les LEDs peuvent s'allumer en vert, jaune ou rouge en fonction des seuils atteints.

Matériel et pièces nécessaires

Pour fonctionner, notre appareil de détection de bruit aura besoin de :

  • 1 microcontrôleur ;
  • 1 microphone, permettant d'écouter le bruit ambiant ;
  • 1 haut-parleur, pour jouer le son d'alerte si le seuil maximal est atteint ;
  • 7 LED et résistances associées, pour afficher l'état du bruit et des seuils ;
  • 1 contrôleur USB permettant la configuration de l'appareil via une connexion à un ordinateur ;
  • 1 batterie, si possible, pour pouvoir utiliser l'appareil sans connexion à un ordinateur.

Contre-proposition

Le périphérique USB doit être de type spécifique (classe 0) et utiliser un point d'accès interruption, vous pouvez vous appuyer sur cet exemple LUFA de base : Média:LUFA_Minimal.zip.

Pour configurer les seuils sonores de la carte, vous écrirez une application avec la bibliothèque libusb.

Carte

I2L-2023-Carte3D-G2.png
I2L-2023-CarteSchema-G2.pdf

Seconde version pour améliorer l'amplification avant le haut-parleur.

I2L-2023-Carte3D-G2-v2.png
I2L-2023-CarteSchema-G2-v2.pdf

Fonctionnement

La carte lit le niveau de son ambiant, et allume un certain nombre de LEDs sur les 8 présentes en fonction de trois paliers de volumes paramétrables par l'utilisateur.

Les 8 LEDs s'activent à ces niveaux, en considérant les trois seuils A, B et C :en décibels

LED Seuil d'allumage
1
2
3
4
5
6
7
8

L'appareil est équipé d'un microphone couplé à un amplificateur pour écouter le son ambiant, et d'une sortie audio pour jouer un son à l'allumage et lorsque le seuil C est atteint.

Pour communiquer avec un ordinateur, la carte possède une sortie micro USB et est configurée pour être détectée comme telle par le PC.

Réalisation

Première approche

N'ayant pas de carte possédant un bouton physique, nous avons utilisé une carte d'un groupe de l'année précédente (cf. I2L 2022 Groupe5). Ce premier programme ayant pour but de nous familiariser avec les développement sur ce type de système embarqué, il permet simplement d'intéragir avec les LEDs en appuyant sur les boutons physiques.

#include <avr/io.h>

// Declarations
#define LED1_B_BIT 0
#define LED1_G_BIT 1
#define LED1_R_BIT 2

#define LED2_B_BIT 3
#define LED2_G_BIT 4
#define LED2_R_BIT 5

#define LED3_B_BIT 5
#define LED3_G_BIT 6
#define LED3_R_BIT 7

#define BUTTON1_BIT 2
#define BUTTON2_BIT 4

#define LED1_DDR DDRD
#define LED2_DDR DDRDun
#define LED3_DDR DDRC

#define BUTTON1_DDR DDRC
#define BUTTON2_DDR DDRC

#define BUTTON1_PORT PORTC
#define BUTTON2_PORT PORTC

#define BUTTON1_PIN PINC
#define BUTTON2_PIN PINC

/**
 * Initialisation des ports
 */
void init(void) {
    // Sortie pour la LED1
    LED1_DDR |= (1 << LED1_B_BIT);
    LED1_DDR |= (1 << LED1_G_BIT);
    LED1_DDR |= (1 << LED1_R_BIT);

    // Sortie pour la LED2
    LED2_DDR |= (1 << LED2_B_BIT);
    LED2_DDR |= (1 << LED2_G_BIT);
    LED2_DDR |= (1 << LED2_R_BIT);

    // Sortie pour la LED3
    LED3_DDR |= (1 << LED3_B_BIT);
    LED3_DDR |= (1 << LED3_G_BIT);
    LED3_DDR |= (1 << LED3_R_BIT);

    // Entrée pour le bouton 1
    BUTTON1_DDR &= ~(1 << BUTTON1_BIT);

    // Entrée pour le bouton 2
    BUTTON2_DDR &= ~(1 << BUTTON2_BIT);

    // Résistance de tirage pour le bouton 1
    BUTTON1_PORT |= (1 << BUTTON1_BIT);

    // Résistance de tirage pour le bouton 2
    BUTTON2_PORT |= (1 << BUTTON2_BIT);
}

/**
 * Main
 */
int main(void)
{
    init();

    while (1) {
        // Si le bouton 1 est appuyé
        if (BUTTON1_PIN & (1 << BUTTON1_BIT)) {
            // Allumer la LED1
            LED1_DDR &= ~(1 << LED1_B_BIT);
            LED1_DDR &= ~(1 << LED1_G_BIT);
            LED1_DDR &= ~(1 << LED1_R_BIT);

        } else {
            // Éteindre la LED1
            LED1_DDR |= (1 << LED1_B_BIT);
            LED1_DDR |= (1 << LED1_G_BIT);
            LED1_DDR |= (1 << LED1_R_BIT);
        }

        // Si le bouton 2 est appuyé
        if (BUTTON2_PIN & (1 << BUTTON2_BIT)) {
            // Allumer la LED2
            LED2_DDR &= ~(1 << LED2_B_BIT);
            LED2_DDR &= ~(1 << LED2_G_BIT);
            LED2_DDR &= ~(1 << LED2_R_BIT);

        } else {
            // Éteindre la LED2
            LED2_DDR |= (1 << LED2_B_BIT);
            LED2_DDR |= (1 << LED2_G_BIT);
            LED2_DDR |= (1 << LED2_R_BIT);
        }
    }
}

Programme LUFA

Boucle principale

Côté carte, le programme s'articule autour d'une boucle qui vérifie, à chaque itération, si l'endpoint de sortie USB est utilisé par le programme PC. Toutes les 10 000 itérations, on vérifie le volume ambiant; cela permet de ne pas surcharger la sortie audio ni de trop solliciter la carte.

int main(void) {
    CLKSEL0 = DEFAULT_CLKSEL0;   // External clock selection
    CLKSEL1 = DEFAULT_CLKSEL1;   // 8Mhz minimum
    CLKPR = DEFAULT_CLKPR;       // Modification of the clock divider (CLKPCE = 1)
    CLKPR = 0;                   // 0 to disable the divider (divisor of 1)
    MCUCR |= (1 << JTD);           // Disable JTAG
    
    SetupHardware();
    GlobalInterruptEnable();

    struct Connector leds[NUMBER_OF_LEDS];
    struct Connector speaker[DAC_SIZE];
    struct Connector microphone;
    struct Configuration config;

    config.level1_alert = eeprom_read_word((uint16_t*)LEVEL1_ALERT_ADDRESS);
    config.level2_alert = eeprom_read_word((uint16_t*)LEVEL2_ALERT_ADDRESS);
    config.level3_alert = eeprom_read_word((uint16_t*)LEVEL3_ALERT_ADDRESS);

    // Initialization
    init_elements(leds, microphone, speaker);

    // Play the welcome effect
    play_welcome(speaker, leds);
    _delay_ms(50);

    int i = 0;

    while (1) {
        i++;
        if (i> CYCLES) {
            read_sound(config, speaker, leds);
            i = 0;
        } else {
            USB_USBTask();
            read_usb(speaker);
        }
    }
}

Au démarrage :

  • La liste des composants utiles est initialisée (microphone, leds et sortie audio)
  • La configuration des seuils est chargée depuis la mémoire de l'EEPROM de la carte
  • Si tout est correct, un son de démarrage se joue et la boucle principale se lance

Lecture du volume ambiant

La lecture du volume se fait grâce à l'appel de la méthode ad_capture qui retourne, en décibels, une valeur entière. Avec cette valeur, on peut déterminer le volume en soustrayant 256 à la valeur retournée.

En fonction de cette valeur, on allume ou non les LEDs selon le tableau explicatif (voir partie Fonctionnement).

void read_sound(struct Configuration config , struct Connector *speaker, struct Connector *leds){
        ad_init(0);
        unsigned int db = MAX_INT - ad_capture();
        for (int j = 0; j < NUMBER_OF_LEDS; j++) {
            *leds[j].PORT &= ~(1 << leds[j].BIT);
        }

        if (db > config.level3_alert) {
            for (int i = 0; i < NUMBER_OF_LEDS; i++) {
                *leds[i].PORT |= (1 << leds[i].BIT);
            }
            play_note(speaker, SI, 1);
            play_note(speaker, LA, 1);
            _delay_ms(50);
        }
        if (db > (config.level3_alert - config.level2_alert) * 2 / 3 + config.level2_alert) {
            *leds[1].PORT |= (1 << leds[1].BIT);
        }
        if (db > (config.level3_alert - config.level2_alert) * 1 / 3 + config.level2_alert) {
            *leds[2].PORT |= (1 << leds[2].BIT);
        }
        if (db > config.level2_alert) {
            *leds[3].PORT |= (1 << leds[3].BIT);
        }
        if (db > (config.level2_alert - config.level1_alert) * 2 / 3 + config.level1_alert) {
            *leds[4].PORT |= (1 << leds[4].BIT);
        }
        if (db > (config.level2_alert - config.level1_alert) * 1 / 3 + config.level1_alert) {
            *leds[5].PORT |= (1 << leds[5].BIT);
        }
        if (db > config.level1_alert) {
            *leds[6].PORT |= (1 << leds[6].BIT);
        }
        if (db > config.level1_alert / 2) {
            *leds[7].PORT |= (1 << leds[7].BIT);
        }
}

Si le seuil maximal est atteint, il faut jouer un son. Pour ce faire, il faut faire vibrer la membrane du haut-parleur à une certaine fréquence (en Hertz) pendant un certain temps.

La période se calcule selon la formule:

Fréquence * (60 / Temps)) / Tempo

Avec le tempo égal à la vitesse d'exécution du mouvement.

Il suffit alors de récupérer la liste des fréquences des notes pour pouvoir jouer n'importe quelle mélodie.

void play_note(struct Connector *speaker, enum Note note, int seconds) {
    unsigned int period = (note * (60 / seconds)) / TEMPO;
    for (int i = 0; i < period / 2; i++) {
        for (int j = 0; j < DAC_SIZE; j++) {
            *speaker[j].PORT |= (1 << speaker[j].BIT);
        }

        make_note_delay(note);

        for (int j = 0; j < DAC_SIZE; j++) {
            *speaker[j].PORT &= ~(1 << speaker[j].BIT);
        }
        make_note_delay(note);
    }
}

Modification de l'EEPROM

Les trois seuils sont enregistrés dans l'EEPROM de la carte pour pouvoir être sauvegardées à chaque redémarrage de l'appareil.

Les trois addresses mémoires utilisées pour l'enregistrement dépendent de la taille des valeurs enregistrées, des uint16_t. La bibliothèque AVR permet de manipuler facilement l'EEPROM avec des méthodes simples:

// Ecriture d'une valeur dans l'EEPROM
eeprom_update_word((uint16_t*) ADDRESS, value);

// Lecture de cette valeur depuis l'EEPROM
eeprom_read_word((uint16_t*) ADDRESS);

Endpoint USB

Enfin, la lecture du flux OUT de l'endpoint se fait en vérifiant que des valeurs ont bien été reçues, puis on lit une par une les trois valeurs des seuils envoyées en mettant à jour l'EEPROM.

void read_usb(struct Connector *speaker){
    Endpoint_SelectEndpoint(ICHUT_OUT_EPADDR);

    if(Endpoint_IsOUTReceived()){
        if(Endpoint_IsReadWriteAllowed()){
            uint16_t stage1 = Endpoint_Read_16_LE();
            eeprom_update_word((uint16_t*)LEVEL1_ALERT_ADDRESS, stage1);

            uint16_t stage2 = Endpoint_Read_16_LE();
            eeprom_update_word((uint16_t*)LEVEL2_ALERT_ADDRESS, stage2);

            uint16_t stage3 = Endpoint_Read_16_LE();
            eeprom_update_word((uint16_t*)LEVEL3_ALERT_ADDRESS, stage3);
        }
    }
    Endpoint_ClearOUT();
}

Programme PC

Pour envoyer les seuils à la carte, la librairie libusb a été utilisée. Elle permet d'intéragir facilement avec la carte avec des méthodes simples pour transférer les données et effectuer la connexion. Après récupération de la saisie de l'utilisateur (trois seuils entre 0 et 255), on envoie les données en une seule fois dans un tableau.

void sendData(uint16_t noise_stage_1, uint16_t noise_stage_2, uint16_t noise_stage_3){
    if(nb_devices == 0){
        printf("No device found\n");
        return;
    }
    int out = devices[0].out[0].address;
    int size;

    uint16_t table[] = {noise_stage_1, noise_stage_2, noise_stage_3};

    int status = libusb_interrupt_transfer(devices[0].handle, out, (char*) &table, sizeof(uint16_t)*3, &size, 1000);
    if(status < 0){
        perror("sendData.libusb_interrupt_transfer");
        exit(-1);
    }
}

Compilation

Deux fichiers Makefile sont présents (un pour le programme LUFA, et un pour le programme PC) pour pouvoir compiler et envoyer les fichiers sur la carte.

Compilation du programme LUFA

make && make dfu

Compilation du programme PC

make

Pour l'exécution du programme PC :

sudo ./IchutPC

Note: après modification des valeurs, il faut redémarrer la carte.

Démonstrations

Fonctionnement général de l'appareil

Fonctionnement du programme PC

Rendus

Projet KiCAD : Fichier:I2L-2023-Carte-G2.zip

Lien Gitlab du projet LUFA/PC : https://gitlab.com/bapt.duquenne/systeme-embarques-m2i1l

Programmes LUFA/PC : Fichier:ILL-2023-Groupe2-Programmes-v2.zip