Melodieklingel mit PWM

Gast #4052474
Lesenswert?

Hallo,

die alte Melodieklingel aus DDR Zeiten mit U880 hat schlapp gemacht. 
Also hab ich mich nach einem Ersatz hier im Forum umgeschaut und diesen 
Artikel Beitrag "Klingel mit 100 Melodien - last minute Weihnachtsgeschenk" gefunden. 
Nichts leichter als das und die ganze Sache nachgebaut.
Leider ist der Klang nicht so gut wie bei der Variante mit dem U880 :(
Woran liegt das? Das Prinzip ist doch im Grunde das gleiche oder? Ein 
Timer der nach einer gewissen Zeit also je nach benötigter Frequenz 
immer den Ausgangspin toggelt.

Wie ließe sich das ganze mit PWM realisieren?
Müsste ja im Grunde so sein das ein Interrupt regelmaßig (Tonfrequenz) 
ausgeführt wird und auf einer Sinustabelle einen Schritt weiter geht. 
Der zweite Timer nimmt den Sinuswert als Tastgrad für die PWM.

Hat da jemand ein paar C-Code Schnipsel damit ich irgendwo aufsetzen 
kann? Gerade die Einstellungen der Timer würden mich interessieren.
Gast #4052768
Lesenswert?

Mein Code sieht so aus:
1
//Türklingel mit 100 Melodien M. Baudisch 12/2008
2

3
#include <inttypes.h>    // uint8_t usw.
4
#include <avr/io.h>
5
#include <avr/interrupt.h>  // Interrupts
6
#include <avr/eeprom.h>
7
#include <avr/pgmspace.h>
8
#include <avr/sleep.h>
9
#include <util/delay.h>    // definiert _delay_ms und _delay_loop_2 
10

11
#include "lied.h"           //Melodien, Offsets und Tonhöhen
12

13
#define     SPEAKER         (1<<PB1)
14
#define     TASTE           (1<<PB2)
15
#define     SOUNDON         TCCR1=(0<<CTC1)|(1<<PWM1A)|(1<<COM1A0)|(1<<CS10)|(0<<CS11)|(0<<CS12)
16

17

18
#define     SOUNDOFF        TCCR1=0
19

20
//TCCR1 = (1<<CTC1) |           Timer wird nach erreichen des Vergleichswertes 
21
//                              OCR1A rückgesetzt OCR1A -> Frequenz
22
//        (1<<COM1A0) |         Output (OC1A/B1) wird getoggled
23
//        (1<<CS10)|(1<<CS11)|(1<<CS12);  //Vorteiler 64
24

25

26
//Initialisierungswert des Zufallsgenerators ist EEPROM Speicherstelle
27

28
uint8_t EEMEM seed;
29

30
//Vars in Registern, spart Platz
31

32
volatile register uint16_t soundtime  asm("r14");
33

34
register uint16_t liedcount    asm("r4");  
35
register uint16_t liedende     asm("r6");
36
register uint8_t liednr        asm("r8");
37
register uint8_t speed         asm("r9");
38
register uint8_t dauer         asm("r10");
39
register uint8_t hoehe         asm("r11");
40
register uint8_t note          asm("r12");
41
register uint8_t i             asm("r13");
42

43
const unsigned char sinus[256] = {133,136,139,142,
44
                            145,149,152,155,
45
                    158,161,164,167,
46
                    169,172,175,178,
47
                    181,184,186,189,
48
                    192,194,197,200,
49
                    202,205,207,209,
50
                    212,214,216,218,
51
                    220,222,224,226,
52
                    228,230,232,233,
53
                    235,237,238,240,
54
                    241,242,243,245,
55
                    246,247,248,248,
56
                    249,250,251,251,
57
                    252,252,252,253,
58
                    253,253,253,253,
59
                    253,253,252,252,
60
                    252,251,251,250,
61
                    249,248,248,247,
62
                    246,245,243,242,
63
                    241,240,238,237,
64
                    235,233,232,230,
65
                    228,226,224,222,
66
                    220,218,216,214,
67
                    212,209,207,205,
68
                    202,200,197,194,
69
                    192,189,186,184,
70
                    181,178,175,172,
71
                    169,167,164,161,
72
                    158,155,152,149,
73
                    145,142,139,136,
74
                    133,130,127,124,
75
                    121,118,115,112,
76
                    109,105,102,99,
77
                    96,93,90,87,
78
                    85,82,79,76,
79
                    73,70,68,65,
80
                    62,60,57,54,
81
                    52,49,47,45,
82
                    42,40,38,36,
83
                    34,32,30,28,
84
                    26,24,22,21,
85
                    19,17,16,14,
86
                    13,12,11,9,
87
                    8,7,6,6,
88
                    5,4,3,3,
89
                    2,2,2,1,
90
                    1,1,1,1,
91
                    1,1,2,2,
92
                    2,3,3,4,
93
                    5,6,6,7,
94
                    8,9,11,12,
95
                    13,14,16,17,
96
                    19,21,22,24,
97
                    26,28,30,32,
98
                    34,36,38,40,
99
                    42,45,47,49,
100
                    52,54,57,60,
101
                    62,65,68,70,
102
                    73,76,79,82,
103
                    85,87,90,93,
104
                    96,99,102,105,
105
                    109,112,115,118,
106
                    121,124,127,127};
107

108
// Interruptroutine ohne Inhalt zum Aufwachen aus dem Sleep
109
EMPTY_INTERRUPT(PCINT0_vect);
110

111

112
ISR(TIM0_COMPA_vect) {    //Interrupt Zeitsteuerung
113

114
  if (soundtime) 
115
  {
116
    soundtime--;
117
    OCR1A=sinus[i++];
118
  }
119
}
120

121

122
//Zufallszahlengenerator
123

124
#define MYRAND_MULTIPLIER  17
125
#define MYRAND_CONSTANT    37
126

127

128
uint8_t g_nLastRandom;
129

130

131
uint8_t MyRandGet()
132
{
133
  //copy into local variables to avoid 16-bit integer expansion
134
  uint8_t Multiplier = MYRAND_MULTIPLIER;
135
  uint8_t Constant = MYRAND_CONSTANT;
136

137
  //implicit %256 by 8-bit datatype overflow
138
  g_nLastRandom = (g_nLastRandom * Multiplier + Constant);
139
  
140
  return g_nLastRandom % ANZ;
141
}
142
 
143

144

145

146
int main()
147
{
148

149
PORTB = TASTE; //Pull up an
150

151
//Timer 0 initialisieren
152

153
TCCR0B=(0<<CS01)|(0<<CS01)|(1<<CS00); //Vorteiler 1024 
154
TCCR0A=(1<<WGM01); //CTC Mode
155
GTCCR = (0<<PWM1B) | (1<<COM1B1)|(0<<COM1B0);
156

157

158
TIMSK = (1 << OCIE0A); //Interrupts auf Comp Wert A zulassen
159
OCR0A=155; //ergibt ca. 20ms bzw. 50Hz
160

161
PCMSK = (1<<PCINT2); //Pichange an PB2 konfigurieren
162

163
GIMSK=(1<<PCIE); // Pinchange Interrupt zulassen
164

165

166
set_sleep_mode(SLEEP_MODE_PWR_DOWN);    
167

168
//Zufallsgenerator mit Seed initialisieren
169
g_nLastRandom = eeprom_read_byte(&seed);
170

171
eeprom_write_byte(&seed,MyRandGet()); //neuen Seed schreiben
172

173
// da Variablen in Registern, explizite Initialisierung nötig!
174

175
liednr=0;
176
speed=0;
177
soundtime=0;
178
OCR1C=255;
179

180
//sei(); //Interrupts ein
181

182

183
for(;;)
184
{
185

186
if (liednr) //wenn Melodie aktiv
187
{ 
188
    if (!soundtime) //wenn kein Ton oder Pause läuft
189
        {  //keine Tonzeitdauer aktiv 
190
        SOUNDOFF;     //Tonwiedergabe aus
191
        if (!speed) //wir sind am Liedanfang
192
            {
193
            liedcount=eeprom_read_word(&liedofs[liednr-1]);
194
            if (liednr==ANZ) liedende=sizeof(lied); else 
195
                                            //bei letztem Lied ist Endwert Größe des Arrays
196
            liedende=eeprom_read_word(&liedofs[liednr]); //ansonsten 1 Byte vor nächstem Lied
197
            
198
            speed=pgm_read_byte(&lied[liedcount]);
199
            DDRB  = SPEAKER; //Speaker an
200
            liedcount++;
201
           } else //mitten im lied
202
           {
203
            _delay_ms(20); // um Töne zu trennen
204
            //Ton ausgeben
205
            note=pgm_read_byte(&lied[liedcount]); //Note holen
206
            hoehe=note & 0x1f; //Tonhöhe ausmaskieren (5 Bit)
207
            dauer= zeit[(note >> 5)]; // 5 Bit nach rechts schieben und Zeit holen
208
            if (hoehe) //wenn keine Pause 
209
                    {
210
                    OCR0A=eeprom_read_byte(&ton[hoehe-1]);
211
                 
212
          SOUNDON;
213
                    }
214

215
            cli();     //da Variable vom Interrupt während der Zuweisung geändert werden könnte  
216
            soundtime=dauer*speed*255;
217
            sei();             
218
            
219
            liedcount++;
220

221
            //Lied zu Ende
222
            if (liedcount>liedende)
223
            {
224
            speed =0;
225
            liednr=0;
226
            
227
            }
228
           }
229

230

231

232

233
        } //(!soundtime)
234

235
} //liednr
236

237

238
else {
239

240
    SOUNDOFF; //falls noch Ton an
241
    
242
    DDRB  &= !(SPEAKER); //Speaker Port hochohmig
243
    sleep_mode();
244
    liednr=MyRandGet()+1;  
245

246
    }
247

248
} //for
249

250

251
} //main

Laut Simulation tut es auch was es soll aber in echt kommt kein Ton 
raus...
Gast #4054770
Lesenswert?

Bacchus schrieb:
> Das Prinzip ist doch im Grunde das gleiche oder?
Jein. Beim DDR-MELO4 wird die Frequenz noch mit einem FF halbiert. 
Dadurch erzeugt man so eine Art Mini-Akkord.

> die alte Melodieklingel aus DDR Zeiten mit U880 hat schlapp gemacht.
Und das kann man nicht reparieren?
Abgesehen vom Stromverbrauch und der Größe, kann man doch bei dem Ding 
nicht meckern...

Jens
#4054783
Lesenswert?

> Hat da jemand ein paar C-Code Schnipsel damit ich irgendwo aufsetzen kann?

Ich würd an deiner Stelle mal kleinere Brötchen backen.
Einfach nur mittels _delay_ms den Pin toggeln lassen.
1
#define F_CPU 8000000   // aber nur wenn du CKDIV8 ausgeschaltet hast!
2

3
#include <avr/io.h>
4
#include <util/delay.h>
5

6
int main()
7
{
8
  DDRB = ( 1 << PB1 );
9

10
  while( 1 ) {
11
    PORTB |= ( 1 << PB1 );
12
    _delay_ms( 1 );
13
    PORTB &= ~( 1 << PB1 );
14
    _delay_ms( 1 );
15
  }
16
}

wenn die Hardware grundsätzlich in Ordnung ist UND die Taktfrequenz 
stimmt, dann trötet dir der µC was vor. Hört sich zwar grauenhaft an 
aber ist immerhin ein erster Funktionstest


Edit:
die 1ms kommen so zu stande.
Der Kammerton A hat eine Frequenz von 440Hz. Kehrwert davon macht rund 
0.00227. Die Hälfte davon (weil ja einmal ein UND aus eine komplette 
Schwingun ergeben) sind ungefähr, über den Daumen 1ms. D.h. den Ton 
sollte man hören und er sollte in etwa im mittleren Hörbereich liegen.
#4054793
Lesenswert?

Bacchus schrieb:

>
> Es geht mir jetzt um die Optimierung!

Wen interessiert Optimieiung solange ... Zitat Ereöffnungsposting
> Laut Simulation tut es auch was es soll aber in echt kommt kein Ton raus...

Wenn nichts raus kommt, brauchst du nichts optimieren sondern musst den 
Fehler finden und beheben.

FAQ: Timer


(Wobei ich persönlich als erstes mal die Registerbindung der Variablen 
aufheben würde. Ich leg mich doch nicht mit dem Compiler an, welche 
Register er nicht benutzen kann. 256 Bytes SRAM sind zwar nicht viel, 
reicht aber für die halbe Handvoll Variablen alle mal.)
#4054803
Lesenswert?

Karl Heinz schrieb:

> Wenn nichts raus kommt, brauchst du nichts optimieren sondern musst den
> Fehler finden und beheben.
>
> FAQ: Timer

Und azu würde ich dann trotzdem wieder bei der einfachen Variante 
anfangen und die _delay_ms Geschichte rauswerfen, den Timer 
konfigurieren und dann den alle 1ms den Pin toggeln lassen.

So führt eines zum anderen.
Du baust dann zwar das Programm im Grunde wieder neu auf, hast aber den 
Vorteil, dass du auch verstehst was du da tust.
Gast #4054811
Lesenswert?

Also nochmal für dich Karl Heinz zur Erklärung:
Ich habe das Projekt aus dem anderen Mikrocontroller Beitrag genommen 
und umgesetzt. Hat alles wunderbar funktioniert. Nun finde ich aber, 
dass die Klingel noch schöner klingen könnte (Stichwort Optimierung). 
Ich habe mich also ran gemacht und das bestehende Projekt nach meinen 
Überlegungen umgeschrieben. Ich suche zurzeit also tatsächlich nur nach 
dem Fehler.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren