For Schleife ohne Delay

Hallo alle zusammen,

Ich heisse Daniel und bin 32 Jahre alt. Ich habe mir vor kurzem einen Arduino UNO gekauft, nun habe ich versucht mehrere Lauflichter in einen Programm zu packen. Habe es verucht mit Switch Case und 4 Arrays verschiedene Lauflichter zu programmieren.
Den Code dafür, habe ich aus Programmschnipsel aus dem Internet zusammen geführt und versucht anzupassenn wie es brauche.

Das Programm funktioniert zum Teil. Nur das Problem ist, das ich im Unterprogramm led_1 bis led_4 ein Delay drin habe. Kann also, nicht mit den Taster weiter schalten, erst dann wenn die For Schleife beendet ist. Eigentlich wollte ich bei case : eine While schleife machen wenn count=1 ist usw . Damit das Lauflicht immer wiederholt wird. Nur das geht leider nicht, weil sonst der Taster nicht abgefragt wird. Ich hoffe ihr könnt mir helfen, ich sitze da jetzt schon mehrere Tage dran, und komme einfach nicht weiter. Habe es natürlich auch mit dem Beispiel BlinkWithoutDelay versucht, habe leider weiss ich nicht wie ich es anzupassen habe, damit es bei mir funktioniert.

Mit Interrupt wollte ich es nicht machen, will ich das Programm noch erweitern will, da der Controller nur 2 Interrupt hat ist es mir zu wenig. Brauche insgesamt 4 oder noch mehr.

const int inputPin = 4;

int count = 1;
int state = LOW;
int laststate = LOW;

byte Lauflicht1[15][8] =
{
   { 0,0,0,0,0,0,0,1 },  
   { 0,0,0,0,0,0,1,0 },  
   { 0,0,0,0,0,1,0,0 },  
   { 0,0,0,0,1,0,0,0 },  
   { 0,0,0,1,0,0,0,0 },  
   { 0,0,1,0,0,0,0,0 },  
   { 0,1,0,0,0,0,0,0 },  
   { 1,0,0,0,0,0,0,0 },  
   { 0,1,0,0,0,0,0,0 },  
   { 0,0,1,0,0,0,0,0 },  
   { 0,0,0,1,0,0,0,0 },  
   { 0,0,0,0,1,0,0,0 },  
   { 0,0,0,0,0,1,0,0 },  
   { 0,0,0,0,0,0,1,0 }  

};

byte Lauflicht2[8][8] =
{
   { 1,0,0,0,0,0,0,1 },  
   { 0,1,0,0,0,0,1,0 },  
   { 0,0,1,0,0,1,0,0 },  
   { 0,0,0,1,1,0,0,0 },  
   { 0,0,1,0,0,1,0,0 },  
   { 0,1,0,0,0,0,1,0 },  
   { 1,0,0,0,0,0,0,1 }  
  
};

byte Lauflicht3[8][8] =
{
   { 1,0,0,0,0,0,0,0 },  
   { 0,1,0,0,0,0,0,0 },  
   { 0,0,1,0,0,0,0,0 },  
   { 0,0,0,1,0,0,0,0 },  
   { 0,0,0,0,1,0,0,0 },  
   { 0,0,0,0,0,1,0,0 },  
   { 0,0,0,0,0,0,1,0 },  
   { 0,0,0,0,0,0,0,1 }  
  
};

byte Lauflicht4[8][8] =
{
   { 0,0,0,0,0,0,0,1 },  
   { 0,0,0,0,0,0,1,0 },  
   { 0,0,0,0,0,1,0,0 },  
   { 0,0,0,0,1,0,0,0 },  
   { 0,0,0,1,0,0,0,0 },  
   { 0,0,1,0,0,0,0,0 },  
   { 0,1,0,0,0,0,0,0 },  
   { 1,0,0,0,0,0,0,0 }  
  
};


void schreibePunkt(byte punkt)
{
  digitalWrite(15, punkt);
}
  
void Lauflicht1Schreiben(byte digit)
{
  byte pin = 5;
  for (byte segmentZaehler = 0; segmentZaehler < 8; ++segmentZaehler)
  {
    digitalWrite(pin, Lauflicht1[digit][segmentZaehler]);
    ++pin;
  }
}

void Lauflicht2Schreiben(byte digit)
{
  byte pin = 5;
  for (byte segmentZaehler = 0; segmentZaehler < 8; ++segmentZaehler)
  {
    digitalWrite(pin, Lauflicht2[digit][segmentZaehler]);
    ++pin;
  }
}

void Lauflicht3Schreiben(byte digit)
{
  byte pin = 5;
  for (byte segmentZaehler = 0; segmentZaehler < 8; ++segmentZaehler)
  {
    digitalWrite(pin, Lauflicht3[digit][segmentZaehler]);
    ++pin;
  }
}

void Lauflicht4Schreiben(byte digit)
{
  byte pin = 5;
  for (byte segmentZaehler = 0; segmentZaehler < 8; ++segmentZaehler)
  {
    digitalWrite(pin, Lauflicht4[digit][segmentZaehler]);
    ++pin;
  }
}

void setup() {                
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(inputPin, INPUT);  
}

void loop()


{
 state = digitalRead (inputPin);
 
 if(laststate == LOW && state == HIGH) {
    
    switch(count) {
     case 1:     while (count = 1)
                 {led_1();}
                 break;
     case 2:     while (count = 2)
                 {led_2();}
                 break;
     case 3:     while (count = 3)
                 {led_3();}
                 break;
     case 4:     while (count = 4)
                 {led_4();}
                 break;
              
    
    }  
    count++;
    if(count == 5) count = 1;
 }
 laststate = state;
}

void led_1()
{
  schreibePunkt(0);  
  for (byte zaehler = 14; zaehler > 0; --zaehler)
  {
   delay(250);
   Lauflicht1Schreiben(zaehler - 1);
  }
}

void led_2()
{
   schreibePunkt(0);
  for (byte zaehler = 7; zaehler > 0; --zaehler)
  {
   delay(250);
   Lauflicht2Schreiben(zaehler - 1);
  }
}

void led_3()
{
  schreibePunkt(0);
  for (byte zaehler = 8; zaehler > 0; --zaehler)
  {
   delay(250);
   Lauflicht3Schreiben(zaehler - 1);
  }
}

void led_4()
{
  schreibePunkt(0);  
  for (byte zaehler = 8; zaehler > 0; --zaehler)
  {
   delay(250);
   Lauflicht4Schreiben(zaehler - 1);
  }
}

Wäre super, wenn ihr mir weiter helfen könntet.

Gruss Daniel

Du kannst auch Interrupts auf allen anderen Pins auslösen. Das Problem dabei ist dass sich immer 8 der normalen Pins einen Interrupt-Vektor teilen und man dann noch abfragen muss welcher Pin das ausgelöst hat. Dafür gibt es aber eine fertige Lib mit der man sich das sparen kann:
http://playground.arduino.cc/Main/PinChangeInt

Am besten du verwendest einen Timer statt Delays. Das muss man auch nicht unbedingt per Hand machen:
http://playground.arduino.cc/Main/MsTimer2

Der set() Funktion dieser Library wird einfach eine andere Funktion übergeben, die alle x ms aufgerufen wird. Mit stop() und nochmal set() sollte sich das auch ändern lassen.

Oder eleganter mit einem Function Pointer. Nicht getestet, aber es kompiliert:

#include <MsTimer2.h>

void(*currentFunction)();

void setup() 
{
  currentFunction = led_1;
  MsTimer2::set(500, currentFunction);
  MsTimer2::start();
}

void loop() 
{
     if(....)
          currentFunction = led_1;
     else if(...)
          currentFunction = led_2;
}

void led_1()
{
}

void led_2()
{
}

Statt den for-Schleifen zählst du das dann per Hand, so dass einfach beim nächsten Methoden-Aufruf eins weiter gezählt wird.

So ähnlich:

void led_1()
{
  schreibePunkt(0);  
  static byte zaehler = 14;
  
  Lauflicht1Schreiben(zaehler - 1);

   if(zaehler > 1)
      zaehler--;
   else
      zaehler = 14;
}

Durch das "static" behält die Variable ihren Wert von Aufruf zu Aufruf

Das kann man generell schöner und einfacher machen wenn man die I/O-Ports direkt anspricht

http://www.pighixxx.com/pgdev/Temp/Arduino_uno_Pinout_Web.png

Du kannst die 8 LEDs halb, halb auf zwei Ports verteilen (z.B. PD4-PD7 und PB0-PB3) Dann kannst du die LEDs in einem einzigen Byte verwalten. Das Byte wird dann per Shift-Operatoren hin-und-hergeschoben und mit ein paar Befehlen auf die zwei Ports geschrieben. Man muss nur die jeweils andere Hälfte des Bytes auf Null setzen.

Auch das Programm Lauflicht2 geht damit, wenn man zwei Bytes nimmt und die nach dem Schieben verodert.

Oder Du nimmst den Resettaster als Schalter: Multiple Modes | Blinkenlight. Dann übernimmt der Bootloader das Entpresslen :wink:

Ansonsten findest Du auf meiner Webseite jede Menge Varianten wie man sowas machen kann.

Wenns nicht gleich mit Timern o.ä. gehen soll

for(int i = 0; i < x)
{
if(millis() - previousMillis > interval) i++;
}

ungetestet. Sollte aber sogar gehen. Würde aber den Weg nehmen, den Serenfly angedeutet hat. Hab gerade leider keine Zeit zu schauen, auf was Udo mit seiner Seite aufmerksam machen möchte. :wink:

Gruß Stefan

Man könnte auch das mit dem Timer2 lassen und eine einfache millis()-Abfrage in loop() machen. Und wenn die Zeit abgelaufen ist den Function Pointer aufrufen. Hat den Vorteil, dass man auch Parameter an die Funktion übergeben kann, z.B. um den Zähler zurückzusetzen.

PeterPan12:
Das Programm funktioniert zum Teil. Nur das Problem ist, das ich im Unterprogramm led_1 bis led_4 ein Delay drin habe.

Einsicht ist der erste Weg zur Besserung.

PeterPan12:
Nur das geht leider nicht, weil sonst der Taster nicht abgefragt wird. Ich hoffe ihr könnt mir helfen, ich sitze da jetzt schon mehrere Tage dran, und komme einfach nicht weiter.

Algorithmen und Datenstrukturen. Diese beiden Dinge müssen im Programm möglichst sinnreich zusammenwirken.

Was Du da programmierst ist im Prinzip eine Phasenanimation mit eine festen Zykluszeit von 250 ms zwischen den anzuzeigenden Bildphasen. Und die Animation soll interaktiv mit einem Taster umschaltbar sein.

Ein passende Loop-Funktion in Pseudocode würde von mir programmmiert ungefähr so aussehen:

unsigned long letzteMillis=0;
byte animationsNummer=0;

void loop()
{
  if (millis()-letzteMillis>=250)
  {
    letzteMillis=millis();
    Ermittle_neues_Bild();
    Zeige_neues_Bild();
  }
  if (TasteGedrueckt)
  {
    animationsNummer++;
    if (animationsNummer>MAXANZAHL) animationsNummer=0;
  }
}

Also jeweils nach 250 Millisekunden wird einmal ganz schnell das neue Bild (also eine Animationsphase) ermittelt und nur diese eine Animationsphase an den LEDs gesetzt,, und ansonsten wird in der loop immer nur abgefragt, ob eine Taste gedrückt wurde. So kann das Programm blitzschnell auf Tastendrücke reagieren.

Bei den Datenstrukturen fällt mir auf, dass Du viel zu verschwenderisch mit RAM-Speicher umgehst, um die Animationen zu speichern. Um zwischen 0 und 1 zu unterscheiden reicht ein einzelnes Bit, Du verwendest aber 1 Byte (1 Byte = 8 Bits). Du kannst die Animationsphasen also locker mit einem Achtel des bisherigen Speicherbedarfs abspeichern. Beispiel:

byte Lauflicht2[] =
{
    0b10000001 ,  
    0b01000010 ,  
    0b00100100 ,  
    0b00011000 ,  
    0b00100100 ,  
    0b01000010 ,  
    0b10000001   
};

Durch die Binärdarstellung des Bytes mit dem Präfix "0b" kannst Du auch im Quellcode sehr schön sehen, welches die gesetzten Bits sind. Und auslesen würdest Du das Bit aus dem Byte mit der Arduino-Funktion bitRead().

Auf Mikrocontrollern ist es beim Programmieren ziemlich wichtig, Speicher zu sparen, weil Mikrocontroller nur wenig davon haben. Um so mehr Speicher Du sparst, um so mehr an Funktionen kannst Du in einem einzigen Sketch realisieren. Nur für den Fall, dass Dein Sketch am Ende noch mehr können soll als diese vier Animationen. So wie Du den RAM-Speicher in multidimensionalen Arrays im RAM reservierst, geht Deinem Programm sonst ziemlich schneller der RAM-Speicher aus.

Und delay() darf in interaktiven Programmen nur so sparsam verwendet werden, dass die Summe der auftretenden Delays nicht länger als die typische menschliche Reaktionszeit beträgt. Also maximal vielleicht 100 ms (0,1s) Delay zwischen zwei Tastaturabfragen ist ggf. noch machbar, damit ein Programm flüssig auf Benutzereingaben reagiert. Alles darüber geht gar nicht. Am besten gewöhnst Du Dir an, auf alle Delays im Programm zu verzichten.

Also ich würde bei dem Sketch schon an etlichen Stellen anders ansetzen.

Nicht mal das ist nötig. Man braucht für dieses einfache Muster nur zwei Bytes:

byte pattern1 = 0b10000000;
byte pattern2 = 0b00000001;

pattern1 = pattern1 >> 1;
pattern2 = pattern2 << 1;

byte pattern = pattern1 | pattern2;

In der Mitte gibt es einen Zustand doppelt bei dem die Bits ineinander geschoben werden, aber das kann man abfragen und zweimal schieben.

Bei den anderen Mustern reicht ein Byte und man muss nach 8 mal schieben die Richtung umschalten.

Da melde ich mich wieder zurück. Habe einige von euren Vorschlägen ausprobiert, aber leider klappte es nicht. Liegt wahrscheinlich daran, das ich alles noch nicht ganz so verstehe.

Habe mein Programm weit es gehend so gelassen wie es vorher war, und habe versucht den Lösungsvorschlag von Serenifly umzusetzen. Das mit Pointer klappte leider nicht so ganz, er fing sofort an, mit Lauflicht1. Also habe ich den Pointer mit im Unterprogramm gepackt. Da klappt es wunderbar. Es ist fast so, wie ich es mir vorgestellt habe. Jetzt habe ich nur das Problem, ich weiss nicht wie man einen Pointer wieder nullen kann. Ich will zum Beispiel, eine LED leuchten lassen, bevor led_1 aufgerufen wird. Wenn ich den Controller resete funktionierts auch. Nur wenn ich mich durch taste und zum Programm 1 wieder komme, leuchte die LED nur einmal ganz kurz auf, und macht mit den letzten Programm weiter bis Delay abegelaufen ist. Kann mir einer von euch erklären wie man den Pointer auf Null setzt? Oder wie man das lösen kann ?

Gruss

Daniel

if(laststate == LOW && state == HIGH) {
//abhängig vom counter passende funktion aufrufen
switch(count) {
case 1: digitalWrite(5, HIGH);
delay(1000);
led_1();
break;
case 2: led_2();
break;

void led_1()
{
currentFunction=led_1;
MsTimer2::set(warten, currentFunction);
MsTimer2::start();

schreibePunkt(0);
static byte zaehler = 14;

Lauflicht1Schreiben(zaehler - 1);

if(zaehler > 1)
zaehler--;
else
zaehler = 14;
}

Ja, sowie ich es geschrieben habe, startet er gleich mit einem Programm. Das ist etwas doof :frowning:

Du darfst aber auf gar keinen Fall den Pointer auf NULL setzten, da das in der MsTimer2 Lib nicht abgefragt wird. Ich habe nachgesehen. Das könnte man zwar in ein paar Sekunden ausbessern wenn man die .cpp Datei editiert, aber das wird für dich zu aufwending sein.

Sowie du das geschrieben hast brauchst du übrigens den Pointer nicht mehr.

Das einfachste ist du erstellst eine Dummy Funktion die nichts macht:

void nichts()
{
}

Die wird dann nur kurz aufgerufen und beendet sich gleich wieder.

Dann wird der Timer mit dieser initialisiert:

void setup() 
{
  currentFunction = nichts;
  MsTimer2::set(500, currentFunction);
  MsTimer2::start();
}

Der Rest dann wie ich ursprünglich geschrieben hatte und wenn du gar kein Programm ausführen willst setzt du den Pointer wieder auf "nichts". Und eventuell natürlich dann noch die LEDs alle ausschalten.

Die andere Möglichkeit ist den Timer zu setzen, aber nicht gleich zu starten:

void setup() 
{
  currentFunction = led_1;
  MsTimer2::set(500, currentFunction);
}

Das start() fehlt da. Das musst du dann in deinem switch-case machen:

if(laststate == LOW && state == HIGH) 
{
    switch(count) 
   {
     case 1:     digitalWrite(5, HIGH);
                 delay(1000);
                 currentFunction = led_1;
                 MsTimer2::start();
                 break;
     case 2:     currentFunction = led_2;
                 MsTimer2::start();
                 break;

Und wenn du nichts machen willst einfach stop() aufrufen. start() bräuchte man hier theoretisch nur beim ersten Mal, aber du änderst das vielleicht auch irgendwann mal so, dass er nicht immer bei 1 anfängt. Und es schadet auch nichts wenn der Timer intern bei jedem Programm zurückgesetzt wird.

Was dann noch ist, dass der Zähler in den Funktionen seinen Wert behält auch wenn dieses Programm schon beendet wurde. Das heißt wenn du bei led_1 auf 3 bist und dann umschaltest und dann wieder auf led_1 schaltest macht er bei 2 weiter. Und nicht bei 14. Das ist eventuell störend.
Die einfachste Lösung dafür ist die Zähler Variable global zu machen, statt sie in der Funktion zu verwalten. Dann kannst du sie von allen Stellen des Programms zurücksetzen. Das ist zwar nicht sehr schön, da die Funktionen verschiedene Start-Werte haben, aber es geht.

Du kannst dir aber Funktionen schreiben die das für dich macht. So ähnlich

byte zaehler;

void resetLed_1()
{
        zaehler = 14;
}

void resetLed_2()
{
        zaehler = 7;
}

Und die dann einmal aufrufen wenn du den Function Pointer änderst:

case 1:    
    resetLed_1();
    currentPointer = led_1;
    break;

Es gibt auch noch eine andere Lösung, aber noch mehr Optionen sind wahrscheinlich eher verwirrend.

Ist wie gesagt auch nur für den Fall dass dich das stört, dass das Programm nicht von vorne anfängt.

Vielen dank, du hast mir sehr geholfen. Habe es jetzt so gelöst, wenn der Taster gedrückt wird, wird gleichzeitig der Timer gestoppt.
Funkltioniert genauso, wie es ich mir erstmal vorgestellt habe. Vielen dank dafür.

Gruss

Daniel