Timer1 stoppen

Guten Morgen zusammen,
ich habe ein Problem mit dem anhalten des Timer1. Der folgende Sketch ist ein das Programm für einen Phasenschnittdimmer der am INT1-Pin den Nulldurchgang der Netzspannung detektiert und dann nach einem bestimmten Zeitintervall (abhängig von der Dimmstufe) die Methode offTriac() ausführen soll. Eigentlich hat der Dimmer soweit funktioniert aber ab und zu kam es zum flackern, sodass ich mir das Signal das den Triac steuert mal näher angeschaut habe (Bild im Anhang: Zerocross-Signal in gelb, Triacsteuersignal in türkis). Für mich sieht es so aus, als ob der Timer einmal gestartet (in diesem fall alle 5000 Millisekunden) die Interrupt-Routine offTriac() ausführt egal ob an INT1 ein Interrupt ausgelöst wurde. Habe jetzt versucht Timer1.start() und Timer1.stop() zu verwenden, aber dann wird der Interrupt gar nicht mehr ausgeführt. Jemand eine Idee wo ich start()und stop() im Sketch einbauen muss um zu erreichen das offTriac() wirklich nur nach einem Nulldurchgang ausgeführt wird?

Vielen Dank und viele Grüße
Simon

#include <TimerOne.h>

const byte lamp = 4;
int dimming = 50;
int dimtime = 5000;


void setup() {
  Serial.begin(57600);
  pinMode(lamp,OUTPUT);
  pinMode(13, OUTPUT);
  digitalWrite(lamp, LOW);
  Timer1.initialize(10000);
  attachInterrupt(1, zero_crosss_int, RISING);
}

void loop(){
}

void zero_crosss_int() {
  Timer1.restart();
  Timer1.attachInterrupt(offTRIAC, dimtime);
}
void offTRIAC() {
  if(dimtime >= 9000) {
    digitalWrite(lamp, LOW);
  }
  else if(dimtime <= 3000) {
    digitalWrite(lamp, HIGH);
  }
  else {
    digitalWrite(lamp, HIGH);
    delayMicroseconds(100);
    digitalWrite(lamp, LOW);
  }
}

Vielleicht habe ich was völlig falsch verstanden, aber wieso verwendest Du nicht Timer1.detachInterrupt() in Deiner offTRIAC() Routine?
Der Timer ist ein Element das immer wiederkehrend Aktionen ausführen soll, es ist also nicht mit einer Eieruhr (man verzeihe mir den Vergleich) zu verwechseln.

Hey pylon!
Hast alles richtig verstanden! Das mit der Eieruhr triffts super. Eigentlich such ich genau das! :wink: detachInterrupt() habe ich auch schon probiert. Vielleicht bau ich das einfach falsch ein oder es ist noch was anderes faul. Was definitiv nicht funktioniert ist:

#include <TimerOne.h>

const byte lamp = 4;
int dimming = 50;
int dimtime = 5000;


void setup() {
  Serial.begin(57600);
  pinMode(lamp,OUTPUT);
  pinMode(13, OUTPUT);
  digitalWrite(lamp, LOW);
  Timer1.initialize(10000);
  attachInterrupt(1, zero_crosss_int, RISING);
}

void loop(){
}

void zero_crosss_int() {
  Timer1.restart();
  Timer1.attachInterrupt(offTRIAC, dimtime);
}
void offTRIAC() {
  Timer1.detachInterrupt();
  if(dimtime >= 9000) {
    digitalWrite(lamp, LOW);
  }
  else if(dimtime <= 3000) {
    digitalWrite(lamp, HIGH);
  }
  else {
    digitalWrite(lamp, HIGH);
    delayMicroseconds(100);
    digitalWrite(lamp, LOW);
  }
}

Wie man am Oszi-Bild sieht, ist der Timer-Interuppt immer an der selben Stelle wie der externe Interrupt vom Nulldurchgang. Dabei sollte er bei dimtime = 5000ms eig. genau zwischen den beiden Nulldurchgängen sein.

Welche Version der Bibliothek setzt Du ein? Diese Sachen sollten in der aktuellen Version eigentlich behoben sein.

oh...seh grad es gibt eine .r11-Version! Meinst du die? Werd ich gleich mal ausprobieren ob sich was ändert! Danke!

Habe bisher die v9 benutzt. Durch die Verwendung von der neuerern Version r11 ändert sich nichts. Liegt das Problem vlt. bei der Dauer die ich unter Timer1.initialize() angegeben habe? Was hat es mit der eig. so genau auf sich? Aus der Beschreibung hier Arduino Playground - Timer1 werd ich irgendwie nicht schlau :-/

pylon:
Der Timer ist ein Element das immer wiederkehrend Aktionen ausführen soll, es ist also nicht mit einer Eieruhr (man verzeihe mir den Vergleich) zu verwechseln.

Es ist eigentlich kein Problem den Timer hierfür zu verwenden. Den Timer-Interrupt zu aktivieren und zu deaktivieren geht ja über ein einzelnes Bit. Das kann man schon ständig machen. Der Timer läuft dann im Hintergrund weiter, aber löst beim Compare Match keinen Interrupt aus.

Für die Timer1 Lib vielleicht mal den Code hier probieren:

Das ist getestet

Eine Alternative wäre auch die Timer1 Lib nicht zu benutzen, vor allem wenn diese buggy sein scheint.
Das Datasheet des AVR ansehen, und die Register von Hand anfassen.

Hier ein Beispiel für Timer2 das ich in Verwendung habe:

 // initialize timer2 
  noInterrupts();           // disable all interrupts
    TCNT2  = 0;
TCCR2A = 0;
  TCCR2B = 0;

  OCR2A = 124;            // compare match register 16MHz/256/1Hz
  
 TCCR2B |= (1 << CS22)|(1 << CS20);     
 TCCR2A |=(1 << WGM21);
 TIMSK2 |= (1 << OCIE2A);  // enable timer compare interrupt
  interrupts();             // enable all interrupts

ISR(TIMER2_COMPA_vect)          // timer compare interrupt service routine

Schaut kompliziert aus aus, aber im Kontext mit dem Datenblatt des AVR ist das recht einfach zu verstehen.
Für Timer1 braucht es andere Register zb. TCCR1B.

Finde den Lerneffekt auch gut, so versteht man besser wie die Timer des AVR funktionieren.
Stoppen kann man den Timer1 in TCCR1B nur den Interrupt abschalten/einschalten wird in TIMSK1 geregelt.
Ich will hier nicht "klugscheissen" , dazu bin auch viel zu schlecht in C(++).

Was ich sagen will, das Arduino Projekt verführt den Anwender Entwickler fertige Lib's zu verwenden.
Und wenn diese nicht so funtionieren wie sie sollen steht man zuerst mal gewaltig auf dem Schlauch.

Deshalb schadet ein Blick über den Zaunpfahl wohl nicht... bei meinem Pascalcompiler gab es keine Timerunit :.

So! Habe deinen Rat befolgt rudirabbit und mal einen Blick ins Datenblatt geworfen! Das werde ich im Rahmen meiner Masterarbeit ab nächsten Monat wohl wirklich häufiger machen müssen. War also wirklich ne gute Übung nur häng ich jetzt wieder am selben Problem: Die ISR von Timer1 wird ausgeführt nur scheint das disablen vom Timer1-Interrupt mit TIMSK1 |= (0 << OCIE1A) keine Wirkung zu zeigen! Interrupt wird weiterhin ausgeführt. Kann ich das Bit in der ISR nicht setzen? Aktuell sieht der Code so aus:

const byte lamp = 4;
int dimtime = 5000;


void setup() {
  Serial.begin(57600);
  pinMode(lamp,OUTPUT);
  pinMode(13, OUTPUT);
  digitalWrite(lamp, LOW);
  TCCR1A = 0;
  TCCR1B = 0;
  TCCR1B |= (1 << WGM12);
  TCCR1B |= (1 << CS11);
  attachInterrupt(1, zero_crosss_int, RISING);
}

void loop(){
}

void zero_crosss_int() {
  setDimmingTime(dimtime);
}
ISR(TIMER1_COMPA_vect) {
 stopTimer();
  if(dimtime >= 9000) {
    digitalWrite(lamp, LOW);
  }
  else if(dimtime <= 3000) {
    digitalWrite(lamp, HIGH);
  }
  else {
    digitalWrite(lamp, HIGH);
    delayMicroseconds(10);
    digitalWrite(lamp, LOW);
  }
}
 
void setDimmingTime(int dim) {
  noInterrupts();
  TCNT1 = 0;
  OCR1A = dim*2;
  TIMSK1 |= (1 << OCIE1A);
}

void stopTimer() {
  TIMSK1 |= (0 << OCIE1A);
}
TIMSK1 |= (0 << OCIE1A);

Falsch. Ein Oder mit 0 macht gar nichts. Um ein Bit zu löschen musst du das Byte mit dem Inversen verunden

TIMSK1 &= ~(1 << OCIE1A);

Dafür kann man sich selbst Makros schreiben, dann muss man sich das nicht merken. Die Arduino IDE hat da auch mit bitSet() und und bitClear() fertige Funktionen dafür.

Außerdem:

noInterrupts();

Das löscht glaube ich das globale Interrupt Enable Flag im SREG Register! Wenn du das auschaltest bringt es nichts, einfach nur einen einzelnen Interrupt zu aktiveren.

EDIT:
Um klar zu machen wieso da geschoben wird. Die Makros für die Bit-Namen enthalten die Nummer des Bits. Also z.B. 2 für das 2. Bit. Dann schiebt man eine 1 entsprechend nach links. Dann wird da z.B. 0100 draus. Das verodert man dann mit dem Register um das Bit zu setzen.
Wenn du aber 0100 invertierst wird da 1111 1011 draus. Ein UND löscht dann das Bit.

Danke Serenifly...das wusste ich irgendwann auch schon mal! :* Ich habe aber das Gefühl, dass in der ISR eh alle Interrupts ausgeschaltet werden und erst nach ausführen der Routine wieder angeschaltet werden, deswegen scheint es mit dem Befehl in der Routine nicht zu funktionieren. Die unelegante Lösung sieht so aus, dass ich in der Routine einfach das OCR1A einfach auf einen Wert größer als die Zeit zwischen 2 Interrupts durch das Zerocross-Signal setze also bei 50Hz (100Hz für die Nulldurchgänge) auf 15 ms. Damit funktioniert es bis jetzt einwandfrei.

const byte lamp = 4;
int dimtime = 5000;


void setup() {
  Serial.begin(57600);
  pinMode(lamp,OUTPUT);
  digitalWrite(lamp, LOW);
  TCCR1A = 0;                // Register zurücksetzen
  TCCR1B = 0;                // Register zurücksetzen
  TCCR1B |= (1 << WGM12);    // CTC Mode setzen 
  TCCR1B |= (1 << CS11);     // Prescaler auf 8 setzen => 8MHz/8 = 1MHz => 1ms entspricht 1000 
  attachInterrupt(1, zero_crosss_int, RISING);
}

void loop(){
  for (int i = 0; i <= 100; i++) {
    dimtime = int(map(i,0,100,9000,1500));
    delay(30);
  }
  for (int i = 100; i >= 0; i--) {
    dimtime = int(map(i,0,100,9000,1500));
    delay(30);
  }
}

void zero_crosss_int() {
  TCNT1 = 0;
  OCR1A = dimtime;
  TIMSK1 |= (1 << OCIE1A);
}

ISR(TIMER1_COMPA_vect) {
  digitalWrite(lamp, HIGH);
  delayMicroseconds(10);
  digitalWrite(lamp, LOW);
  OCR1A = 15000;
}

Simon12:
Ich habe aber das Gefühl, dass in der ISR eh alle Interrupts ausgeschaltet werden und erst nach ausführen der Routine wieder angeschaltet werden, deswegen scheint es mit dem Befehl in der Routine nicht zu funktionieren.

Ja, das ist so. Auf dem AVR können sich Interrupts nicht gegenseitig unterbrechen, da beim Eintritt in die ISR automatisch das Interrupt Enable Bit im SREG Register gelöscht wird.

Das normale delay() für ms geht daher nicht, da das den millis() Zähler abfragt der auf Timer0 läuft. delayMicroseconds() dagegen macht in einer Schleife NOPs in inline Assembler.

EDIT:
Du musst Variablen die innerhalb und außerhalb von ISRs verwendet werden unbedingt als "volatile" deklarieren. Der Compiler kann bei der Codepfad Analyse nicht feststellen, dass die ISR von der Hardware aufgerufen wird. Für den ist das eine ganz normale Funktion. Er kann daher denken, dass die Variable nicht gebraucht wird und sie wegoptimieren. "volatile" verhindert das und sorgt außerdem dafür dass die Variable immer aus dem RAM ausgelesen wird statt aus einem Cache oder Register.

Das Problem hast du eventuell mit "dimtime"

Dafür kann man sich selbst Makros schreiben, dann muss man sich das nicht merken. Die Arduino IDE hat da auch mit bitSet() und und bitClear() fertige Funktionen dafür.

Ich widerspreche Serenifly immer ungerne, aber wenn man mal was ohne den Arduinodialekt programmieren will, dann versteht man den Code nicht so einfach.
Auch die Lib's sind meist nicht so geschrieben, denke mal weil dies auch oft nicht so performant sind.
Als Umsteiger von Pascal versuche ich das Bitschubsen und den AVR Register Zugriff in C zu lernen.

@Simon: Deine Lösung scheint wohl das optimale zu sein, nur irgendwie kommt das Gefühl auf es geht noch besser.
Idee habe ich aber im Moment auch keine :grin:

Im Prinzip gebe ich dir Recht. Das Arduino Zeug ist meistens oft nicht ideal. Und natürlich ist es gut Dinge selbst zu lernen. Gerade wenn man so nah an der Hardware programmiert.

Aber bei so trivialen Sachen sehe wie ein Bit zu setzen ich den Nachteil nicht. Habe gerade mal in Arduino.h nachgesehen. Das sind gar keine Funktionen, sondern genau die Makros die man auch selbst schreiben würde:

#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit))

Das einzige wo man vielleicht meckern könnte ist das UL. Das ist nötig damit man auch unsigned long verwenden kann, aber bei Bytes erzeugt es unnötigen Code. Aber so schnell muss das hier auch nicht sein.

Off Topic:
Es gibt mit radians() und degrees() sogar Makros da drin die nicht dokumentiert sind. sq() auch, wobei es da mit sqrt() woanders eine inline Funktion dafür gibt. Oder Makros für Pi, 2 * Pi und Pi / 2. Fällt auch in Visual/Atmel Studio auf, aber da sieht man auch nicht auf den ersten Blick ind welcher Datei die stehen. Erst wenn man mit der Maus bei Auto-Vervollständigen über die Liste geht.

Hallo,
den Timer1 stoppst du mit

  TCCR1B &= ~(1 << CS11);                      // stop counter

und starten mit

  TCNT1=0;                                      // Reset Counter
  TCCR1B |= (1 << CS11);                      // start counter with prescaler 8

hier mit vorherige Reset des Counters.
Ich habe gerade ein ganz ähnliches Projekt programmiert, eine Triacsteuerung mit Vollwellensteuerung. Ich detektiere den Nulldurchgang mit INT0, starte den Timer1 und beim Überlaufinterrupt steuer ich den Triac an.

Gruß
Reinhard

Ja, das ist sicher etwas sauberer als den Timer weiterzählen zu lassen und nur den Interrupt auszuschalten und das Counter-Register zurückzusetzen. :slight_smile:

Wobei beides gehen sollte wenn man es nicht auf den letzten Takt genau braucht.

Hallo Richard,
man sollte im Datenblatt wirklich immer alles lesen :wink: Hab mir nur angeschaut was für den Prescaler 8 brauche...Aber genau sowas habe ich gesucht! Danke!