/* Source code name: RingPlay.c Author: Manoel Gomes de Andrade (andrade.manoel@gmail.com) Processor: Microcontroller AT89S8252 Compiler: sdcc Parmeters: -V --vc --no-xinit-opt --use-stdout --model-small --debug Date: 2006-08-11 (yyyy-dd-mm) License: Postcardware (see Disclains) Description: This software interprets "RingTone Text Transfer Language" (RTTTL), wich was originally intended to Cell Phones, extracts frequency and a time duration for a particular note and sends it to a speaker correctly attached to a pin port. Timer 1 (TC1) is responsible for the square wave generation or the note sound. The principle is to load TH1,TL1 with a value so that the time to overflow should be half the period of frequency´s note and turn on and of the speaker at each overflow ocorrency. speaker on off on |--------------| |-------- ________| |_____________| | half-period | |<-- full period of note --> Timer 0 (TC0) manages the note duration. There is an Interrupt Service Routine for it to decrement a variable responsible by the duration and a flag that signal time is up (ended). For each iteration, where the square wave is generated, the flag is checked to see if it is time to get another note. Comments on RTTTL Specification Expressing it in words,the RTTTL Specification, says that a music must have a name with a maximum of ten characters, followed by a colon. After that it may have an optional field containing default values for duration, scale and beats-per-minute. The absence of any default values are automatically filled by default values, i.e., preset values and so another colom ends this field. After the colom follows a sequency o notes separated by commas. For each note may be particullar inforation about its particular duration and scale but if there isn´t the default are assumed. I must make a special comment about the relationship betweem beats-per-minute and duration found in the default field and the time-unit used in the entire music. Beats-per-miunute dictates the music´s rithm and must be converted to beats-per-second for more convinience. beats-per-second = beats-per-minute / 60 (eq.1) Beats-per-second is a measure of frequency at wich notes occurs, so the time or period of notes is: rithm-time = 60/beats-per-minute (eq.2) Suppose "beats-per-minute=60" wich gives "rithm-time = 1" or one note per second. But the the duration that appears in the default field stablilishes the full time figure. For instance, if "def-note-duration" "d=4", when it appears a note without duration or even, redundantly, with d=4 it will sounds for a full time. Lets demonstrate it: time-fraction = rthin-time * def-note-duration (eq.3) So "time-fraction = 4" or in words, duraton=4 is the full time or 1 second. From this point on, the interpreter can calculate the currente time for any note based on its particular duration and "dotted" punctuation. Below are shown the calculation for all possible durations when "beats-per-minute = 60" (too slow) and "def-note-duration = 4" duration = time-fraction / 1 = 4/1 = 4.000 s (seconds) duration = time-fraction / 2 = 4/2 = 2.000 s duration = time-fraction / 4 = 4/4 = 1.000 s duration = time-fraction / 8 = 4/8 = 0.500 s duration = time-fraction / 16 = 4/16 = 0.250 s duration = time-fraction / 32 = 4/32 = 0.125 s when a "dotted" found duration is multiplied by 1.5. That´s all folks ! If you wants to contacte me: e-mail: andrade.manoel@gmail.com (subject: RTTTL) Hardware I use a the 1/8 of ULN2803 (8 darlingtons with open collectors) to driver to driver the speaker (8 Ohms 1/4 Watt) with an 33o Ohm resistor in series. You can Vcc = +5V --- | -/| Speaker -\| | / \ / R = 330 Ohms \ | O (PIN 11 ULN 2803) | | / |/ AUDIO_OUTPUT O---|\ (1/8 ULN 2803) O (PIN 8 ULN 2803)| \ | } | (emmisor) (PIN 10 ULN 2803 = Vcc ) | O (PIN 9 ULN 2803) | ------ --- (GND internally conected) - References 1. RTTTL Specification: http://www.csoft.co.uk/ringtones/rtttl_rtx.htm (and others see google) 2. RTTTL Musics : http://nokiatone.ifrance.com/nokiatone/ (guess ?) Disclains: 1. All trademarks (MS-DOS, TURBO-C, RTTTL, and so on) belongs to its respective owners; 2. This is Postcardware, also called just cardware. The only condition to use this software or its ideas contained in it is that users send the author a postcard to the following address. Manoel Gomes de Andrade Avenida Anisio Silva, 1196 Jardim Soares CEP 14784-346 Barretos - SP Brazil 3. Sorry about the English 4. Be happy. 5. There is a warning message but the object code is succesfully generated " ../SDCC/include/malloc.h(46) : warning 187: ISO C90 does not support flexible array members" */ #include // Relative to Microcontroller used #include // UNIX like header #include // UNIX like header #include // UNIX like header #include // UNIX like header #define ONE_SECOND 100 // defines a constante for one second #define AUDIO_OUTPUT P3_0 // audio output pin #define AUX_LED P3_1 // auxliar led #define HBYTE(valor) ((valor >> 8) & 0xFF) #define LBYTE(valor) (valor) // Functions Prototypes int get_index_note(char t_rttl_n,char t_rttl_s,char t_rttl_o); void TC0_int(void) interrupt 1 using 1; void play(int n,int d); code unsigned int note_freq[]={ 0x0000, // silent 63441,63559,63670,63775,63874,63967,64055,64138,64217,64291,64360,64426, // A= 220Hz (o=4) 64489,64547,64603,64655,64705,64751,64795,64837,64876,64913,64948,64981, // A= 440Hz (o=5) 65012,65042,65069,65096,65120,65144,65166,65187,65206,65225,65242,65259, // A= 880Hz (o=6) 65274,65289,65303,65316,65328,65340,65351,65361,65371,65380,65389,65397, // A= 1.76KHz (o=7) 65405,65412,65419,65426,65432,65438,65443,65449,65454,65458,65463,65467 // A= 3.52KHz (o=8) }; // RTTL music collection code char *rtttl_music[]={ {"countdown:d=4,o=5,b=125:p,8p,16b,16a,b,e,p,8p,16c6,16b,8c6,8b,a,p,8p,16c6,16b,c6,e,p,8p,16a,16g,8a,8g,8f#,8a,g.,16f#,16g,a.,16g,16a,8b,8a,8g,8f#,e,c6,2b.,16b,16c6,16b,16a,1b"}, {"aadams:d=4,o=5,b=160:8c,f,8a,f,8c,b4,2g,8f,e,8g,e,8e4,a4,2f,8c,f,8a,f,8c,b4,2g,8f,e,8c,d,8e,1f,8c,8d,8e,8f,1p,8d,8e,8f#,8g,1p,8d,8e,8f#,8g,p,8d,8e,8f#,8g,p,8c,8d,8e,8f"}, {"ScoobyDoo:d=4,o=5,b=160:8e6,8e6,8d6,8d6,2c6,8d6,e6,2a,8a,b,g,e6,8d6,c6,8d6,2e6,p,8e6,8e6,8d6,8d6,2c6,8d6,f6,2a,8a,b,g,e6,8d6,2c6"}, {"The Simpsons:d=4,o=5,b=160:c.6,e6,f#6,8a6,g.6,e6,c6,8a,8f#,8f#,8f#,2g,8p,8p,8f#,8f#,8f#,8g,a#.,8c6,8c6,8c6,c6"}, {"Muppet:d=4,o=5,b=250:c6,c6,a,b,8a,b,g,p,c6,c6,a,8b,8a,8p,g.,p,e,e,g,f,8e,f,8c6,8c,8d,e,8e,8e,8p,8e,g,2p,c6,c6,a,b,8a,b,g,p,c6,c6,a,8b,a,g.,p,e,e,g,f,8e,f,8c6,8c,8d,e,8e,d,8d,c"}, {"Indiana: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"}, {"GirlFromIpane:d=4,o=5,b=160:g.,8e,8e,d,g.,8e,e,8e,8d,g.,e,e,8d,g,8g,8e,e,8e,8d,f,d,d,8d,8c,e,c,c,8c,a#4,2c"}, {"TakeOnMe:d=4,o=4,b=160:8f#5,8f#5,8f#5,8d5,8p,8b,8p,8e5,8p,8e5,8p,8e5,8g#5,8g#5,8a5,8b5,8a5,8a5,8a5,8e5,8p,8d5,8p,8f#5,8p,8f#5,8p,8f#5,8e5,8e5,8f#5,8e5,8f#5,8f#5,8f#5,8d5,8p,8b,8p,8e5,8p,8e5,8p,8e5,8g#5,8g#5,8a5,8b5,8a5,8a5,8a5,8e5,8p,8d5,8p,8f#5,8p,8f#5,8p,8f#5,8e5,8e5"}, {"Barbie girl:d=4,o=5,b=125:8g#,8e,8g#,8c#6,a,p,8f#,8d#,8f#,8b,g#,8f#,8e,p,8e,8c#,f#,c#,p,8f#,8e,g#,f#"}, {"Entertainer:d=4,o=5,b=140:8d,8d#,8e,c6,8e,c6,8e,2c.6,8c6,8d6,8d#6,8e6,8c6,8d6,e6,8b,d6,2c6,p,8d,8d#,8e,c6,8e,c6,8e,2c.6,8p,8a,8g,8f#,8a,8c6,e6,8d6,8c6,8a,2d6"}, {"Xfiles:d=4,o=5,b=125:e,b,a,b,d6,2b.,1p,e,b,a,b,e6,2b.,1p,g6,f#6,e6,d6,e6,2b.,1p,g6,f#6,e6,d6,f#6,2b.,1p,e,b,a,b,d6,2b.,1p,e,b,a,b,e6,2b.,1p,e6,2b."}, }; // Global Variables char rtttl_def_d; // default duration char rtttl_def_o; // default escale int rtttl_def_b; // default beats per minute float rtttl_tf; // current music "time fraction" int rtttl_curr_d; // current duration char rtttl_curr_n; // current note int rtttl_curr_o; // current scale char rtttl_curr_s; // current sharp flag char rtttl_curr_dotted; // current dotted flag (multiplies duration by 1.5) int rtttl_index_note; // index for look-up-table and get note frequency int duration; // calculated duration note int TC0_int_duration; // note duration inside interrupt on TC0 overflow // Global Bit Variables bit bit_has_time; unsigned char *music; // pointer do CODE Segment residing in DATA Segment void main(void) { char temp[5]; int m,i; IE = 0x82; // Enable interrupts on TC0 overflow TMOD = 0x11; // T0 e T1 as 16 bit Timer TH0 = 0xdc; // loads TH0 TL0 = 0x00; // loads TL0 TR0 = 1; while (1) // opens infinite looping { for (m=0;m<11;m++) { // opens play many musics music = rtttl_music[m]; // gets a pointer to one of the musics encoded in RTTTL rtttl_def_d = 4; // default duration 1/4 rtttl_def_o = 6; // default scale 5:A=440Hz; 6:A= 880Hz; 7:A=1.76KHz; 8:A=3.52KHz rtttl_def_b = 63; // default beats per minute // this code segment assumes the correct music name construction: // := + " . Where is ':' // No erros are checked i=0; // starts array name index to zero while (*music != ':') music++; // ignores music name music++; // skips ':' // this code segment assumes the correct construction: "d=+," // to obtain the current "note duration". No erros are checked. if (*music == 'd') { music++; // skips 'd' music++; // skips '=' i = 0; // starts array duration index to zero while (isdigit(*music)) temp[i++] = *music++; // gets note duration temp[i]='\0'; // inserts Null terminator rtttl_def_d = atoi(temp); // convert string to integer and sets current "note duration" music++; // skips comma } // this code segment assumes the correct construction: "o=," // to obtain the current music scale. No erros are checked. if (*music == 'o') { music++; // skip s 'o' music++; // skips '=' rtttl_def_o = *music - '0'; // gets the current music scale music++; // skips scale number indicator music++; // skips comma } // this code segment assumes the correct construction: "b=+," // to obtain the current music scale. No erros are checked. if (*music == 'b') { music++; //skips 'b' music++; // skips '=' i=0; // starts array beats-per-minute index to zero while (*music != ':') //collect caracters,unless ':' if (isdigit(*music)) temp[i++] = *music++; //must be digits music++; // skips ':' temp[i]='\0'; // inserts Null terminator rtttl_def_b = atoi(temp); //sets current beats-per-minute } rtttl_tf = (60/(float)rtttl_def_b) * (float)rtttl_def_d; // calculater "time fraction" while (*music != '\0') { // opens play single music // this code section searches for note duration,i.e,how long the speaker... // sounds or stay in silent i = 0; // starts array beats-per-minute index to zero while ( isdigit(*music) ) // collects digits temp[i++]=*music++; // store is temporarely temp[i] = '\0'; // inserts Null terminator if ( i>0 ) // if any digit found rtttl_curr_d = atoi(temp); // converts then integer else rtttl_curr_d = rtttl_def_d; //otherwise, stays with default value // until this point does not know if it is a "dotted time fraction note" // Next will come the letter representing a "note" (a,b,...,g,h) ou 'p' for pause if ( *music == 'p') // checks for pause or silent rtttl_curr_n = 0; // if silent, zero is the code for the note else if (*music == 'h') // the same a "b" note (isn´t it strange ?) rtttl_curr_n = 2 ; // the number for "b" note else // there is a letter correspondig to the "note" rtttl_curr_n = *music - 'a' + 1; // "note" a='1',b='2' and so forth... music++; // skips "note" or whatever if (*music == '#') // checks if it is a "sharp note" { rtttl_curr_s = 1; // turns "sharp note" flag on music++; // skips "sharp note" sign off } else rtttl_curr_s = 0; // or, turns "sharp note" flag off if (*music == '.') // checks if it is a "dotted note" { rtttl_curr_dotted = 1; // turns "dotted note" flag on music++; // skips "dotted note" sign off } else rtttl_curr_dotted = 0; //or, turns "sharp note" flag off if ( isdigit(*music)) // checks if "note" belongs to another "scale" { rtttl_curr_o = *music - '0'; // if yes, get "scale" and stores it music++; // skips "scale" digit indicator } else rtttl_curr_o = rtttl_def_o; // or assumes the "default scale" if (*music == ',') // and, at last, if there is a comma music++; // skips it off // calculates "note" duration based on duration = (int)((rtttl_tf/(float)rtttl_curr_d) * (float)ONE_SECOND ); // "time fraction" wich is by your turn based on // "beats per minute". // if "dotted note" increases duration by 1.5 factor if (rtttl_curr_dotted==1) duration = duration + (duration/2); // below this: where the note sound really sounds rtttl_index_note = get_index_note(rtttl_curr_n,rtttl_curr_s,rtttl_curr_o); // get the frequency of note and sounds it // waits amount of time of silent or sound play(note_freq[rtttl_index_note],duration); // above this: where the note sound really sounds } } } } /* This function receives as arguments the code of a note, a flag of sharp and the code of "musical scale". A full scale has twelve notes: a,a#,b,c,c#,d,d#,e,f,f#,g,g# The notes are asigned numbers from 1 to 11 respectively. The job of this function is to set up a correct index to access the note frequency in the "notes" array. Example: index = get_note(1,1,5); // 5a# : fifth scale 'a' sharp Returns: index = 14 // index 1 to 12 corresponds to first scale. */ int get_index_note(char t_rtttl_n,char t_rtttl_s,char t_rtttl_def_o) { // p a b c d e f g : where p=pause, and a,b,...,g are notes code int ts[]={0,1,3,4,6,8,9,11}; int index; if (t_rtttl_n == 0) // if pause or silent index = 0; // do not calculate index for freqency note else index = ts[t_rtttl_n]+ t_rtttl_s + ((t_rtttl_def_o - 4) * 12); return index; } void play(int n,int d) { EA=0; // disable interrupts TC0_int_duration = d; // adjusts duration bit_has_time=1; // turn flag bit_existe_temp on EA=1; while (bit_has_time) // while has time { if (n != 0) // checks if its silent { TH1 = HBYTE(n); // loads TH0 TL1 = LBYTE(n); // loads TL0 (1/2 note Period) TR1 = 1; // starts TC0 while (!TF1); // waits overflow TF1 = 0; // turns overflow flag off TR1 = 0; // stops TC0 AUDIO_OUTPUT = !AUDIO_OUTPUT; // gets the complemente of AUDIO_OUTPUT } } AUDIO_OUTPUT = 0; // after generating the electrical square wave signal turns it off } // Interrupt Service Routine (ISR) ou Rotina de Atindimento a Interrupções geradas por T0 void TC0_int(void) interrupt 1 using 2 { TR0 = 0; // stops TC0 TH0 = 0xdc; // reloads TH0 TL0 = 0x00; // reloads TL0 TR0 = 1; // run TC0 if (TC0_int_duration) // verifica se é maior que zero (Verdadeira) TC0_int_duration--; // Se sim, realiza decremento else bit_has_time=0; // se duração for zero, sinaliza fim do tempo }