ATtiny 85 PWM timer0 reset nach sei();

#7538375
Lesenswert?

Liebes Forum,

ich bin am verzweifeln, weil ich es nicht hinbekomme, bei einem ATtiny 85 einen PWM Output mit dem timer0 zu erzeugen. Ich bin seit Tagen am Recherchieren und Debuggen, komme aber nicht weiter. An den PB0/pin 5 habe ich eine LED angeschlossen und mit einem Widerstand mit Masse verbunden. Die Schaltung funktioniert. Mein Code läuft bis sei(); und resetet dann. Hat jemand eine Idee?

Folgende Dinge habe ich bereits ausprobiert:

  • Mit Atmel ICE debuggen. Ich habe im allgemeinen den Eindruck, dass die Register in der IDE teils etwas verzögert aktualisiert werden bzw. in diesem Fall der ATtiny sich resetet, bevor da die finalen Werte drin stehen.
  • Anderen ATtiny 85 verwendet
  • Der ATtiny kann die angeschlossene LED steuern, z.b. CTC mode und OCR0A interrupt
  • mit _delay_ms() an diversen Stellen gehofft, dass irgend ein bit "zu langsam" gesetzt wird
  • mit while(!(register & (1 << bit)){} das setzen aller bits abgewartet
  • timer0 als CTC zu configurieren funktioniert problemlos (Überprüfung der Hardware und meines Verständnisses des Datenblatts;) )

Hier ein paar Infos:

  • Ich nutze Mplab X IDE, Atmel ICE und avr-g++ (der Code soll, wenn er tut, in einer c++ Applikation laufen)
  • Spannungsversorgunge über USB
  • die Schaltung habe ich aktuell in einem Breadboard gesteckt
  • der ATtiny läuft mit 8 MHz ohne clock divider
  • Watchdog ist deaktiviert (siehe config bits, WDTCR = 0x00; hat auch nichts gebracht)

Hier ein Minimalbeispiel. Das Setzen der LED vor dem Aktivieren der Interrupts zeigt, dass der ATtiny danach resetet. Mit dem Debugger konnte ich es auf sei(); eingrenzen.

1
#include <avr/io.h>
2
#include <util/delay.h>
3
#include <avr/interrupt.h>
4

5
int main(void) {
6
    //set output pin
7
    DDRB |= (1 << DDB0); //set pin output
8
    PORTB |= (1 << PB0); //set pin high
9

10
    //config fast PWM , Top = 0xff data sheet p 79
11
    TCCR0B &= ~(1 << WGM02);
12
    TCCR0A |= (1 << WGM01);
13
    TCCR0A |= (1 << WGM00);
14

15
    //configure overflow interrupt
16
    TIMSK |= (1 << TOIE0);
17

18
    //prescaler = 1024
19
    TCCR0B |= (1 << CS02);
20
    TCCR0B &= ~(1 << CS01);
21
    TCCR0B |= (1 << CS00);
22

23
    //toggle LED manually
24
    PORTB |= (1 << PB0);
25
    _delay_ms(500);
26
    PORTB &= ~(1 << PB0);
27
    _delay_ms(1000);
28

29
    sei(); //muc resets here ???
30
    OCR0A = 20;
31
    _delay_ms(100);
32
    OCR0A = 120;
33
    _delay_ms(100);
34
    OCR0A = 255;
35
    _delay_ms(100);
36
    return 0;
37
}

Wo ich hier schon eure Aufmerksamkeit habe: Aus dem Datenblatt geht für mich nicht eindeutig hervor, welche Interrupts ich in TIMSK setzen muss. In anderen Codes habe ich immer nur den overflow interrupt gesehen. Wieso muss ich den output compare match interrupt nicht setzen? Für mich wäre entweder logisch, dass ich beide setzen muss oder dass das in Hardware umgesetzt wird und ich keinen setzen muss.

Vielen Dank für eure Unterstützung! Grüße Johannes

Angehängte Dateien:
#7538387
Lesenswert?

1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3

4
ISR( . . . . )
5
{
6
  . . . . . .
7
  . . . . . .
8
}
9

10
int main(void)
11
{
12
  . . . . . . .
13
  . . . . . . .
14
  . . . . . . .
15

16
  while(1)
17
  {
18
    . . . . . .
19
    . . . . . .
20
    . . . . . .
21
    . . . . . .
22
  }
23
}
(Firma: 1984now) #7538389
Lesenswert?

Johannes schrieb:

Mein Code läuft bis sei(); und resetet dann. Hat jemand eine Idee?

Das passiert üblicherweise, wenn man einen Interrupt freigibt, aber keinen Handler dafür im Code hat. Also entweder Handler schreiben oder den Interrupt halt nicht freigeben.

Für die PWM-Ausgabe ist der auch nicht nötig! Die passiert rein in Hardware. Allerdings nur an genau dem/den Pin(s), die in Hardware mit dem Timer verbunden sind und auch nur dann, wenn die entsprechende Verbindung freigeschaltet ist.

Für mich wäre entweder logisch, dass ich beide setzen muss oder dass das in Hardware umgesetzt wird und ich keinen setzen muss.

Wie schon gesagt: Grundsätzlich braucht man erstmal überhaupt keinen Interrupt. Sie können aber nützlich sein in folgenden Fällen:

  1. Man will die PWM an einem anderen Pin ausgeben, als durch die reine Hardware möglich. Dann braucht man beide Interrupts.

  2. Man will irgendeine Datenquelle mit der PWM-Ausgabe synchronisieren. Dann braucht man nur den Overflow-Interrupt.

#7538437
Lesenswert?

Hey,

vielen Dank für die super schnellen Antworten. Das sei(); zum Reset führt, wenn kein ISR implementiert ist, werd ich mir merken. Das war schon mal interessant. Dass ich den Interrupt garnicht setzen muss beruhigt mich, weil das hat so eigentlich keinen Sinn gemacht. Jetzt läuft der Code auch über sei(); hinaus, aber die LED lässt sich nicht dimmen. Sie blinkt einmal auf (Zeile 24) und geht dann aus. Der ATtiny geht aber nicht in Reset.

Mit _delay_ms() hab ich schon relativ schlechte Erfahrungen gesammelt;) Ich wollt die Funktion hier nur verwenden, um das Codebeispiel klein zu halten und keinen Timer zu implementieren. _delay_ms() beschäfftigt soweit ich weiß den CPU. Hardware PWM müsste aber ja trotzdem parallel weiterlaufen. Ist das richtig?

Alternative Ansätze wie

1
OCR0A = 20;
2
uint32_t i = 1;
3
while(i){
4
i++;
5
}
6
OCR0A = 120;

oder

1
while(1){
2
OCR0A = 20;
3
}

führen aber auch nicht dazu, dass die LED überhaupt leuchtet, geschweige denn gedimmt ist.

Habe zu _delay_ms() nur den hinweise gefunden, dass die Optimierung des Compilers auf 1 gesetzt sein muss, was sie tut. _delay_ms() funktioniert auch hervorragend beim manuellen Schalten der LED...

Vielen Dank! Johannes

#7538470
Lesenswert?

Ob S. schrieb:

Ja. Die läuft auch weiter, wenn du nach der (korrekten!) Initialisierung des Timers einfach in eine Endlosschleife gehst.

Das mit der korrekte Initialisierung ist wohl der springende Punkt... Gegenüber dem Listing oben habe ich lediglich Zeile 16 TIMSK |= (1 << TOIE0); auskommentiert. Aber irgendwas scheint ja immer noch falsch zu sein:(

(Firma: 1984now) #7538478
Lesenswert?

Johannes schrieb:

Das mit der korrekte Initialisierung ist wohl der springende Punkt... Gegenüber dem Listing oben habe ich lediglich Zeile 16 TIMSK |= (1 << TOIE0); auskommentiert. Aber irgendwas scheint ja immer noch falsch zu sein:(

Da fehlt schlicht was. Ich zitiere mich mal selber:

Die passiert rein in Hardware. Allerdings nur an genau dem/den Pin(s), die in Hardware mit dem Timer verbunden sind und auch nur dann, wenn die entsprechende Verbindung freigeschaltet ist.

Beachte insbesondere den letzten Halbsatz.

#7538515
Lesenswert?

Ob S. schrieb:

Die passiert rein in Hardware. Allerdings nur an genau dem/den Pin(s), die in Hardware mit dem Timer verbunden sind und auch nur dann, wenn die entsprechende Verbindung freigeschaltet ist.

Beachte insbesondere den letzten Halbsatz.

Laut Datenblatt ist das PB0: "PortB, Bit 0 OC0A: Output Compare Match output. The PB0 pin can serve as an external output for the Timer/Counter0 Compare Match A when configured as output (DDB0 set (one)). The OCA0 pin is also the output pin for the PWM mode timer function."

Bei Timer steht dann: "The actual OC0x value will only be visible on the port pin if the data direction for the port pin ist set as output:"

In meinem Code ist das Zeile 7: DDRB |= (1 << DDB0);. Das meinst du vermutlich mit "Verbindung freigeschaltet"?

Meine LED ist an PB0 angeschlossen. Und weil mir bei sowas schon oft Fehler unterlaufen sind habe ich an PB1 auch eine LED angeschlossen, PB1 auch als output gesetzt und in OCR0B auch etwas eingetragen. Keine der beiden LEDs leuchtet:(

@Oliver: Das erklärt, dass beim Debuggen der grüne Pfeil in der IDE sofort auf Zeile 5 int main(void) gesprungen ist. Normalerweise dauert ein Reset etwas länger.

(Firma: 1984now) #7538521
Lesenswert?

Johannes schrieb:

In meinem Code ist das Zeile 7: DDRB |= (1 << DDB0);. Das meinst du vermutlich mit "Verbindung freigeschaltet"?

Nein. Da du offensichtlich nicht in der Lage bist, das selbst aus dem DB zu extrahieren: es geht (in deinem Fall mit PB0) um die COM0Ax-Bits. Die musst du passend setzen für deinen TimerModus und deinen Anwendungsfall.

Details, wie genau, entnimmst du aber bitte dann doch selber dem DB. Ich bin kein DB-Vorleser.

#7538673
Lesenswert?

Danke für den Hinweis. Die sind beim hin und her kopieren leider irgendwann unter den Tisch gefallen. Über die bits bin ich im Datenblatt schon gesolpert. Aber klar, wenn ich das Thema mit der ISR und dem TIMSK-Register mit dem Datenblatt verstanden hätte, hätte ich nicht hier im Forum nachgefragt;)

Danke für die Unterstützung. Jetzt kann ich mich wieder spannenderen Themen widmen, als den Bugs in den paar Zeilen Code.

Hier nochmal der finale Code für die Nachwelt:D

Grüße Johannes

1
#include <avr/io.h>
2
#include <util/delay.h>
3
#include <avr/interrupt.h>
4

5
int main(void) {
6
    //set output pin
7
    DDRB |= (1 << DDB0); //set pin output
8

9
    //config fast PWM , Top = 0xff data sheet p 79
10
    TCCR0B &= ~(1 << WGM02);
11
    TCCR0A |= (1 << WGM01);
12
    TCCR0A |= (1 << WGM00);
13

14
    //prescaler = 1024
15
    TCCR0B |= (1 << CS02);
16
    TCCR0B &= ~(1 << CS01);
17
    TCCR0B |= (1 << CS00);
18

19
    //clear on compare match, set at bottom
20
    TCCR0A |= (1 << COM0A1); 
21
    TCCR0A &= ~(1 << COM0A0);
22
    
23
    OCR0A = 20;
24
    _delay_ms(2000);
25
    OCR0A = 80;
26
    _delay_ms(2000);
27
    OCR0A = 255;
28
    _delay_ms(2000);
29
    return 0;
30
}
(Firma: matzetronics) #7538701
Lesenswert?

Johannes schrieb:

return 0;

Wohin soll denn ein main() auf einem Mikrocontroller zurückkehren?

Üblich ist eine Schleife in main(), um den MC weiterhin arbeiten zu lassen.

Also z.B.:

1
#include <avr/io.h>
2
#ifndef F_CPU
3
#define F_CPU 1000000L    // factory setting
4
#endif
5
#include <util/delay.h>
6

7
int main(void) {
8
// init stuff
9
TCCR0A = (1 << WGM01) | (1 << WGM00) | (1 < COM0A1);
10
TCCR0B = (1 << CS02) | (1 << CS00);
11
// die Hauptschleife
12
 while(1) {
13
     OCR0A = 20;
14
     _delay_ms(2000);
15
     OCR0A = 80;
16
     _delay_ms(2000);
17
     OCR0A = 255;
18
     _delay_ms(2000);
19
 } //end while
20
} //end main

Ausserdem habe ich dir mal geschrieben, wie man Register in einem Schwupps setzt.

#7538753
Lesenswert?

Hey Matthias,

ich hab gelernt, dass int main(void) den Rückgabewert int hat und dass ich deswegen in die letzte Zeile return 0; schreibe. Dass das nichts bringt, ist mir vollkommen klar.

Wieso brauch ich zwangsweise eine Endlosschleife? Ich schreibe gerade Bibliotheken, damit ich mich nicht jedes mal durch das komplette Datenblatt wälzen muss, wen ich den timer konfigurieren möchte. Der Code oben ist Teil einer Testprozedur, die genau einmal durchlaufen wird. Dann brauche ich auch keine Endlosschleife.

Bist du dir sicher, dass du mir schonmal irgendwas in diesem Forum erklärt hast? Ich hab mir für den Post hier einen neuen Useraccount anlegen müssen... Einzige Möglichkeit wäre, dass ich jetzt zwei Accounts habe;) Dass man Register auf einmal setzten kann, weiß ich. Danke für den Tipp, ich finde allerdings die von mir gewählte Schreibweise übersichtlicher. Auch wenn das wahrscheinlich dazu führt, dass ich im Assembler-Code dann drei einzelne Anweisungen habe. Ich setze ja sogar Werte auf 0, die per default als 0 initialisiert sind.

Grüße Johannes

(Firma: matzetronics) #7538793
Lesenswert?

Johannes schrieb:

Wieso brauch ich zwangsweise eine Endlosschleife?

Brauchst du nicht. Aber dann wird das ganze eben nur einmal durchlaufen.

Johannes schrieb:

Bist du dir sicher, dass du mir schonmal irgendwas in diesem Forum erklärt hast?

Ja, da oben im kurzen C-Programm.

Johannes schrieb:

Danke für den Tipp, ich finde allerdings die von mir gewählte Schreibweise übersichtlicher.

Das musst du mir mal erklären. Statt deiner Löscherei und Setzerei einzelner Bits einmal ans Register zu gehen, soll unübersichtlicher sein?

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