Pages: [1] 2   Go Down
Author Topic: Jouer du .wav avec Arduino  (Read 2671 times)
0 Members and 1 Guest are viewing this topic.
Near Paris in France
Offline Offline
Full Member
***
Karma: 0
Posts: 193
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


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 :

           C:\Users\JLB\Desktop>mode com5 baud=115200 parity=n data=8 stop=1 to=off xon=off odsr=off octs=off dtr=off rts=off idsr=off
puis
           C:\Users\JLB\Desktop>copy/b test01.wav com5

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  :

Code:
//-----------------------------------------------
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--;
      }
    }
  }
}


JLB
Logged

France
Offline Offline
Faraday Member
**
Karma: 55
Posts: 5347
Arduino Hacker
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Bonjour,

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 :
Code:
// Changement de l'horloge du timer 2
point = 0;
point+= 0xB1;
*point = *point & 0xF8;
*point = *point | 0x01;
Ce n'est absolument pas portable !

Code:
TCCR2B &= ~((1 << CS22) | (1 << CS21))
TCCR2B |= (1 << CS20);
Est beaucoup plus lisible et rend le code portable sur divers ATmega smiley-wink
Logged

Des news, des tuto et plein de bonne chose sur http://skyduino.wordpress.com !

Near Paris in France
Offline Offline
Full Member
***
Karma: 0
Posts: 193
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.


JLB
Logged

Offline Offline
Faraday Member
**
Karma: 34
Posts: 5088
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


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  smiley-mr-green
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 ?
Logged

Near Paris in France
Offline Offline
Full Member
***
Karma: 0
Posts: 193
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.

Peux tu me confimer ce point ?

JLB
Logged

Near Paris in France
Offline Offline
Full Member
***
Karma: 0
Posts: 193
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Artouste, le .ino figure depuis le début avec les exemples de .wav à l'url donnée.

http://balbarie.com/arduino/waves


JLB
Logged

France
Offline Offline
Faraday Member
**
Karma: 55
Posts: 5347
Arduino Hacker
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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 smiley-wink

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).

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.

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.
La meilleur doc -> le datasheet constructeur : http://www.atmel.com/Images/doc8271.pdf
Quelque exemples basique : http://www.arduino.cc/en/Reference/PortManipulation
Sinon pour avoir de vrai exemples d'utilisation de registre -> avrfreaks, tu ne trouvera pas grand chose du côte arduino vu que c'est "trop poussé".
Logged

Des news, des tuto et plein de bonne chose sur http://skyduino.wordpress.com !

Offline Offline
Faraday Member
**
Karma: 34
Posts: 5088
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Artouste, le .ino figure depuis le début avec les exemples de .wav à l'url donnée.

http://balbarie.com/arduino/waves


JLB


j'ai essayé de récupéré ça ce matin en buvant mon café  smiley-cool

les liens sur le  .ino et le .bat me renvoie une erreur 404  smiley-mr-green
Le .bat j'ai reconstitué (ça je sais encore faire !  smiley )
les .wav (au moins le 1er , ) c'est ok




Logged

France
Offline Offline
Faraday Member
**
Karma: 55
Posts: 5347
Arduino Hacker
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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 smiley-wink

Tu peut même faire un programme 100% avr-c sans loop() ni setup(), avec juste main() via l'ide arduino smiley-wink
Logged

Des news, des tuto et plein de bonne chose sur http://skyduino.wordpress.com !

France
Offline Offline
God Member
*****
Karma: 4
Posts: 971
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Bonjour
Merci jhelbi pour cet exemple.
Chaque méthode de 'jeu  de fichier Wav' a ses avantages et ses inconvénients.....

Nous avions eu sur ce forum des échanges concernant la librairie PCMaudio dans les fils suivants :
http://arduino.cc/forum/index.php/topic,72025.0.html
http://arduino.cc/forum/index.php/topic,74439.0.html
Logged

Near Paris in France
Offline Offline
Full Member
***
Karma: 0
Posts: 193
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.

Merci Skywodd pour les tuyaux. J'avais bien entendu l'intention d'écrire mon propre main() mais j'ai commencé par jouer un peu.

J'ai par ailleurs écrit un petit moniteur qui est disponible ici : http://balbarie.com/arduino/jlbmonitor.zip



Logged

Offline Offline
Faraday Member
**
Karma: 34
Posts: 5088
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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  smiley-mr-green
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  smiley-cool
Logged

Offline Offline
Faraday Member
**
Karma: 34
Posts: 5088
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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 !  smiley-mr-green

Code:
#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--;
      }
    }
  }
}
Logged

Near Paris in France
Offline Offline
Full Member
***
Karma: 0
Posts: 193
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Artouste j'ai la solution pour ta lecture sur SD.

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.

Code:
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();
    }
  }
}

Logged

Near Paris in France
Offline Offline
Full Member
***
Karma: 0
Posts: 193
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

La version avec tous les bugs corrigés est ici :

http://balbarie.com/arduino/waves

et ici :

Code:
byte buffer[128];
byte inP = 0;
byte ouT = 0;
byte count = 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;           // Désolé pour les puristes masi sur tous les ATMega les bits prescaler sont cadrés à droite
  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 &= 0x7F;
    count--;
  }
}

void loop()
{
byte value;

  for (;;)
  {
    // Gestion d'un buffer FIFO
    if (Serial.available())
    {
      value = Serial.read();
      noInterrupts();
      if (count < 128)
      {
        buffer[inP] = value;
        ++inP &= 0x7F;
        count++;
      }
      interrupts();
    }
  }
}


JLB
Logged

Pages: [1] 2   Go Up
Jump to: