Et oui avec juste quelques lignes de code on peut envoyer un fichier wave sur le port com Arduino et le synthétiser en interne sur une seule pin.
Le format exigé est du mono 8 bits PCM (non codé) échantilloné à 11025 Hz. On peut utilisez le freeware Audacity pour convertir des MP3. Je vous mets ici des exemples tout fait : http://balbarie.com/arduino/waves
Un haut-parleur doit être branché sur la sortie 3 avec une résistance en série (j'ai mis 100 ohms). L'Arduino doit être configuré à 115200 bauds.
En invite de commande vous pouvez envoyer un fichier ainsi :
J'ai joint avec les exemple un petit .bat qui exécute les deux commandes (il vous faudra sans doute y changer le N° du port com) :
C:\Users\JLB\Desktop>play test01.wav
Enfin voici la fonction :
//-----------------------------------------------
void playWave()
{
unsigned long previousTime = 0;
unsigned long currentTime = 0;
byte buffer[128];
byte count = 0;
byte inP = 0;
byte ouT = 0;
byte *point;
// Changement de l'horloge du timer 2
point = 0;
point+= 0xB1;
*point = *point & 0xF8;
*point = *point | 0x01;
for (;;)
{
// Gestion d'un buffer FIFO
if (Serial.available())
{
if (count < 128)
{
buffer[inP] = Serial.read();
inP = inP++ & 0x80;
count++;
}
}
// Respect de l'échantillonage à 11025 Hz
currentTime = micros();
if (currentTime >= (previousTime + 90))
{
previousTime = currentTime;
if (count > 0)
{
analogWrite(3, buffer[ouT]);
ouT = ouT++ & 0x80;
count--;
}
}
}
}
L'utilisation de micros() et analogWrite() pour faire de la lecture de sample audio PCM est une mauvaise idée ...
AnalogWrite() est une fonction très lente (en terme d'éxécution), et la fonction micros() est tout sauf précise.
La "vrai" solution consiste à utiliser deux timer, un en mode "Fast PWM" pour générer le son de maniére fluide, et un autre à 8KHz pour charger les échantillons sonore.
Un exemple est disponible sur le playground : http://arduino.cc/playground/Code/PCMAudio
De plus ceci est tout simplement immonde :
// Changement de l'horloge du timer 2
point = 0;
point+= 0xB1;
*point = *point & 0xF8;
*point = *point | 0x01;
Je sais bien mais on s'en fout ici puisque le débit est limité à 11025 Hz.
Le but était de faire un code très court et facilement lisible. C'est un exemple didactique pas une appli définitive.
Pour micros() c'est faux. Cette fonction est rapide (uniquement lecture d'un compteur, aucune init, aucun controle). De toute façon la précision de ce compteur est de 4 us pour un quartz 16 MHz.
analogWrite est lent mais on a le temps dans cet exemple. On travaille même à 11,025 KHz alors que dans ton exemple on est à 8KHz.
Pour l'écriture des registres j'ai du faire cela parce que cela ne fait qu'une semaine que je connais Arduino et je n'ai pas encore réussi à trouver la documentation sur les definitions des registres et n'ai vu que quelques exemples. Si tu sais où je peux les trouver je suis preneur.
jihelbi:
Pour l'écriture des registres j'ai du faire cela parce que cela ne fait qu'une semaine que je connais Arduino et je n'ai pas encore réussi à trouver la documentation sur les definitions des registres et n'ai vu que quelques exemples. Si tu sais où je peux les trouver je suis preneur.
JLB
bonjour jihelbi
Comme je suis curieux, mais aussi quelquefois un peu cossard, surtout le WE
je m'attendais à un .ino exemple directement compilable/exploitable.
Il est plus facile ensuite pour reutiliser/deriver de partir d'un "truc" de base qui fonctionne que l'inverse.
pourquoi ce choix de ne pas mettre un .ino (pde) compilable directement utilisable avec le serial et tes .Wav d'exemples ?
Finalement j'ai l'impression que l'implémentation Arduino utilise exactement les mêmes labels que ceux de la datasheet ATMega et propose même des masques pour chaque bit. Si c'est le cas j'ai vraiment perdu du temps à chercher ces définitions.
jihelbi:
Je sais bien mais on s'en fout ici puisque le débit est limité à 11025 Hz.
Certes, mais vouloir bien faire les choses même si ce n'est pas utile est toujours une bonne source de connaissances
jihelbi:
Pour micros() c'est faux. Cette fonction est rapide (uniquement lecture d'un compteur, aucune init, aucun controle). De toute façon la précision de ce compteur est de 4 us pour un quartz 16 MHz.
micros() pour maintenir une fréquence d'échantillonnage ça reste trés moyen, ça marche mais c'est pas trés propre.
De plus l'utilisation d'une interruption sur un timer rend le code beaucoup plus propre (avis perso).
jihelbi:
analogWrite est lent mais on a le temps dans cet exemple. On travaille même à 11,025 KHz alors que dans ton exemple on est à 8KHz.
8KHz c'est standard dans le domaine du sample PCM, vouloir faire plus avec des sample sur 8 bits ne donnera pas vraiment un meilleur rendu sonore.
jihelbi:
Pour l'écriture des registres j'ai du faire cela parce que cela ne fait qu'une semaine que je connais Arduino et je n'ai pas encore réussi à trouver la documentation sur les definitions des registres et n'ai vu que quelques exemples. Si tu sais où je peux les trouver je suis preneur.
j'ai essayé de récupéré ça ce matin en buvant mon café 8)
les liens sur le .ino et le .bat me renvoie une erreur 404
Le .bat j'ai reconstitué (ça je sais encore faire ! )
les .wav (au moins le 1er , ) c'est ok
jihelbi:
Finalement j'ai l'impression que l'implémentation Arduino utilise exactement les mêmes labels que ceux de la datasheet ATMega et propose même des masques pour chaque bit. Si c'est le cas j'ai vraiment perdu du temps à chercher ces définitions.
Le "langage arduino" n'est rien d'autre qu'une surcouche au langage C/C++ pour AVR donc tous ce que tu peut faire en C/C++ avr et utilisable avec arduino
Tu peut même faire un programme 100% avr-c sans loop() ni setup(), avec juste main() via l'ide arduino
jihelbi:
Désolé de réinventer la poudre mais cela m'amuse. J'ai découvert cette petite carte il y a une semaine et j'en suis tombé amoureux.
Oui , on se laisse vite prendre au jeu
finalement le rendu est tres acceptable, le code est simple et pour faire de l'annonce de base en utilisant une seule pin c'est intéressant.
étape suivante lire le(s) .wav à partir d'une sd card, je regarderais semaine prochaine...
... et puis quelqu'un qui apprécie (entre autres) P Desproges ne peut pas fondamentalement être mauvais 8)
bonjour
test rapide avec une sd (sur eth shield), le test05.wav de jihelbi
c'est fonctionnel , mais la restitution est "chevrotante" , je pense que cela est du au remplissage/vidage du buffer par la lecture de la sd.
mais pour un dimanche ça suffit !
#include <SD.h>
// On the Ethernet Shield, CS is pin 4. Note that even if it's not
// used as the CS pin, the hardware CS pin (10 on most Arduino boards,
// 53 on the Mega) must be left as an output or the SD library
// functions will not work.
const int chipSelect = 4;
void setup()
{
pinMode(10, OUTPUT);
}
void loop()
{
unsigned long previousTime = 0;
unsigned long currentTime = 0;
byte buffer[128];
byte count = 0;
byte inP = 0;
byte ouT = 0;
byte *point;
// Changement de l'horloge du timer 2
point = 0;
point+= 0xB1;
*point = *point & 0xF8;
*point = *point | 0x01;
if (!SD.begin(chipSelect)) {
//Serial.println("Card failed, or not present");
// don't do anything more:
return;
}
File dataFile = SD.open("test05.wav");
for (;;)
{
// Gestion d'un buffer FIFO
if (count < 128)
{
buffer[inP] = dataFile.read();
inP = inP++ & 0x80;
count++;
}
// Respect de l'échantillonage à 11025 Hz
currentTime = micros();
if (currentTime >= (previousTime + 90))
{
previousTime = currentTime;
if (count > 0)
{
analogWrite(3, buffer[ouT]);
ouT = ouT++ & 0x80;
count--;
}
}
}
}
Tout d'abord il y avait un bug dans ma gestion du buffer FIFO. je faisais (inP++ & 0x80) au lieu de faire (++inP & 0x80) et pareil pour ouT. Cela ne pouvait marcher qu'avec la fifo ne contenant qu'un seul octet.
Si tu réessayes maintenant cela devrait aller mieux.
Si cela ne suffit pas tu peux utiliser la lecture bufferisée de la librairie SD qui est annoncée beaucoup plus rapide. Pour cela utilise ce nouveau code fourni ci-dessous qui utilise une interruption périodique pour vider le buffer FIFO. Tu peux donc prendre ton temps pour remplir la FIFO après une lecture bufferisée de la carte.
byte buffer[128];
byte count = 0;
byte inP = 0;
byte ouT = 0;
void setup()
{
// Init Timer 1 pour interruptions périodiques à 11025 Hz
noInterrupts();
TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12); // Set CTC mode (Clear Timer on Compare Match)
TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10)); // ...
TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10); // No prescaler
OCR1A = 1451; // Set the compare register (16e6 Hz / 11025 Hz = 1451)
TIMSK1 |= _BV(OCIE1A); // Enable interrupt when TCNT1 == OCR1A
interrupts();
// Changement de l'horloge du timer 2 pour du 31250 Hz à la place du 488,28125 Hz Arduino
TCCR2B &= 0xF8;
TCCR2B |= 0x01;
analogWrite(3, 1); // Init du PWM par Arduino (pourquoi se compliquer la vie)
OCR2B = 0; // PWM à 0 surtout pas par analogWrite(3, 0) qui dévaliderait le PWM
Serial.begin(115200);
}
ISR(TIMER1_COMPA_vect)
{
if (count)
{
OCR2B = buffer[ouT];
ouT = ++ouT & 0x80;
count--;
}
}
void loop()
{
for (;;)
{
// Gestion d'un buffer FIFO
if (Serial.available())
{
noInterrupts();
buffer[inP] = UDR0;
inP = ++inP & 0x80;
count++;
interrupts();
}
}
}
Bonsoir Jhelbi
Merci, par pure et simple curiosité je regarderais demain. 8)
Avec humour et d'expérience, je n'aurais simplement jamais pu écrire cette phrase
Ce n'était qu'un euphémisme... L'informatique en elle même n'est finalement due qu'à un bug de la matière où quelques trous (appelées pompeusement zones déplétives) deviennent boulimiques d'électrons... ou pas... c'est selon...
jihelbi:
Ce n'était qu'un euphémisme... L'informatique en elle même n'est finalement due qu'à un bug de la matière où quelques trous (appelées pompeusement zones déplétives) deviennent boulimiques d'électrons... ou pas... c'est selon...