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
intmain(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
return0;
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
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.
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_ti=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
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.
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:(
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.
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
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.
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.
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
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
intmain(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.
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
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.
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?