Forum: Mikrocontroller und Digitale Elektronik Tiny13 mit PWM und Timer


von Pal .. (pal)


Lesenswert?

Hallo,

ich verzweifel gerade daran meinen Tiny13 zu konfigurieren. Eigentlich 
kann das nichts großes sein, aber es funkt irgendwie nicht.

Und zwar möchtet ich mit dem Timer ein PWM-Signal erzeugen (Signal liegt 
auf PB0 bzw. OC0A). Gleichzeitig soll aber auch das Timing der main() 
gesteuert werden - das müsste ja dann über OC0B gehen.

Kann mir jemand helfen?

Vielen Dank!!!

1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
volatile uint8_t event;
5
6
ISR(TIM0_COMPB_vect) {
7
  event++;
8
}
9
10
11
12
void init() {
13
  DDRB = 0xff;
14
  DDRB &= ~(1 << PB3);   // PB3 wieder als Eingang
15
16
  /** PWM definieren **/
17
  OCR0A  = 200;          // Ändert sich zwischen 0 und 255 !!!
18
  TCCR0A |= (1 << COM0A1) | (1 << WGM01) | (1 << WGM00);
19
  TCCR0B |= (1 << CS01);  // Prescaler = 8
20
21
  /** Taktzeit definieren **/  
22
  TIMSK0 |= (1 << OCIE0B);
23
  OCR0B  = 100;
24
25
  sei();
26
}
27
28
29
30
int main(void) {
31
  static uint8_t test = 0;
32
  init();
33
34
35
  for (;;) {
36
37
    if(event == 200) {   // Programm alle X-Sekunden durchlaufen
38
      event = 0;
39
40
      if(test == 0) {
41
        PORTB &= ~(1 << PB2);
42
        test = 1;
43
      } else if(test == 1) {
44
        PORTB |= (1 << PB2);
45
        test = 0;
46
      }
47
    }
48
  }
49
}

von Karl H. (kbuchegg)


Lesenswert?

pal ... schrieb:
> Hallo,
>
> ich verzweifel gerade daran meinen Tiny13 zu konfigurieren. Eigentlich
> kann das nichts großes sein, aber es funkt irgendwie nicht.

'funkt irgendwie nicht'
ist die schlechteste Fehlerbeschreibung, die du geben kannst. Denn 
darunter kann sich auf dieser Seite des Bildschirms keiner was 
vorstellen.

> auf PB0 bzw. OC0A). Gleichzeitig soll aber auch das Timing der main()
> gesteuert werden - das müsste ja dann über OC0B gehen.

Was meinst du mit 'Timing steuern'?
Wird der Compare Match Interrupt nicht aufgerufen?

Abgesehen davon. Nein, du kannst du nicht viel steuern (im Sinne von 
einstellen). Denn der Compare Match Interrupt wird nach wie vor 
regelmässig während eines Timer-Durchlaufs aufgerufen.
Eine Analogie: Auf deiner Armbanduhr mit Sekundenzeiger machst du etwas, 
jedes mal wenn der Sekundenzeiger auf einer bestimmten Zahl steht. Aber: 
Egal welche Sekunde du dir aussuchst, es ändert nichts daran, dass der 
Zeiger jedesmal einen kompletten Durchlauf rund ums Zifferblatt braucht, 
bis er wieder bei dieser Sekunde angelangt ist. Du kannst zwar durch 
Wahl einer anderen Sekunde den Zeitpunkt verschieben, aber die 1 Minute 
die der Zeiger einmal rundum braucht, die ändert sich dadurch nicht 
(Jetzt mal von der einen Ausnahme abgesehen, wenn du die Zahl umsetzt).

Also: Was erwartest du? Was passiert statt dessen. Mit 'funkt nicht' hat 
hier keiner eine Ahnung was du dir eigentlich vorstellst was passieren 
sollte (und ich hab im vorhergehenden Absatz auch nur ins Blaue geraten, 
weil diese 'Problemstellung' häufig vorkommt).

von Pal .. (pal)


Lesenswert?

Karl Heinz Buchegger schrieb:
> 'funkt irgendwie nicht'
> ist die schlechteste Fehlerbeschreibung, die du geben kannst. Denn
> darunter kann sich auf dieser Seite des Bildschirms keiner was
> vorstellen.

Stimmt! Sorry, war wieder zu voreilig.

Also das PWM-Signal tut was es soll.

Jetzt will ich, dass mein Programm jede Millisekunde durchlaufen wird.
1
if(event == 200) {   // Programm alle X-Sekunden durchlaufen
Zum testen steht dort zunächst eine 200

Der Compare Match Interrupt wird durchlaufen, aber eine Änderung an 
OCR0B zeigt keine Änderung an meiner Programmablaufzeit.

Rein rechnerisch müsste ich eine Zeit von 33,67mSek [CLK /((OCR0B+1) * 
Prescaler) --> 4800000 / ((100+1)*8) = 0,16833mSek --> 0,16833mSek * 200 
= 33,67mSek]. Tatsächlich habe ich aber eine Zeit von 86mSek.

von Stefan E. (sternst)


Lesenswert?

pal ... schrieb:
> Der Compare Match Interrupt wird durchlaufen, aber eine Änderung an
> OCR0B zeigt keine Änderung an meiner Programmablaufzeit.
>
> Rein rechnerisch müsste ich eine Zeit von 33,67mSek [CLK /((OCR0B+1) *
> Prescaler) --> 4800000 / ((100+1)*8) = 0,16833mSek --> 0,16833mSek * 200
> = 33,67mSek].

Nein!

Hast du den Post von Karl-Heinz überhaupt gelesen? Anschaulicher kann 
man das doch kaum erklären.

Anderer Ansatz: versuch mal in Worten zu erklären, wie du dir den Ablauf 
in Bezug auf den Timer vorstellst. Also er fängt bei 0 an zu zählen. 
Irgendwann erreicht er die 100. Was passiert jetzt? Was danach? Wie geht 
es weiter?

von spess53 (Gast)


Lesenswert?

Hi

>Der Compare Match Interrupt wird durchlaufen, aber eine Änderung an
>OCR0B zeigt keine Änderung an meiner Programmablaufzeit.

Wie sollte er auch? Damit änderst du nur den Zeitpunkt des Interrupts 
aber nicht den Abstand zwischen zwei Interrupts. Genau so gut könntest 
du den Overflow-Interrupt benutzen.

MfG Spess

von Pal .. (pal)


Lesenswert?

> Hast du den Post von Karl-Heinz überhaupt gelesen?
Eigentlich schon

> Irgendwann erreicht er die 100. Was passiert jetzt? Was danach? Wie geht
> es weiter?
Der Interrupt wird ausgelöst - event++ - Interrupt beginnt bei 0

Wie kann ich das "Timing" denn anders realisieren? Ich müsste den 
Interrupt eigentlich zurücksetzen. Aber wie?


Schon einmal vielen Dank!!!

von Stefan E. (sternst)


Lesenswert?

pal ... schrieb:
>> Irgendwann erreicht er die 100. Was passiert jetzt? Was danach? Wie geht
>> es weiter?
> Der Interrupt wird ausgelöst - event++ - Interrupt beginnt bei 0

Was bitte soll denn "Interrupt beginnt bei 0" bedeuten?
Ich will wissen, wie es mit dem Zähler weitergeht.
Also:
1
0 - 1 - 2 - 3 ... 99 - 100 - ??????
2
                        |
3
                       INT

von spess53 (Gast)


Lesenswert?

Hi

>Wie kann ich das "Timing" denn anders realisieren?

Über deine event-Variable.

>Ich müsste den Interrupt eigentlich zurücksetzen. Aber wie?

Einen Interrupt kann man nicht zurücksetzen. Nur das zugehörige Flag.

MfG Spess

von Pal .. (pal)


Lesenswert?

Stefan Ernst schrieb:
> Ich will wissen, wie es mit dem Zähler weitergeht.
> Also:
Birher war ich der Meinung, dass der Interrupt wieder auf 0 gesetzt 
wird. Karl-Heinz hat mich aber mit diesem echt guten Beispiel eines 
besseren belehrt.
Er zählt weiter... 101, 102, 103, ... 255

> Über deine event-Variable.
Habe ich auch schon versucht. Dadurch schaffe ich aber nur eine Zeit von 
3ms und ich würde gerne auf 1ms kommen.

> Nur das zugehörige Flag.
mmmhhh.....aber das sagt mir doch auch nur, dass der Interrupt 
stattgefunden hat.

von Stefan E. (sternst)


Lesenswert?

Nachdem du nun anscheinend verstanden hast, wo dein Denkfehler lag ...

pal ... schrieb:
> Wie kann ich das "Timing" denn anders realisieren?
1
ISR(TIM0_COMPB_vect) {
2
  OCR0B += 100;
3
  event++;
4
}

von Pal .. (pal)


Lesenswert?

Meinen Denkfehler habe ich nun verstanden :-)
1
ISR(TIM0_COMPB_vect) {
2
  OCR0B += 100;
3
  event++;
4
}

Aber dieses brachte auch keine Änderung.

von Karl H. (kbuchegg)


Lesenswert?

pal ... schrieb:
> Meinen Denkfehler habe ich nun verstanden :-)
>
>
1
ISR(TIM0_COMPB_vect) {
2
>   OCR0B += 100;
3
>   event++;
4
> }
>
> Aber dieses brachte auch keine Änderung.

Dann hast du bei der Programmerstellung oder beim Brennen in den µC 
irgendwas verhaut. Denn genau so funktioniert das.

In der Uhrenanalogie ist das nichts anderes, als dass du bei deiner 
festgelegten Sekunde, zb 23, vereinbarts, dass dein nächster 
Stichzeitpunkt die Sekunde 43 sein soll, also 20 Sekunden später. Und zu 
diesem Zeitpunkt vereinbarst du wiederrum, dass dein nächster 
Vergleichszeitpunkt die Sekunde 3 (also wieder 20 Sekunden später) sein 
soll. Fazit: der 'Auslöser' wird alle 20 Sekunden betätigt. Warum ist 
das so? Na ganz einfach, weil du selbst genau den nächsten 
Auslösezeitpunkt 20 Sekunden in die Zukunft gelegt hast.

-> Genauso, mit Berechnen des nächsten OCR0B Wertes in der ISR wird das 
gemacht.

von Stefan E. (sternst)


Lesenswert?

pal ... schrieb:
> Aber dieses brachte auch keine Änderung.

Dann zeige deinen kompletten aktuellen Code.

von Pal .. (pal)


Angehängte Dateien:

Lesenswert?

Der geänderte und vollständige Code
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
volatile uint8_t event;
5
6
7
8
ISR(TIM0_COMPB_vect) {
9
  OCR0B += 100;
10
  event++;
11
}
12
13
14
void init() {
15
  DDRB = 0xff;             // Alles als Ausgang definieren
16
  DDRB &= ~(1 << PB3);     // PB3 wieder als Eingang
17
18
  /** PWM definieren **/
19
  OCR0A  = 200;            // Ändert sich zwischen 0 und 255 !!!
20
  TCCR0A |= (1 << COM0A1) | (1 << WGM01) | (1 << WGM00);
21
  TCCR0B |= (1 << CS01);   // Prescaler = 8
22
23
  /** Taktzeit definieren **/  
24
  TIMSK0 |= (1 << OCIE0B);
25
  OCR0B  = 100;
26
  
27
  sei();
28
}
29
30
31
32
int main(void) {
33
  init();
34
35
  static uint8_t test = 0;
36
37
38
  for (;;) {
39
40
    if(event == 200) {   // Alle  1ms Programm durchlaufen
41
      event = 0;
42
43
      if(test == 0) {
44
        PORTB &= ~(1 << PB2);
45
        test = 1;
46
      } else if(test == 1) {
47
        PORTB |= (1 << PB2);
48
        test = 0;
49
      }
50
    }
51
  }
52
}

Außerdem habe ich noch die Fuses in den Anhang gestellt.

von Pal .. (pal)


Angehängte Dateien:

Lesenswert?

@Karl Heinz: Danke noch einmal für die Erklärung!


Aber wenn ich mir meine Messung mit (s. Bild1)
1
ISR(TIM0_COMPB_vect) {
2
  OCR0B += 100;
3
  event++;
4
}

und ohne (s. Bild2)
1
ISR(TIM0_COMPB_vect) {
2
  //OCR0B += 100;
3
  event++;
4
}

ansehe, erkenne ich keinen Unterschied.

von Uwe (Gast)


Lesenswert?

Guckst du eventuel PB0 an und nicht PB2 ?

von Pal .. (pal)


Lesenswert?

Uwe schrieb:
> Guckst du eventuel PB0 an und nicht PB2 ?

Nein, Messspitze klemmt am STK auf PB2

von PWMmi (Gast)


Lesenswert?

Hast Du in der Zeile

if(event == 200) {   // Alle  1ms Programm durchlaufen

die 200 mal verringert?

Damit teilst Du Deine PWM ja herunter.

von Karl H. (kbuchegg)


Lesenswert?

pal ... schrieb:

> ansehe, erkenne ich keinen Unterschied.


Autsch. Das haben wir alle übersehen
1
Table 11-8. Waveform Generation Mode Bit Description
2
Mode WGM2 WGM1 WGM0   Mode of  Operation TOP  Update of OCRx
3
4
 0     0   0    0     Normal             0xFF Immediate        MAX
5
 1     0   0    1     PWM(Phase Correct) 0xFF TOP              BOTTOM
6
 2     0   1    0     CTC                OCRA Immediate        MAX
7
 3     0   1    1     Fast PWM           0xFF TOP              MAX

Beachte die Spalte 'Update of OCRx'.
Der Timer unterscheidet da nicht. Sobald er im Fast PWM Modus ist, 
werden die OCRx Register erst bei Erreichen des Top Wertes upgedatet.
D.h. du kannst OCR0B nicht zuverlässig in der ISR weiterrechnen, weil 
der Wert erst beim Timerüberlauf ins echte OCR0B Register überführt 
wird.

von Karl H. (kbuchegg)


Lesenswert?

PWMmi schrieb:
> Hast Du in der Zeile
>
> if(event == 200) {   // Alle  1ms Programm durchlaufen
>
> die 200 mal verringert?
>
> Damit teilst Du Deine PWM ja herunter.

Die PWM hat damit nichts zu tun. Die wird sowieso vom Timer 
hardwaremässig bedient.
Er möchte sich zusätzlich noch zur PWM einen 1ms Systemtakt erzeugen.

von Pal .. (pal)


Lesenswert?

Ja, durch Änderung der 200 kann ich das Signal teile. Allerdings komme 
ich damit nur auf 3ms herunter - benötige aber 1ms

von Karl H. (kbuchegg)


Lesenswert?

> Habe ich auch schon versucht. Dadurch schaffe ich aber nur eine Zeit von 3ms und 
ich würde gerne auf 1ms kommen.

Ich fürchte, du wirst die Krot schlucken müssen.
Oder den Timer mit einem Prescaler von 1 laufen lassen und dann damit 
weiterarbeiten.

von Uwe (Gast)


Lesenswert?

Dann braucht er nur den PWM modus auf Normal zu schalten. Und wenn die 
PWM Frequenz gleichbleiben soll dann noch den Prescaler auf 4 (falls 
verfügbar).

von PWMmi (Gast)


Lesenswert?

Ween ich das richtig sehe hast Du

4,8Mhz Systemtakt und /8 für den Timer-Prescaler eingesetllt, der im 
FastPWM-Mode läuft.

Dann ergibt sich eine PWM-Frewuqnz von

fclkio/(8*256) = 2343,75 Hz

Das ist gleich der Interruptfrequenz wenn Du OCR0B konstant läßt.

Wenn Du Deine Hauptprogrammschleife jede 1ms durchlaufen willst, also 
mit 1000Hz, mußt Du 2 INT-Events warten.

Wenn  Du also

if(event == 2) {

einsetzt, sollte das doch machbar sein!?

Oder habe mich irgendwo verrechnet?

von Pal .. (pal)


Lesenswert?

Uwe schrieb:
> Dann braucht er nur den PWM modus auf Normal zu schalten
Sobald ich auf Mode0 schalte bekomme ich kein PWM-Signal mehr raus.
Prescaler = 4 ist nicht möglich.

PWMmi schrieb:
> sollte das doch machbar sein!?
mmmhhhh.... Stimmt!!! So sollte es funktionieren

Hab mich vielleicht einfach zu sehr auf die Register fixiert.


VIELEN DANK noch mal an alle für die guten Vorschläge.
Jetzt gehts los :-)

von Uwe (Gast)


Lesenswert?

> Sobald ich auf Mode0 schalte bekomme ich kein PWM-Signal mehr raus.
Stimmt ist ja nicht "Normal PWM" gemeint, sondern "Normal operation", 
also
als counter. hab mir jetz halt nicht extra das Datenblatt angeguckt. 
Sorry

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.