Hallo,
ich habe ein Problem mit PWM auf'nem ATMega32U2. Auf PB7(OC0A)habe ich
einen kleinen Lautsprecher gegen Masse angeschlossen um Sound (8Khz
8Bit) auszugeben.
Es ist ein 16MHz Quarz mit 22pF dran. Fuses hierfür sind folgendermaßen
gesetzt:
BODLEVEL = 3V0
HWBE = [X]
DWEN = [ ]
RSTDISBL = [ ]
SPIEN = [X]
WDTON = [ ]
EESAVE = [ ]
BOOTSZ = 2048W_3800
BOOTRST = [ ]
CKDIV8 = [X]
CKOUT = [ ]
SUT_CKSEL = EXTLOFXTAL_32KCK_65MS
EXTENDED = 0xF4 (valid)
HIGH = 0xD9 (valid)
LOW = 0x65 (valid)
Versorgt wird der uC mit 5V.
1
// Set Timer0
2
DDRB|=(1<<PB7);
3
TCCR0A|=(1<<WGM01|1<<WGM00|1<<COM0A1|1<<CS00);
4
OCR0A=128;
5
6
//Set TIMER1
7
TIMSK1|=(1<<TOIE1);
8
//Prescaler 64
9
TCCR1A=(1<<CS10|1<<CS11);
10
11
TCNT1=65535-(16000000/64/8000);
12
13
sei();
Ich setzte mit Timer0 ein FastPWM auf und zusätzlich starte ich Timer1.
1
ISR(TIMER1_OVF_vect)
2
3
{
4
5
TCNT1=65535-(16000000/64/8000);
6
bytes++;
7
OCR0A=pgm_read_byte(&music[bytes]);
8
if(bytes>musicbytes)
9
{
10
bytes=0;
11
}
12
}
Das ist die Interrupt Routine mit der ich mein Byte Array "quasi"
abstasten will.
Irgendetwas stimmt hier nicht. Könnte mir jemand bitte vielleicht einen
Hinweis geben, wo ich einen Denkfehler gemacht haben könnte?!
> Irgendetwas stimmt hier nicht.
Du hast vergessen, dein problem zu beschreiben. Was genau stimmt nicht?
Wenn du "bytes" schon vor dem Auslesen des Arrays erhöhst, wirst du das
allererste Byte (mit Index 0) niemals lesen. Aber das ist vermutlich
nicht das problem, um das es Dir geht, gell?
Stefan Us schrieb:> Du hast vergessen, dein problem zu beschreiben. Was genau stimmt nicht?
Ich bekomme nichts an PB7 ergo kein Sound.
Stefan Us schrieb:> Wenn du "bytes" schon vor dem Auslesen des Arrays erhöhst, wirst du das> allererste Byte (mit Index 0) niemals lesen. Aber das ist vermutlich> nicht das problem, um das es Dir geht, gell?
Stimmt, aber erstmal nicht so tragisch wenn das erste Byte wegfällt
Dann reduziere das Programm erstmal auf nur einen Timer, um einen
simplen Ton auszugeben.
COM0A1 = Clear OC0A on Compare Match
Du brauchst aber COM0A0 = Toggle OC0A on Compare Match
Stefan Us schrieb:> Dann reduziere das Programm erstmal auf nur einen Timer, um einen> simplen Ton auszugeben.
Was ist, wenn der Timer1 Interrupt nicht an ist?
Dann muß nach der letzten Konfiguration des Timer0:
// Set Timer0
DDRB |= (1 << PB7 );
TCCR0A |= (1<<WGM01|1<<WGM00|1<<COM0A1);
TCCR0B |= (1<<CS00);
OCR1A = 128;
ein PWM Signal an PB7 erscheinen.
Sonst mal das komplette aber reduzierte Programm MIT dem Problem
einstellen.
Uwe schrieb:> ich habe ein Problem mit PWM auf'nem ATMega32U2.> Auf PB7(OC0A)habe ich> einen kleinen Lautsprecher gegen Masse angeschlossen um Sound (8Khz> 8Bit) auszugeben.
OK, das Ziel der Operation ist verständlich beschrieben.
> Es ist ein 16MHz Quarz mit 22pF dran. Fuses hierfür sind folgendermaßen> gesetzt:
[...]
> SUT_CKSEL = EXTLOFXTAL_32KCK_65MS
Effektiv also 2MHz Systemtakt. Oder (wahrscheinlicher) gar keiner, denn
du hast den falschen Erregertyp gewählt. EXTLOFXTAL ist nicht korrekt
für einen 16MHz Quarz. Das kann zwar u.U. auch mal schwingen,
normalerweise wird es das aber nicht tun.
Aber auch mit richtiger Wahl des Erregers ist die Frequenzwahl ungünstig
für eine Tonerzeugung per PWM. Da der Aufwand für den
Rekonstruktionsfilter umso geringer ist, je weiter der Abstand der
PWM-Frequenz von der höchsten Nutzfrequenz ist, wird man immer
versuchen, das System mit einem möglichst hohen Takt zu betreiben. Bei
8kHz Samplefrequenz ist die höchste Nutzfrequenz 4kHz, bei 2MHz
Systemtakt und einem 8Bit-Timer ist die höchste nutzbare PWM-Frequenz
2.000.000/256=knapp 8kHz, also weniger als das Doppelte der höchsten
Nutzfrequenz. Das läßt sich nicht sinnvoll filtern, nichtmal mit extrem
aufwendigen Filtern.
Also muß der erste Schritt sein, die Fuses dem Problem anzupassen. Den
richtigen Erreger wählen und die Taktteilung durch 8 ausschalten. Dann
hast du erstmal 16MHz Systemtakt und damit 62,5kHz PWM-Frequenz
(Prescaler=1). Damit kann man eine 8kHz-Tonausgabe in akzeptabler
Qualität realisieren.
> BODLEVEL = 3V0> HWBE = [X]
[...]
> Versorgt wird der uC mit 5V.
Das paßt nicht so recht zueinander und ganz sicher nicht zu dem
anzustrebenden 16MHz Systemtakt, denn bei 16MHz ist bei 3V schon längst
nicht mehr mit einem stabilen Betrieb zu rechnen. Also auch hier noch
nachbessern.
> Ich setzte mit Timer0 ein FastPWM auf und zusätzlich starte ich Timer1.
Wozu zwei Timer? Du kannst den PWM-Timer sowieso maximal einmal pro
PWM-Cycle mit einem neuen OCR-Wert versehen, also bietet es sich an, die
Zuführung der Samples in einer (geeigneten) ISR dieses Timers zu machen.
Die Verwendung eines zweiten Timers bringt keine nennenswerte Vorteile.
Schon garnicht, wenn er so unsinnig erfolgt, wie du es getan hast. Wenn
man schon mit zwei Timern arbeitet, dann wäre der zweite Timer natürlich
im CTC-Modus zu betreiben und nicht am TCNT-Register herumzupfuschen,
sonst bekommst du niemals eine exakte Samplefrequenz hin, schon garnicht
in C, wo du keine Kontrolle über die Laufzeit deines Codes hast.
Also, am Besten machst du die Zuführung der Samples im Overflow- oder
CompareMatch-Interrupt von Timer0, natürlich mit einer Routine zur
Verteilung des Taktfehlers (62,5kHz PWM-Zyklusfrequenz vs. 8 khZ
Samplefrequenz).
Diese Routine muß sicherstellen, daß im Schnitt alle 7,8125 PWM-Zyklen
ein neuer OCR-Wert geschrieben wird. Tatsächlich geht das natürlich
nicht, vielmehr muß sie in der Regel alle 8 Zyklen ein neues Sample
holen, gelegentlich (genau: in 7 von 400 Fällen) aber schon nach 7
Zyklen. Sowas implementiert man üblicherweise mit einem
Bresenham-Zähler.
Der Rechenzeitaufwand für die Fehlerverteilung ist allerdings (relativ)
hoch. Die Umgehung dieses Problems wäre der einzige sinnvolle Grund zur
Verwendung eines zweiten Timers. Dann müßte man den aber auch sinnvoll
konfigurieren, nämlich im CTC-Mode mit 8kHz Überlauffrequenz. Und dann
natürlich den CompareMatch-Interrupt statt des Overflow-Interrupt
verwenden.
> Irgendetwas stimmt hier nicht. Könnte mir jemand bitte vielleicht einen> Hinweis geben, wo ich einen Denkfehler gemacht haben könnte?!
Ich denke mal, daß dein Grundfehler ist, daß du glaubst, die
PWM-Frequenz müßte gleich der Samplefrequenz sein. Dem ist aber für
solch eine PWM-Anwendung, bei der letztlich ein "analoger" Wert
ausgegeben werden soll, nicht so. Ganz im Gegenteil ist hier immer
anzustreben, daß die PWM-Frequenz so weit wie nur irgend möglich darüber
liegt.
Hallo CHater,
danke für die Hilfe...
Anbei habe ich nun folgende Fuse Settings:
BODLEVEL = 3V0 (Das ist der Brown-out detector Level)
HWBE = [X]
DWEN = [ ]
RSTDISBL = [ ]
SPIEN = [X]
WDTON = [ ]
EESAVE = [ ]
BOOTSZ = 2048W_3800
BOOTRST = [ ]
CKDIV8 = [ ]
CKOUT = [ ]
SUT_CKSEL = EXTXOSC_8MHZ_XX_1KCK_65MS
EXTENDED = 0xF4 (valid)
HIGH = 0xD9 (valid)
LOW = 0xCF (valid)
Vom Prinzip her habe ich vor mit dem Timer1 einen Overflow Interrupt in
einer Frequenz von 8kHz auslösen.
Innerhalb der Interrupt Routine soll aus einem Bytearray immer nur ein
Byte (inkrementell je Overflow des Timer1) aus dem Array gelesen und an
OCR vom Timer0 übergeben werden.
Uwe schrieb:> Anbei habe ich nun folgende Fuse Settings:> BODLEVEL = 3V0 (Das ist der Brown-out detector Level)> HWBE = [X]
Nichts gelernt.
Der BOD dient dazu, zu verhindern, daß der Controller bei zu geringer
Spannung (z.B. langsam steigender oder fallender Spannung beim Ein- oder
Ausschalten) abstürzt. Dazu muß er auslösen, sobald die Spannung nicht
mehr genügt, um bei gegebener Taktfrequenz einen sicheren Betrieb zu
ermöglichen.
Wie du im Datenblatt auf Seite 266 (Abschnitt "26.3 Speed grades")
erkennen kannst, benötigt der Controller bei 16MHz mindestens etwa 4,5V,
um stabil zu laufen. Die Triggerschwelle des BOD muß also so eingestellt
werden, daß unterhalb 4,5V so schnell wie möglich abgeschaltet wird,
sonst ist er sinnlos und die kannst ihn gleich ganz abschalten. Das
spart dann wenigstens noch etwas Strom.
Besser ist aber, ihn anzulassen und die Triggerschwelle korrekt auf 4,3V
zu fusen.
> CKDIV8 = [ ]> SUT_CKSEL = EXTXOSC_8MHZ_XX_1KCK_65MS
OK.
> Vom Prinzip her habe ich vor mit dem Timer1 einen Overflow Interrupt in> einer Frequenz von 8kHz auslösen.
Nichts gelernt.
Nochmal: Wenn du schon einen zweiten Timer zur Generierung der
Samplerate einsetzt (was ja unter gewissen Umständen immerhin sinnvoll
sein kann), dann solltest du den in einem CTC-Modus betreiben. Und im
CTC-Modus benutzt man dann NICHT den Overflow-Interrupt, sondern den
CompareMatch des Registers, mit dem man den Zählumfang der CTC
einstellt, denn nur für diesen Interrupt ist garantiert, daß er einmal
pro Zyklus ausgelöst wird, wohingegen die anderen Interrupts nur bei
bestimmten Inhalten der zugehörigen Compare-Register oder auch garnicht
ausgelöst werden. Letzteres betrifft insbesondere den
Overflow-Interrupt, genau der wird im CTC-Modus nämlich praktisch
niemals ausgelöst.
Für den CTC-Modus hast du beim Timer1 zwei Möglichkeiten: Du benutzt
entweder OCR1A (Timer-Mode 4) oder ICR (Timer-Mode 12) zur Einstellung
des Zählumfangs. Im Falle der Verwendung von ICR nennt sich der
entsprechende Interrupt allerdings nicht CompareMatch, sondern wegen der
eigentlich eher typischen Verwendung von ICR als Captureeinheit
Capture-Interrupt. In der Anwendung als CTC entspricht er aber exakt
einem CompareMatch-Interrupt.
Und in der (passend gewählten) ISR von Timer1 spielst du dann nicht an
TCNT1 rum, sondern holst einfach immer nur das nächste Sample aus dem
Flash und schreibst es nach OCR0A.
Die Überlauffrequenz für Timer1 stellst du folgendermaßen ein:
1)
Du wählst den kleinsten Prescaler, der garantiert, daß bei vollem
Zählumfang des Timers die erzeugte Überlauffrequenz gerade noch
kleiner ist als die gewünschte. Der volle Zählumfang eines
16Bit-Timers ist 65536, dein Systemtakt ist 16MHz. Bei Prescaler 1
ergeben sich 16.000.000/(1*65536)=244Hz. Einen noch kleineren Prescaler
gibt es nicht, also ist das bereits der am besten geeignete, wenn man
8kHz Überlaufrate erzeugen will.
2)
Mit dem gewählten Register (also entweder OCR1A oder ICR) stellst du
dann den tatsächlichen Zählumfang (in der Formel: N) unter
Berücksichtigung des unter 1) gewählten Vorteilers (in der Formel: P)
ein.
16.000.000
N = ---------- - 1
P * 8.000
Das ergibt 1999. Und genau diesen Wert schreibst du je nach gewähltem
CTC-Modus entweder nach OCR1A oder nach ICR1 und aktivierst entweder den
CompareMatch-Interrupt für OCR1A oder den Capture-Interrupt für ICR.
Uwe schrieb:> So, jetzt klappt es gut. Vielen Dank.
Es hat mich gefreut, dir helfen zu können.
Und ich bedanke mich bei dir auch für die höchst erfreuliche (und leider
viel zu seltene) Erfahrung, daß es scheinbar irgendwo da draußen doch
noch C-Programmierer gibt, deren Kompetenz über die reine Anwendung von
Copy&Paste hinausgeht...
Übrigens: nachdem du nun softwaremäßig so ziemlich das Optimum auf der
gegebenen Plattform erreicht hast, wäre es vielleicht an der Zeit, die
Hardware zu überdenken. Nötig ist das natürlich nur, wenn die erzielte
Tonqualität dir für deine Anwendung möglicherweise nicht genügt und du
deshalb bereit wärst, Mehraufwand bei der Hardware zu betreiben, um das
von der Software gelieferte Signal möglichst gut in Schall zu wandeln.
Du würdest staunen, mit welch geringen Mitteln hier noch deutliche
Verbesserungen zu erzielen wären. Allerdings sind dazu möglichst genaue
Angaben über den verwendeten Schallwandler nötig, um den Hardwareaufwand
wirklich gering halten zu können.