I2L 2025 Groupe2
Proposition de système (étudiants)
Guitar Tuner est un projet dont l’objectif, comme son nom l’indique, est de faciliter l’accordage des cordes d’une guitare.
Son principe est simple : lorsqu’une corde est jouée, le microcontrôleur capte le son produit à l’aide d’un microphone, analyse sa fréquence, puis affiche sur l’écran la note correspondante.
Une fréquence étant une donnée continue, on affiche par défaut la note la plus proche de la fréquence mesurée.
Comme piste d’amélioration, on pourrait aller plus loin en proposant un affichage plus intuitif :
- afficher la note au centre de l’écran lorsque la fréquence est correcte (ou très proche de la note visée) ;
- déplacer la note légèrement vers la gauche si la fréquence est inférieure à la note attendue ;
- déplacer la note vers la droite si la fréquence est supérieure.
Ainsi, plus la note affichée se rapproche du centre de l’écran, plus l’accord est précis.
Ce principe permettrait également d’accorder plusieurs cordes, et ce, dans différents accordages différent (standart, ré...).
Le circuit se compose des éléments suivants :
- Un écran LCD pour afficher la note détectée.
- Un microphone pour capter la fréquence produite par la corde.
- Une interface USB assurant la communication avec un ordinateur.
- Une LED d’alimentation indiquant que le microcontrôleur est sous tension.
- Un bouton permettant d’activer l’enregistrement.
- Une LED d'action indiquant que la fonction d’enregistrement est activée.
Contre-proposition (intervenant)
OK pour la proposition. Comme périphérique USB vous implanterez un périphérique spécifique à votre projet (classe USB "vendeur") permettant à un ordinateur de récupérer les fréquences detectées en temps réel et de les afficher.
Votre objet doit pouvoir être autonome et marcher sur batterie.
Proposition définitive (étudiants)
Répartition du travail (étudiants)
Jules:
- Mise en place de la bibliothèque permettant le fonctionnement de l’écran LCD
- Analyse du signal sonore
- Conversion des fréquences en notes musicales
- Ajustement fin des paramètres (fine-tuning) et tests du projet fina
Clément:
- Initialisation du projet, de l’environnement de travail collaboratif et du code source
- Création et mise en place de la bibliothèque permettant l’utilisation du microphone
- Développement de la boucle principale du programme et des différentes fonctionnalités.
- Tests du microcontrôleur en fonctionnement sur batterie
Jules & Clément:
- Installation et configuration de la bibliothèque LUFA pour la communication USB
- Développement du script Python permettant la réception et l’affichage des notes sur ordinateur
Carte
Schéma initial (étudiants)
- schéma (projet KiCAD) : Fichier:I2L-2025-Carte-G2.zip
Carte routée (intervenant)
Vous utiliserez la carte avec batterie.
Composants (intervenant)
Il manque au 10 octobre 2025, un connecteur femelle 16 broches.
Carte réalisée (intervenant)
La carte est entiérement soudée à l'exception d'un connecteur 16 broches pour l'écran.
Au 15 octobre 2025 la carte est entiérement soudée.
Travaux (étudiants)
Le programme développé constitue un accordeur de guitare.
Branché sur un ordinateur ou sur batterie, le microcontrôleur s’allume. On peut apercevoir une LED qui clignote sur toute la durée de fonctionnement du microcontrôleur.
L’écran s’initialise ensuite et affiche « Initializing screen ». Lorsqu’il a fini de s’initialiser, il affiche « Not capturing ».
Lorsque l’on appuie sur le bouton, une deuxième LED s’allume jusqu’à la fin du traitement. L’écran affiche « Capturing… » et le microphone capte le son pendant quelques dixièmes de seconde.
Ensuite, l’écran affiche le volume, la fréquence et la note sous le format suivant :"V:%3d F:%4uHz N:%s"
exemple : V:124 F:294Hz N:D4
Le système envoie la note, par exemple D4, via le descripteur USB au PC branché, si présent.
Après deux secondes, l’écran affiche de nouveau « Not capturing ».
Limitations:
Dû aux limites matérielles du microcontrôleur, spécifiquement au niveau de la RAM & du microphone, les résultats sont parfois brouillé à cause du bruit ambiant et du temps de récupération du son trop court pour avoir des résultats cohérents à tout moment.
Notre implémentation fonctionne correctement sur notre microcontrôleur avec de la pratique pour un sous-ensemble de notes principalement de la 3e octave.
Il faut, comme démontré dans la vidéo, appuyer sur le moment juste avant de jouer la note pour avoir le plus de chance que l'analyse se passe correctement.
De plus, pour certaines notes, on peut parfois apercevoir un petit décalage -> un ré3 va parfois afficher do3# (ce sont des fréquences très proches.)
Tests:
Les tests sont effectués à partir de vidéo de note de guitare filmée sur un iPhone 13. La guitare est une Yamaha CG192C de 2012.
Utiliser des vidéos de notes permet de faciliter les tests.
La guitare a été accordée avant de filmer les vidéos.
Extraits significatifs de code (étudiants)
1. Boucle principale et architecture logicielle
📁 guitar-tuner/lufa-LUFA-210130-NSI/I2L/Minimal/main.c
La boucle principale de l'application repose sur une logique simple qui gère l’ensemble des interactions entre l’utilisateur, le matériel et l'algorithme de traitement du signal.
Lorsque l’utilisateur appuie sur le bouton, le microcontrôleur capture un bloc d’échantillons audio, analyse le signal pour en extraire la fréquence fondamentale, convertit cette fréquence en note musicale, puis affiche le résultat sur l’écran LCD.
La fonction suivante illustre cette logique:
void loop_guitar_tuner(char *note_buf)
{
if (!(PINC & (1 << BTN2))) // Bouton appuyé (actif à l'état bas)
{
display_message("Capturing");
collect_audio_samples();
int16_t volume = get_max_sample();
float frequency = detect_frequency();
frequency_to_note(frequency, note_buf);
char buffer[32];
snprintf(buffer, sizeof(buffer),
"V:%d F:%uHz %s",
volume, (uint16_t)frequency, note_buf);
display_message(buffer);
_delay_ms(800);
display_message("Not capturing");
}
}
2. Acquisition du signal audio et contraintes matérielles
📁 guitar-tuner/lufa-LUFA-210130-NSI/I2L/Minimal/adc/adc.c
📁 guitar-tuner/lufa-LUFA-210130-NSI/I2L/Minimal/sound_analysis.h
📁 guitar-tuner/lufa-LUFA-210130-NSI/I2L/Minimal/sound_analysis.c
Le signal audio provient d’un microphone électret amplifié, dont la sortie est centrée autour de la tension médiane de l’ADC (environ 2,5 V). Le convertisseur du microcontrôleur fournit une valeur sur 10 bits, comprise entre 0 et 1023.
L’acquisition est réalisée à une fréquence d’échantillonnage fixée à 4096 Hz, valeur suffisante pour couvrir le spectre utile de la guitare (jusqu’à environ 1,3 kHz pour la corde Mi aiguë).
Définition des hyper paramètres:
#ifndef SOUND_ANALYSIS_H
#define SOUND_ANALYSIS_H
#include <stdint.h>
#define SAMPLE_COUNT 1024
#define DESIRED_FS 4096
#define GTR_LOW 82 // Hz d2
#define GTR_HIGH 659 // Hz e5
void collect_audio_samples(void);
float detect_frequency(void);
int16_t get_max_sample(void);
#endif
La fonction permettant de récupérer des samples audios: Le centrage immédiat du signal autour de zéro est une étape essentielle. Sans cette opération, la composante continue dominerait le calcul de corrélation et fausserait la détection de la période fondamentale.
void collect_audio_samples(void)
{
const uint16_t prescaler = 128;
const float adc_clk = (float)F_CPU / prescaler;
const float conv_us = 13.0f * 1e6f / adc_clk;
const float period_us = 1e6f / DESIRED_FS;
uint16_t delay_us = (uint16_t)(period_us - conv_us - 2);
if (delay_us < 1)
delay_us = 1;
for (int i = 0; i < SAMPLE_COUNT; i++) {
ADCSRA |= (1 << ADSC);
while (ADCSRA & (1 << ADSC));
samples[i] = (int16_t)ADC - 512; // centrage autour de zéro
_delay_us(delay_us);
}
}
3. Analyse du signal sonore : choix de l’autocorrélation
📁 guitar-tuner/lufa-LUFA-210130-NSI/I2L/Minimal/sound_analysis.c
3.1 Justification de l’algorithme
Dans le cadre de ce projet, plusieurs méthodes de détection de fréquence ont été envisagées, notamment la Transformée de Fourier Rapide (FFT). Toutefois, l’utilisation d’une FFT sur le microcontrôleur présente plusieurs inconvénients majeurs : consommation mémoire élevée, nécessité de calculs complexes en nombres flottants, et sensibilité accrue aux harmoniques de la guitare.
Lors de notre implémentation, nous avons eu plusieurs soucis notamment lié au manque de RAM.
L’autocorrélation a donc été utilisée car elle permet d’extraire directement la période fondamentale d’un signal quasi périodique, même en présence d’harmoniques. Le principe consiste à comparer le signal avec une version retardée de lui-même et à mesurer la similarité pour différents décalages temporels. Le décalage maximisant cette similarité correspond à la période du signal.
float detect_frequency(void)
{
// Suppression de la composante continue
int32_t mean = 0;
for (int i = 0; i < SAMPLE_COUNT; i++)
mean += samples[i];
mean /= SAMPLE_COUNT;
for (int i = 0; i < SAMPLE_COUNT; i++)
samples[i] -= (int16_t)mean;
// Bornes des retards (lags)
uint16_t min_lag = DESIRED_FS / GTR_HIGH;
uint16_t max_lag = DESIRED_FS / GTR_LOW;
if (max_lag >= SAMPLE_COUNT)
max_lag = SAMPLE_COUNT - 1;
int32_t best_corr = 0;
uint16_t best_lag = 0;
for (uint16_t lag = min_lag; lag <= max_lag; lag++) {
int32_t corr = 0;
for (uint16_t i = 0; i < SAMPLE_COUNT - lag; i++) {
corr += (int32_t)samples[i] * samples[i + lag];
}
if (corr > best_corr) {
best_corr = corr;
best_lag = lag;
}
}
if (best_lag == 0)
return 0.0f;
return (float)DESIRED_FS / (float)best_lag;
}
Les bornes min_lag et max_lag sont calculées à partir des fréquences extrêmes de la guitare. Cette restriction est cruciale : elle évite que l’algorithme ne sélectionne des maxima correspondant à des harmoniques ou à du bruit basse fréquence. Le choix de ces bornes améliore significativement la stabilité de la détection et s'assure que les notes affichées sont des notes jouable à la guitare.
4. Conversion fréquence → note musicale
📁 guitar-tuner/lufa-LUFA-210130-NSI/I2L/Minimal/sound_analysis.c
La conversion d’une fréquence en note musicale repose sur le système tempéré occidental, dans lequel l’octave est divisée en 12 demi-tons logarithmiquement espacés. La référence universelle est le A4 (LA) à 440 Hz.
Il est important de noter que le tableau des notes commence par C (Do) et non par A (La). Cette convention n’est pas arbitraire : elle découle de l’organisation historique des octaves dans la notation scientifique, où chaque octave commence à Do. Ainsi, C4 correspond au Do central du piano, tandis que A4 (440 Hz) se situe au milieu de cette octave.
En notation française, C correspond à Do, D à Ré, E à Mi, etc. L’ordre C–D–E–F–G–A–B est donc la traduction directe de Do–Ré–Mi–Fa–Sol–La–Si.
Dans le cadre de guitar_tuner, nous avons décidé d'utiliser la notation anglo-saxonne, car la plupart des partitions sur internet et la plupart des outils d'accordement utilise cette notation.
void frequency_to_note(float freq, char *note)
{
if (freq <= 0.0f) {
strcpy(note, "---");
return;
}
float semitones = 12.0f * log2(freq / 440.0f);
int midi = (int)(69 + semitones + 0.5f);
const char *note_names[] = {
"C","C#","D","D#","E","F","F#","G","G#","A","A#","B"
};
int octave = (midi / 12) - 1;
sprintf(note, "%s%d", note_names[midi % 12], octave);
}
Cette méthode prend en entrée la fréquence & insert dans le deuxième paramètre la note & l'octave correspondant.
5. LUFA et architecture USB bas niveau
📁 guitar-tuner/lufa-LUFA-210130-NSI/I2L/Minimal/descriptors.c
LUFA permet d’implémenter un périphérique USB directement sur le microcontrôleur AVR. Le cœur de ce mécanisme repose sur les descripteurs USB, structures de données décrivant précisément l’appareil au système hôte.
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,
.Endpoint0Size = FIXED_CONTROL_ENDPOINT_SIZE,
.VendorID = 0x4242,
.ProductID = 0x0001,
};
Ce descripteur indique au PC qu’il s’agit d’un périphérique USB personnalisé (Vendor Specific). Lors de l’énumération, le système d’exploitation lit ces informations pour charger un pilote générique et autoriser la communication. Le descripteur de configuration définit ensuite les endpoints utilisés pour l’échange de données.
.InEndpoint =
{
.Header = {.Size = sizeof(USB_Descriptor_Endpoint_t),
.Type = DTYPE_Endpoint},
.EndpointAddress = IN_EPADDR,
.Attributes = EP_TYPE_BULK,
.EndpointSize = IN_EPSIZE,
}
6. Boucle principale LUFA
📁 guitar-tuner/lufa-LUFA-210130-NSI/I2L/Minimal/Minimal.c
int main(void)
{
SetupHardware();
GlobalInterruptEnable();
init_guitar_tuner();
char note[6] = {0};
for (;;)
{
USB_USBTask();
loop_guitar_tuner(note);
if (note[0] != '\0')
{
USB_Manage_In(note);
note[0] = '\0';
}
}
}
Cette boucle illustre l’intégration entre l’application et la pile USB. La fonction USB_USBTask() permet à LUFA de gérer les événements USB (requêtes, transferts, états). Le reste du temps processeur est dédié à l’application métier. Cette cohabitation maîtrisée garantit que la communication USB reste fonctionnelle sans perturber l’analyse du signal.
7. Communication USB côté hôte : réception des notes via LUFA
📁 guitar-tuner/lufa-LUFA-210130-NSI/I2L/Minimal/receive.py
Le projet ne se limite pas au microcontrôleur : une partie essentielle est la communication avec l’ordinateur. Le microcontrôleur, grâce à LUFA, se présente comme un périphérique USB de type Vendor Specific. Le script Python receive.py agit comme client USB côté hôte et permet de lire directement les données envoyées par l’AVR sans pilote spécifique.
import usb.core
import usb.util
import time
ID_VENDOR = 0x4242
ID_PRODUCT = 0x0001
ENDPOINT_IN = 0x81
def read_guitar_note(device):
try:
data = device.read(ENDPOINT_IN, 6, 50)
# Convert raw bytes to string, stop at first null byte
note_str = bytes(data).split(b'\x00')[0].decode('ascii', errors='ignore')
return note_str
except usb.core.USBError:
return None
if __name__ == "__main__":
dev = usb.core.find(idVendor=ID_VENDOR, idProduct=ID_PRODUCT)
assert dev is not None
print("Ecoute en cours...")
try:
if dev.is_kernel_driver_active(0):
dev.detach_kernel_driver(0)
except (NotImplementedError, usb.core.USBError):
pass
while True:
if dev is None:
break
note = read_guitar_note(dev)
if note:
print(f"Note: {note}")
Les identifiants Vendor ID et Product ID correspondent exactement à ceux définis dans le descripteur USB LUFA du microcontrôleur. Cette correspondance permet au script de retrouver le bon périphérique parmi tous ceux connectés au PC.
Une fois le périphérique détecté, le script s’attache explicitement à l’interface USB. Sur certains systèmes, le noyau peut déjà avoir revendiqué l’interface ; il est alors nécessaire de le détacher afin d’autoriser une communication directe via le paquet PyUSB.
Le format texte permet d’éviter toute complexité de décodage et de faciliter le débogage.
Démonstration (étudiants)
Voici une vidéo qui montre guitar_tuner. Avec deux enregistrements de la guitare, nous affichons les notes correspondantes sur le microcontroller & avec LUFA sur le terminal de l'ordinateur.
La première note est un Si (B3) qui est celui de la 2ème corde de la guitare à vide. La deuxième note est un Sol (F3) qui est celui de la 3ème corde de la guitare à vide.
Rendus (étudiants)
Projet KiCAD : Fichier:I2L-2025-Carte-G2-final.zip
Programmes :
- microcontrôleur : Fichier:I2L-2025-Programmes-uC-G2.zip
- ordinateur Fichier:I2L-2025-Programmes-PC-G2.zip
Projet final:
- Code source: Fichier:Code-source-guitar-tuner.zip