Forum: Mikrocontroller und Digitale Elektronik ATtiny 85 PWM timer0 reset nach sei();


von Johannes (windy)


Angehängte Dateien:

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

: Bearbeitet durch User
von Sebastian W. (wangnick)


Lesenswert?

Du hast anscheinend keine Overflow-ISR für den Timer 0 definiert?

LG, Sebastian

von Georg M. (g_m)


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
}

von Ob S. (Firma: 1984now) (observer)


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.

von Johannes (windy)


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

von Ob S. (Firma: 1984now) (observer)


Lesenswert?

Johannes schrieb:

> _delay_ms() beschäfftigt
> soweit ich weiß den CPU. Hardware PWM müsste aber ja trotzdem parallel
> weiterlaufen. Ist das richtig?

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

von Johannes (windy)


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:(

von Ob S. (Firma: 1984now) (observer)


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.

von Oliver S. (oliverso)


Lesenswert?

Johannes schrieb:
> HeyDas sei(); zum Reset
> führt, wenn kein ISR implementiert ist, werd ich mir merken.

Der linker setzt alle unbenutzten ISR-Vectoren auf eine default-ISR, in 
der dann ein Rücksprung auf Adresse 0 stattfindet. Da ist also kein 
echter reset, das Programm läuft nur von ganz vorne wieder los.

Oliver

von Johannes (windy)


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.

von Ob S. (Firma: 1984now) (observer)


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.

von Johannes (windy)


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
}

von Matthias S. (Firma: matzetronics) (mschoeldgen)


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.

: Bearbeitet durch User
von Johannes (windy)


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

: Bearbeitet durch User
von Steve van de Grens (roehrmond)


Lesenswert?

Johannes schrieb:
> Wieso brauch ich zwangsweise eine Endlosschleife?

Beim avr-gcc enden Programme automatisch mit einer Endlosschleife, wenn 
die main() Funktion endet. Man muss das nicht hinschreiben.

: Bearbeitet durch User
von Matthias S. (Firma: matzetronics) (mschoeldgen)


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?

: Bearbeitet durch User
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.