PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Tonerzeugung mit Timer funktioniert nicht wie gewollt



Patrick91
26.03.2011, 21:45
Hallo, ich hänge seit 2 Stunden an einem einfachen Problem.

Mein Timer funktioniert nicht wie er soll, ich will Töne mit möglichst exakten Frequenzen erzeugen.
Aus diesem Grund verwende ich Timerß0 ohne Prescaler. Zusätzlich beträgt der reload 255.
Allerdings passen die töne nicht, und ich komme nicht drauf, wieso...


Es wäre nett wenn mir jemand zeigt, wo sich das gehölz vor meinem kopf befindet :)


volatile unsigned short sound_count=0;
volatile unsigned short sound_fcount=0;
volatile unsigned char sound_state=0;
volatile unsigned short sound_length=0;
volatile unsigned short sound_freq=0;

void Sound_Beep(unsigned short freq, unsigned short length)
{
sound_length=(F_CPU*length)/1000;
sound_freq=(F_CPU/2/freq);
sound_state=0;
sound_count=0;
sound_fcount=0;

//Enable timer
TCNT0=255;
TCCR0=(1<<CS00);
}


ISR (TIMER0_OVF_vect)
{
sound_count++;
if (sound_count>sound_length)
{
PORTD&=~(1<<7);
TCCR0=0;
};
sound_fcount++;
if (sound_fcount>sound_freq)
{
sound_fcount=0;
if (sound_state==0)
{
sound_state=1;
PORTD|=(1<<7);
}
else
{
sound_state=0;
PORTD&=~(1<<7);
};
};
TCNT0 = 255;
}

Searcher
26.03.2011, 22:22
Hallo,
ohne daß ich jetzt die Sprache C verstehen würde, fehlten mir noch einige Infos:
Welchen µC verwendest Du?
Mit welchem Takt soll er laufen?
Mit welchem Takt läuft er tatsächlich? (Fuses alle richtig?)
Was bedeutet Töne passen nicht? Wie groß ist die Abweichung

Gruß
Searcher

Patrick91
26.03.2011, 22:31
Der quarz ist 3686400 Hz. Es ist ein Atmega8.
Die Fuses sitzen auf: High: 0xFD Low: 0xD9. Die müssten eigentlich passen...(ExternalResonator middle frequency).
Die Töne haben geschätzt eine abweichung von einer million Herz. :D
Also wenn ich einen Ton spiele mit 2134 als Vorhabe, dann Messe ich ca. 685 Hz :D

sternst
26.03.2011, 22:38
Es ist ein Atmega8.Also ist es es ein 8-Bit-Counter.
Dann verrate uns jetzt mal ganz genau im Detail, wie du dir hiermit

Timerß0 ohne Prescaler. Zusätzlich beträgt der reload 255.eigentlich den genauen Ablauf vorgestellt hast.

Im Augenblick werden die Interrupts direkt hintereinander ausgeführt. Genauso gut hättest du den Code auch einfach nur in eine Endlosschleife in main packen können, das wäre auf das Selbe raus gekommen.

TobiKa
26.03.2011, 22:40
Wo ist die Initialisierung des Timers, bzw. wie sieht sie aus?

Patrick91
26.03.2011, 22:41
Ich hatte anfangs keinen reload, da ich es erstmal mit dem normalen Pufferüberlauf probieren wollte, hier war die auflösung der erzeugbaren Frequenzen aber zu Grob (2100 und 2300) ergaben den gleichen ton.
Aus diesem Grund habe ich das reload inzugefügt, jetzt passt aber überhauptnix mehr.
Natürlich hast du recht, hätte das jetzt so funktioniert, dann hätte ich den Timer auch wieder rausgeschmissen ;)

EDIT:
Die initialisierung:

//Enable 8-Bit Counter0 overflow Interrupt
TIMSK|=(1<<TOIE0);
sei();

sternst
26.03.2011, 22:47
Ich sehe immer noch keine detaillierte Beschreibung wie du dir den Ablauf vorstellst. Was denkst du z.B., wie deine Zeitbasis aussieht?

Patrick91
26.03.2011, 22:55
ich nehme die Frequenz mit der ich den uC betreibe und teile sie durch die frequenz die ich als ton haben will.
Nun Weis ich wie der Abstand in Takten von Steigender Flanke zur nächsten steigenden sein muss.
Da ich nun aber auch noch fallende Flanken brauche, teile ich meinen wert nochmal durch zwei.
Nun habe ich den wert, der mir sagt, nach wie vielen Takten ich den Zustand des "sound-pins" ändern muss.

Hmm ich ... ich glaube ich hab das Brett vorm kopf gefunden, die Schleife dauert ja länger als einen Takt...also ich noch aurechnen wie viel Takte ein schleifendurchlauf ist, oder?

sternst
26.03.2011, 23:12
Hmm ich ... ich glaube ich hab das Brett vorm kopf gefunden, die Schleife dauert ja länger als einen TaktA-HA! ;-)


...also ich noch aurechnen wie viel Takte ein schleifendurchlauf ist, oder?Nein, denn die Laufzeit variiert ja (je nach dem welches if oder else da nun gerade konkret durchlaufen wird).
Statt dessen lässt du den Timer Interrupts in einem festen Abstand erzeugen (das ist dann deine Zeitbasis). Dieser Abstand muss aber groß genug sein, dass der Interrupt-Code (und zwar der Worst-Case) darin auch "Platz findet". Eine weitere Erhöhung der Auflösung ist dann nur noch durch einen schnelleren Prozessor-Takt möglich.

Patrick91
26.03.2011, 23:56
Gut , ich bin zu dem ergebnis gekommen, das mein takt zu langsam ist um den Ton so zu erzeugen, wie ich es ursprünglich vor hatte.

Insgesammt sind es 12 Töne, die ich erzeugen möchte, wäre es eine idee, für jeden ton eine eigene funktion zu schreiben und die dann in inline assembler zu implementieren?

z.B. den Ton 2000Hz, wie müsste ich da vorgehen?

sowas in der art:

void ton(void) {
for(i; i<dauer;i++) {
asm volatile ("sbi PORTD, 7");
asm volatile ("nop");
...
asm volatile ("nop");
asm volatile ("cbi PORTD, 7");
asm volatile ("nop");
...
asm volatile ("nop");
}
}

Da müsste man das ja dann nur ausrechnen, wieviele nops man braucht :D
(werden dann wohl einige sein)

oder hat noch jemand eine andere idee?...

EDIT²:
Hat sich erledigt, habs mit assembler und for-schleifen gemacht :D
nach ein bisschen feintuning funktionierts wunderbar.

oberallgeier
27.03.2011, 11:35
Hi Patrick,

schön wenns jetzt funktioniert.
... oder hat noch jemand eine andere idee? ...Ich habe (m)einen Tongenerator mit meiner Standard-wait-Funktion geschrieben . . . die ich vor Ewigkeiten irgendwo abgekupfert hatte. (Zweck in diesem Fall: der Controller wartet auf ein high oder low an einem Pin und startet bei erkanntem Level einen kurzen Ton - das Ganze dient dazu, SEHR kurze Spikes zu erkennen - ohne dauernd aufs Oszi schauen zu müssen). Diese Funktion habe ich als Millisekunden- und manchmal (beim 20-MHz-Quarz) als Mikrosekunden-routine. Allgemein bekannter Nachteil: der Controller tut sonst "nix" - was nicht ganz stimmt: z.B. Interrupts muss er trotzdem arbeiten - dabei stimmt dann natürlich die Routinendauer nicht mehr.

/* ================================================== ===============================
*** Aufgabenstellung : Tongenerator am baby orangutan B-168-20MHz
Der Pin PD7 am "linken, unteren" Eingang der pololu-Platine wird auf
Pegel 1 (ca. + 5V) überwacht. WENN dieser Pegal ansteht, dann wird am
Port PB2 (links, fünfter von oben) ein Signal von etwa 1000 Hz ausgegeben.
...
Ton: aktuell (11. Jan. 10) ca. 2,5 kHz bei "990" in waitms */
// ================================================== ===============================
// ===== Subroutinen ================================================== ===========
// ================================================== ===============================

// ================================================== ===============================
// ================================================== ===============================
/*### Programm pausieren lassen !! Der Pausenwert ist nur experimentell !*/
void waitms(uint16_t ms)
{
for(; ms>0; ms--)
{
uint16_t __c = 4000; // Dieser Wert bestimmt die Laufdauer ! ! !
__asm__ volatile (
"1: sbiw %0,1" "\n\t"
"brne 1b"
: "=w" (__c)
: "0" (__c)
);
}
}
// ================================================== ===============================
// ===== ENDE Subroutinen ================================================== ===
// ================================================== ===============================

lokirobotics
29.03.2011, 16:39
Die Tonerzeugung könntest du wesentlich einfacher mit Timer2 im CTC Mode haben.
Du müsstest nur den Compare Match Wert entsprechend der gewünschten Frequenz setzen.
Den Timer lässt du so lange laufen, wie du den Ton ausgeben möchtest.

oberallgeier
29.03.2011, 18:54
Die Tonerzeugung könntest du wesentlich einfacher ... haben ...Nein. Denn das ziemlich alte Programm läuft, ist im Controller, die Schaltung funktioniert - also wäre alle neue Mühe nur mehr Arbeit. Die Timermethode habe ich stellenweise auch. Aber die zitierte Fragestellung war ja anders:
... oder hat noch jemand eine andere idee? ...

lokirobotics
29.03.2011, 19:00
Sry, aber ich meinte Patrick91 ;-)