AVR-GCC. Μουσική (μελωδίες) και AVRs.

Οι περισσότεροι θα θυμάστε πριν μερικά χρόνια τον χαμό που γινόταν με τα κινητά της Nokia που μπορούσε κάποιος να συνθέσει όποια μελωδία ήθελε για ήχο κλήσης. Αυτό γινόταν με την RTTTL (Ring Tone Text Transfer Language). Μπορούμε να χρησιμοποιήσουμε έναν AVR για να διαβάσουμε τις μελωδίες στην μορφή αυτή; Η απάντηση είναι ναι.

Άρθρο της Wikipedia για το RTTTL.
RTTTL για Arduino.
Τεκμηρίωση του ATmega8.
Τεκμηρίωση του Buzzer.
Τεκμηρίωση του BC547.

Το RTTTL μας περιγράφει ποιες νότες χρησιμοποιεί η μελωδία, για πόσο χρόνο διαρκεί η κάθε νότα, τον ρυθμό κ.τ.λ. Κάθε νότα αντιστοιχεί σε κάποια συγκεκριμένη συχνότητα ήχου. Ο ποιο εύκολος τρόπος να παράγουμε συγκεκριμένες συχνότητες είναι με την χρήση ενός Timer/Counter σε λειτουργία PWM. Συγκεκριμένα σε λειτουργία Phase and Frequency Correct PWM. Η έξοδος της PWM οδηγεί ένα ηλεκτρομαγνητικό buzzer. Υπάρχουν πολλές σελίδες που μπορούμε να βρούμε μελωδίες σε RTTTL.

Αρχεία που περιέχουν μελωδίες σε RTTTL μορφή. Αρχείο1, Αρχείο2.

Τα αρχεία του προγράμματος: music.zip.

Στο αρχείο note.h υπάρχουν οι νότες σε αντιστοιχία με την συχνότητα.

#ifndef _NOTES_H
#define _NOTES_H

#define NOTE_B3  247
#define NOTE_C4  262
#define NOTE_CS4 277
#define NOTE_D4  294
#define NOTE_DS4 311
#define NOTE_E4  330
#define NOTE_F4  349
#define NOTE_FS4 370
#define NOTE_G4  392
#define NOTE_GS4 415
#define NOTE_A4  440
#define NOTE_AS4 466
#define NOTE_B4  494
#define NOTE_C5  523
#define NOTE_CS5 554
#define NOTE_D5  587
#define NOTE_DS5 622
#define NOTE_E5  659
#define NOTE_F5  698
#define NOTE_FS5 740
#define NOTE_G5  784
#define NOTE_GS5 831
#define NOTE_A5  880
#define NOTE_AS5 932
#define NOTE_B5  988
#define NOTE_C6  1047
#define NOTE_CS6 1109
#define NOTE_D6  1175
#define NOTE_DS6 1245
#define NOTE_E6  1319
#define NOTE_F6  1397
#define NOTE_FS6 1480
#define NOTE_G6  1568
#define NOTE_GS6 1661
#define NOTE_A6  1760
#define NOTE_AS6 1865
#define NOTE_B6  1976
#define NOTE_C7  2093
#define NOTE_CS7 2217
#define NOTE_D7  2349
#define NOTE_DS7 2489
#define NOTE_E7  2637
#define NOTE_F7  2794
#define NOTE_FS7 2960
#define NOTE_G7  3136
#define NOTE_GS7 3322
#define NOTE_A7  3520
#define NOTE_AS7 3729
#define NOTE_B7  3951

#endif

Το κυρίως πρόγραμμα.

#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include <stdlib.h>
#include "lcd.h"
#include "notes.h"


/*Η παρακάτω δήλωση ελέγχει αν στον compiler έχουμε βάλει την παράμετρο F_CPU.
  Αν όχι, την περνά. */

#ifndef F_CPU
    #warning "H F_CPU den exei oristei 8a parei thn timh 4000000"
    #define F_CPU 4000000UL  //  Use unsigned long
#endif

// Μακροεντολή για τον έλεγχο αν το byte
// αντιστοιχεί σε κάποιο ψηφίο (0~9).
#define isdigit(n) (n >= '0' && n <= '9')

// Καθορισμός οκτάβας.
#define OCTAVE_OFFSET 0

#define delay(x) _delay_ms(x)


void rtttl_parser(const char *);
void pwm_init(void);
void play(int);
void start(void);
void stop(void);


// Πίνακας με τις νότες.
int notes[] = { 0,
NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4,
NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5,
NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6,
NOTE_C7, NOTE_CS7, NOTE_D7, NOTE_DS7, NOTE_E7, NOTE_F7, NOTE_FS7, NOTE_G7, NOTE_GS7, NOTE_A7, NOTE_AS7, NOTE_B7
};

// Μερικές μελωδίες αποθηκευμένες στην μνήμη FLASH.
const char melody_1[] PROGMEM = "Mission Imp:d=16,o=6,b=95:32d,32d#,32d,32d#,32d,\
32d#,32d,32d#,32d,32d,32d#,32e,32f,32f#,32g,g,8p,g,8p,a#,p,c7,p,g,8p,g,8p,f,p,f#,\
p,g,8p,g,8p,a#,p,c7,p,g,8p,g,8p,f,p,f#,p,a#,g,2d,32p,a#,g,2c#,32p,a#,g,2c,a#5,8c,\
2p,32p,a#5,g5,2f#,32p,a#5,g5,2f,32p,a#5,g5,2e,d#,8d";

const char melody_2[] PROGMEM = "Indiana Jones:d=4,o=5,b=250:e,8p,8f,8g,8p,1c6,\
8p.,d,8p,8e,1f,p.,g,8p,8a,8b,8p,1f6,p,a,8p,8b,2c6,2d6,2e6,e,8p,8f,8g,8p,1c6,p,d6,\
8p,8e6,1f.6,g,8p,8g,e.6,8p,d6,8p,8g,e.6,8p,d6,8p,8g,f.6,8p,e6,8p,8d6,2c6";

const char melody_3[] PROGMEM = "Moonlight Sonata:d=4,o=5,b=70:8g#4,8c#,8e,8g#4,\
8c#,8e,8g#4,8c#,8e,8g#4,8c#,8e,8a4,8c#,8e,8a4,8c#,8e,8a4,8d,8f#,8a4,8d,8f#,8g#4,\
8c,8f#,8g#4,8c#,8e,8g#4,8c#,8d#,8f#4,8c,8d#,c#";



int main(void){

    // PB0 (OC1A) ως έξοδος.
    DDRB |= (1<<PB1);

    // Αρχικοποίση του LCD.
    lcd_init();
    lcd_clear();

    // Αρχικοποίση PWM.
    pwm_init();


    // Εκκίνηση της PWM λειτουργίας.
    start();

    // Ανάλυση των δεδομένων RTTTL.
    rtttl_parser(melody_1);

    // Σταμάτημα της PWM.
    stop();

    delay(1000);

    start();
    rtttl_parser(melody_2);
    stop();

    delay(1000);

    start();
    rtttl_parser(melody_3);
    stop();

    lcd_clear();
    lcd_str("That's it!");


    while(1){


    };

    return 0;
}


// Στην συνάρτηση αυτή ρυθμίζουμε τον
// ICR1 για την κατάλληλη συχνότητα.
void play(int frequency){


   /*
    Η συχνότητα για Phase & Frequency correct PWM
    είναι Fpwm = Fcpu/(2*Prescaler*TOP). Οπότε
    TOP = Fcpu/(2*Prescaler*Fpwm). Έχουμε ICR1=TOP,
    Prescaler 1 και Fcpu 4 MHz.
*/

    ICR1 = F_CPU /(2*frequency);


}

// Εκκίνηση του Timer/Counter1.
void start(){

    // Prescaler=1 και εκκίνηση.
    TCCR1B |= (1 << CS10);

}

// Σταμάτημα του Timer/Counter1.
void stop(){

    TCCR1B &= ~(1<<CS10);

}

void pwm_init(void){

    // Ρύθμιση του Duty Cycle.
    // Ρυθμίζοντας το OCR1A αλλάζουμε και
    // την ένταση του ήχου.
    OCR1A = 200;

    // Μη αναστρέφουσα λειτουργία.
    TCCR1A |= (1 << COM1A1);

    // Phase & Frequency Correct PWM
    // με TOP τον ICR1.
    TCCR1B |= (1 << WGM13);

}

void rtttl_parser(const char *p){

    // Στην συνάρτηση αυτή δεν γίνεται κανένας
    // για την ορθότητα των δεδομένων RTTTL.

    uint8_t i=0;
    char name[16],s[16];
    uint8_t default_dur = 4;
    uint8_t default_oct = 6;
    int bpm = 63;
    int num;
    long wholenote;
    long duration;
    uint8_t note;
    uint8_t scale;



    // Ανάγνωση του ονόματος της μελωδίας.
    while(pgm_read_byte(p) != ':') {
        name[i]=pgm_read_byte(p);
        i++;
        p++;
    };

    name[i]='\0';

    lcd_clear();
    lcd_str(name);
    lcd_setcursor(0,2);

    // Αγνόηση του ":".
    p++;

    // Ανάγνωση της διάρκειας.
    if(pgm_read_byte(p) == 'd'){

        // Αγνόηση του "d=".
        p++; p++;

        num = 0;
        while(isdigit(pgm_read_byte(p))){

            num = (num * 10) + (pgm_read_byte(p++) - '0');
        };

        if(num > 0) default_dur = num;

        // Αγνόηση του ",".
        p++;
    };


    // Ανάγνωση της οκτάβας.
    if(pgm_read_byte(p) == 'o'){

        // Αγνόηση του "ο=".
        p++; p++;
        num = *p++ - '0';
        if(num >= 3 && num <=7) default_oct = num;

        // Αγνόηση του ",".
        p++;
    };

    // Ανάγνωση του ρυθμού (BPM).
    if(pgm_read_byte(p) == 'b'){

        // Αγνόηση του "b=".
        p++; p++;
        num = 0;
        while(isdigit(pgm_read_byte(p))){
            num = (num * 10) + (pgm_read_byte(p++) - '0');
        };
        bpm = num;

        // Αγνόηση του ":".
        p++;
    };

    itoa(bpm,s,10);
    lcd_str("BPM: ");
    lcd_str(s);

    // Ο ρυθμός εκφράζει συνήθως τον αριθμό τετάρτων
    // της νότας ανά λεπτό. Εδώ είναι η διάρκεια της
    // ολόκληρης της νότας σε msec.
    wholenote = (60 * 1000L / bpm) * 4;



    while(pgm_read_byte(p)){

        // Ανάγνωση της διάρκειας της νότας
        // αν είναι διαθέσιμη.
        num = 0;
        while(isdigit(pgm_read_byte(p))){
            num = (num * 10) + (pgm_read_byte(p++) - '0');
        };

    if(num){
        duration = wholenote / num;

    } else {
         duration = wholenote / default_dur;
    };

    // Ανάγνωση της νότας.
    note = 0;

    switch(pgm_read_byte(p)){
        case 'c':
            note = 1;
            break;
        case 'd':
            note = 3;
            break;
        case 'e':
            note = 5;
            break;
        case 'f':
            note = 6;
            break;
        case 'g':
            note = 8;
            break;
        case 'a':
            note = 10;
            break;
        case 'b':
            note = 12;
            break;
        case 'p':
            default:
            note = 0;
    };

    p++;

    // Ανάγνωση του "#".
    if(pgm_read_byte(p) == '#'){
        note++;
        p++;
    };

    // Ανάγνωση του ".".
    if(pgm_read_byte(p) == '.'){
        duration += duration/2;
        p++;
    };

    // Ανάγνωση της κλίμακας.
    if(isdigit(pgm_read_byte(p))){
        scale = pgm_read_byte(p) - '0';
        p++;
    } else {
        scale = default_oct;
    };

    scale += OCTAVE_OFFSET;

    // Αγνόηση του ",".
    if(pgm_read_byte(p) == ',') p++;


    if(note){

        //Αναπαραγωγή της νότας.
        play(notes[(scale - 4) * 12 + note]);

        // Διάρκεια της νότας.
        delay(duration);

    } else{
      delay(duration);
    }
  }
}
Advertisements
This entry was posted in AVR, Electronics and tagged , , , , , , , , . Bookmark the permalink.

2 Responses to AVR-GCC. Μουσική (μελωδίες) και AVRs.

  1. JimL says:

    Καλησπέρα, πολυ χρησιμο blog! Εφτασα εδω ψαχνοντας για pwm avr και κατεληξα να διαβαζω διαφορα posts για ξεσκουριασω την μνημη μου στα τρανζιστορ :p. Anywayz, βλεποντας την ιδιαιτερη ενασχοληση με avr και pwm θα ηθελα να ρωτησω αν εχετε ασχοληθει με πρωτοκολλα επικοινωνιας pwm οπως το sae j1850 pwm?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s