[Blink without delay] Unterschiedliche Ein- und Ausschaltdauer

Hallo,

ich bin gerade dabei, mich in die Thematik des Blink without Delay einzuarbeiten und hab es im großen und ganzen schon soweit verstanden, dass ich verschiedene Funktionen unabhängig voneinander steuern kann. Da ich den Arduino (bzw. die damit programmierten Attinys) für den Modellbau nutze, um bei Einsatzfahrzeugen die Lampen und LEDs zu steuern, bin ich nun auf ein Problem gestoßen, bei dem ich nicht weiter komme und Hilfe benötige:

Es geht darum, bei einer LED die Ein- und Ausschaltdauer unterschiedlich lang zu gestalten. Mit meinem bisherigen Sketch schaffe ich es bloß, sie gleichmäßig blinken zu lassen (z.B. 100ms ein, 100ms aus). Aber wie kann ich es individuell gestalten, so wie man es mit Delays bewerkstelligen könnte (z.B. 50ms ein, 100ms aus)? Oder noch verrückter, 50ms ein, 100ms aus, 50ms ein, 200ms aus? Kann mir da jemand weiter helfen?

Im folgenden mal ein einfacher Sketch, den ich bisher nutze.

byte ledPin1 = 3;

boolean value1 = LOW;

unsigned long previousMillis1 = 0;

unsigned long interval1 = 100;

void setup()
{
  pinMode(ledPin1, OUTPUT);
}

void loop()
{
  if (millis() - previousMillis1 > interval1)
  {
    previousMillis1 = millis()
    value1 = !value1;
    digitalWrite(ledPin1, value1);
  }
}

Der Code ist noch umfangreicher, deswegen kann ich auch kein Delay verwenden. Ich habe jetzt aber die unrelevanten Stellen weggelassen.
Vielen Dank schonmal! :slight_smile:

Eine "Blink ohne Delay" Variante:

const byte led = 13; // pin

const unsigned long onZeit  =  10; //ms
const unsigned long offZeit = 990; //ms


void setup() 
{
  pinMode(led,OUTPUT);
}

void loop() 
{
  digitalWrite(led,(millis()%(onZeit+offZeit))<onZeit);
}

Nachtrag: Vorsicht!
Die Modulo Methode ist falsch!
Sie funktioniert im Überlauf nicht richtig.

Hier ist der Beweis, und wie man es besser macht

1 Like

Das ist aber böse :slight_smile:

Kannst du das auch mal in verständliche Einzelzeilen aufdröseln?

das ist echt fies!!!

typischer C++ Code: eine Zeile Code, 5 Zeilen Erklärung. (die leider fehlen)
Aber, ja, wenn ich etwas drüber nachdenke, dann verstehe ich es auch.
Ein schöner Code!

Aber viele Wege führen nach Rom.
Zum Beispiel auch der hier:

void loop()
{
  if (millis() - previousMillis1 > Ontime1){
    digitalWrite(ledPin1, LOW);
  }
  if (millis() - previousMillis1 > interval1){
    previousMillis1 = millis();    
    digitalWrite(ledPin1, HIGH);
  }
}

Wenn auch nicht mit der gleichen Eleganz die der von Combie.

ElEspanol:
Das ist aber böse :slight_smile:

Kannst du das auch mal in verständliche Einzelzeilen aufdröseln?

Was willst Du da aufgedröselt haben, es steht doch alles da: Aus dem stetig hochzählenden Millisekundenzähler bildet er durch Modulo-Division einen Zähler, der in wesentlich kürzeren Zyklen überläuft:

millis() % zyklenDauer ==> das ist ein Zähler, der ständig von 0 bis zyklenDauer-1 hochläuft, und dann bei 0 neu startet

Und so lange dieser Zähler kleiner als eine vorgegebene Einschaltzeit "onZeit" ist, wird der Pin eingeschaltet.

Kleine Schwächen des Codes:
1.) Es wird eine Modulo-Division verwendet, die vergleichsweise zeitaufwändig ist, und es wird digitalWrite() bei jedem Durchlaufen der loop() aufgerufen und nicht nur bei Änderungen des Schaltstatus, was sich bei manchen extrem zeitkritischen Programmen, bei denen es auf jede einzelne Mikrosekunde Rechenzeit ankommt, nachteilig auf das Laufzeitverhalten auswirken kann
2.) Einmal alle knapp 50 Tage Programmlaufzeit wird ein Zyklus zeitlich falsch gesteuert, wenn der millis() Zähler überläuft, was ggf. nachteilig für Programme sein kann, die mehrere Monate am Stück laufen sollen.

Aber für "normale" Programme völlig einwandfrei verwendbar.

Zu 1 und 2: Ja!

Vielen Dank für die Antworten!

combie:
Eine "Blink ohne Delay" Variante:

Das sieht doch recht einfach und simpel aus, danke! Damit werde ich nachher mal rum experimentieren.

einfach und simpel

Nöö.. nicht wirklich....

Nur knapp formuliert.
Und, wie jurs richtig festgestellt hat, mit versteckten Hürden.
(das findet man bei (fast) allen radikalen Vereinfachungen)

Aber von der Funktion (beide combie und guntherb) her eine perfekte Lösung.

Wurde von mir gleich in einem Projekt umgesetzt. :wink: :grin:

Also der Sketch von combie funktioniert super, hab ich gleich ausprobiert. Ich habe ja keine großen Ansprüche daran, von daher reicht der vollkommen aus.
Jetzt wäre es bloß noch toll, wenn ich einen Doppelblitz machen könnte (z.B. 100ms ON, 100ms OFF, 100ms ON, 2000ms OFF). Kann mir da noch jemand helfen?

Den Code von guntherb habe ich noch nicht zum Laufen gebracht, den verstehe ich noch nicht so ganz.

EDIT: Dies ist keine Lösung für die Frage in #9 ! Sondern ein einfaches Blinken!

Man kann es ganz ausführlich schreiben, so dass man es auf den ersten Blick versteht. Oder man abkürzen, so dass der Code kurz ist.

Hier ist ein Kompromiss, mit einer typischen Abfrage, aber etwas gekürzt, so dass man nur eine if Abfrage braucht:

const int ledPin = 13;

const int onTime = 500;
const int offTime = 2000;

void setup()
{
  pinMode(ledPin, OUTPUT);
}

void loop() 
{
  static unsigned long previousMillis;
  static bool ledState;

  if (millis() - previousMillis > (ledState ? onTime : offTime))
  {
    previousMillis = millis();
    ledState = !ledState;
    digitalWrite(ledPin, ledState);
  }
}

"x ? y : z" bedeutet dass x auf true/false ausgewertet wird und bei true y zurückgegeben wird und bei false z. Ähnlich wie ein if/else

Clemi:
Also der Sketch von combie funktioniert super, hab ich gleich ausprobiert. Ich habe ja keine großen Ansprüche daran, von daher reicht der vollkommen aus.
Jetzt wäre es bloß noch toll, wenn ich einen Doppelblitz machen könnte (z.B. 100ms ON, 100ms OFF, 100ms ON, 2000ms OFF)

Das geht mit Combies Variante nicht so leicht.

Ich würde einen Gesamtzylus machen, und (um mal auch einen Trick zu zeigen) :wink:
mit festen 100ms Schritten rechnen:

const int ZYKLUS = 2300;
const int SCHRITT = 100;
void loop() {
  static unsigned long lastcycle;
  if (millis-lastcycle > ZYKLUS) lastcycle = millis();
  int phase = (millis - lastcycle)/SCHRITT;  //  0 .. 22
  switch (phase) {
  case 0:
  case 2:
    digitalWrite(13,HIGH);
    break;  // probier mal spasseshalber hier delay(20); statt break ;)
  default:
    digitalWrite(13, LOW);
  } 
}

Clemi:
Also der Sketch von combie funktioniert super, hab ich gleich ausprobiert. Ich habe ja keine großen Ansprüche daran, von daher reicht der vollkommen aus.
Jetzt wäre es bloß noch toll, wenn ich einen Doppelblitz machen könnte (z.B. 100ms ON, 100ms OFF, 100ms ON, 2000ms OFF). Kann mir da noch jemand helfen?

Für Schaltzyklen mit diversen und unterschiedlich langen ein/aus Schaltdauern gibt es auch immer noch meinen Leuchtturm-Sketch hier:
http://forum.arduino.cc/index.php?topic=146086.msg1099068#msg1099068

In dem Sketch werden sechs verschiedene Leuchtturmfeuer geschaltet, mit wahlweise verschieden vielen Ein- und Ausschaltzeiten innerhalb eines Gesamtzyklus.

Die Anzahl der ein/aus Unterzyklen innerhalb eines Gesamtzyklus kann noch deutlich größer werden wie im Sketch gezeigt und über den Wert 'n' innerhalb der Datenstruktur für jedes Leuchtfeuer gesondert gesetzt werden.

Für 2 Ein- und 2 Ausschaltungen innerhalb eines Schaltzyklus wäre n=4, wie am Beispiel "Pilsum_timing[] " im Sketch.

Der Sketch wurde von mir damals zwar für langsam schaltende Leuchtfeuer-Simulationen geschrieben, aber die zeitliche Auflösung ist Millisekunden.

Wenn man die Schaltungsmöglichkeiten nicht so variabel haben möchte, sondern fest für 2 Einschaltzeiten und zwei Ausschaltzeiten pro Gesamtzyklus, ließen sich natürlich sowohl die Datenstruktur wie auch der Code natürlich vereinfachen.

Clemi:
Den Code von guntherb habe ich noch nicht zum Laufen gebracht, den verstehe ich noch nicht so ganz.

Echt nicht?

Einfach nur die Variablen deklarieren und schon läuft er.

const int ledPin1 = 13;
const unsigned long Ontime1 = 200;  // Andauer der LED ms
const unsigned long interval1 = 1000;  // Periodendauer (=AN + AUS) der LED ms 
unsigned long previousMillis1;

void setup() {
  pinMode(ledPin1,OUTPUT);

}

void loop()
{
  if (millis() - previousMillis1 > Ontime1){
    digitalWrite(ledPin1, LOW);
  }
  if (millis() - previousMillis1 > interval1){
    previousMillis1 = millis();   
    digitalWrite(ledPin1, HIGH);
  }
}

Aber wenn du komplexere Blinkfolgen willst, ist diese Version nicht geeignet.
Ich dachte eigentlich, die sei einfacher zu verstehen als die von Combie und Jurs.
So kann man sich täuschen.

guntherb:
Aber wenn du komplexere Blinkfolgen willst, ist diese Version nicht geeignet.
Ich dachte eigentlich, die sei einfacher zu verstehen als die von Combie und Jurs.
So kann man sich täuschen.

Doch, ist schon einfach zu verstehen, vom Aufbau her ist er sehr simpel. Ich glaube ich habe irgendeinen Fehler bei den Variablen gemacht, deswegen hat er nicht funktioniert. Jetzt läuft es :slight_smile: Danke!