Switch-Case durch Tastendruck abbrechen (sofort!)

Hi, direkt zum nächsten Problem wo mich Google nicht direkt weiter bringt.

Frage vorab: Was ist für eine int-Variable besser und performanter? Mehrere IF-Abfragen oder ein switch-case?

Anwendung: NeoPixel mit Arduino UNO als Controller.
Stolperstein: Bei Tastendruck soll das Programm gewechselt werden.
Problem: Wenn ich z.B. bei Case "2" auf den Button drücke, macht er erst noch seinen Task komplett zu Ende, dann hat er gemerkt, hey der Zähler wurde erhöht und springt zum nächsten passenden case :confused:

Ziel: Er soll sofort zum nächsten Case gehen, wie kann ich hier ein Abbruch o.ä. erzwingen?

Hier mein Versuch mit Interrupt:

// NeoPixel
#include <Adafruit_NeoPixel.h>
#define PIN 6
#define NUMPIXELS 60
#define BRIGHT 50
#define DELAY 15
const int potPin1 = 1;
const int potPin2 = 2;
const int potPin3 = 3;
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

// this constant won't change:
const int buttonPin = 2;    // the pin that the pushbutton is attached to

// Variables will change:
int buttonPushCounter = 0;   // counter for the number of button presses
int buttonState = LOW;         // current state of the button
int lastButtonState = 0;     // previous state of the button
int nrProgs = 3;             // number of different programs in switch-case

void setup() {
  pixels.begin();
  pixels.clear();
  pinMode(buttonPin, INPUT_PULLUP);
  // "Bei wechselnder Flanke auf dem Interruptpin" --> "Führe die ISR aus"
  attachInterrupt(digitalPinToInterrupt(buttonPin), increment, RISING);

  // ONLY FOR DEBUGGING & TESTING
  Serial.begin(9600);
  Serial.println("Starte 5Prog:");
}

void loop() {
  // reset buttonPushCounter
  if (buttonPushCounter > nrProgs)
    buttonPushCounter = 0;

  // Switch-Case to run different LED programs
  switch (buttonPushCounter) {
      Serial.println("SW");
    case 0:
      Serial.println("0");
      FillPot();
      break;
    case 1:
      Serial.println("1");
      Fade3In();
      Fade3Out();
      break;
    case 2:
      Serial.println("2");
      PartyModus(10, 3);
      break;
    case 3:
      Serial.println("3");
      for (int i = 0; i < NUMPIXELS; i++)
        setWhite(i);
      break;
    default:
      pixels.clear();
      break;
  }



  // turns on the LED every four button pushes by checking the modulo of the
  // button push counter. the modulo function gives you the remainder of the
  // division of two numbers:
  //  if (buttonPushCounter % 4 == 0) {
  //    digitalWrite(ledPin, HIGH);
  //  } else {
  //    digitalWrite(ledPin, LOW);
  //  }

}
void increment(){
  Serial.println("increment"); // ONLY FOR DEBUGGING
  buttonPushCounter++;
}

// LED Programs - ONLY FOR TEST PURPOSE, STILL NOT FINAL
// ReadIn from 3 potentiometers with gamma-correction
void FillPot() {
  pixels.fill(pixels.Color(pixels.gamma8(analogRead(potPin2) >> 2), pixels.gamma8(analogRead(potPin1) >> 2), pixels.gamma8(analogRead(potPin3) >> 2)), 0, NUMPIXELS);
  pixels.show();
  delay(50);
}

void setWhite(unsigned int px) {
  setColorDelay(px, 255, 255, 255);
}

//   FadeIn Begin2End
void Fade3In() {
  for (int i = 0; i < NUMPIXELS; i += 3) {
    setColorDelay(i, BRIGHT, 0, 0);
    setColorDelay(i + 1, 0, BRIGHT, 0);
    setColorDelay(i + 2, 0, 0, BRIGHT);
  }
}


//   FadeOff End2Begin
void Fade3Out() {
  for (int i = NUMPIXELS - 1; i > 1; i -= 3) {
    setColorDelay(i, 0, 0, 0);
    setColorDelay(i - 1, 0, 0, 0);
    setColorDelay(i - 2, 0, 0, 0);
  }
}

void PartyModus(unsigned int del, unsigned int crazyness) {
  // Party Modus!
  // More Random pixels.setPixelColor(random(60), 0); makes it more crazy
  // When less set 0, then reduce random to (200/120/120)
  // delay(30); makes it more quite
  pixels.setPixelColor(random(60), pixels.Color(pixels.gamma8(random(200)), pixels.gamma8(random(120)), pixels.gamma8(random(120))));
  for (int i = 0; i < crazyness; i++)
    pixels.setPixelColor(random(60), 0);
  pixels.show();
  delay(del);
}


void setColorDelay(unsigned int px, unsigned int r, unsigned int g, unsigned int b) {
  pixels.setPixelColor(px, pixels.Color(r, g, b));
  delay(DELAY);
  pixels.show();
}

tispokes:
Stolperstein: Bei Tastendruck soll das Programm gewechselt werden.
Problem: Wenn ich z.B. bei Case "2" auf den Button drücke, macht er erst noch seinen Task komplett zu Ende, dann hat er gemerkt, hey der Zähler wurde erhöht und springt zum nächsten passenden case :confused:

Ziel: Er soll sofort zum nächsten Case gehen, wie kann ich hier ein Abbruch o.ä. erzwingen?

Was Du brauchst, ist je case einen endlichen Automaten. Ob das mit Sachen wie FadeIn() geht bzw. kompatibel ist, weiß ich nicht. Vermutlich nicht.

Gruß

Gregor

Dann mußt Du Deinen Sketch total umstrukturieren.

Der Tastendruck muß nach jeder Datenversand an die NEOPixel kontrolliert werden. Entweder muß in jeder Schleife die Du hast kontrolliert werden oder Du programmierst nur eine Schlaife (loop()) und speicherst das was der Sketch gerade machen soll in Statusvariablen ( zb welcher Effekt aktiv ist und bei welchen Schritt des effekts Du gerade bist). Eben ein Endlicher Automat.
Keine Delay, keine wie auch immer gearteten Schleifen ( for, while oder ähnliches) oder sonstiges Programmkonstrukt das im loop() abzweigt und sein eigenes Süppchen in einer schleife kocht.

Grüße Uwe

noiasca:
Und nur so nebenbei zur deiner Ausgangsfrage: ob ein If oder Switch ein schneller wäre: "Das ist für dich nicht messbar". Ob das eine oder andere zwei Prozessor-Takte "schneller" wäre nimmt sich nichts. Schon gar nicht, wenn du auf der anderen Seite schnarchlangsame 9600 Baud verwendest. Was der Compiler aus einem C++ Code macht, sieht der Arduino-Nutzer eh nicht einfach. Geh davon aus, dass der Compiler das für dich beste draus macht und es an der Einzelsitutation liegt was zu weniger Prozessortakten führt. Du schreibst C++ Code, also schreibe den Code so, wie du (und andere) es auch in 6 Monaten verstehen.

Danke für deine ausführliche Antwort. Im Bezug zur Nutzung der Serial-Klasse, hast du den Fakt überlesen, dass dieses gerade nur zum testen dort ist und später komplett rausfliegt.

Mich interessiert schon, auf Microinstruktionen-Ebene, ob eine switch-case oder ein if im Bytecode weniger Platz benötigt. Damit kann ich auf Dauer meinen Code besser machen bzw wenn ich neue Projekte habe, diese damit direkt auf die bessere Weise schreiben.
Dass ein Switch-Case am Ende nur eine Branch BREQ oder ähnliches ist, ist mir schon klar.

Das Tutorial hab ich mir angesehen, delay() war halt erstmal praktisch. Ja werde ich umsetzen.

Auf so einer niedrigen Ebene muss man Code nicht voreilig optimieren. Das ist Zeitverschwendung. Es gibt sicherlich Anwendungen wo man da Zeit herausschlagen kann und es relevant ist. Aber nicht bei dem was du da machst. Hier ist gut lesbarer Code viel wichtiger

Und du hast es hier mit einem optimierenden Compiler zu tun. Da kann man nicht immer genau voraussagen was besser ist.

Hallo,

was ich pauschal sagen kann ist, das die Abarbeitungszeit eines switch case immer konstant ist. Ausnahme Fallthrough.
if/else oder if/else if Orgien sind nicht konstant.

Wie Serenifly schon sagt sind das Mikrooptimierungen die im Grunde nichts bringen. Ich betrachte das eher von der Seite der Lesbarkeit/Wartbarkeit was mir mehr bringt. Deswegen mache ich viel mit switch case anstatt in if/else Orgien abzusaufen.

Mich interessiert schon, auf Microinstruktionen-Ebene, ob eine switch-case oder ein if im Bytecode weniger Platz benötigt. Damit kann ich auf Dauer meinen Code besser machen bzw wenn ich neue Projekte habe, diese damit direkt auf die bessere Weise schreiben.
Dass ein Switch-Case am Ende nur eine Branch BREQ oder ähnliches ist, ist mir schon klar.

Naja....

Wenn der Compiler das schön anordnen kann, macht er aus einem Switch Case eine Sprungtabelle, und schneller gehts nimmer.

Ist es nicht schön anzuordnen, pervertiert das Konstrukt zu einer if/else Kaskade.

Also:
Immer den Einzelfall betrachten.
In den generierten Assemblercode schauen, dort findest du die Wahrheit!

Ein erneutes Danke.
Warum die Frage zu Optimierung?
Es geht nicht um die Geschwindigkeit. Klar, wenn ich den Button drücke soll er reagieren. Aber da sind 50 ms hin oder her egal. Dazu ersetze ich das delay() sinnvoll, hab daraus dann n Haufen gelernt und es läuft.

Es geht vielmehr um die Größe des Bytecodes, da mir vorstelle einen ATTiny85 oder 45 zu nutzen, sind evtl. einfache Einsparungen schon sinnvoll zu wissen.

Auf der Microcontroller-Ebene kann ich das null einschätzen. In Java oder C# schreibt man halt lesbaren Code, Speicherplatz ist meist genug vorhanden. (Jetzt nicht töten - das war etwas überspitzt. Aber ein PC hat im vgl zu einem µC nunmal exponentiell viel viel mehr Speicher - das wollte ich ausdrücken)

combie:
Also:
Immer den Einzelfall betrachten.
In den generierten Assemblercode schauen, dort findest du die Wahrheit!

Ja, rein aus Interesse werde ich mir den Assemblercode mal anschauen. Ich sag immer Bytecode, weil der Assemblercode ja auch aus Hex-Paaren, also Bytes besteht, sorry.

Hallo,

und jetzt noch mal die Frage: Was hat der bereits vom Ansatz her fasche Tittel des Posts damit zu tun ??

  1. Thema verfehlt
  2. Problem nicht erkannt

Du hängst in irgendeiner Funktion blockierend fest und willst das aufrufende Swich Case unterbrechen. Nein das willst Du nicht. Bevor jetzt noch irgend Jemand auf die völlig unsinnige Idee kommt das mit einen Interupt machen zu wollen - nein auch das ist falsch.

Heinz

tispokes:
Ich sag immer Bytecode, weil der Assemblercode ja auch aus Hex-Paaren, also Bytes besteht, sorry.

Das ist nach das gleiche. Byte Code in Java und C# ist ein Zwischencode der zur Laufzeit von einem JIT-Compiler in einer virtuellen Maschine übersetzt wird.
Assembler wird direkt in Maschinencode übersetzt

Ja, rein aus Interesse werde ich mir den Assemblercode mal anschauen. Ich sag immer Bytecode, weil der Assemblercode ja auch aus Hex-Paaren, also Bytes besteht, sorry.

Ich meine weder Bytecode noch Bytes!

Sondern die Assembler Mnemonics!

combie:
Ich meine weder Bytecode noch Bytes!

Sondern die Assembler Mnemonics!

Schon klar, aber die bekommt man ja aus dem Assemblercode. Oder speichert Arduino die Mnemonics ab? Denke nicht.


Hier hab ich mal versucht mit millis() statt delay() zu arbeiten.
Geht um case = 1 (FadeIn, dann FadeOut - immer im Loop). Was kann ich hier verbessern? Erscheint mir zu umständlich.

Interrupt ist noch drin, wird noch gefixt.

// NeoPixel
#include <Adafruit_NeoPixel.h>
#define NUMPIXELS 60
#define BRIGHT 50
const byte pinCtrl = 6;
const byte potPin1 = 1;
const byte potPin2 = 2;
const byte potPin3 = 3;
const byte buttonPin = 2;

// Variables will change:
byte buttonPushCounter = 0;   // counter for the number of button presses
char buttonState = LOW;         // current state of the button
byte lastButtonState = 0;     // previous state of the button
byte nrProgs = 2;             // number of highest case
unsigned long currMillis = 0; // Vars for calculation of delayless timeout
unsigned long prevMillis = 0;
unsigned long intervall = 50; // timeout in ms
bool fadein = true;
byte currLED = 0;                 // save the current LED for a loop

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, pinCtrl, NEO_GRB + NEO_KHZ800);

void setup() {
  pixels.begin();
  pixels.clear();

  // Lege den Interruptpin als Inputpin mit Pullupwiderstand fest
  pinMode(buttonPin, INPUT_PULLUP);
  // Lege die ISR 'blink' auf den Interruptpin mit Modus 'CHANGE':
  // "Bei wechselnder Flanke auf dem Interruptpin" --> "Führe die ISR aus"
  attachInterrupt(digitalPinToInterrupt(buttonPin), interrupt, RISING);
}


void loop() {
  // reset buttonPushCounter
  if (buttonPushCounter > nrProgs)
    buttonPushCounter = 0;

  // Switch Case to run Programm
  switch (buttonPushCounter) {
    case 0:
      FillPot();
      break;
    case 1:
      Fade3In();
      Fade3Out();
      break;
    case 2:
      pixels.fill(0, 0, NUMPIXELS);
      break;
    default:
      pixels.clear();
      break;
  }

}
void interrupt() {
  buttonPushCounter++;
  return;
}

// LED Programs
// ReadIn from 3 potentiometer with gamma-correction
void FillPot() {
  pixels.fill(pixels.Color(pixels.gamma8(analogRead(potPin2) >> 2), pixels.gamma8(analogRead(potPin1) >> 2), pixels.gamma8(analogRead(potPin3) >> 2)), 0, NUMPIXELS);
  pixels.show();
}

//   FadeIn Begin2End
void Fade3In() {
  if (fadein && currLED < NUMPIXELS) {
    if (millis() - prevMillis >= intervall) {
      prevMillis = millis();
      pixels.setPixelColor(currLED++, pixels.Color(BRIGHT, 0, 0));
      pixels.show();
      pixels.setPixelColor(currLED++, pixels.Color(0, BRIGHT, 0));
      pixels.show();
      pixels.setPixelColor(currLED++, pixels.Color(0, 0, BRIGHT));
      pixels.show();
    }

    // End of LED Strip reached, now fadein is completed, set for FadeOut
    if (currLED >= NUMPIXELS) {
      fadein = !fadein;
      currLED = NUMPIXELS;
    }
  }
}

//   FadeOut Begin2End - Update ToDo
//for (int i = 0; i < NUMPIXELS; i++) {
//  setColorDelay(i, 0, 0, 0);
//}

//   FadeOut End2Begin
void Fade3Out() {
  if (!fadein && currLED >= 0 && currLED <= NUMPIXELS && millis() - prevMillis >= intervall >> 2) {
    prevMillis = millis();
    pixels.setPixelColor(--currLED, pixels.Color(0, 0, 0));
    pixels.show();
  }

  // FadeOut done, reset
  if (currLED > NUMPIXELS && millis() - prevMillis >= intervall)
  {
    prevMillis = millis();
    fadein = !fadein;
    currLED = 0;
  }
}

Schon klar, aber die bekommt man ja aus dem Assemblercode. Oder speichert Arduino die Mnemonics ab? Denke nicht.

Falsch gedacht!

Beides ist möglich!
Entweder überredet man den Compiler die Temporären Dateien stehen zu lassen, oder man bemüht einen Disassembler.

Erscheint mir zu umständlich.

Mir auch!

Kann dir nur den Tipp geben, das alles aus endlichen Automaten zusammenzusetzen.
Nur so kann man das schön trennen und übersichtlich gestalten.

combie:
Falsch gedacht!

Beides ist möglich!
Entweder überredet man den Compiler die Temporären Dateien stehen zu lassen, oder man bemüht einen Disassembler.

Also ich glaube wir reden aneinander vorbei.
Mnemonics sind für mich z.B. BNEQ 0x10
Der compiler erstellt doch aber Code eben den Assembler Code. Binär oder in HEX gespeichert. Ich meine Arduino speichert das in der .hex-Datei (Temp Files) Ind nicht in Mnemonics.

combie:
Mir auch!

Kann dir nur den Tipp geben, das alles aus endlichen Automaten zusammenzusetzen.
Nur so kann man das schön trennen und übersichtlich gestalten.

Bisher hab ich FSM nur auswerten oder konstruieren müssen, ich werde es Mal Zeichen und dann versuchen in Code umzusetzen.

Danke für die Unterstützung.

tispokes:
Es geht vielmehr um die Größe des Bytecodes, da mir vorstelle einen ATTiny85 oder 45 zu nutzen, sind evtl. einfache Einsparungen schon sinnvoll zu wissen.

#define NUMPIXELS 60

Wenn Du in Deinem fertigen Projekt 60 NEOPIXEL verwenden willst dann muß ich dich stark entäuschen.
60 Neopixel brauchen 180 Byte Datenspeicher. Da ist es schwierig mit einem Controller mit 128 bzw 512 RAM auszukommen. Auch bei bester Optimierung nicht. Da sind nicht 2K bzw 8K Flash das Nadelöhr.

Grüße Uwe

Ich habe mit dem Tiny85 schon 75 Neopixel angesteuert und mit den FastLED Farbpaletten. Das geht schon, aber es passen vielleicht 6 Simple Animationen drauf. Beim Testen ist es dann aber auch Glückssache, dass das Programm nicht Crasht.

Also ich glaube wir reden aneinander vorbei.
Mnemonics sind für mich z.B. BNEQ 0x10
Der compiler erstellt doch aber Code eben den Assembler Code. Binär oder in HEX gespeichert. Ich meine Arduino speichert das in der .hex-Datei (Temp Files) Ind nicht in Mnemonics.

Wenn du meinst...

combie:
Wenn du meinst...

Okay, ja meine ich:
Store the normally temporary intermediate files( *.s, *.i, *.o) permanently.

Das findet man hinter deinem Link.

Ich hab da auch ein Link: Overall Options (Using the GNU Compiler Collection (GCC))

  • *.i -> C source code that should not be preprocessed.
  • *.s -> Assembler code.
  • *.o -> output file

Ich hab das jetzt mal gemacht:
a-Datei sind schonmal keine Mnemonics
o-Datei auch nicht
i-Datei gibts keine

Das einzige was ich finde, auch ohne die besagte Option ist eine .hex-Datei
Und da steht Assemblercode in HEX-Form drin.

Also bitte, wenn du so überzeugt davon bist, dass die Arduino IDE Mnemonics ausspuckt (warum sollte sie das?) dann zeig es an einem Beispiel.

Edit: "Wer seine Meinung nie zurückzieht, liebt sich selbst mehr als die Wahrheit." sag ich da nur... :slight_smile:

Such mal nach avr-objdump.
Maschinencode lässt sich eindeutig in Assembler-Mnemonics zurück übersetzen.

Richtiger Assembler Code mit selbsterklärenden Variablennamen und Kommentaren ist das zwar nicht, aber msn kann mit etwas Übung erkennen, was der Compiler erzeugt hat.

Machinencode in Assembler rückübersetzen ist relativ einfach. Es kann bei komplexeren Assembler Dialekten Mehrdeutigkeiten geben, aber meistens haben Maschinencodes ein direkt entsprechendes Mnemonic. Andere Sachen gehen natürlich verloren, aber nicht das

AVR objdump zeigt auch die ursprünglichen Funktionen noch an:

size_t Print::print(unsigned char b, int base)
{
  return print((unsigned long) b, base);
 5ee: 68 81       ld r22, Y
 5f0: 70 e0       ldi r23, 0x00 ; 0
 5f2: 90 e0       ldi r25, 0x00 ; 0
 5f4: 80 e0       ldi r24, 0x00 ; 0
}

Hier sind die Schritte von Source Code zu Hex Datei auch gut erklärt:
https://www.nongnu.org/avr-libc/user-manual/group__demo__project.html