Problem mit delay, random und einem Schalter

Liebe Menschen im Forum, ich bin völlig neu in der Arduino- und Programmier-Welt und hoffe, ich finde bei euch Hilfe bei einem Projekt. Ich habe schon viel in Foren etc. gestöbert und konnte nichts finden, was mir wirklich hilft.

Projekt:
Ein altes analoges Telefon wird zum Klingeln gebracht (in einem bestimmten Rhythmus und mit random Pausen zwischen den einzelnen "Anrufen").
Das mache ich mit Hilfe eines Relais, da die Klingel mehr als 5V braucht.
Wenn man dann den Hörer abnimmt, muss das Klingeln (sofort) stoppen und ein anderes Programm (Sound) wird gestartet. Wenn man den Hörer wieder auflegt, ist wieder eine Random Pause und das Klingelprogramm startet (rhythmisches Klingeln, random Pause).

Ich habe nun das Problem, dass die delays, die in meinem Klingel-Programm sind, dazu führen, dass das Klingeln nicht sofort stoppt, wenn man den Hörer abnimmt. (Logisch eigentlich.) Ich habe versucht, das Klingeln ohne delay hinzukriegen, da schaffe ich es aber nicht einzubauen, dass das Klingeln nach 3x wieder aufhört UND dann auch noch eine Random Pause kommt. Und: würde das überhaupt das Problem lösen?

Brauche ich vielleicht ein interrupt oder so?
Ich wäre sehr dankbar für Hilfe! Beste Grüße!

Und hier das (der?) Sketch, um das es geht:

const int buttonPin = 2;     // the number of the pushbutton pin
const int LedPin =  13;      // the number of the LED pin - not needed in the moment. Needed it for testing.

// variables:
int buttonState = 0;         // variable for reading the button status
int RelPin = 6;              // Relais connected to digital pin 6
long On = 1500;              // RING
long Off = 2000;             // PAUSE
long randOff = 0;            // Initialize a variable for the OFF time

void Relais ()               // Programm for (rhythmical) phone ring and random pause
{
 randOff = random (10000, 20000); // generate OFF time between 10 and 20 seconds
   delay(randOff); 
   digitalWrite(RelPin, HIGH);   
   delay(On);               
   digitalWrite(RelPin, LOW);   
   delay(Off);               
   digitalWrite(RelPin, HIGH);   
   delay(On);               
   digitalWrite(RelPin, LOW);    
   delay(Off);                
   digitalWrite(RelPin, HIGH);   
   delay(On);                
   digitalWrite(RelPin, LOW);    
   delay(Off);                
   delay(randOff);              
}

void setup() {
  
  pinMode(LedPin, OUTPUT);     // initialize the LED pin as an output - not needed in the moment. Needed it for testing. 
  pinMode(buttonPin, INPUT);   // initialize the pushbutton pin as an input
  randomSeed (analogRead (0));  // randomize
  pinMode(RelPin, OUTPUT);      // sets the digital Relais pin as output  
}

void loop(){
  
  buttonState = digitalRead(buttonPin); // read the state of the pushbutton value:
                                        // check if the pushbutton is pressed.
  if (buttonState == LOW) {            // if it is play program Relais (phone rings)
  Relais ();  
  } 
}

Dein Problem hast du schon sehr schön erklärt. Das Problem sind die delays.
Diese müssen komplett raus. Während der delays macht der Atmega nichts auser warten.
Sieh dir doch den Beitrag "BlinkWithoutDelay" an. Da werden millis verwendet, aber der Atmega arbeitet dennoch weiter.

Lösung: Nimm einen endlichen Automaten :smiley:

Du wirst einige gute Beispiele finden, wenn du danach googelst

Oha! Das mit dem endlichen Automaten sieht interessant aus - da habe ich doch jetzt eine schöne Bettlektüre.

Und das mit dem Blink-without-delay hatte ich versucht, aber mein Hirn ist an seine Grenzen gestoßen.
Ich habe was fast Funktionierendes hinbekommen, aber leider nur fast ... Der Rhythmus ist halbwegs okay (wobei der nicht so super wichtig wäre), das Klingeln habe ich mit einem Counter begrenzt. Aber ich weiß nicht, wie ich aus dem ausgezählten Ding wieder an den Anfang des loops komme. Und eben dann die Random-Pause.

Und das erste Blinken nach der random Pause ist nur ein kurzes Flackern ... mä.

Nun denn, dann stöbere ich jetzt mal nach Automaten.

Aber: mein Klingeln wird ja in einem recht gemächlichen Rhythmus sein (wie ein altes Telefon halt) - spult der Arduino nicht trotzdem sein Programm weiter ab, trotz abgenommenem Hörer - sollte ich evt. den Schalter öfter abfragen oder steh ich jetzt völlig (noch mehr) auf dem Schlauch?

Hier aber mein kläglicher blink-without-delay-versuch:

const unsigned long onTime = 1500;
const unsigned long offTime = 2500;
long randOff = 0;                 // Initialize a variable for the OFF time
long WAIT = 0;
int myCounter = 0;
 
unsigned long previousMillis=0;
 
int interval = onTime;
 
boolean LED13state = true;
 
void klingeln ()
{
  digitalWrite(13, LED13state);
  unsigned long currentMillis = millis();
  if ((unsigned long)(currentMillis - previousMillis) >= interval) 
{
if (LED13state) 
{
  interval = onTime;      
} 
else 
{
  interval = offTime;
  myCounter = myCounter + 1;
}
LED13state = !(LED13state);
   previousMillis = currentMillis;
 } 
}
 
// Setup
void setup() 
{
  pinMode(13, OUTPUT);
}
  
  
 
void loop() 
{
  randOff = random (5000, 20000);    // generate OFF time between 5 and 20 seconds
  randomSeed (analogRead (0));
 
 if(myCounter<4)
 {
 klingeln();
   }
   else {
     delay (randOff);
     myCounter = 0;
     }
  }

Nahezu grundsätzlich gilt: delay ist Mist.

Ausnahmen: Wenn du genau weißt was du tust. Nicht, wenn du nur meinst, es zu wissen.

inuz:
Aber: mein Klingeln wird ja in einem recht gemächlichen Rhythmus sein (wie ein altes Telefon halt) - spult der Arduino nicht trotzdem sein Programm weiter ab, trotz abgenommenem Hörer - sollte ich evt. den Schalter öfter abfragen oder steh ich jetzt völlig (noch mehr) auf dem Schlauch?

Jedes Klingeln und jede Pause in deinem Sketch wird durch ein delay erzeugt. In dieser Zeit wartet der Atmega und tut nichts weiter.

Daher klingelt es auch weiter, wenn du abnimmst.
Den Schalter mehr abzufragen bringt nichts. Die Loop läuft ständig durch und wird nur durch deine vielen delays gebremst.

Deinen Sketch (ohne delays) habe ich kurz überflogen und mir ist aufgefallen, das da eine Klammersetzung völlig falsch ist.

 if ((unsigned long)(currentMillis - previousMillis) >= interval)

muss sein

 if (unsigned Long currentMillis - previousMillis >= interval)

Und hier

http://www.arduinoforum.de/arduino-Thread-Blink-Without-Delay-mal-anders-erklärt?pid=23856#pid23856

findest du eine andere/bessere Erklärung zu BlinkWithoutDelay.

Danke! Verrückterweise bekomme ich beim code-testen eine Fehlermeldung, wenn ich die Klammer nicht drin hab. (Sehr rätselhaft)

Das mit den delays habe ich kapiert. Ich frage mich nur, ob auch in dem anderen Sketch (wenn ich es denn hinbekommen sollte) evt. das Problem bleibt, wenn ich den Schalter nicht superoft abfrage, weil ich in der Endfassung mit einer Random-Pause von einigen Minuten arbeiten muss ... deswegen fragte ich mich, ob das ein Fall für ein interrupt wäre ...

Auf jeden Fall schon mal 1000 Dank. Ich versuch's weiter. Morgen. Gute Nacht!

inuz:
Danke! Verrückterweise bekomme ich beim code-testen eine Fehlermeldung, wenn ich die Klammer nicht drin hab. (Sehr rätselhaft)

Das mit den delays habe ich kapiert. Ich frage mich nur, ob auch in dem anderen Sketch (wenn ich es denn hinbekommen sollte) evt. das Problem bleibt, wenn ich den Schalter nicht superoft abfrage, weil ich in der Endfassung mit einer Random-Pause von einigen Minuten arbeiten muss ... deswegen fragte ich mich, ob das ein Fall für ein interrupt wäre ...

Auf jeden Fall schon mal 1000 Dank. Ich versuch's weiter. Morgen. Gute Nacht!

Wie willst du den Schalter "superoft" abfragen?

Willst du den 10 mal (sehr übertrieben) in die Loop einsetzen?

Das bringt nix.

Die Loop läuft ständig durch. Und woanders macht es keinen Sinn, da ein anderer Punkt (Funktion) aus der Loop aufgerufen wird.

Was die Klammer betrifft, ist wohl noch mehr faul.

Dann auch eine gute Nacht.

inuz:
Das mit den delays habe ich kapiert. Ich frage mich nur, ob auch in dem anderen Sketch (wenn ich es denn hinbekommen sollte) evt. das Problem bleibt, wenn ich den Schalter nicht superoft abfrage, weil ich in der Endfassung mit einer Random-Pause von einigen Minuten arbeiten muss ... deswegen fragte ich mich, ob das ein Fall für ein interrupt wäre ...

Auf den Satz hin bezweifle ich, dass du das mit den delays kapiert hast.

....weil ich in der Endfassung mit einer Random-Pause von einigen Minuten arbeiten muss...

Warum müssen es einige Minuten sein.
Und auch das kann man ohne delay lösen.

Danke! Verrückterweise bekomme ich beim code-testen eine Fehlermeldung, wenn ich die Klammer nicht drin hab. (Sehr rätselhaft)

das unsigned long in der if abfrage mußt du rausschmeißen, das hast du schon in der Zeile darüber Deklariert

unsigned long currentMillis = millis();
if ((unsigned long)(currentMillis - previousMillis) >= interval)

du könntest auch einfach schreiben if (millis() - previousMillis >= interval),
{

Klingelterror startet nur bei aufgelegtem Hörer (pin2 INPUT_PULLUP gegen GND) und wird sofort unterbrochen,
wenn er aufgenommen wird. (oder habe ich das falsch verstanden?)

// https://github.com/thomasfredericks/Bounce2
#include <Bounce2.h>

const int buttonPin = 2;     // the number of the pushbutton pin
const int LedPin =  13;      // the number of the LED pin - not needed in the moment. Needed it for testing.

// variables:
int buttonState = 0;         // variable for reading the button status
int RelPin = 6;              // Relais connected to digital pin 6
unsigned long On = 1500;      // RING
unsigned long Off = 2000;     // PAUSE
unsigned long randOff;

Bounce button;

enum states {
  idle,
  rndWait,
  relOn1,
  relOff1,
  relOn2,
  relOff2,
  relOn3,
};
byte State = idle;
unsigned long LoopEntered;
unsigned long StartTime;

void setup() {
  button.attach(buttonPin, INPUT_PULLUP);
  pinMode(LedPin, OUTPUT);     // initialize the LED pin as an output - not needed in the moment. Needed it for testing.
  pinMode(RelPin, OUTPUT);      // sets the digital Relais pin as output
  randomSeed (analogRead (0));  // randomize
}
// change state and record time of entry
void doStep(byte toState) {
  StartTime = LoopEntered;
  State = toState;
}
// if duration expired since StartTime, enter state and set relay
void timedStep(unsigned long duration, byte toState, bool relay) {
  if (LoopEntered - StartTime > duration) {
    digitalWrite(RelPin, relay);
    doStep(toState);
  }
}

void loop() {
  LoopEntered = millis(); // baseTime
  button.update();
  if (button.rose()) {  // picked up, cancel all
    digitalWrite(RelPin, LOW);
    State = idle;
  }
  switch (State) {
    case idle:
      if (button.fell()) {  // hung up
        randOff = random (10000, 20000);
        doStep(rndWait);
      }
      break;
    case rndWait: timedStep(randOff, relOn1, HIGH); break;
    case relOn1:  timedStep(On, relOff1, LOW);     break;
    case relOff1: timedStep(Off, relOn2, HIGH);    break;
    case relOn2:  timedStep(On, relOff2, LOW);     break;
    case relOff2: timedStep(Off, relOn3, HIGH);    break;
    case relOn3:  timedStep(On, idle, LOW);        break;
  }
}

Genau richtig verstanden!

(Und genau wegen des Klingelterrors müssen Pausen von mehreren Minuten drin sein, sonst kriegen alle Anwesenden einen Nervenzusammenbruch.)

Ich danke euch sehr für's hilfreiche Feedback. (Als totaler Programmieranfänger ist es erstmal schwer sich in das alles so richtig reinzufuchsen, aber es wird besser.)

Wird heute abend alles ausprobiert! Und entweder melde ich mich dann jubelnd oder flennend.

Ich wünsche einen wunderbaren Tag!

Yay! Fast!

Ich probiere gerade den Sketch von Whandall, 1000 Dank dafür! Das Unterbrechen des Klingelns klappt damit perfekt! (Ich muss noch hinkriegen, dass das Klingeln auch mal ingnoriert werden kann, d.h., dass das Klingeln sich auch irgendwann wiederholt, wenn niemand abnimmt ...). Aber auch die anderen Hinweise und Links zu Tutorials haben mir sehr geholfen, das ganze (ein bisschen) besser zu durchschauen:
Ich danke euch allen für die super konstruktive Hilfe!

Gestartet wird nach wie vor mit dem ersten Auflegen,
wird nicht abgenommen, wiederholt sich der Spuk nach 2-10 Minuten.

// https://github.com/thomasfredericks/Bounce2
#include <Bounce2.h>

const int buttonPin = 2;     // the number of the pushbutton pin
const int LedPin =  13;      // the number of the LED pin - not needed in the moment. Needed it for testing.

// variables:
int buttonState = 0;         // variable for reading the button status
int RelPin = 13;              // Relais connected to digital pin 6
unsigned long On = 1500;      // RING
unsigned long Off = 2000;     // PAUSE
unsigned long randOff;

Bounce button;

enum states {
  idle,
  rndWait,
  relOn1,
  relOff1,
  relOn2,
  relOff2,
  relOn3,
};
byte State = idle;
unsigned long LoopEntered;
unsigned long StartTime;

void setup() {
  button.attach(buttonPin, INPUT_PULLUP);
  pinMode(LedPin, OUTPUT);     // initialize the LED pin as an output - not needed in the moment. Needed it for testing.
  pinMode(RelPin, OUTPUT);      // sets the digital Relais pin as output
  randomSeed (analogRead (0));  // randomize
}
// change state and record time of entry
void doStep(byte toState) {
  StartTime = LoopEntered;
  State = toState;
}
// if duration expired since StartTime, enter state and set relay
bool timedStep(unsigned long duration, byte toState, bool relay) {
  if (LoopEntered - StartTime > duration) {
    digitalWrite(RelPin, relay);
    doStep(toState);
    return true;
  }
  return false;
}

void loop() {
  LoopEntered = millis(); // baseTime
  button.update();
  if (button.rose()) {  // picked up, cancel all
    digitalWrite(RelPin, LOW);
    State = idle;
  }
  switch (State) {
    case idle:
      if (button.fell()) {  // hung up
        randOff = random (10000, 20000);
        doStep(rndWait);
      }
      break;
    case rndWait: timedStep(randOff, relOn1, HIGH); break;
    case relOn1:  timedStep(On, relOff1, LOW);     break;
    case relOff1: timedStep(Off, relOn2, HIGH);    break;
    case relOn2:  timedStep(On, relOff2, LOW);     break;
    case relOff2: timedStep(Off, relOn3, HIGH);    break;
    case relOn3:
      if (timedStep(On, rndWait, LOW)) {
        randOff = random (120000, 600000);
      }
      break;
  }
}

Beim Durchlesen kapiere ich's (so halbwegs), muss aber zugeben, dass ich niemals selbst drauf gekommen wäre. Deswegen: doppelt 1000 Dank! Mein Hirn wäre sicher explodiert ansonsten.

Ich weiß, es passt jetzt nicht mehr direkt zu meinem Subject - aber weil mir hier so fantastisch geholfen wurde und ich mein Projekt hier schon kurz erklärt habe, noch mal in diesem Thread (wenn ich darf?):

Ich habe nun endlich mein Adafruit Waveshield (mit SD-Card) gelötet (alles funktioniert) und würde gerne, wenn der Hörer abgenommen wird, dass ein Random Sound von der SD-Card gespielt wird.

Da ich ja noch völlig neu bin im Programmieren, bin ich etwas überfordert von der Frage, an welcher Stelle im Sketch ich das einfügen muss/kann. Zumal das Abspielen von Sound schon so ein langes Script ist ... (das hat mich ehrlich gesagt, etwas erschreckt!)

Kann ich so etwas, wie den daphc-Sketch (von Ada, der hat zwar noch kein random, aber immerhin spielt was ...) noch mit in die if-Funktion (if (button.rose)) packen? Ich gebe zu, ich bin überfordert.

daphc-Sketch:

/*
 * This example plays every .WAV file it finds on the SD card in a loop
 */
#include <WaveHC.h>
#include <WaveUtil.h>

SdReader card;    // This object holds the information for the card
FatVolume vol;    // This holds the information for the partition on the card
FatReader root;   // This holds the information for the volumes root directory
WaveHC wave;      // This is the only wave (audio) object, since we will only play one at a time

uint8_t dirLevel; // indent level for file/dir names    (for prettyprinting)
dir_t dirBuf;     // buffer for directory reads


/*
 * Define macro to put error messages in flash memory
 */
#define error(msg) error_P(PSTR(msg))

// Function definitions (we define them here, but the code is below)
void play(FatReader &dir);

//////////////////////////////////// SETUP
void setup() {
  Serial.begin(9600);           // set up Serial library at 9600 bps for debugging
  
  putstring_nl("\nWave test!");  // say we woke up!
  
  putstring("Free RAM: ");       // This can help with debugging, running out of RAM is bad
  Serial.println(FreeRam());

  //  if (!card.init(true)) { //play with 4 MHz spi if 8MHz isn't working for you
  if (!card.init()) {         //play with 8 MHz spi (default faster!)  
    error("Card init. failed!");  // Something went wrong, lets print out why
  }
  
  // enable optimize read - some cards may timeout. Disable if you're having problems
  card.partialBlockRead(true);
  
  // Now we will look for a FAT partition!
  uint8_t part;
  for (part = 0; part < 5; part++) {   // we have up to 5 slots to look in
    if (vol.init(card, part)) 
      break;                           // we found one, lets bail
  }
  if (part == 5) {                     // if we ended up not finding one  :(
    error("No valid FAT partition!");  // Something went wrong, lets print out why
  }
  
  // Lets tell the user about what we found
  putstring("Using partition ");
  Serial.print(part, DEC);
  putstring(", type is FAT");
  Serial.println(vol.fatType(), DEC);     // FAT16 or FAT32?
  
  // Try to open the root directory
  if (!root.openRoot(vol)) {
    error("Can't open root dir!");      // Something went wrong,
  }
  
  // Whew! We got past the tough parts.
  putstring_nl("Files found (* = fragmented):");

  // Print out all of the files in all the directories.
  root.ls(LS_R | LS_FLAG_FRAGMENTED);
}

//////////////////////////////////// LOOP
void loop() {
  root.rewind();
  play(root);
}

/////////////////////////////////// HELPERS
/*
 * print error message and halt
 */
void error_P(const char *str) {
  PgmPrint("Error: ");
  SerialPrint_P(str);
  sdErrorCheck();
  while(1);
}
/*
 * print error message and halt if SD I/O error, great for debugging!
 */
void sdErrorCheck(void) {
  if (!card.errorCode()) return;
  PgmPrint("\r\nSD I/O error: ");
  Serial.print(card.errorCode(), HEX);
  PgmPrint(", ");
  Serial.println(card.errorData(), HEX);
  while(1);
}
/*
 * play recursively - possible stack overflow if subdirectories too nested
 */
void play(FatReader &dir) {
  FatReader file;
  while (dir.readDir(dirBuf) > 0) {    // Read every file in the directory one at a time
  
    // Skip it if not a subdirectory and not a .WAV file
    if (!DIR_IS_SUBDIR(dirBuf)
         && strncmp_P((char *)&dirBuf.name[8], PSTR("WAV"), 3)) {
      continue;
    }

    Serial.println();            // clear out a new line
    
    for (uint8_t i = 0; i < dirLevel; i++) {
       Serial.write(' ');       // this is for prettyprinting, put spaces in front
    }
    if (!file.open(vol, dirBuf)) {        // open the file in the directory
      error("file.open failed");          // something went wrong
    }
    
    if (file.isDir()) {                   // check if we opened a new directory
      putstring("Subdir: ");
      printEntryName(dirBuf);
      Serial.println();
      dirLevel += 2;                      // add more spaces
      // play files in subdirectory
      play(file);                         // recursive!
      dirLevel -= 2;    
    }
    else {
      // Aha! we found a file that isnt a directory
      putstring("Playing ");
      printEntryName(dirBuf);              // print it out
      if (!wave.create(file)) {            // Figure out, is it a WAV proper?
        putstring(" Not a valid WAV");     // ok skip it
      } else {
        Serial.println();                  // Hooray it IS a WAV proper!
        wave.play();                       // make some noise!
        
        uint8_t n = 0;
        while (wave.isplaying) {// playing occurs in interrupts, so we print dots in realtime
          putstring(".");
          if (!(++n % 32))Serial.println();
          delay(100);
        }       
        sdErrorCheck();                    // everything OK?
        // if (wave.errors)Serial.println(wave.errors);     // wave decoding errors
      }
    }
  }
}