Forum: Mikrocontroller und Digitale Elektronik ATMega32U2 PWM Problem


von Uwe (Gast)


Lesenswert?

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?!

von Stefan F. (Gast)


Lesenswert?

> 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?

von Uwe (Gast)


Lesenswert?

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

von Uwe (Gast)


Lesenswert?

Müsste es nicht so sein? Aber es funktioniert immer noch nicht
1
// Set Timer0
2
  DDRB |= (1 << PB7 );
3
  TCCR0A |= (1<<WGM01|1<<WGM00|1<<COM0A1);
4
  TCCR0B |= (1<<CS00);
5
  OCR1A = 128;
6
  
7
  
8
  //Set TIMER1
9
  TIMSK1 |= (1 << TOIE1);
10
  //Prescaler 64
11
  TCCR1B = (1<<CS10 | 1<<CS11);
12
  
13
  TCNT1 = 65535 - (16000000/64/8000);
14
  
15
  sei();

von Stefan F. (Gast)


Lesenswert?

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

von G.Kugel (Gast)


Lesenswert?

Stefan Us schrieb:
> Du brauchst aber COM0A0 = Toggle OC0A on Compare Match

Stimmt das, und wenn ja, warum?

von Uwe (Gast)


Lesenswert?

Stefan Us schrieb:
> Du brauchst aber COM0A0 = Toggle OC0A on Compare Match

Das verstehe ich nicht.

von Uwe (Gast)


Lesenswert?

keiner ne Idee?

von Uwe (Gast)


Lesenswert?

Push

von Uwe (Gast)


Lesenswert?

Hat hier denn niemand Ahnung über PWM?

von G.Kugel (Gast)


Lesenswert?

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.

von c-hater (Gast)


Lesenswert?

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.

von Uwe (Gast)


Lesenswert?

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.

von c-hater (Gast)


Lesenswert?

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.

von Uwe (Gast)


Lesenswert?

1
ISR (TIMER1_COMPA_vect)
2
3
{
4
  
5
    
6
  OCR0A = pgm_read_byte(&music[bytes]);
7
  if(bytes>musicbytes)
8
  {
9
    bytes = 0;
10
  }
11
  bytes++;
12
}
13
14
15
int main (void)
16
17
{
18
  
19
    
20
  // Set Timer0
21
  DDRB |= (1 << PB7 );
22
  TCCR0A |= (1<<WGM01|1<<WGM00|1<<COM0A1);
23
  TCCR0B |= (1<<CS00);
24
  OCR0A = 128;
25
  
26
  // // Set Timer1
27
  TCCR1B |= (1<<WGM12); //CTC Mode 4
28
  TCCR1B |= (1<<CS10);  // Prescaling 1
29
  OCR1A = 1999;      
30
  TIMSK1 |= (1<<OCIE1A);
31
  
32
  
33
    
34
  sei();
35
  
36
  
37
  while(1)
38
  {
39
  }

So, jetzt klappt es gut. Vielen Dank.

von c-hater (Gast)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.