main.c


1
/* ********************************************************************
2
 *               Melodiegenerator fuer ATtiny85
3
 *               ==============================
4
 *                     Uwe Berger; 2009
5
 * 
6
 * Software:
7
 * ---------
8
 * Inputformat der Melodien ist Nokias RTTTL-Format.
9
 * Der Rest ist aus dem Quelltext ersichtlich!
10
 * 
11
 * Idee: http://hackvalue.de/hv_atmel_rtttl
12
 * 
13
 * Wobei im vorliegenden Programm aber der RTTTL-Parser gleich mit drin 
14
 * ist und somit ohne externen Konverter auskommt.
15
 * 
16
 * Vorteil: man kann RTTTL-Klingeltoene ohne Anpassung an der 
17
 *          entsprechenden Stelle im Programm eingefuegt werden
18
 * 
19
 * Nachteil: es wird etwas mehr Speicherplatz fuer das Programm verbraucht
20
 * 
21
 * 
22
 * Hardware:
23
 * ---------
24
 * Ein ATtiny85 in Standardbeschaltung, also ohne externen Quarz, reicht
25
 * aus. Folgende Peripherie:
26
 *  - an PB1 ist ein Lautsprecher angeschlossen. Ich habe dazu eine dieser
27
 *    laermenden Geburtstagskarten gepluendert. Dieser Lautsprecher wurde
28
 *    direkt angeschlossen, eventuell sollten aber bei anderen Modellen
29
 *    Strombegrenzungswiderstaende vorgesehen werden.
30
 *  - an PB2 ist ein Taster angeschlossen, der den Tiny aus dem Schlaf
31
 *    reisst und ihn die naechste Melodie dudeln laesst. Der geschlossene
32
 *    Taster legt GND an den Pin, es sollte ein PullUp-Widerstand von
33
 *    10kOhm vorgesehen werden.
34
 * 
35
 * --------------------------------------------------------------------
36
 *  
37
 * RTTTL-Formatbeschreibung z.B.:
38
 *     http://de.wikipedia.org/wiki/Ringing_Tones_Text_Transfer_Language
39
 * 
40
 * freie Nokia-Klingeltoene (RTTTL-Format):
41
 * http://www.2thumbswap.com/members/tones/nokia/tones_nokia_main.html
42
 * 
43
 *********************************************************************/
44
#ifndef F_CPU
45
#define F_CPU 1000000UL           
46
#endif
47
48
#include <avr/io.h>
49
#include <util/delay.h>
50
#include <avr/interrupt.h>
51
#include <avr/pgmspace.h>
52
#include <avr/sleep.h>
53
54
55
// Lautsprecherport
56
#define SPEAKER_DDR    DDRB
57
#define SPEAKER_PORT  PORTB
58
#define SPEAKER      PB1
59
60
// Taster
61
#define TASTER_DDR  DDRB
62
#define TASTER_PORT PORTB
63
#define TASTER_PIN  PB2
64
#define TASTER_INT  PCINT2
65
66
// Vorteiler fuer Timer0 (in ctc_on() TCCR0B entsprechend mit anpassen!)
67
#define PRESCALE 8
68
69
// Tonfrequenzen
70
const uint16_t freq[] PROGMEM = {
71
  //   C,   C#,    D,   D#,    E,    F,   F#,    G,   G#,    A,   A#,    B
72
   262,  277,  294,  311,  330,  349,  370,  392,  415,  440,  466,  494, //Oktave 5
73
   523,  554,  587,  622,  659,  698,  740,  784,  831,  880,  932,  988, //Oktave 6
74
  1046, 1109, 1175, 1244, 1328, 1397, 1480, 1568, 1661, 1760, 1865, 1975, //Oktave 7
75
  2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951  //Oktave 8
76
};
77
78
79
const char m1[] PROGMEM = "99luv:d=4,o=5,b=180:d6,8e6,c6,e.6,d6,8c6,a.,8p,8a,8f6,f6,f6,f.6,8f6,e6,d6,c.6,d6,8e6,c6,e.6,d6,8c6,a.,8p,8a,f6,f6,f6,f6,8f6,e6,d.6,8c6,8d6,e6,c6,e.6,d6,8c6,a.,8p,8a,c6,8a,c6,f6,f6,e6,d.6,8c6,d6,e6,c6,e.6,d6,8c6,a.,8p,8f6,f6,8f6,f6,f6,f6,e6,2d6";
80
const char m2[] PROGMEM = "letitbe:d=4,o=5,b=112:16g.,8g,g,8g,a,8e,8g,g.,8c6,d.6,e6,8e6,e6,8d6,8d6,8c6,2c6,e6,8e6,f6,8e6,8e6,d.6,8e6,8d6,2c6";
81
const char m3[] PROGMEM = "Peter Schilling - Major Tom:d=4,o=6,b=200:c.,8p,c,c,c.,8p,c.,8p,2a5,2c,c,8a#5,8a5,g.5,8p,2a#5,2d,2d,c.,8p,2a5,2c,c,8a#5,8a5,g.5,8p,2a#5,2d,2d,e.,8p,1f,2e,d,c,2d.,c,2d,e.,8p,1f,2e,d,c,2d.,c,2d,2";
82
const char m4[] PROGMEM = "yday:d=4,o=5,b=125:g,8f,f,p,8a,8b,8c#6,8d6,e6,8p,8f6,e6,8d6,d6,p,8d6,8d6,8c6,8a#,8a,8g,8a#,8a,8a,a,8p,g,f,8a,8g,g,8p,8d,f,8p,8a,a";
83
84
const char *ma[] PROGMEM = {m4,  m1,  m2,  m3};
85
86
87
//*********************************************************************
88
ISR(PCINT0_vect){
89
  // vorhanden, um MCU definiert aus dem Sleep-Mode zu holen
90
}
91
92
93
//*********************************************************************
94
static void ctc_on(void) {
95
96
  // CTC mode mit toggeln von OC0A/OC0B (Tiny85)
97
  TCCR0A |= (1<<COM0A0 | 1<<COM0B0 | 1<<WGM01);
98
99
  // Prescaler 8
100
  TCCR0B = 1<<CS01;
101
102
}
103
104
//*********************************************************************
105
static void ctc_off(void) {
106
  TCCR0A = 0;
107
  TCCR0B = 0;
108
}
109
110
111
//*********************************************************************
112
int f(int n, int o) {
113
  // lustiger Nebeneffekt des Zugriffs auf den Flash-RAM: die Zeit
114
  // die dazu notwendig ist, fuegt eine kleine Pause zwischen den 
115
  // Toenen ein, die auch ganz sinnvoll ist...
116
  //  
117
  // lt. Datenblatt fuer CTC-Mode ist
118
  // Focnx = Fclk / (2 * Prescale * (1 + OCRnx)
119
  // umgestellt nach OCRnx:
120
  // OCRnx = (Fclk / Focnx / 2 / Prescale) - 1
121
  return (F_CPU/pgm_read_word(&freq[n+(o-5)*12])/2/PRESCALE)-1;
122
}
123
124
125
//**********************************************************************
126
char rf(const char *p) {
127
  return pgm_read_byte(p);
128
}
129
130
//**********************************************************************
131
void my_delay_ms(uint16_t ms) {
132
  uint16_t i;
133
  // es ist mir nicht gelungen, die Pause via Timer zu
134
  // realsieren: es gab merkwuerdige Seiteneffekte, denen ich
135
  // nicht auf die Spur gekommen bin...
136
  //
137
  // _delay_ms generiert nur dann wenig Maschinencode, wenn 
138
  // es mit konstanten Werten gefuettert wird, also deshalb dieser
139
  // Konstrukt....
140
  for (i=0; i<ms; i++) _delay_ms(1);
141
}
142
143
//**********************************************************************
144
void play(uint8_t i) {
145
  const char *p;
146
  
147
  uint16_t def_noten_laenge = 4; 
148
  uint8_t def_oktave = 6;
149
  uint16_t def_bpm = 63;
150
  
151
  // Anfangspointer setzen
152
  p = (const char*)(pgm_read_word(&(ma[i])));
153
  
154
  // Titel ueberspringen
155
  while (rf(p) && rf(p) != ':') p++;
156
  if (!rf(p)) return;
157
  p++;
158
  
159
  // als naechstes kommen die Song-Defaults
160
  while (rf(p)) {
161
    uint8_t param;
162
    uint16_t value;
163
164
    // fuehrende Leerzeichen ueberspringen
165
    while (rf(p) == ' ') p++;
166
    if (!rf(p)) return;
167
      
168
    // hier ist das Ende des Default-Bereiches erreicht  
169
    if (rf(p) == ':') break;
170
171
    param = rf(p);
172
    p++;
173
    if (rf(p) != '=') return;
174
175
    p++;
176
    value = 0;
177
    while (rf(p) >= '0' && rf(p) <= '9') {
178
      value = value * 10 + (rf(p) - '0');
179
      p++;
180
    }
181
    switch (param) {
182
      //case 'd': def_noten_laenge = 32 / value; break;
183
      case 'd': def_noten_laenge = value; break;
184
      case 'o': def_oktave = value; break;
185
      case 'b': def_bpm = value; break;
186
    }
187
    // folgende Leerzeichen ueberspringen
188
    while (rf(p) == ' ') p++;
189
    if (rf(p) == ',') p++;
190
  }
191
  p++;
192
193
  // Noten selbst analysieren
194
  while (rf(p)) {
195
    uint8_t note = 63;
196
    uint16_t oktave = def_oktave;
197
    uint16_t noten_laenge = def_noten_laenge;
198
    uint16_t dauer_ms;
199
    uint8_t punkt_note = 0;
200
201
    // Leerzeichen ueberspringen
202
    while (rf(p) == ' ') p++;
203
    if (!rf(p)) return;
204
205
    // Notenlaenge bestimmen
206
    if (rf(p) >= '0' && rf(p) <= '9') {
207
      uint16_t value = 0;
208
      while (rf(p) >= '0' && rf(p) <= '9') {
209
        value = value * 10 + (rf(p) - '0');
210
        p++;
211
      }
212
      noten_laenge = value;
213
    }
214
215
    // Note bestimmen
216
    switch (rf(p)) {
217
      case 0: return;
218
      case 'C': case 'c': note = 0; break;
219
      case 'D': case 'd': note = 2; break;
220
      case 'E': case 'e': note = 4; break;
221
      case 'F': case 'f': note = 5; break;
222
      case 'G': case 'g': note = 7; break;
223
      case 'A': case 'a': note = 9; break;
224
      case 'H': case 'h': note = 11; break;
225
      case 'B': case 'b': note = 11; break;
226
      case 'P': case 'p': note = 63; break;
227
    }
228
    p++;
229
    
230
    // Halbnote hoeher
231
    if (rf(p) == '#') {
232
      note++;
233
      p++;
234
    }
235
    
236
    // Halbnote tiefer(?)
237
    if (rf(p) == 'b') {
238
      note--;
239
      p++;
240
    }
241
242
    // Punktnote=
243
    if (rf(p) == '.') {
244
      //noten_laenge += noten_laenge / 2;
245
      punkt_note = 1;
246
      p++;
247
    }
248
249
    // Oktave ermitteln
250
    if (rf(p) >= '0' && rf(p) <= '9') {
251
      oktave = (rf(p) - '0');
252
      p++;
253
    }
254
255
    // hier koennte auch ein Punkt sein...
256
    if (rf(p) == '.') {
257
      //noten_laenge += noten_laenge / 2;
258
      punkt_note = 1;
259
      p++;
260
    }
261
262
    // nachfolgende Leerzeichen ueberspringen
263
    while (rf(p) == ' ') p++;
264
    
265
    // Komma ist Notentrenner
266
    if (rf(p) == ',') p++;
267
    
268
    // Tondauer berechnen (60s = 60000ms)
269
    dauer_ms = (((60000 / def_bpm) * def_noten_laenge) / noten_laenge);
270
    // war eine Punktnote dabei?
271
    if (punkt_note) dauer_ms += dauer_ms/2;
272
    
273
    if (note != 63) {
274
      // Tonfrequenz einstellen
275
      OCR0A = f(note, oktave);
276
      OCR0B = f(note, oktave);
277
      ctc_on();        // Ton einschalten
278
      my_delay_ms(dauer_ms);  // Ton fuer berechnete Dauer halten
279
      ctc_off();        // Ton ausschalten      
280
    } else {
281
      // Pause fuer berechnete Dauer
282
      ctc_off();        // Ton ausschalten
283
      my_delay_ms(dauer_ms);
284
    }
285
  }
286
}
287
288
289
int main(void) {
290
  
291
  uint8_t i = 0;
292
293
  // Taster 
294
  TASTER_PORT &= ~(1 << TASTER_PIN);  // Taster-Pin als Eingang
295
296
  // PCINT0 initialisieren  
297
  PCMSK    |=  (1 << TASTER_INT);    // Taster-Pin maskieren
298
  GIMSK    |=  (1 << PCIE);      // Port Change Interrupt frei
299
  
300
  sei();                // Interrupts einschalten
301
302
  // Lautsprecher als Ausgang
303
  SPEAKER_DDR |= (1<<SPEAKER);
304
305
  while(1) {
306
    
307
    if (i >= (sizeof(ma)/sizeof(ma[0]))) i = 0;
308
      play(i);
309
      // Sleep-Mode
310
      set_sleep_mode(SLEEP_MODE_PWR_DOWN);
311
      sleep_mode();
312
      i++;
313
  }
314
315
  return 0;
316
}