Probleme mit Interrupt und serieller Schnittstelle

Hallo zusammen!
Ich bastle gerade an einem Phasenschnittdimmer (Anleitung: http://www.instructables.com/id/Arduino-controlled-light-dimmer-The-circuit/?ALLSTEPS) den ich gerne über die serielle Schnittstelle steuern würde. Der Dimmer an sich funktioniert einwandfrei aber die Steuerung über die serielle Schnittstelle macht Schwierigkeiten. Das Problem ist, dass der Arduino die gesendeten Werte aufgrund der häufigen Unterbrechnung (jede Millisekunde) durch den Interrupt der den Triac steuert nicht einlesen kann. Die Lampe reagiert erst extrem spät auf den geänderten Wert oder beginnt zu flackern. Anbei die abgespeckte Version des Codes mit allen wesentlichen Funktionen (der Dimmer ist Teil einer Hausautomation, daher ist der gesamte Code ziemlich unübersichtlich):

char tag;                     
char HEADER = 'X';
char BULB = 'D';
const byte lamp = 4;
volatile int dimming = 100;       


void setup() {
  Serial.begin(57600);
  attachInterrupt(0, zero_crosss_int, RISING);
}

void loop(){
}

void zero_crosss_int() {
  int dimtime = (75*dimming);    // For 60Hz =>65    
  delayMicroseconds(dimtime);    // Wait till firing the TRIAC
  digitalWrite(lamp, HIGH);   // Fire the TRIAC
  delayMicroseconds(10);         // triac On propogation delay (for 60Hz use 8.33)
  digitalWrite(lamp, LOW);    // No longer trigger the TRIAC (the next zero crossing will swith it off) TRIAC
}



void serialEvent() {
  if(Serial.available() >= 4) {
    if(Serial.read() == HEADER) {
      tag = Serial.read();
      if(tag == BULB) {
        dimming = Serial.read() * 256;
        dimming += Serial.read();
      }
    }
  }
}

Ich habe auch schon versucht mit detachInterrupt() bzw. noInterrupts() den Interrupt während des serialEvent() zu pausieren aber ohne Erfolg. Hat jemand von euch vielleicht eine Idee wie man das Einlesen des Dimmerwertes realisieren könnte? Oder liegt das Problem eurer Meinung nach ganz wo anders? Der Code aus dem oben geposteten Link zur Steuerung über die serielle Schnittstelle funktioniert bei mir übrigens nicht:-/
Vielen Dank und viele Grüße
Simon

Du kannst nicht in einer Interruptfunktion delays verwenden die nominell sol lange sein könnten wie die Zeit zwischen 2 Intrrupts

Die Lösung ist mit dem Trigger der Zero cross Schaltung einen Timer zu starten und der Timer startet nach der eingestellten verzögerungszeit den Triac.
In dem Sketch brauchst Du dann nur den wert von der schnittstelle einlesen und den Timer zu programmieren.

Grüße Uwe

Das Dumme ist, dass deine ISR unheimlich lange alles blockiert mit delaymicroseconds.
Deine Triac-Wechselspannung ist doch 50 Hz ( 20 ms) und nicht 1000 Hz ( 1 ms ) ?
Da würde ich das Ganze eher in loop machen und die ISR nur zum Merken des micros() Zählers nehmen.

Mit deinem Faktor 75 sollte doch ein Byte für dimming fast reichen.
Und wird der Wert tatsächlich binär mit dem high byte zuerst übertragen ?

michael_x:
Das Dumme ist, dass deine ISR unheimlich lange alles blockiert mit delaymicroseconds.
Deine Triac-Wechselspannung ist doch 50 Hz ( 20 ms) und nicht 1000 Hz ( 1 ms ) ?
Da würde ich das Ganze eher in loop machen und die ISR nur zum Merken des micros() Zählers nehmen.

Die Netzfrequenz sind zwar 50 Hz aber der Triac muß bei jeder Halbwelle gezündet werden deshalb ist die max Verzögerungszeit damit auf null gedimmt wird 10mS und nicht 20mS.
Grüße Uwe

Richtig. aber 10 ms und 1 ms ist noch ein Unterschied.
Bei einem Arduino der sonst nichts macht ausser seriell ein paar Buchstaben lesen, sollte das schon in loop direkt gehen, ohne dass man einen Timer braucht.
Aber 10,000 µs in 75er Schritten beschränkt dimming doch auf <133, damit noch >10 µs vor dem nächsten Nulldurchgang Zeit bleibt.

Das passt nicht ganz zu

      if(tag == BULB) {

dimming = Serial.read() * 256;
        dimming += Serial.read();
      }

Guten Morgen!
Danke für die Tipps! Ihr habt natürlich recht...es sind 10 Millisekunden!!! Der Hinweis mit dem Delay stimmt natürlich! Man sollte Code den man übernimmt einfach mal hinterfragen :blush:. Ich habe jetzt mal versucht das Delay zu ersetzen aber nicht wirklich erfolgreich:

const byte lamp = 4;
int dimming = 100;
volatile unsigned long startTime = 0;
unsigned long stopTime = 0;
boolean interrupt = false;


void setup() {
  Serial.begin(57600);
  pinMode(lamp,OUTPUT);
  attachInterrupt(0, zero_crosss_int, RISING);
}

void zero_crosss_int() {
  startTime = micros();
  interrupt = true;
}

void loop(){
  stopTime = micros();
  if (((stopTime - startTime) >= (dimming*75)) && interrupt) {
    interrupt = false;
    digitalWrite(lamp, HIGH);
    delayMicroseconds(10);
    digitalWrite(lamp, LOW);
  }
}

Die Lampe flackert jetzt unabhängig vom dimming-Wert ziemlich deutlich. Liegt das am vermeintlich ungenauen Timing? Es ist jetzt ja nicht gewährleistet, dass der Triac wirklich nach genau der Zeit (dimming*75) feuert. Gibt es irgend eine Timer-Library die die Aufgabe besser erfüllt und PWM nicht beeinflusst? Würde mich über weitere Anregungen freuen!

Gruß, Simon

Schau dir doch mal dieses Beispiel an:
Arduino Playground AC Phase Control

Hier wird der Phasenanschnitt über TImerinterrupts gemacht, es ist also genug Zeit in der Loop für anderes.

Da interessiert mich jetzt, ob man bei 50Hz mit einer kleinen loop Probleme kriegt und tatsächlich Timer braucht :wink:

Kannst du das bei dir mal bitte testen, ob die error Led (13) permanent an ist ?

const int STEP = 100; // 10,000 µs in 100% unterteilen
const byte lamp = 4;
const byte errLed = 13;  // test led 
const byte dimming = 50;   // sollte ca. 50% dimmen ( 99 ist max. für fast aus ) 

volatile unsigned long startTime = 0;
volatile boolean interrupt = false;
volatile boolean error = false;

void setup() {
  // Serial.begin(57600);  brauchen wir hier nicht
  pinMode(lamp,OUTPUT);
  pinMode(errLed,OUTPUT);
  attachInterrupt(0, zero_crosss_int, RISING);
}

void zero_crosss_int() {
  startTime = micros();
  if (interrupt) error = true; // loop hats verpennt
  interrupt = true;
}

void loop(){
  noInterrupts();
     unsigned long diff = startTime - micros();
  interrupts(); 
  if ((diff >= (dimming*STEP)) && interrupt) {
    interrupt = false;
    digitalWrite(lamp, HIGH);
    delayMicroseconds(8);
    digitalWrite(lamp, LOW);
  }
  digitalWrite( errLed, error);
  if (error && ((int)millis() & 0x7FE == 0) error = false; // alle 2 Sekunde ( ca. ) error zurücksetzen. loop darf 2 ms brauchen
}

Wie erzeugst du übrigens dein zerocross Signal ?

edit: interrupt zu während starttime gelesen wird

Danke für den Link guntherb, bin in einem spanischen Blog auf eine ähnliche Realisierung gestoßen aber die englische war doch deutlich leichter zu verstehen :wink: Funktioniert jetzt auf jeden Fall besser als erwartet! Anbei noch mal der Code für alle die sich mal mit ähnlichen Problemen rumschlagen.

@michael_x: Die Glühbirnen gehen nicht an, die LED am Pin 13 ist dauerhaft aus! Ich schau gleich mal ob ich noch rausfinde woran es liegt. Als Bauanleitung hab ich den zu Beginn geposteten Link verwendet wobei ich kleine Modifikationen durchgeführt habe, sodass der Dimmer diesem jetzt stark ähnelt: http://www.inmojo.com/store/inmojo-market/item/digital-ac-dimmer-module-lite-v.2/.

#include <TimerOne.h>

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


void setup() {
  Serial.begin(57600);
  pinMode(lamp,OUTPUT);
  pinMode(13, OUTPUT);
  digitalWrite(lamp, LOW);
  dimtime = int(map(dimming,0,100,9000,3000));
  Timer1.initialize(10000);
  attachInterrupt(0, zero_crosss_int, RISING);
}

void loop(){
}

void zero_crosss_int() {
  Timer1.restart();
  Timer1.attachInterrupt(offTRIAC, dimtime);
}
void offTRIAC() {
  digitalWrite(lamp, HIGH);
  delayMicroseconds(10);
  digitalWrite(lamp, LOW);
}

die LED am Pin 13 ist dauerhaft aus

Na prima, dann ist loop() doch schnell genug, jeden Nulldurchgang rechtzeitig zu verarbeiten.