« I2L 2023 Groupe2 » : différence entre les versions
(Une version intermédiaire par le même utilisateur non affichée) | |||
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| | [[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 === |
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
Seconde version pour améliorer l'amplification avant le haut-parleur.
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