ISR Problem für Wasserausgabe

Hallo liebes Forum,
ich habe mir ein kleines Board mit einer Ausgabeeinheit gebastelt und benötige mit der Programmierung selbst ein wenig Hilfe, da ich derzeit nicht weiter komme.

Den Code selbst füge ich hier mal mit ein.
Grundsätzlich soll der Arduino später eine Mengenangabe zugeschoben bekommen die er dann verarbeiten soll und über die vier Ventile ausgeben.

Wenn der Wert erreicht ist soll die Ausgabe stoppen und auf den nächsten Wert warten.
Das Ganze soll Zyklisch verarbeitet werden.
Ich habe die Werte der Pumpe hier eingetragen und sie dann einem delay zugeschoben der dann die Pumpe laufen lässt bis der delay abgelaufen ist.

Leider komme ich dem Problem nicht ganz auf die Schliche,
der “BWmenge_ges” wird unzyklisch kleiner und dann wieder größer.

Kann es vllt daran liegen dass ich im ISR rechne ?

Hier erstmal mein Code:

#include<Wire.h>

#define vent1 8
#define vent2 9
#define vent3 10
#define vent4 12
#define motor 11

#define mgeschw 255.0  //maximal Geschwindigkeit Pumpe x / 255
#define pumpgeschw 0.05833  //Liter pro Sekunde

float bwzeit();

void setup() {

  Wire.begin(2);
  Serial.begin(9600);

  // Timer 1
  noInterrupts();           // Alle Interrupts temporär abschalten
  TCCR1A = 0; // set TCCR1A register to 0
  TCCR1B = 0; // set TCCR1B register to 0
  TCNT1  = 0; // set counter value to 0

  OCR1A = 1561.5;; // set compare match register

  TCCR1B |= (1 << CS12) | (1 << CS10); // Set CS12 and CS10 bits for 1:1024 prescaler

  TCCR1B |= (1 << WGM12); // turn on CTC mode
  TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
  interrupts();

  //deklaration der Ausgänge
  pinMode(vent1, OUTPUT);
  pinMode(vent2, OUTPUT);
  pinMode(vent3, OUTPUT);
  pinMode(vent4, OUTPUT);
  pinMode(motor, OUTPUT);

  //Ausgänge vor beginn abgeschaltet
  digitalWrite(vent1, 0);
  digitalWrite(vent2, 0);
  digitalWrite(vent3, 0);
  digitalWrite(vent4, 0);
  digitalWrite(motor, 0);
}

float WMAusg = 0.12;  //je 2 Ventile
float WMAusgV = WMAusg*2;
int k = 0;
float _BWmenge_ges = WMAusgV * 3.0; // Liter * Ventilausgänge * Zweigmenge
float BWmenge_ges=0.0;
int fall = 0;
int timeron = 0;
float BWZeit_aus = bwzeit(WMAusg, 2.0);

void loop() {
  int   WLevel = 1.0;

  //Wassermenge einmalig laden
  //Wassermenge berechnen
  //bewässerung einschalten + Wassermenge zyklisch herunterrechnen
  BWmenge_ges=_BWmenge_ges;
  if ((BWmenge_ges > 0.0) && ( WLevel > 0.0)) {


    for (int i = 0; i < 5; i++) {
      Serial.println(BWmenge_ges);
      
      switch (i) {

        case 0:
          digitalWrite(vent1, 0);
          digitalWrite(vent2, 0);
          digitalWrite(vent3, 0);
          digitalWrite(vent4, 0);
          digitalWrite(motor, 0);
          timeron = 0;
          break;

        case 1:
          digitalWrite(vent1, 1);
          digitalWrite(vent2, 0);
          digitalWrite(vent3, 0);
          digitalWrite(vent4, 0);
          digitalWrite(motor, 1);
          timeron = 1;
          break;

        case 2:
          digitalWrite(vent1, 0);
          digitalWrite(vent2, 1);
          digitalWrite(vent3, 0);
          digitalWrite(vent4, 0);
          digitalWrite(motor, 1);
          timeron = 1;
          break;

        case 3:
          digitalWrite(vent1, 0);
          digitalWrite(vent2, 0);
          digitalWrite(vent3, 1);
          digitalWrite(vent4, 0);
          digitalWrite(motor, 1);
          timeron = 1;
          break;

        case 4:
          digitalWrite(vent1, 0);
          digitalWrite(vent2, 0);
          digitalWrite(vent3, 0);
          digitalWrite(vent4, 1);
          digitalWrite(motor, 1);
          timeron = 1;
          break;

        default:
          digitalWrite(vent1, 0);
          digitalWrite(vent2, 0);
          digitalWrite(vent3, 0);
          digitalWrite(vent4, 0);
          digitalWrite(motor, 0);
          timeron = 0;
          break;
      }
      if (timeron > 0) {
        delay(BWZeit_aus);
        //Serial.println(BWZeit_aus);
        timeron = 0;
      }
    }
  }
}

float bwzeit(float menge, float ausgaenge) { //berechnung der bewässerungszeit * Ausgänge
  float zeit = menge / pumpgeschw * ausgaenge * 1000 ;
  return zeit;
}

ISR(TIMER1_COMPA_vect) { // ISR aufruf
  //Wasserlevel prüfen
  k++;
  if(k<10){
  BWmenge_ges=_BWmenge_ges-WMAusgV;
  k=1;
  }
  
}

Hi

Ändere bitte Deinen Post - Du hast Quote-Tags für den Code benutzt, nimm bitte Code-Tags (statt url steht halt code in den Klammern).
Den Rest schaue ich mir gerade kurz an.

MfG

Edit
Kommentare würden nicht schaden - was machst Du mit dem Timer, wie bestimmst Du die delay() (Welche übrigens 'böse' sind).
Per #define definierte 'Dinge' per const.
Pin-Nummern - const byte
Liter pro Stunde wird wohl ebenfalls konstant sein - const float literprostunde=0.00233;

Was Dein Problem sein könnte:
Du benutzt die Variablen innerhalb UND außerhalb der ISR - Diese müssen 'volatile' geklariert werden!!
Also nur Die, Die INNEN UND AUSSEN benutzt werden.
Dadurch wird der Kompiler gezwungen, die Werte immer frisch aus dem Speicher zu lesen, statt den bereits in Händen gehaltenen Wert - Er hat ja gerade erst damit gerechnet, erneut zu benutzen.
Im normalem Programmablauf bringt Das Geschwindigkeitsvorteile, wenn die Variable aber 'sonst wo und egal wann' den Wert ändern kann, muß Das Alles immer separat ausgelesen werden.
Bei Werten, Die größer als Byte sind - trifft bei float zu, muß außerdem der Wert atomar ausgelesen werden.
Während des Auslesen darf kein Interrupt auslösen, weil Dieser den Wert ändern könnte.
Dazu gibt's Lib's, Die man includieren kann, man kann aber auch einfach 'nur' Interrupts verbieten (CLI) - muß Diese dann aber danach auch wieder zulassen (STI).
CLI = CLear I-Flag
STI - SeT I-Flag
I steht für Interrupt.

Wenn Du STI vergisst, wird auch millis() nicht mehr ausgeführt und Du wirst millis() in Zukunft brauchen, alleine, um von delay() weg zu kommen.

Auf Multi-Byte Variablen die in ISRs geändert werden musst du außerhalb bei abgeschalteten Interrupts zugreifen

1.) Interrupts deaktivieren
2.) Variable in temporäre Variable kopieren
3.) Interrupts wieder aktivieren
4.) mit der temporären Variable arbeiten

Serenifly:
Auf Multi-Byte Variablen die in ISRs geändert werden musst du außerhalb bei abgeschalteten Interrupts zugreifen

1.) Interrupts deaktivieren
2.) Variable in temporäre Variable kopieren
3.) Interrupts wieder aktivieren
4.) mit der temporären Variable arbeiten

Vielen dank für die echt schnelle Antwort.

Das ganze klingt sehr überzeugend, kann ich das einfach so wie mit dem Timer machen in der Setup Routine ?
Oder muss ich da auf etwas besonderes achten?

Vielen Dank schonmal !!!

Hi

Google die Stichworte.
volatile
atomar

MfG

BasJoe:
Das ganze klingt sehr überzeugend, kann ich das einfach so wie mit dem Timer machen in der Setup Routine ?

Ja. Oder so:
https://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html

Ganz sauber immer mit ATOMIC_RESTORESTATE, aber hier würde auch ATOMIC_FOREON gehen

Und wie gesagt, diese Variablen als volatile deklarieren. Das hatte ich übersehen.

postmaster-ino:
Hi

Ändere bitte Deinen Post - Du hast Quote-Tags für den Code benutzt, nimm bitte Code-Tags (statt url steht halt code in den Klammern).
Den Rest schaue ich mir gerade kurz an.

MfG

Edit
Kommentare würden nicht schaden - was machst Du mit dem Timer, wie bestimmst Du die delay() (Welche übrigens 'böse' sind).
Per #define definierte 'Dinge' per const.
Pin-Nummern - const byte
Liter pro Stunde wird wohl ebenfalls konstant sein - const float literprostunde=0.00233;

Was Dein Problem sein könnte:
Du benutzt die Variablen innerhalb UND außerhalb der ISR - Diese müssen 'volatile' geklariert werden!!
Also nur Die, Die INNEN UND AUSSEN benutzt werden.
Dadurch wird der Kompiler gezwungen, die Werte immer frisch aus dem Speicher zu lesen, statt den bereits in Händen gehaltenen Wert - Er hat ja gerade erst damit gerechnet, erneut zu benutzen.
Im normalem Programmablauf bringt Das Geschwindigkeitsvorteile, wenn die Variable aber 'sonst wo und egal wann' den Wert ändern kann, muß Das Alles immer separat ausgelesen werden.
Bei Werten, Die größer als Byte sind - trifft bei float zu, muß außerdem der Wert atomar ausgelesen werden.
Während des Auslesen darf kein Interrupt auslösen, weil Dieser den Wert ändern könnte.
Dazu gibt's Lib's, Die man includieren kann, man kann aber auch einfach 'nur' Interrupts verbieten (CLI) - muß Diese dann aber danach auch wieder zulassen (STI).
CLI = CLear I-Flag
STI - SeT I-Flag
I steht für Interrupt.

Wenn Du STI vergisst, wird auch millis() nicht mehr ausgeführt und Du wirst millis() in Zukunft brauchen, alleine, um von delay() weg zu kommen.

Habe gerade alles durchgelesen ich hoffe man möge mir etliche Fehler verzeihen. ^^

Liter pro Stunde ? Ich hatte dort sekunde geschrieben ^^ ist sogar auch kommentiert. Aber ich verstehe worum es geht.

Ich versteh nur nicht wo ich den Unterschied zwischen const und #define mache, habe versucht im Netz grad herauszufinden warum aber so wirklich klar ist mir das nicht geworden.

Ich ahne nur warum delays böse sind aber würde gerne nocheinmal aus erfahrener quelle lesen warum.
Interrupts sollten doch auch delay unterbrechen oder nicht ?

Und bei Volatile habe ich verstanden, dass diese für den compiler unvorhergesehene Änderungen gut sind da der Compiler versucht den Rechenweg schnellstmöglich zu lösen und dann schnell externe eingaben an Pins übersieht.

Da ich hier keine Sensoren dran habe denke ich ist volatile überflüssig in diesem Zusammenhang oder nicht ?

Danke schonmal für die Lösung STI funktioniert nicht das sollte "cli();sei();" heißen meine ich gelesen zu haben.

Dennoch sehr gute hinweise die ich mir zu Herzen nehmen werde.
Vielen dank dafür, hab selten nette Leute in Foren kennengelernt :slight_smile:

Ich versteh nur nicht wo ich den Unterschied zwischen const und #define mache

#define ist lediglich eine Text-Ersetzung. Bei const hast du einen richtigen Datentyp. Das ist wesentlich besser

Danke schonmal für die Lösung STI funktioniert nicht das sollte "cli();sei();" heißen meine ich gelesen zu haben.

Ja. sei() = set interrupt. Das interrupts() von Arduino ruft auch nur das auf. Die dritte Option sind eben die Makros aus atomic.h

Ich ahne nur warum delays böse sind aber würde gerne nocheinmal aus erfahrener quelle lesen warum.

Das geht solange gut wie dein Programm nur eine Sache machen soll. Sobald mehrere Dinge nebeneinander erledigt werden sollten bekommst du Probleme.

Und Interrupts sind nicht so einfach wie sich Anfänger immer denken. Wie du hier siehst verursachen die ihre eigenen Probleme und sind nicht so einfach zu handhaben. Es gibt natürlich Anwendungen dafür, aber sie ersetzen keine richtige Programmstruktur. Normal verwendet man die eher wenn Dinge extrem schnell und/oder gleichmäßig ablaufen müssen. Für langsame Sachen gibt es bessere Optionen

Hallo,

gibts einen Grund das mit Interupt zu machen ?

ich hätte jetzt eine Statemashine gebaut und mit millis() zeitgesteuert gearbeitet. Irgendwie willst Du ja letztlich Mengen vorgeben und daraus Ansteuerzeiten für die Ventile berechnen.

Heinz

Vielen Dank schonmal an Serenifly und postmaster-ino und alle anderen für die Hilfe.

Rentner:
Hallo,

gibts einen Grund das mit Interupt zu machen ?

ich hätte jetzt eine Statemashine gebaut und mit millis() zeitgesteuert gearbeitet. Irgendwie willst Du ja letztlich Mengen vorgeben und daraus Ansteuerzeiten für die Ventile berechnen.

Heinz

Ich habe den wegen dem Motor verwendet da dieser genaue Wassermengen ausgeben soll. Da ich andere Möglichkeiten sonst nicht wirklich kenne habe ich nach ner ISR gegriffen die mir logisch erschien. Für andere Möglichkeiten bin ich offen und hab lust neues zu lernen.

Vielen dank nochmal!

Hi

Sorry für das STI - müsste Mal recherieren, wo ich Das her habe - vll. 286er Assembler?
Du hast aber das Richtige davon ableiten können - passt also doch halbwegs.

Der Timer-Interrupt ist auch nicht genauer, als die Zeit, Die der Arduino selber nutzt (millis() ).
Und ob Du nun den Ausgang 23µs früher abgeschaltet bekommst, dürfte hier eine nahe Null gehende Rolle spielen.

Interrupts unterbrechen ALLES, wenn Sie halt dürfen.
Dafür müssen Diese konfiguriert und eingeschaltet sein, wie global erlaubt sein (das I-Flag).
So kann auf den AVR-Arduinos (nahezu?) JEDER Pin einen Interrupt (PCINT) auslösen, wie auch Pins von Interrupts gesteuert werden können - z.B. PWM oder Servos (was in die gleiche Richtung geht).

Im Allgemeinem wird in einem Interrupt 'nur' schnell auf externe Dinge reagiert, die Haupt-Arbeit übernimmt dann wieder loop() - im Interrupt wird ein eigenes Flag gesetzt, Welches in loop() geprüft wird (globale Variable, aber ebenfalls volatile, da in Beiden in Benutzung).

Dort sind einige Links zum Thema State-maschine, unter Anderem wird auf das IDE-Beispiel Blink_without_delay eingegangen wie auch eine einfachere State-Maschine anhand eines Nachtwächter erklärt.

MfG

BasJoe:
Vielen Dank schonmal an Serenifly und postmaster-ino und alle anderen für die Hilfe.

Ich habe den wegen dem Motor verwendet da dieser genaue Wassermengen ausgeben soll. Da ich andere Möglichkeiten sonst nicht wirklich kenne habe ich nach ner ISR gegriffen die mir logisch erschien. Für andere Möglichkeiten bin ich offen und hab lust neues zu lernen.

Vielen dank nochmal!

Hallo,

also das macht ja wenig Sinn, für den Fall. Mit einer Zeitvorgabe mit milllis() bist Du auf jeden Fall schnell genug für Ventile und den Motor.

Ich hab mal in meiner Wühlkiste gewühlt und was gefunden was ich mal für mich gemacht hatte. Ich nuzte das auch hin und wieder als Vorlage. Ist jetzt nix besonderes irgendwie sehen die alle so ähnlich aus. Sowas käme dem aber nahe wie man Deine Aufgabe lösen würde. Irgenwie musst Du noch die einzelnen Wartezeiten berechnen, aber das hattst Du ja schon. Könnte man dann machen wenns gebraucht wird, oder am Anfang im Setup und dann die Zeitwerte in einem Array ablegen. Ich habs hat fest codiert.

wichig ist das man mal verstanden hat wie millis() und so eine Statemashine / Endlicher Automat / Schrittkette funtioniert.

Heinz

/*Ablaufsteuerung mit 5 Zusänden und zeitlichem Ablauf
   Zustand 1 wartet auf Start an Pin 2 LOW
   Wenn die 4 Arbeitszustände abgelaufen sind erfolgt
   erneut Warten auf das Startsignal.Pin2 schaltet gegen GND
   Hardware UNO                   Mai 2018
*/
uint32_t altzeit;   // speicher millis()
uint32_t wartezeit; // Wartezeit weiter schalten
bool switchein;     // status Schalter

// Ablauf fstlegen
enum Ablauf {aus = 1, eins, zwei, drei, vier};
Ablauf zustand = aus;

//Pin´s festlegen
const byte schalterpin = 2;
const byte led1 = 5;
const byte led2 = 6;
const byte led3 = 7;


void setup() {
  // put your setup code here, to run once:
  pinMode(schalterpin, INPUT_PULLUP);
  pinMode(led1, OUTPUT);
  pinMode(led2, OUTPUT);
  pinMode(led3, OUTPUT);

  Serial.begin(9600);
  Serial.println("zustand warte auf start ");
}

void loop() {
  // put your main code here, to run repeatedly:

  switchein = !digitalRead(schalterpin);    // pin einlesen

  switch (zustand) {
    case aus:
      digitalWrite(led1, LOW);// Ausgabe für zustand aus
      digitalWrite(led2, LOW);
      digitalWrite(led3, LOW);

      if (switchein) {        // Ablauf starten
        zustand = eins;       // zustand auf 1 setzten
        wartezeit = 1000;     // laufzeit für zustand 1 hier setzen
        altzeit = millis();   // reset zeit
        Serial.println ("zustand 1 gestartet ");
      }
      break;

    case eins:
      digitalWrite(led1, HIGH); // Ausgabe für zustand 1
      digitalWrite(led2, LOW);  // was auch immer
      digitalWrite(led3, LOW);

      if (millis() - altzeit > wartezeit) { // weiter
        zustand = zwei;       // auf zwei setzen
        wartezeit = 2000;     // laufzeit für zwei
        altzeit = millis();
        Serial.println ("zustand 2 gestartet");
      }
      break;

    case zwei:
      digitalWrite(led1, LOW);
      digitalWrite(led2, HIGH);
      digitalWrite(led3, LOW);

      if (millis() - altzeit > wartezeit) { //weiter
        zustand = drei;
        wartezeit = 3000;
        altzeit = millis();
        Serial.println ("zustand 3 gestartet");
      }
      break;

    case drei:
      digitalWrite(led1, HIGH);
      digitalWrite(led2, HIGH);
      digitalWrite(led3, LOW);

      if (millis() - altzeit > wartezeit) { //weiter
        zustand = vier;
        wartezeit = 4000;
        altzeit = millis();
        Serial.println ("zustand 4 gestartet");
      }
      break;

    case vier:
      digitalWrite(led1, LOW);
      digitalWrite(led2, LOW);
      digitalWrite(led3, HIGH);
      if (millis() - altzeit > wartezeit) { //weiter
        zustand = aus;
        Serial.println ("Ablauf beendet warte erneut");
      }
      break;
  }
} //************** loop ende