PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : ATtiny2313 ISR wirft mein Timing durcheinander



Gareth
28.07.2011, 16:25
Mein Ziel ist es von zwei LEDs die eine im definierten Abstand blinken zu lassen, während die andere über den Fast PWM Modus von Timer0 an/aus fadet. Dabei verwende ich zum hoch/runterzählen der OCR0A keine Schleife mit den (üblichen) 12ms als Wartezeit (also nicht _delay_ms(12); wie ich es vorher hatte), sondern hab den Prescaler auf 64 eingestellt und lasse eine ISR bei Compare Match OCR0A verändern. Das kommt auch ganz gut hin, also der Verlauf sieht so flüssig aus, wie ichs gerne hätte, jedoch kommt die andere, blinkende LED immer wieder ins Stocken.

#define F_CPU 1000000UL

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

volatile uint8_t OCR0Adirection=0, OCR0Avalue=1;
//0->OCR0A raufzählen 1-> OCR0A runterzählen

ISR(TIMER0_COMPA_vect){

if(OCR0Adirection==0){
OCR0Avalue += 1;
if(OCR0Avalue==254) OCR0Adirection=1;
}else

if(OCR0Adirection==1){
OCR0Avalue -= 1;
if(OCR0Avalue==1) OCR0Adirection=0;
}
OCR0A = OCR0Avalue; //nur einmal schreiben, statt zweimal lesen und einmal schreiben
}



int main (void)
{

TCCR0A = (1 << COM0A1) | (1 << COM0A0) | (1 << WGM00) | (1 << WGM01);


TCCR0B = (1 << CS00) | (1 << CS01);

OCR0A = 1;

DDRB = (1 << PB2); //pin PB2 auf Ausgang (das ist OC0A)

DDRD = (1 << PD5);//die blinkende led

TIMSK = (1 << OCIE0A);

sei();

while(1){

PORTD &= ~(1<<PD5); //pins auf 0
_delay_ms(100);
PORTD |= (1<<PD5); //pins auf 1
_delay_ms(100);

}
}

Immer wenn die LED an PB2 grade besonders hell oder besonders dunkel ist, also an den Wechselpunkten,
macht die LED an PD5 nur noch ganz kurze delays im Aus-Zustand.
Meine Theorie ist, dass es an den Ifs liegt, die OCR0Adirection umschreiben, was anderes, besonderes passiert ja nicht.
Kann man das irgentwie besser ins Gleichgewicht bringen? Meine ISR ist ja eigentlich gar nicht so lang und wenn die den Programmablauf schon so sehr beeinflusst, wie würde es dann erst bei etwas längeren Methoden aussehen...

Besserwessi
28.07.2011, 19:49
Bei dem Programm hätte ich erwartet das die LED immer relativ schnell blinkt. 100 ms sind ja nicht so lang, auch wenn das dann durch die Interrupts nochmal etwas gedehnt wird. Länger wird die Verzögerung, wenn ohne Optimierung übersetzt wurde.

So langsam sollten die Interrupts so auch nicht werden. Da muss noch was anderes Faul sein.

Gareth
29.07.2011, 11:39
Also die blinkende LED blinkt vom Empfinden her ca. eine viertel Sekunde lang, also so, dass man es noch deutlich als blinken wahrnimmt. Wie gesagt, sind die Aus-Phasen an den (ich nenne sie mal) Scheitelpunkten des Timers nur noch ganz kurz, man sieht kaum noch, dass sie überhaupt mal aus geht.
Was mir noch aufgefallen ist; wenn man statt der 5V USB_Spannung zwei Batterien anschließt (~3V), sind die LEDs natürlich dunkler, aber außerdem scheint das Problem ins Gegenteil umzuschlagen.
Die fadende LED am Timer geht nun so langsam an/aus, dass man sie flackern sieht (sie ist an Timer0-Fast PWM mit 255 Schritten angeschlossen). Die blinkende LED hingegen blinkt dann konstant mit immer der selben Geschwindigkeit.
Ich dachte durch eine niedriegere Spannung verringert sich der CPU-Takt und es wären alle Komponenten gleichermaßen betroffen. Aber anscheinend muss es zwischen Taktgeber und Timer0 etwas geben, dass den Timer bei niedrigerer Spannung nochmals zusätzlich verlangsamt und dass es zwischen Taktgeber und CPU (also der main-Funktion) nicht gibt.
Wie es aussieht wird die ISR wegen dem verlangsamten Timer nun nur noch so selten aufgerufen, dass mehr Leistung für die Schleife im Hauptprogramm übrig bleibt.

Hubert.G
29.07.2011, 13:07
Ich habe dein Programm mal in mein Testboard gespielt. Deinen beschriebenen Fehler kann ich nicht nachvollziehen.
Die LED blinkt konstant, auch das faden ist konstant.

Gareth
29.07.2011, 15:56
Das ist ja sehr merkwürdig. Worin könnten sich unsere Aufbauten denn unterscheiden... Ich hab den 2313 auf einem Steckbrett mit Leitungen von den Pins zu vier Transistoren (versorgen die LEDs mit Strom, da ich nicht wusste ob mir sonst irgentwann noch der Chip durchbrennt) und als Board das DFRobot USBTinyISP.
Jetzt bin ich noch verwirrter als vorher^^. Aber danke fürs Austesten.
Eigentlich war das sowieso nur eine Art Verständnistest; ums mal gemacht zu haben.
Ich denke mal diese Wartefunktion wird man in der Praxis, also einem etwas "sinnvolleren" Vorhaben sowieso möglichst nicht einsetzen. Inzwischen hab ich die rausgenommen und statt der einen blinkenden jetzt drei LEDs angeschlossen, die im Zeitabstand der Reihe nach leuchten sollen. Dieser Abstand kommt aber nicht mehr durchs Warten zu stande, sondern ist an die Variable, die ich für den OCR0A-Wert hab, gekoppelt. Das funktioniert auch alles so wie ichs mir vorgestellt hab.



#define F_CPU 1000000UL

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

volatile uint8_t OCR0Adirection=0, OCR0Avalue=1;
//0->OCR0A raufzählen 1-> OCR0A runterzählen

ISR(TIMER0_COMPA_vect){

if(OCR0Adirection==0){
OCR0Avalue += 1;
if(OCR0Avalue==254) OCR0Adirection=1;
}else

//if(OCR0Adirection==1){ //kann eingespart werden
{
OCR0Avalue -= 1;
if(OCR0Avalue==1) OCR0Adirection=0;
}
OCR0A = OCR0Avalue; //nur einmal schreiben, statt zweimal lesen und einmal schreiben
}

uint8_t CurrentLED=0;

void LEDleuchten(){

if(OCR0Avalue==1 || OCR0Avalue==64 || OCR0Avalue==128
|| OCR0Avalue==192 || OCR0Avalue==254){//wenn timer0 am endpunkt

switch(CurrentLED){//erst die betroffene LED aus
case 0: PORTB &= ~(1<<PB4); break;
case 1: PORTD &= ~(1<<PD5); break;
case 2: PORTA &= ~(1<<PA0);
}

CurrentLED++;//dann zur nächsten wechseln
if(CurrentLED==3)CurrentLED=0;

switch(CurrentLED){//und diese anschalten
case 0: PORTB |= (1<<PB4); break;
case 1: PORTD |= (1<<PD5); break;
case 2: PORTA |= (1<<PA0);
}

while(OCR0Avalue==1 || OCR0Avalue==64 || OCR0Avalue==128
|| OCR0Avalue==192 || OCR0Avalue==254){}//weil sonst sofort weitergeschaltet wird
}
}

int main (void)
{


TCCR0A = (1 << COM0A1) | (1 << COM0A0) | (1 << WGM00) | (1 << WGM01);


//TCCR0B = (1 << CS01);
//Prescaler auf CPU/8 -> perfekt^^
TCCR0B = (1 << CS00) | (1 << CS01);

OCR0A = 1; //Startwert, pendelt dann zwischen 0..255

DDRB = (1 << PB2); //pin PB2 auf Ausgang (das ist OC0A)

//blinkende led PB4, PD5, PA0
DDRD |= (1 << PD5);
DDRB |= (1 << PB4);
DDRA |= (1 << PA0);

TIMSK = (1 << OCIE0A);

sei();

while(1){

LEDleuchten();





}

}

Besserwessi
29.07.2011, 16:39
Ein Unterschied könnten die Optionen beim compilieren sein. Wenn man delay verwendet sollte man unbedingt mit Optimierung übersetzen. Auch kann sich die Länger der ISR dadurch verändern. Es macht da schon einen Unterschied ob die ISR 40% oder 90 % der Rechenzeit benötigt.

Gareth
29.07.2011, 16:44
Bei Optimierungen steht bei mir im Avr-Studio 4 -Os aber weiß nicht was das genau bedeutet. Dass man die bei delay an haben sollte, hab ich auch schonmal gelesen, und dachte die wären default-mäßig an.

Hubert.G
29.07.2011, 16:51
Mit -Os kompiliere ich auch. Muss doch was mit dem Aufbau zu tun haben. Ich habe das Eval-Board von Pollin.
Wie nach deinem letzten Code die Led an PortA leuchtet würde mich interessieren.

Gareth
29.07.2011, 17:02
Die leuchtet eigentlich wie die anderen auch, oder hast du irgenteine Unstimmigkeit entdeckt?
Das Einzige was mich etwas wundert ist, wieso am Anfang die an PD5 leuchtet. Es sollte doch mit PB4 anfangen..

Hubert.G
29.07.2011, 17:38
Ich habe einen Quarz dran, daher ist der PortA bei mir nicht verfügbar, das ist die Unstimmigkeit.
Du schaltetst die Leds aus, erhöhst und schaltest dann ein.

Gareth
30.07.2011, 16:14
Ja, so kriegt man zwar nicht ohne Weiteres die perfekten Zeitabstände hin (bei delayms gibt mans ja direkt an), aber das auch nicht besonders schlimm.
Kann es sein, dass wenn man bei einem 0..255-Timer, bei dem man sowohl die OCR-Compare-Match ISR, als auch die Overflow-ISR verwenden will und man einen PRescaler von 0 oder 8 nimmt, die Compare-Match ISR gar nicht mehr zum Zug kommt?

Ich hab nämlich versucht eine Hardware-PWM und und eine Software-PWM gleichzeitig am selben Timer zu benutzen. Wenn ich den 64er Prescaler benutze (damit fadet die LED an der Hardware-PWM optimal) und die selbst gemachte Counter-Variable der Software-PWM immer von der Overflow-ISR weiterzählen lasse, läuft selbiges natürlich 256mal langsamer ab, als alles an der Hardware-PWM; aber es läuft.
Als ich die Software-PWM dann aber beschleunigen wollte, indem ich den Prescaler runtersetzte (egal, dass die LED an der Hardware-PWM dann nur noch flackert), hat sich an der Software-PWM-LED gar nichts mehr getan.
Ist es vielleicht so, dass die OCR-Compare-Match-ISR eine höhere Priorität hat, als die Overflow-ISR und sich die Overflow-ISR dann schon jedesmal anstellt, aber dass die Compare-Match-ISR selber so lange läuft, dass die nächste schon ansteht, sobald eine fertig ist und sich somit immer die Compare-Match-ISRs vordrängeln?

So siehts bei mir aus:

#define F_CPU 1000000UL

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

volatile uint8_t OCR0Adirection=0, OCR0Avalue=1;
//0->OCR0A raufzählen 1-> OCR0A runterzählen

ISR(TIMER0_COMPA_vect){

if(OCR0Adirection==0){
OCR0Avalue += 1;
if(OCR0Avalue==254) OCR0Adirection=1;
}else

//if(OCR0Adirection==1){ //kann eingespart werden
{
OCR0Avalue -= 1;
if(OCR0Avalue==1) OCR0Adirection=0;
}
OCR0A = OCR0Avalue; //nur einmal schreiben, statt zweimal lesen und einmal schreiben
}


/* Erstmal mit nur einer LED, nämlich PB4.Nachgebildet werden soll eine Fast PWM. Bei jedem
Overflow des Timers wird die Funktion einmal ausgeführt und erhöht den Counter (softTCNT).
Trifft dieser den OCR-Wert der LED, wird diese angeschaltet. Erreicht der Counter sein max.
und wird wieder auf 0 gesetzt, wird die LED wieder ausgeschaltet.
Bei der Hardware-PWM kann man wählen, ob man den OCR-Wert in der Hauptschleife mit im Abstand
von z.B. 12ms um einen bestimmten Wert heraufsetzt oder das (besser) in einer ISR macht.
In letzterem Fall muss darauf geachtet werden, dass die den OCR verändernde ISR nicht zu
schnell hintereinander aufgerufen wird, da man sonst wesentlich weniger als 12ms zwischen
den LED-Helligkeiten haben kann und sie somit viel zu schnell fadet. Deshalb sollte der
Prescaler so hoch eingestellt werden, dass die LED gerade noch nicht-flackernd aussieht
und der Wert um den der OCR jedes Mal verändert wird, sollte möglichst klein sein.
Bei gleichzeitiger Verwendung einer Hardware- und einer Software-PWM kann dieser hohe
Prescaler zum Problem werden. Denn während die Hardware-PWM ihren Counter 256 mal erhöht, tut
dies die Software-PWM (bei Verwendung des selben Timers) nur einmal.
*/
volatile uint8_t softLED1direction=0, softLED1OCR=0, softTCNT=0;
ISR(TIMER0_OVF_vect){ //software PWM
softTCNT++;//counter bei jedem aufruf weiterzählen
if(softTCNT==255){//einmal pro counter-runde, also am ende, bei 255
softTCNT=0;//wird der counter auf anfang gesetzt
PORTB &= ~(1<<PB4);//die led geht aus
if(softLED1direction==0){//und es wird der OCR-wert der LED weitergezählt
softLED1OCR++;//und zwar entweder rauf
if(softLED1OCR==255) softLED1direction=1;
} else{//oder runter, je nach direction
softLED1OCR--;
if(softLED1OCR==0) softLED1direction=0;
}
}

if(softTCNT == softLED1OCR){//sobald der counter in einer runde (0..255) den OCR der
PORTB |= (1<<PB4);//LED erreicht, wird diese angeschaltet
}


}/* Theoretisch sollte diese soft-PWM genauso schnell faden wie die Hardware-PWM (oben)
denn auch hier wird der OCR einmal pro Counter-Zyklus verändert. Jedoch läuft das Ganze
255 mal langsamer ab, sofern man nichts am Prescaler ändert oder nicht einen Timer
verwendet, mit einem Modus, dessen obere Zählgrenze kleiner (in OCR gesetzt) ist
und der diese Overflow-ISR somit ofter, als alle 255 Takte ausführt.
*/


/* //zum Testen von Hard- und Software-PWM gleichzeitig, erstmal diese Funktion rausnehmen
uint8_t CurrentLED=0;

void LEDleuchten(){

if(OCR0Avalue==1 || OCR0Avalue==64 || OCR0Avalue==128
|| OCR0Avalue==192 || OCR0Avalue==254){//wenn timer0 am endpunkt

switch(CurrentLED){//erst die betroffene LED aus
case 0: PORTB &= ~(1<<PB4); break;
case 1: PORTD &= ~(1<<PD5); break;
case 2: PORTA &= ~(1<<PA0);
}

CurrentLED++;//dann zur nächsten wechseln
if(CurrentLED==3)CurrentLED=0;

switch(CurrentLED){//und diese anschalten
case 0: PORTB |= (1<<PB4); break;
case 1: PORTD |= (1<<PD5); break;
case 2: PORTA |= (1<<PA0);
}

while(OCR0Avalue==1 || OCR0Avalue==64 || OCR0Avalue==128
|| OCR0Avalue==192 || OCR0Avalue==254){}//weil sonst sofort weitergeschaltet wird
}
}*/

int main (void)
{


TCCR0A = (1 << COM0A1) | (1 << COM0A0) | (1 << WGM00) | (1 << WGM01);


//TCCR0B = (1 << CS00);//no Prescaler
TCCR0B = (1 << CS01);//Prescaler CPU/8
//TCCR0B = (1 << CS00) | (1 << CS01);//Prescaler CPU/64
//TCCR0B = (1 << CS02);//Prescaler CPU/256
//TCCR0B = (1 << CS00) | (1 << CS02);//Prescaler CPU/1024

OCR0A = 1; //Startwert, pendelt dann zwischen 0..255

DDRB = (1 << PB2); //pin PB2 auf Ausgang (das ist OC0A)

//blinkende led PB4, PD5, PA0
DDRD |= (1 << PD5);
DDRB |= (1 << PB4);
DDRA |= (1 << PA0);

//ISRs für OCR0A Compare Match und für Timer Overflow aktivieren
TIMSK = (1 << OCIE0A) | (1 << TOIE0);

sei();

while(1){

//LEDleuchten();





}

}

Besserwessi
30.07.2011, 16:42
Beim Timer 0 wird erst der overflow Interrupt bedient und dann der Compare match. Beim Timer 1 ist es andersherum. Man kann im Dateblatt unter Interrupt Vectors nachsehen in welcher Reihenfolge das geht. Ohne Prescaler kann das schon passieren das die ISR zu lange braucht, mit optimierung sollte es von der Laufzeit aber noch reichen.

Gareth
31.07.2011, 12:18
Ich habs jetzt doch nochmal mit dem Timer versucht, aber zusätzlich einen Servo angeschlossen; dadurch sieht man noch genauer wann das delay wie oft übersprungen wird. Und zwar läuft das so ab, dass der Servo (Hauptschleife) ca. 25 Schritte korrekt macht, also mit 500ms dazwischen und die nächsten zehn dann mit geschätzten 50ms delay. Komischerweise hab ich das Problem auch in diesem Fall nicht mehr, wenn ich Batterien anstatt der USB-Stromversorgung nehme. (Und wenn man dem Servo eigene Batterien gibt, flackert nichtmal irgentwas).


#define F_CPU 1000000UL

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

volatile uint8_t OCR0Adirection=0, OCR0Avalue=1;
//0->OCR0A raufzählen 1-> OCR0A runterzählen

ISR(TIMER0_COMPA_vect){

if(OCR0Adirection==0){
OCR0Avalue += 1;
if(OCR0Avalue==254) OCR0Adirection=1;
}else
{
OCR0Avalue -= 1;
if(OCR0Avalue==1) OCR0Adirection=0;
}
OCR0A = OCR0Avalue;
}


int main (void)
{
TCCR0A = (1 << COM0A1) | (1 << COM0A0) | (1 << WGM00) | (1 << WGM01)
| (1 << COM0B1) | (1 << COM0B0);//OC0B für den servo

//TCCR0B = (1 << CS00);//no Prescaler
//TCCR0B = (1 << CS01);//Prescaler CPU/8
TCCR0B = (1 << CS00) | (1 << CS01);//Prescaler CPU/64
//TCCR0B = (1 << CS02);//Prescaler CPU/256
//TCCR0B = (1 << CS00) | (1 << CS02);//Prescaler CPU/1024

OCR0A = 1; //Startwert, pendelt dann zwischen 0..255

DDRB = (1 << PB2); //pin PB2 auf Ausgang (das ist OC0A)

//blinkende led PB4, PD5, PA0
DDRD |= (1 << PD5);
DDRB |= (1 << PB4);
DDRA |= (1 << PA0);

//ISRs für OCR0A Compare Match
TIMSK = (1 << OCIE0A);

sei();

uint8_t i=215;
//servoausschlag liegt bei 8bit-Timer, Prescaler von 64 und Fast-PWM
//bei 215: -90° 245: +90°
while(1){

for(;i<245;i++){
OCR0B=i;
_delay_ms(500);
}
for(;i>=215;i--){
OCR0B=i;
_delay_ms(500);
}

}
}

Gareth
31.07.2011, 14:50
Nochmal eine andere Frage: Um Strom zu sparen und da der Servo nicht permanent mit Kraft die Stellung halten muss, wollte ich den jetzt ohne Timer versorgen. Und zwar über eine ganz einfache Funktion, die in einer Schleife 30 mal erst für 1-2ms ein High am Port ausgibt, gefolgt von 15ms Low. (Die Länge des High-Zustandes bestimmt die Position und da sich der Servo scheinbar nur in den kurzen 15ms lang bewegt und in der Zeit keine 180° schafft, mach ich es 30 mal).
Meine Frage bezieht sich auf den Typ der Zählvariablen in der Schleife für die 1-2ms Wartezeit.
Um RAM zu sparen wollte ich nämlich eine 8bit statt einer 16bit Variablen benutzen, aber leider funktioniert das nicht.


#define F_CPU 1000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

volatile uint8_t OCR0Adirection=0, OCR0Avalue=1;
//0->OCR0A raufzählen 1-> OCR0A runterzählen

ISR(TIMER0_COMPA_vect){

if(OCR0Adirection==0){
OCR0Avalue += 1;
if(OCR0Avalue==254) OCR0Adirection=1;
}else
{
OCR0Avalue -= 1;
if(OCR0Avalue==1) OCR0Adirection=0;
}
OCR0A = OCR0Avalue;
}


void moveServo(uint8_t time){
cli();
PORTB |= (1<<PB4);//Servo Stromversorgung an

uint8_t i=0;
uint16_t j=0; //<-----Diese Stelle ist gemeint----->

for(;i<50;i++){//es wird ein paar mal der folgende drehbefehl durchgegeben

PORTD |= (1<<PD5);//servo-takt-pin an
for(j=0;j<time;j++){//eine ganz bestimmte zeit lang an lassen (1-2ms)
_delay_us(1);}

PORTD &= ~(1<<PD5);//dann wieder aus
_delay_ms(15);//und bis zum nächsten warten (10-20ms)
}

PORTB &= ~(1<<PB4);//Servo Stromversorgung aus
sei();
}

int main (void)
{


TCCR0A = (1 << COM0A1) | (1 << COM0A0) | (1 << WGM00) | (1 << WGM01);


//TCCR0B = (1 << CS00);//no Prescaler
//TCCR0B = (1 << CS01);//Prescaler CPU/8
TCCR0B = (1 << CS00) | (1 << CS01);//Prescaler CPU/64
//TCCR0B = (1 << CS02);//Prescaler CPU/256
//TCCR0B = (1 << CS00) | (1 << CS02);//Prescaler CPU/1024

OCR0A = 1; //Startwert, pendelt dann zwischen 0..255

DDRB = (1 << PB2); //pin PB2 auf Ausgang (das ist OC0A)

//blinkende led PB4, PD5, PA0
DDRB |= (1 << PB4);//Strom für Servo
DDRD |= (1 << PD5);//OC0B (Servo)
DDRA |= (1 << PA0);

//ISRs für OCR0A Compare Match
TIMSK = (1 << OCIE0A);

sei();

uint8_t i;

while(1){

if(OCR0Avalue==20)
{ i=70;
moveServo(i);
}
if(OCR0Avalue==240)
{ i=170;
moveServo(i);
}
if(OCR0Avalue==128)
{ i=250;
moveServo(i);
}
}
}

Die anderen, betroffenen Variablen (i in der main und time als Parameter) sind auch nur 8bit groß.
D.h. wenn ich die Zählvariable j auch nur 8bit groß mache, müsste es doch alles glatt laufen (einen Überlauf kanns nicht geben). Aber trotzdem scheint die Schleife mit (uint8_t j;) schon ganz früh abzubrechen, denn der Servo dreht sich bis zum Anschlag in die Richtung der kurzen Impulsdauern. Mit uint16_t j; gehts komischerweise.