Mélodies avec un Arduino UNO

Voici un programme écrit par Pierre-Yves Rochat, professeur du MOOC sur les µcontrôleurs de l’EPFL qui montre comment utiliser les timers et les interruptions de l’Arduino UNO (ou du Diduino) pour jouer des mélodies.

Le fonctionnement du programme est décrit dans ce document :

https://d396qusza40orc.cloudfront.net/microcontroleurs/lecture_doc/MelodieDoc.pdf

Voir aussi la vidéo d’introduction au MOOC :

https://www.youtube.com/watch?v=wCgJRtzTvmI

/*
 * JOUER DES MÉLODIES SUR ARDUINO UNO
 *
 * Programme original de Pierre-Yves Rochat — pyr.ch
 * Source :
 * https://d396qusza40orc.cloudfront.net/microcontroleurs/lecture_doc/MelodieDoc.pdf
 *
 * Modifié par Nicolas Jeanmonod, décembre 2014
 *
 */





#define HPPin               PORTD1 // Le HP du LearnCBot est sur la pin PORTD1
#define HautParleurEnSortie DDRD  |= ( 1 << HPPin )
#define TicHautParleur      PORTD ^= ( 1 << HPPin )

// DURÉE DES NOTES
// La durée des notes est définie sur les 3 bits de poids forts. La Noire doit
// être définie à la valeur 0. Cela permet de ne pas devoir la spécifier
// explicitement dans les partitions. Les valeurs des autres durées sont sans
// importance.
#define TripleCroche 0b00100000
#define DoubleCroche 0b01000000
#define Croche       0b01100000
#define Noire        0b00000000
#define NoireP       0b10000000
#define Blanche      0b10100000
#define BlancheP     0b11000000
#define Ronde        0b11100000

#define tCr          DoubleCroche
#define dCr          DoubleCroche
#define Cr           Croche
#define Nr           Noire
#define Np           NoireP
#define Bl           Blanche
#define Blp          BlancheP
#define Ro           Ronde


#define Fin       0
#define Reprise   0xFE

#define DivTimer8 0b010


const unsigned int NotePeriode[] =
{
      4545,  4290,  4050,  3822,  3608,  3405,  3214,  3034,  2863,  2703,  2551,  2408,
      2273,  2145,  2025,  1911,  1804,  1703,  1607,  1517,  1432,  1351,  1276,  1204,
      1136,  1073,  1012,   956,   902,   851,   804,   758
};

const byte NoteFrequenceDiv8[] =
{
        28,    29,    31,    33,    35,    37,    39,    41,    44,    46,    49,    52,
        55,    58,    62,    65,    69,    73,    78,    82,    87,    92,    98,   104,
       110,   117,   123,   131,   139,   147,   156,   165
};

enum notes
{
       la1,  la1d,   si1,   do2,  do2d,   re2,  re2d,   mi2,   fa2,  fa2d,  sol2, sol2d,
       la2,  la2d,   si2,   do3,  do3d,   re3,  re3d,   mi3,   fa3,  fa3d,  sol3, sol3d,
       la3,  la3d,   si3,   do4,  do4d,   re4,  re4d,   mi4
};

unsigned int Pique;

byte MasqueDuree = 0b11100000;
byte MasqueNote  = 0b00011111;

byte* DebutMelodie;
byte* PtMelodie;
unsigned int PeriodesRestantes;

unsigned int PeriodeCourante;
unsigned int PeriodesOff;

byte NoteCourante;

byte FrereJacques[] =
{
do3+Cr,   re3+Cr,  mi3+Cr,   do3+Cr,  do3+Cr,  re3+Cr, mi3+Cr,  do3+Cr,
mi3+Cr,   fa3+Cr,  sol3,     mi3+Cr,  fa3+Cr,  sol3,
sol3+dCr, la3+dCr, sol3+dCr, fa3+dCr, mi3+Cr,  do3+Cr, sol3+dCr, la3+dCr, sol3+dCr, fa3+dCr, mi3+Cr, do3+Cr,
do3+Cr,   sol2+Cr, do3,      do3+Cr,  sol2+Cr, do3,

// Fin ou Reprise
Reprise
};

// https://books.google.ch/books?id=RCYakK7vmoEC&pg=PA459&lpg=PA459&dq=jingle+bells+garageband&source=bl&ots=Nxg1Yr-DCE&sig=4DhKrlQlvWNL8qNni7kGt5UcWss&hl=fr&sa=X&ei=H46YVI_eDoHlaLqqgYAF&ved=0CGIQ6AEwCQ#v=onepage&q=jingle%20bells%20garageband&f=false
byte JingleBells[]=
{

// Intro
fa3+Cr, fa3+Cr, fa3+Cr, fa3+Cr, fa3+Cr, mi3+Cr, mi3+Cr, mi3+dCr, mi3+dCr,
    sol3+Cr, sol3+Cr, fa3+Cr, re3+Cr, do3, sol3,

// Verse
sol2+Cr, mi3+Cr, re3+Cr, do3+Cr, sol2+Np, mi2+dCr, fa2+dCr,
    sol2+Cr, mi3+Cr, re3+Cr, do3+Cr, la2+Bl,
        la2+Cr, fa3+Cr, mi3+Cr, re3+Cr, si2+Bl,
            sol3+Cr, sol3+Cr, fa3+Cr, re3+Cr, mi3+Bl,
sol2+Cr, mi3+Cr, re3+Cr, do3+Cr, sol2+Bl,
    sol2+Cr, mi3+Cr, re3+Cr, do3+Cr, la2+Np, la2+Cr,
        la2+Cr, fa3+Cr, mi3+Cr, re3+Cr, sol3+Cr, sol3+Cr, sol3+Cr, sol3+Cr,
            la3+Cr, sol3+Cr, fa3+Cr, re3+Cr, do3, sol3,

// Chorus / Refrain
mi3+Cr, mi3+Cr, mi3, mi3+Cr, mi3+Cr, mi3,
    mi3+Cr, sol3+Cr, do3+Cr, re3+Cr, mi3+Bl,
        fa3+Cr, fa3+Cr, fa3+Cr, fa3+Cr, fa3+Cr, mi3+Cr, mi3+Cr, mi3+dCr, mi3+dCr,
            mi3+Cr, re3+Cr, re3+Cr, mi3+Cr, re3, sol3,
mi3+Cr, mi3+Cr, mi3, mi3+Cr, mi3+Cr, mi3,
    mi3+Cr, sol3+Cr, do3+Cr, re3+Cr, mi3+Bl,
        fa3+Cr, fa3+Cr, fa3+Cr, fa3+Cr, fa3+Cr, mi3+Cr, mi3+Cr, mi3+dCr, mi3+dCr,
            sol3+Cr, sol3+Cr,  fa3+Cr, re3+Cr, do3+Bl,

// Outro
fa3+Cr, fa3+Cr, fa3+Cr, fa3+Cr, fa3+Cr, mi3+Cr, mi3+Cr, mi3+Cr,
    sol3+Cr, sol3+Cr, fa3+Cr, re3+Cr, do3+Bl,

// Fin ou Reprise
Fin
};





/*
 *
 */
ISR( TIMER1_COMPA_vect )
{
    if( PeriodesRestantes == 0 )
    {
        NoteCourante = *PtMelodie;
        switch( NoteCourante )
        {
            case Fin:
                TIMSK1 &= ~( 1 << OCIE1A );
                return;
            case Reprise:
                PtMelodie    = DebutMelodie;
                NoteCourante = *PtMelodie;
                break;
            default:
                break;
        }
        PtMelodie++;
        PeriodeCourante   = NotePeriode[ NoteCourante & MasqueNote ];
        PeriodesRestantes = NoteFrequenceDiv8[ NoteCourante & MasqueNote ];

        NoteCourante     &= MasqueDuree;
        switch( NoteCourante )
        {
            case TripleCroche:  PeriodesRestantes *= 1;  break;
            case DoubleCroche:  PeriodesRestantes *= 2;  break;
            case Croche:        PeriodesRestantes *= 4;  break;
            case 0:             PeriodesRestantes *= 8;  break;
            case NoireP:        PeriodesRestantes *= 12; break;
            case Blanche:       PeriodesRestantes *= 16; break;
            case BlancheP:      PeriodesRestantes *= 24; break;
            case Ronde:         PeriodesRestantes *= 32; break;
        }
        PeriodesOff = ( PeriodesRestantes * Pique ) / 100;
    }
    OCR1A = TCNT1 + PeriodeCourante;
    if( PeriodesRestantes > PeriodesOff ){ TicHautParleur; }
    PeriodesRestantes--;
}





/*
 *
 */
void
InitMelodie()
{
    HautParleurEnSortie;
    TCCR1B  = ( DivTimer8 << CS10 ); // choix de la fréquence : 16 MHz / 8 = 2 MHz
    TIMSK1 |= ( 1 << OCIE1A );       // enclenche l’interrupt du timer
    sei();                           // activation générale des interruptions
}





/*
 *
 */
void
JoueMelodie( byte* melodie )
{
    DebutMelodie      = melodie;
    PtMelodie         = DebutMelodie;
    PeriodesRestantes = 0;
    InitMelodie();
}





/*
 *
 */
int
mainInit()
{
    // Nécessaire pour utiliser la pin PORTD1 sur le Diduino.
    Serial.end();

    // Toutes les pins de tous les ports en INPUT-PULLUP
     DDRB = 0b00000000;
    PORTB = 0b11111111;
     DDRC = 0b00000000;
    PORTC = 0b11111111;
     DDRD = 0b00000000;
    PORTD = 0b11111111;

    // Excepté la LED du board
     DDRB |= ( 1<<PORTB5 );
    PORTB &=~( 1<<PORTB5 );
}





/*
 *
 */
int
main()
{
    mainInit();
    Pique = 20;
    switch( 1 )
    {
        case 0:
            JoueMelodie( FrereJacques );
            break;
        case 1:
            JoueMelodie( JingleBells );
            break;
        default:
            break;
    }

    volatile unsigned int i;
    while( true )
    {
        // Clignote la LED du board
        for( i=0; i<65535; i++ ){}
        PORTB ^= ( 1<<PORTB5 );
    }
}