Setup abbruch durch Funktionsaufruf

Bin zum ersten mal hier. Habe bereits von euch vieles gelesen und gelernt und darf an dieser Stelle ein grosses Lob schicken, da ich finde dass hier ein sehr guter Umgang gepflegt wird.
Nun brauche ich spezielle Hilfe. Im Forum hab ich nichts gefunden. Ich bin nicht ganz neu in der Arduinowelt. Praxisnahe Hobbyanwendung geht mal gerade so, Systemverständnis also was im Prozessor abläuft eher rudimentär. Bin halt Maschinenbauer und kein Elektroniker...
Aber nun zu meinem Code Problem.
Ich stelle gerade einen Drehzahlmesser (Messscheibe - schwarz/weiß) zusammen mit Phototransistor, Interruptroutine, kleine und hohe Drehzahlen, Anzeige auf OLED. Das funktioniert alles. Nun möchte ich noch zwei Taster zufügen, mit denen ich die Anzahl der Messfelder im Code verändern kann. Die Messscheibe wird vom Phototransistor abgetastet. Hierzu habe ich eine Tasterabfrage mit entprellen und short/long press. Funktioniert stand alone. Füge ich dieses aber als Funktion dem Programm bei (egal ob im Setup (weil ja nur einmal abgefragt wird) oder im loop bleibt der Setup hängen. Programm kompiliert fehlerfrei und lädt hoch. Ich verwende Arduino IDE 1.89 und UNO Rev 3.

Hier der Code.
Ich hoffe

  1. alles in tags richtig dargestellt,
  2. lesbar und verständlich. (Habe den Code gekürzt zum besseren lesen
  3. falls alles gebraucht wird kann ich das natürlich posten
  4. hoffe ich konnte das Problem ausführlich genug schildern
  5. hoffe auf Hilfe
  6. wenn nix geht, läuft der Code auch ohne Taster ... ich möchte aber gern was lernen...

```
/*
   
               V03.1 Taster zur Auswahl Messteilung zugefuegt. -- bleibt im Setup haengen. Funktion stand alone ok.
*/
//***** OLED 0.96" Display *****
#include <Adafruit_GFX.h>  // Include core graphics library for the display.
#include <Adafruit_SSD1306.h>  // Include Adafruit_SSD1306 library to drive the display.
Adafruit_SSD1306 display(128, 64);  // Create display.
#include <string.h> // Inkludiere die string.h-Bibliothek
#include <Fonts/FreeMonoBold18pt7b.h>  // Add a custom font.
//***** INTERRUPT *****
int motordrzPin1 = 7;
int motordrzPin = 2;                            // INT0; interruptPin 2 - obstacle avoidance sensor interface
volatile unsigned long zeitGemessen = 0;
volatile unsigned long letzteMessung = 0;
volatile float drehzahlMotor = 0;
volatile float drehzahldurchschnitt = 0;
unsigned long anzahl = 0;
unsigned long anzahlAlt = 0;
unsigned long drehzahlMotorSumme = 0;
unsigned long drehzahlMotorSummeAlt = 0;

//***** KLEINE DREHZAHLEN *****
unsigned long messZeit = millis();
unsigned long zeitProUmdrehung = 0;
bool motorPin = true;
bool status_motorPin = true;
//bool status_Impuls = true;
float abwDrz = 2;
float faktor_Durchschnitt = 1.0;
float faktor_drz = 0.0;
char stabilitaet[20];
unsigned int zaehler = 0;

//***** TASTER *****
const int button1Pin = 5; // Pin für Taster 1
const int button2Pin = 6; // Pin für Taster 2

int button1State = HIGH; // Aktueller Zustand von Taster 1
int button2State = HIGH; // Aktueller Zustand von Taster 2

int button1PreviousState = HIGH; // Vorheriger Zustand von Taster 1
int button2PreviousState = HIGH; // Vorheriger Zustand von Taster 2

unsigned long button1PressStartTime = 0; // Startzeit des Taster-Drucks von Taster 1
unsigned long button2PressStartTime = 0; // Startzeit des Taster-Drucks von Taster 2

const unsigned long debounceDelay = 50; // Entprellungsverzögerung (in Millisekunden)
const unsigned long longPressDelay = 1000; // Zeit für langen Tasterdruck (in Millisekunden)
bool button2LongPressDetected = false; // Flag für erkannten Long-Press von Taster 2
int counter = 1; // Variable zum Hoch- und Herunterzählen
int teilung = 1;                              //Messscheibe , Angabe fuer Impulse pro Umdrehung
int freigabe = true;

void setup() {
  Serial.begin(115200);
  pinMode(button1Pin, INPUT_PULLUP); // Taster 1 als Eingang mit Pull-Up-Widerstand
  pinMode(button2Pin, INPUT_PULLUP); // Taster 2 als Eingang mit Pull-Up-Widerstand
  pinMode(motordrzPin, INPUT);                    // Define obstacle avoidance sensor as

  // OLED 0.96" Display:
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // Initialize display with the I2C address of 0x3C.

  delay(1000);                    //OLED Vorbereitung
  display.clearDisplay();  // Clear the buffer.
  display.setTextColor(WHITE);  // Set color of the text to white.
  display.setRotation(0);  // Set orientation. Goes from 0, 1, 2 or 3.
  display.setTextWrap(false);  // By default, long lines of text are set to automatically “wrap” back to the leftmost column.
  display.dim(0);  // Set brightness (0 is maximum and 1 is a little dim).
  display.clearDisplay();  // Clear the display so we can refresh.

  display.setTextSize(2);  // OLED Darstellung Version
  display.setCursor(10, 0);
  display.println("Drehzahl-");
  display.println("  messer");
  display.setTextSize(1);
  display.setCursor(0, 38);
  display.println("      Vers. V0.4");
  display.setCursor(0, 52);
  display.println("     v 26.06.2023");
  display.display();
  delay (5000);
  display.clearDisplay();

  display.setTextSize(1); // OLED Darstellung Auswahl Messteilung soll spaeter in den Tasteraufruf
  display.setCursor(18, 5);
  display.println("Messteilung");
  display.setCursor(24, 20);
  display.println ("FIX 1-Teilung");
  display.setCursor(10, 35);
  display.println ("Start Messung");
  display.setCursor(10, 50);
  display.println ("press Taster");
  display.display();
  delay (3000);

  Serial.println("Initalisierung");  //das ist das einzige was noch im Serial zu sehen ist

  /*    Tasteraufruf nur bsp, beinhaltet in einer funktionierenden Version 2 Taster, short/long press
  // Problemcode im Setup wenn auskommentiert bleibt Setup haengen 
 while (freigabe == true) {
        int button2Reading = digitalRead(button2Pin);
            if (button2Reading == LOW) {
            freigabe = false;
             Serial.print("Start Messung "); Serial.println (freigabe);
      }
    }
  */
  attachInterrupt(digitalPinToInterrupt(motordrzPin), drehzahl, RISING);
}
void loop() {

  /* alternativer code im loop, wenn auskommentiert bleibt Setup haengen 
 if (freigabe == true) {
    taster();
    }
  */
  rpm_Anzeige ();                                             //wird normal ausgefuehrt obwohl auch Funktion
  while (drehzahlMotor <= 200) {                 //wird normal ausgefuehrt, obwohl auch While Schleife

     motorPin = digitalRead (motordrzPin);           //Umdrehungsberechnung, hier soll ohne Interrupt nur der Pin ausgelesen werden
    if (motorPin != status_motorPin) {              //aendert Status nach LOW / HIGH
      status_motorPin = motorPin;
      if (motorPin == HIGH) {                       //ausgefuehrt,wenn PIN HIGH, LOW passiert (egal wie lange) nichts.   
       
        zeitProUmdrehung = millis() - messZeit;     //alte Startzeit verwenden
        drehzahlMotor = 60000.0  / (zeitProUmdrehung * teilung); //Berechnung der Umdrehungszeit
        messZeit = millis();                        //neue Startzeit festlegen
        abwDrz  = drehzahlMotor - drehzahldurchschnitt;

    // .......code gekuerzt hier steht nur etwas zur Messwertabweichung

        drehzahldurchschnitt = faktor_Durchschnitt * drehzahldurchschnitt + faktor_drz * drehzahlMotor;
//... code gekuerzt, hier sind nur OLED befehle
    
      }
    }
  }

  if (drehzahlMotor >= 200)                    //Berechnung Motordrehzahl  groesser 200 Umin und Anzeige 
  {
  
    drehzahlMotor = (drehzahlMotorSumme - drehzahlMotorSummeAlt) / (anzahl - anzahlAlt);
   //... code gekuerzt nur OLED und Serial befehle
    anzahlAlt = anzahl;
    drehzahlMotorSummeAlt = drehzahlMotorSumme;
  }
}
void drehzahl()               //Interruprroutine
{                                        
  anzahl++;
  zeitGemessen = micros() - letzteMessung;
   letzteMessung = micros();
  drehzahlMotor = 60000000 / zeitGemessen;
  drehzahlMotorSumme += drehzahlMotor;
  drehzahldurchschnitt = 0.9 * drehzahldurchschnitt + 0.1 * drehzahlMotor;
}
void rpm_Anzeige () {          // OLED Anzeige statisch RPM oberste Zeile, funktioniert 
  display.clearDisplay();
  display.setTextSize(2); 
  display.setCursor(0, 0);  // (x,y).
  display.println("RPM:");  // Text or value to print.
  display.display();
}
/*
Aufruf dieser Funktion erfolgt nicht wg Setup Problem
void taster() {
  while (freigabe == true) {
    int button2Reading = digitalRead(button2Pin);
      if (button2Reading == LOW) {
      freigabe = false;
      Serial.print("Start Messung "); Serial.println (freigabe);
    }
  }
}
*/
```

gib dir mal ein Serial Println als letzte Zeile im Setup.
Ergänze ein Serial.println im loop.
Was wird ausgegeben?

Zeig mal die Beschaltung deines Tasters ... echte Bilder aus denen wir einwandfrei erkennen, dass das richtig angeschlossen ist.

Wenn du in loop auf volatile Variable (die in der ISR geändert werden) zugreifst, und diese größer als 1 byte sind, muss das unter geschlossenem Interrupt erfolgen. Wird außerhalb von Arduino auch gern atomic-block genannt.

Da könnte es hängen...

  • while in loop ist grundsätzlich ein Design-Fehler ... :slight_smile:
  • Eine ISR allein ist noch kein Multitasking.

Woran machst du das fest?

Hi noiasca,
anbei Serial Monitor copies.
mit dem Serial.print Befehl im Setup macht er nix. Normalerweise würde der OLED ja den Versions-Bildschirm zeigen. Auch der bleibt ohne Reaktion.
Ohne Serial.prints im Setup funktioniert alles.
Foto Testaufbau

Das ist die Ausgabe ohne zus. Serial.prints.
aus Setup:
  */
  attachInterrupt(digitalPinToInterrupt(motordrzPin), drehzahl, RISING);
  //Serial.println("die letzte Setup Zeile");
}
void loop() {
 //Serial.println("die erste Loop Zeile");
  /* if (freigabe == true) {
   taster();
    }//alternativer Versuch aus loop, nur einmalig, setup b

14:18:54.746 -> Initalisierung
14:18:57.332 -> , DRZklein: 5.14, durch: 3.75, abwDrz: 4.642, faktor_drz: 0.7
14:18:58.298 -> , DRZklein: 64.24, durch: 47.90, abwDrz: 54.465, faktor_drz: 0.7

******************************************************************

Hier die Ausgabe mit zus Serial.prints:

keine Ausgabe am Serial Monitor , nix, blanker Bildschirm

*******************************************************************


Hier die Ausgabe ohne  Serial.prints im Setup, mit Serial Print im loop

14:26:17.402 -> Initalisierung
14:26:17.402 -> die erste Loop Zeile
14:26:18.804 -> , DRZklein: 5.73, durch: 4.94, abwDrz: 0.791, faktor_drz: 0.0
14:26:20.220 -> , DRZklein: 41.58, durch: 31.67, abwDrz: 33.034, fakt

or_drz: 0.7

Hi Plumps,
Das hängenbleiben mache ich daran fest, dass er

  1. im Setup den OLED Bildschirm initialisiert und die Version darauf anzeigt
  2. im Loop gar nichts passiert, auch keine Serial.prints

daraus schließe ich, dass er das Setup nie verlässt.
Es gab auch Situationen, wo er wiederholt "Initialisierung" auf dem Serial Monitor angezeigt hatte.
Das passiert, wenn ich einen Serial.println befehl vor die Initialisierung des OLED schreibe
(also hier zB in Zeile 76 Serial.println("Initalisierung1"); eingefügt. Das ist direkt nach pinMode(...); und vor OLED Initialisierung.
Ergebnis:
14:47:20.414 -> Initalisierung1
14:47:21.425 -> Initalisierung1
14:47:22.396 -> Initalisierung1
14:47:23.428 -> Initalisierung1
14:47:24.402 -> Initalisierung1
14:47:25.413 -> Initalisierung1

und wenn ich den Serial Befehl in Zeile 87 zw.
display.clearDisplay(); // Clear the display so we can refresh.
Serial.println("Initalisierung1");
display.setTextSize(2); setze schafft er es noch nicht einmal den Text auf dem Monitor vollständig anzuzeigen. Aber auch keine Wiederholungen.
Das sieht dann so aus: (sogar ohne Zeitstamp)
Initial

Hi Michael,
ähm... also interrupts sind für mich ja noch so ein Buch mit 7 Siegeln. Könnte ich in der Interruptausführung zB einen Flag setzen und damit dann die While Schleife steuern? oder mus die Drehzahlberechnung da ganz raus (Stimmt dann die gemessene Zeit noch?)...

Kleine misst man über die Zeit zwischen den Flanken.
Große über die Anzahl pro Zeiteinheit

Dabei kann man durchaus die Timerhardware zur Hilfe nehmen.
Das Datenblatt liefert Information dazu.

In der Interrupt-Routine sowenig wie möglich machen, ist immer eine gute Idee.
Aus dem while ein if machen und statt zu warten, beim nächsten loop Durchlauf nochmal schauen, ob was zu tun ist, ist der Denkansatz in Richtung blockadefreiem Programmieren.
Wenn so ein Puls deutlich länger als einige Microsekunden ist, wäre es ohne Interrupts wohl auch möglich. Im Millisekundenbereich sicher einfacher.

Ein Motor mit 120 bis 6.000 RPM mit der zweigeteilten Scheibe und also einem Tastverhältnis von 1:1 macht Pulse von 250 ms bis 5 ms Dauer. (Bei 60.000 RPM wären es noch 500 µs, da müsste man schauen, ob das Display den Arduino trödeln lässt)

hiermit hast du ja praktisch eine schleife in der schleife gebaut (while innerhalb von loop). ist ja im prinzip auch schon das was michael_x sagt.
wenn deine drehzahl jetzt (zufällig) genau gleich 200 ist wird erst dieser teil durchlaufen und der untere Teil mit der Berechnung zu größeren drehzahlen auch.
für die taster würde ich die lib ezButton empfehlen. die macht einem das Leben bzgl entprellen usw. erheblich leichter.

bezüglich des "hängen" bleibens wenn du die taster funktion einkommentierst könnte es daran liegen, dass dort auch noch mal ein while (freigabe == true) auftaucht und letztlich mit dem loop bzw. dem dortigen while konkurriert und deshalb ein Teil deines Programms nicht mehr ausführt.

Liest sich so als ob freigabe = false die Messung startet, was zwar nicht weiter dramatisch ist sich aber doch etwas merkwürdig liest :slight_smile:
So als Anregung:

void taster() {
	int button1Reading = digitalRead(button1Pin); 
	int button2Reading = digitalRead(button2Pin);   
	if (button2Reading == LOW) {
		freigabe = false;
		Serial.print("Start Messung "); Serial.println (freigabe);
	} else if (button1Reading == LOW) {
		freigabe = true;
		Serial.print("Stoppe Messung "); Serial.println (freigabe);
    }
}

Wieviel RAM ist eigendlich nach dem Compilieren frei?

Hi Rintin,
da ist noch Spielraum denke ich, so viel los ist da nicht....
Der Sketch verwendet 19062 Bytes (59%) des Programmspeicherplatzes. Das Maximum sind 32256 Bytes.
Globale Variablen verwenden 845 Bytes (41%) des dynamischen Speichers, 1203 Bytes für lokale Variablen verbleiben.

Hi Michael x,
ist das nur eine "programmier hygienische" Aussage? Das While habe ich ja nun schon öfter im loop verwendet weil ja genau an dieser Stelle etwas gemacht werden soll, zB zählen oder einen Taster freigeben. Auslagern der Schleife in eine Funktion, die vom Loop aufgerufen wird hätte ja den gleichen Effekt wie im Loop.
Mache ich zB eine Tasterabfrage über if... dann weiss ich ja nie wann ich den richtigen Zeitpunkt zum drücken des Tasters erwische weil der loop ja gerade an einer anderen Stelle ist.
In meinem obigen Programm zB soll ja die Drehzahl gemessen und ausgegeben werden, wenn sie kleiner als 200 ist mehr will ich da ja gar nicht.
Versuche aber nun mal mein Programm ohne die While Schleife zu bauen. Mal sehen ob mir das gelingt... :face_with_head_bandage:

Hi db-engeneer,
vielen Dank für die Lib, werde ich mal ausprobieren. Bislang hab ich das halt "klassisch" umgesetzt. Braucht immer eine menge Nachdenkzeit (und das alles nur wegen dem Nachtwächter...).
Ich stimme zu, das das merkwürdig aussieht, aber die while schleife soll ja auch solange laufen, bis sie durch "freigabe" verlassen werden kann.
Lese ich aber nun michael x Kommentar, ist die if Lösung wohl die bevorzugte. :face_with_head_bandage:

Mir fällt gerade auf dass du in deinem loop eine division drin hast. Du stellst aber sicher das du nicht durch 0 teilst oder :wink:
ZeitProUmdrehung wird schließlich mit 0 initialisiert...

Das ist dein "Design-Fehler".
Wenn ein loop-Durchlauf keine Zeit braucht, dafür unendlich oft drankommt und so alles gleichzeitig machen kann,
wird aus "an dieser Stelle" ein "wenn der Zustand soundso ist" und aus dem while ein if.

oh Gott, nee das stelle noch nicht sicher, daher die ovf Meldungen am Display. Hab mir da schon mehrfach die definitionen angeschaut , alles unsigned long. Aber wenn ich das durch Null teile....
Viiiiiielen Dank.

Du könntest direkt vor der division mit

if (zeitProUmdrehung > 0 && teilung > 0) {
  // nur dann dividieren
}

abfangen bzw sicherstellen dass niemals nie durch 0 geteilt wird.

Also, ganz herzlichen Dank, das war mir überhaupt nicht klar. Mit der Änderung von while(..) in if (..) funktioniert nun auch die Tasterfreigabe und das Programm bleibt nicht mehr im Setup hängen. Jetzt muss ich das ganze noch aufräumen und die zittrige Displayanzeige beruhigen.....
PS.: Hast Du noch eine Empfehlung zur Hand, wo ich mich nochmal über die Interruptfunktion einlesen kann? Im Netz gibt es ja vieles, aber auch verwirrendes....wäre toll :slightly_smiling_face: :heart_eyes: