Taster abfragen während loop läuft

Moin, bin neu hier aber hab das Thema noch nicht gefunden. Ich versuche mit einem Taster, programiert als Schalter einen Motor ein und auszuschalten. In der Loop wird der Motor angesteuert und macht seine Moves. Hoch, Delay, Runter, Delay usw. Im jetzigen Zustand wird der Taster nur am Anfang der Loop abgefragt. Wenn ich also den Taster betätige während der Motor läuft merkt sich das Programm den Schaltvorgang nicht. Ich würde gerne nebenher eine Funktion laufen lassen, die die Tastereingabe speichert. Geht das?

LG Patrick

Hallo. Willkommen.
In Arduino IDE gibt's Beispile. Schaue dir BlinkWithoutDelay an.

Hi, habs mir angeschaut. Das trifft jedoch nicht das Problem. Ich habe einen Schrittmotor, der eine Position anfahren soll, dort inne hält und dann wieder zurück fährt. Das ganze habe ich mit einer for schleife realisiert:

for(int x = 0; x < 180; x++)
{
         digitalWrite(stepPin,HIGH); 
         delayMicroseconds(800); 
         digitalWrite(stepPin,LOW); 
         delayMicroseconds(800); 
}

Doch auch wenn ich jetzt hier delay durch millies ersetze habe ich doch noch immer eine Zeit in der mein Motor sich bewegt und der Taster nicht abgefragt wird.

dann hast du nicht verstanden den Vorteil.
delay() blockiert jegliche andere Funktion so lange bis die Zeit um ist.
Aber mit millis() , du startest eine Aktion (digitalWrite(x, HIGH); ) und merkst die Zeit, danach lässt etwas anderes machen, ab und zu prüfst ob schon die richtige Zeit ist die angefangene Aktion zu beenden.

wenn es nur eine einzige oder nur zwei Taster sind, könnte man Interrupts benutzen, dann egal was gerade das Programm macht, sie wird unterbrochen und eine definierte Funktion gestartet, wenn diese zu Ende ist, geht Programm weiter.

Danke, ich verstehe so langsam was du meinst. Wenn ich dann also meine Taster-Funktion in einer void habe, muss ich ihn jedes mal beim Warten an den Knopf erinnern?
Interrupt wäre wahrscheinlich besser...

Du könntest für die Ansteuerung des Schrittmotors eine Library verwenden. Z.B. meine MobaTools. Die steuern den Schrittmotor im Hintergrund an ( per Timerinterrupt ). Da läuft der loop weiter während sich der Motor dreht, und Du kannst problemlos deine Taster abfragen.
Was für einen Arduino verwendest Du?

Interrupts für Taster ist eigentlich immer eine ganz schlechte Idee.

1 Like

Echt jetzt ?
Eine Tasterabfrage mit einem Interrupt?
Das geht genau so in der Loop und ohne IRQ.

Du musst nur eine Variable einsetzen, in der du den Zustand speicherst, in welche Rintung der Motor dreht. Und dies in der Loop abfragen. Und am Ende wieder zurücksetzen.

Hier erst einmal ein Demo-Code der zwei Dinge zeigt:

  1. wie im Prinzip nicht-blockierendes warten funktioniert.
  2. wie man eine sogenannte State-Machine programmiert.
    an manchen Stellen sehr ausführlich kommentiert.
// dear newbee,
// this program demonstrates how "state-machines" work
// at the bottom of this file there is an explanation you should read first

const byte ButtonPin   = 12;
const byte OnBoard_LED = 13;

// constants for the different states
const byte WaitForButtonPressed           = 1;
const byte SwitchON_LED                   = 2;
const byte WaitUntil_LED_ON_PeriodIsOver  = 3;
const byte SwitchOFF_LED                  = 4;
const byte WaitUntil_LED_OFF_PeriodIsOver = 5;

byte MyStateVar;

// variables for non-blocking-timing
unsigned long myTestTimer;
unsigned long LED_ON_PeriodStart;
unsigned long LED_OFF_PeriodStart;
//unsigned long TimeDifferencePassedBy;

// variables for timeperiods
unsigned long LED_ON_Period  = 6000;
unsigned long LED_OFF_Period = 10000;

long CountLoops = 0;

#define pressed LOW


void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  PrintFileNameDateTime();

  pinMode(ButtonPin, INPUT_PULLUP);
  digitalWrite(OnBoard_LED, LOW);
  pinMode(OnBoard_LED, OUTPUT);
  MyStateVar = WaitForButtonPressed;
  Serial.println("press button at least for two seconds to start the demo");
}


void loop() {
  CountLoops++;           // increment variable "CountLoops" by one
  slowDownSerialOutPut(); // wait some time to slow down (execute lines of code inside function "slowDownSerialOutPut"
  PrintStateInfo();       // print State the state-machine has (execute lines of code inside function "PrintStateInfo()"
  myStateMachine();       // execute lines of code inside function "myStateMachine"
}


boolean TimePeriodIsOver (unsigned long &expireTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();  
  if ( currentMillis - expireTime >= TimePeriod ){
    expireTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  } 
  else return false;            // not expired
}


void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);
  
  if ( TimePeriodIsOver(MyBlinkTimer,BlinkPeriod) ) {
    digitalWrite(IO_Pin,!digitalRead(IO_Pin) ); 
  }
}


void myStateMachine() {  
  // this is the state-machine based on the switch-case-statement
  // depending on the value of variable "MyStateVar" only the commands
  // below one "case" get executed 
  switch (MyStateVar) {
    case WaitForButtonPressed:
      // a state that waits for an external input to occur
      if (digitalRead(ButtonPin) == pressed)  {
        MyStateVar = SwitchON_LED;              // set state-variable to a new state
        Serial.println("button is pressed");
      }
      break;  // jump immidiately down to the point commented with "xx-EXIT-xx"


    case SwitchON_LED:
      // a state that does an action just ONE time 
      // because the state-variable is set immediately to the next state
      Serial.println("I switch on the LED start non-blocking-timing..");
      digitalWrite(OnBoard_LED, HIGH);
      LED_ON_PeriodStart = millis();              // store timestamp for nn-blocking timing
      MyStateVar = WaitUntil_LED_ON_PeriodIsOver; // set state-variable to a new state
      break; // jump immidiately down to the point commented with "xx-EXIT-xx"


    case WaitUntil_LED_ON_PeriodIsOver:
       
      // a state that waits for a timeperiod to pass by
      if ( TimePeriodIsOver(LED_ON_PeriodStart,LED_ON_Period) ) {
        Serial.println("Wait-Time with LED ON is over)");
        MyStateVar = SwitchOFF_LED;
      }
      break; // jump immidiately down to the point commented with "xx-EXIT-xx"


    case SwitchOFF_LED:
      // a state that does an action just ONE time 
      // because the state-variable is set immetiately to the next state
      Serial.println("I switch OFF LED. Start non-blocking timing...");
      digitalWrite(OnBoard_LED, LOW);
      LED_OFF_PeriodStart = millis();
      MyStateVar = WaitUntil_LED_OFF_PeriodIsOver;
      break; // jump immidiately down to the point commented with "xx-EXIT-xx"


    case WaitUntil_LED_OFF_PeriodIsOver:
      // a state that waits for a timeperiod to pass by
      if ( TimePeriodIsOver(LED_OFF_PeriodStart,LED_OFF_Period) ) {
        Serial.println("Wait-Time with LED OFF is over");
        Serial.println("I'm ready for a new buttonpress");
        MyStateVar = WaitForButtonPressed;
      }
      break; // jump immidiately down to the point commented with "xx-EXIT-xx"
  } // xx-EXIT-xx  after  "break" code-execution goes on HERE
}

// below here are "helper-functions" for the visualisation 
// of what the program-FLOW does

// slowing down the speed of the program to reduce the number of lines
// printed to the serial monitor
// in real applications this slowing down is just not needed
// it reduces the responsiveness of your code
void slowDownSerialOutPut() {
  delay(500); // stops ALL code-execution for 0,5 seconds = is blocking
}


void PrintStateInfo() {  
  Serial.println();
  Serial.print("CountLoops ");
  Serial.print(CountLoops);
  Serial.print(" I'm in state ");
  Serial.print(MyStateVar);
  PrintActualStateText(MyStateVar); // execute lines of code inside function "PrintActualStateText"
  Serial.println();
}


void PrintActualStateText(byte p_state) {
  if (p_state == WaitForButtonPressed) {
    Serial.print(" Wait for Button-Press");
  }

  if (p_state == SwitchON_LED) {
    Serial.print(" Switch ON LED ");
  }

  if (p_state == WaitUntil_LED_ON_PeriodIsOver) {
    Serial.print(" Wait until LED-ON-Time has passed by ");
  }

  if (p_state == SwitchOFF_LED) {
    Serial.print(" Switch OFF LED ");
  }

  if (p_state == WaitUntil_LED_OFF_PeriodIsOver) {
    Serial.print(" Wait until some is time is over");
  }    
}

// to be able to determine what exactly sourcecode is running
// I add this function to all my programs
// the content is updated EVERY time you compile new (at least the time-stamp)
void PrintFileNameDateTime() {
  Serial.println("Code running comes from file ");
  Serial.println(__FILE__);
  Serial.print("  compiled ");
  Serial.print(__DATE__);
  Serial.print(" ");
  Serial.println(__TIME__);
}

// explanation:
// dear newbee,

// the program expects a button connected to IO-pin 12
// and does switch on / off IO-pin 13 which is connected to the onboard-LED
// on an arduino uno-board

// what this demo-code does:
// 1. waiting for a button-press (button connected to IO-pin 12)
// 2. switching on the LED 
// 3. and start a waiting-time in a NON-blocking way
// 4. switching off the LED 
// 5. and start a waiting-time in a NON-blocking way

// then start over again with 1.

// a state-machine is a medium advanced programming-technique.
// So if you hardly know what a constant, a variable, an if-condition is
// understanding this program can be ambitious and hard
// in this case I recommend learning the fundamental basics first

// I'm very interested in your experience if this program is easy to understand
// or hard to understand
// so whatever questions you have about this code your questions from YOU are a 
// very welcomed feedback to improve the understandability

// the main code has around 100 lines. So indeed it will take some time to understand it.

// for watching what the program-FLOW does start the serial monitor and adjust baud to
// 115200 baud

// deactivate "autoscroll" in the serial monitor or unplug your microcontroller-board
// and then scroll back the serial monitor and start reading the lines
// to understand what the program does.

// again: If you have any questions feel free to ask the questions in the Arduino-Forum
// I'm very interested in your questions because your questions are feedback what is still
// difficult to understand.

vgs

2 Likes

Das "nebenher" trifft es einigermaßen.
Ein Beispiel aus dem menschlichen Alltag

Stelle dir jemanden vor der gerade Essen zubereitet.
Es soll Pizza mit Gurkensalat geben.
Die Pizza ist schon im Ofen und backt vor sich hin
gleichzeitig wird die Gurke geschnitten.
Ca. alle 30 Sekunden schaut man auf die Uhr wie viel (Back)Zeit ist schon vergangen?
Das bedeutet: die ganze Zeit werden Gurkenscheiben geschnitten und zwischen zwei Schnitten wird auf die Uhr geschaut.
Und das wiederholt sich die ganze Zeit
als Programm auf das allerwesentlichste reduziert

loop() {
  EineScheibeAbschneiden();
  AufDieUhrSchauen();
}

Zurückübertragen auf dein Programm Schrittmotor und Taster abfragen

loop() {
  EinenSchrittImpulsErzeugen();
  TasterAbfragen();
}

Das ist das Grundprinzip die einzige Schleife die es gibt ist loop()
Null for-schleifen, Null-while-schleifen
Dadurch wird erreicht dass man mehrere Dinge "gleichzeitig" oder "nebenher" machen kann.

loop() {
  EinenSchrittImpulsErzeugen();
  TasterAbfragen();
  LED_Blinken();
  SummerPiepsen();
  DatenAufSD_KarteSchreiben();
  usw();
  usf();
}

Dabei müssen ausnahmslos alle functions nach dem Jump_in____Jump_out-prinzip arbeiten
Das heißt ein kleines schnell durchzuführendes Detail erledigen und dann sofort wieder raus aus der function

Der Schrittmotor will seine Impulse ganz regelmäßig alle 0,001 Sekunden bekommen
Dieses ganz schnelle rein/raus wird durch State-Machines realisiert.
Zu deutsch könnte man auch sagen Schrittkette.
Mache ein schnell auszuführendes Schrittchen und dann sofort raus
(du bist ja in 0,001 Sekunden schon wieder mit dem nächten Schrittchen dran)

Das ist nicht-blockierendes Programmieren.

vgs

1 Like

Man kann Interrupts benutzen, löst aber i.d.R. einen ziemlichen Shitstorm im Forum aus :wink:

Der Hintergrund ist das erforderliche "Debouncing/Entprellen" mechanischer Taster, da diese dazu tendieren beim Umschalten sehr häufig zwischen Kontakt/kein Kontakt hin- und herzuschwingen. Damit kann man per Interrupt den Controller unschön beschäftigen ...

Für Deine Aufgabe sollte es genügen, auf eine Schleife zu verzichten und stattdessen das Takten per Zeitabfrage zu erledigen. Am Einfachsten mit der millis()-Funktion, man kann auch micros() verwenden, wenn es "schneller" gehen muss .

Hier nur ein Ausschnitt:

unsigned long beginToWait = 0;
const int wait1s   =  1000;

boolean TimeIsOver(unsigned long TimeToWait){
   if(millis()-beginToWait > TimeToWait) {
          return true;
   } else return false;
}  

void loop(){
    if (TimeIsOver(wait1s)){
          // Do something
           beginToWait = millis();
   } 
} 

Man muss allerdings etwas umdenken, und Schleifen durch zeitlich getaktete und ggf. durch boolsche Variable gegenseitig verriegelnde Routinen ersetzen ... Und man braucht ggf. je eine eigene Variable "beginToWait" für jede parallel zu taktende Funktion . Um die Routine nicht mehrfach zu schreiben, kann man dafür ein Array beginToWait[3] o.ä. nehmen, desse Index man in TimeIsOver() einarbeitet:

const byte NoOfTimes = 3;
unsigned long beginToWait[NoOfTimes];
const int wait1s   =  1000;
const int wait3s   =  3000;

boolean TimeIsOver(int Index, unsigned long TimeToWait){
   if(millis()-beginToWait[Index] > TimeToWait) {
          return true;
   } else return false;
}  

void setup(){
  for (int i=0; i< NoOfTimes;i++) beginToWait[i] = 0;
}

void loop(){
    if (TimeIsOver(0, wait1s)){
          // Do something
           beginToWait[0] = millis();
   } 
    if (TimeIsOver(1, wait3s)){
          // Do something
           beginToWait[1] = millis();
   } 
}

P.S.: (Ungetester Code, da hier direkt hineingeschrieben :wink: ).

1 Like

Nur damit wir uns verstehen: "void" heißt nichts und bezieht sich auf den Variablentyp, der von einer Funktion zurückgegeben wird. Bei "int" ist es eine ganze Zahl, bei "void" eben nichts. "Wenn ich meine Taster-Funktionalität in einer Taster-Funktion habe, ..." wäre also richtiger.

In anderen Umgebungen würde ich Dir sofort zustimmen, bei einem Arduino eher nicht. Andersherum wie in #8 beschrieben, finde ich besser.

Interrupts nutzt man bei kurzen Sensorimpulsen, damit man sie nicht verpaßt.

1 Like

loop() ist schon eine Schleife. Solche for-Schleifen kann man daher durch if-Abfragen in loop() ersetzen. Dann reagieren die Taster auch vielleicht selbst wenn man einige wenige Millisekunden wartet.

So hier ist jetzt ein Programm das folgendes macht:

Auf Taster Start warten
Wenn Taster gedrückt wurde Schrittmotor drehen
wenn "unten" angekommen Richtungswechsel auf "oben"
wenn oben angekommen dann Richtungswechsel auf "unten"
ständige TasterAbfrage
Wenn Taster gedrückt wurde vormerken und den momentan aktiven Weg noch zu Ende fahren dann anhalten

lieber Patrick,
Ich war jetzt selbst überrascht wie viel Code das ergibt, wenn man es als Schrittkette programmiert.
Ich habe es im "Trockenlauf" per serial Debug-Ausgaben getestet. Es funktioniert.

Du wirst vermutlich eine Menge fragen zu diesem Code haben.
Dann Frage einfach drauflos

#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope

#define dbgi(myFixedText, variableName,timeInterval) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);

const byte stepPin   = 4;
const byte dirPin    = 5;
const byte buttonPin = 6;

int SchrittZahl;

const byte nachOben  = LOW;
const byte nachUnten = HIGH;

#define losgelassen HIGH
#define gedrueckt   LOW

int StepCounter = 0;
boolean IstOben = false;
boolean MerkerTasteGedrueckt = false;

const byte WarteAufStart    = 0;
const byte MotorStartRauf   = 1;
const byte MotorStartRunter = 2;
const byte SchritteAusgeben = 3;
const byte CheckTastenDruck = 4;
const byte RichtungsWechsel = 5;

byte SchrittNr = WarteAufStart;

void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("  compiled ") );
  Serial.print( F(__DATE__) );
  Serial.print( F(" ") );
  Serial.println( F(__TIME__) );
}


boolean TimePeriodIsOver (unsigned long &expireTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - expireTime >= TimePeriod ) {
    expireTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  }
  else return false;            // not expired
}



void setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  PrintFileNameDateTime();

  pinMode(stepPin, OUTPUT);
  pinMode(dirPin,  OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
}


void loop() {
  buttonPressedForSomeTime();

  SchrittKetteSchrittMotor();
}


void EinenSchrittAusgeben() {
  digitalWrite(stepPin, HIGH);
  delayMicroseconds(4000);
  digitalWrite(stepPin, LOW);
  delayMicroseconds(4000);
}



void SchrittKetteSchrittMotor() {

  dbgi("sk", SchrittNr, 1000);

  switch (SchrittNr) {

    case WarteAufStart:

      if (MerkerTasteGedrueckt == true) {
        dbg("sk Taster gedrueckt", IstOben);
        MerkerTasteGedrueckt = false;
        SchrittNr = RichtungsWechsel;
      }
      break;


    case MotorStartRunter:
      dbg("sk MotorStartRunter", IstOben);
      digitalWrite(dirPin, nachUnten);
      SchrittZahl = 180;
      SchrittNr = SchritteAusgeben;
      break;


    case MotorStartRauf:
      dbg("sk MotorStartRauf", IstOben);
      digitalWrite(dirPin, nachOben);
      SchrittZahl = 180;
      SchrittNr = SchritteAusgeben;
      break;


    case SchritteAusgeben:
      //dbg("sk SchritteAusgeben", SchrittZahl);
      EinenSchrittAusgeben();
      SchrittZahl--;
      if (SchrittZahl <= 0) {
        SchrittNr = CheckTastenDruck;
      }
      break;


    case CheckTastenDruck:
      if (MerkerTasteGedrueckt == true) {
        dbg("sk CheckTastenDruck", 99);
        MerkerTasteGedrueckt = false;
        SchrittNr = WarteAufStart;
      }
      else {
        SchrittNr = RichtungsWechsel;
      }
      break;


    case RichtungsWechsel:
      dbg("sk RichtungsWechsel", IstOben);
      if (IstOben == true) {
        IstOben = false;
        SchrittNr = MotorStartRunter;
      }
      else {
        IstOben = true;
        SchrittNr = MotorStartRauf;
      }
  }
}


// function for debouncing a button
void buttonPressedForSomeTime() {

  // Variables will change:
  static byte buttonState;             // the current reading from the input pin
  static byte lastButtonState;// = LOW;   // the previous reading from the input pin

  // the following variables are unsigned longs because the time, measured in
  // milliseconds, will quickly become a bigger number than can be stored in an int.
  static unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
  const unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

  static byte debouncedState = 0;
  static byte reading;
  // read the state of the switch into a local variable:
  reading = digitalRead(buttonPin);
  //dbg("b1", reading);
  //dbg("b1", digitalRead(buttonPin));

  // check to see if you just pressed the button
  // (i.e. the input went from LOW to HIGH), and you've waited long enough
  // since the last press to ignore any noise:

  // If the switch changed, due to noise or pressing:
  if (reading != lastButtonState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
    //dbg("reading !=lastlastButtonState",reading);
    //delay(500);
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    // whatever the reading is at, it's been there for longer than the debounce
    // delay, so take it as the actual current state:
    //dbg("3(millis() - lastDebounceTime) > debounceDelay)",millis() - lastDebounceTime);
    // if the button state has changed:
    if (reading != buttonState) {
      buttonState = reading;
      //dbg("reading != buttonState", buttonState);

      // ButtonState does state-CHANGE-detection
      if (buttonState == gedrueckt) {
        MerkerTasteGedrueckt = true;
        debouncedState = 1;
      }

      if (buttonState == losgelassen) {
        debouncedState = 0;
      }
    }
  }

  // save the reading. Next time through the loop, it'll be the lastButtonState
  lastButtonState = reading;
  return debouncedState;
}
1 Like

Gibt es jetzt nicht nur die Nachtwächtererklährung sondern auch noch die Pizza-Gurkensalaterklährung? :wink: :wink: :wink: :wink:

bei delay() macht der Controller nichts. Er wartet bis die Zeit vorbei ist. For Schleifen sind auch etwas Problematisch da auch diese den Programmfluß anhalten und wenn die Tastenabfrage außerhalb der Schleife ist diese nicht durchführen solange bis die Schleife beendet ist.

Grüße Uwe

1 Like

Bei der Pizza fehlt mir - damit sie nicht verbrennt - noch der Zusatz, dass beim "auf die Uhr schauen" natürlich eine Aktion erfolgen soll wenn die Backzeit abgelaufen ist. So gesehen würde ich die Funktion lieber anders nennen:

AufDieUhrSchauenUndGgfAusDemOfenHolen();

:wink:

Und hier (damit es nicht zu langweilig wird :wink: ) eine "Stepper-Miniklasse" und eine entprellende Buttonabfrage, die mit millis() arbeiten:

#define DEBUG false


// Klassendefinition Stepper

class Stepper{
   public:
     Stepper(byte stepPin, unsigned long MilliSeconds);
     setNoOfSteps(int NoOfSteps);
     boolean stepped();
   private:
     byte _stepPin;
     int _noOfSteps = 0;
     unsigned long _milliseconds = 1;
     unsigned long _lastStepTime = 0;
     _doNextStep();
     byte    _stepState = HIGH;
};

// Konstruktor mit dem Pin für den Stepper und einer Zeit pro digitalWrite() in Millisekunden
Stepper::Stepper(byte stepPin, unsigned long MilliSeconds){
   _milliseconds = MilliSeconds;
   _stepPin      = stepPin;
   pinMode(_stepPin,OUTPUT);
};

// Mit diesem Aufruf setzt man die Anzahl der Steps:
Stepper::setNoOfSteps(int NoOfSteps){
  if (NoOfSteps >= 0) _noOfSteps = NoOfSteps;
}

// step() wird in der loop() aufgerufen und steuert den Stepper bei jedem Aufruf  an,
// solange der Zähler _noOfSteps größer als Null ist, wobei die Werte HIGH und LOW 
// sich abwechseln
boolean Stepper::stepped(){
    if (_noOfSteps > 0) {
      _doNextStep();
    }
  return (_noOfSteps > 0);  // 
};

// Die klasseninterne Funktion, die nach Ablauf der Zeit pro digitalWrite() zunächst 
// den Stepper mit HIGH und nach Ablauf der zweiten Periode mit LOW ansteuert
Stepper::_doNextStep(){
  if (millis()- _lastStepTime > _milliseconds){
    _lastStepTime = millis();
    digitalWrite(_stepPin,_stepState);
    _stepState = !_stepState; 
    if (_stepState) {
       _noOfSteps--;  // jeden zweiten Teilstep den Zähler reduzieren
        if (DEBUG) Serial.println("Steps to go :\t"+String(_noOfSteps));
    } else {
       if (DEBUG) Serial.println("Halfstep for :\t"+String(_noOfSteps));
    }
  }
};

// Ab hier beginnt das eigentliche Programm

const byte button_pin  = 13;
const byte aStepPin = 3;
const unsigned long MilliSecondDelay = 1; // 1 Millisekunde pro digitalWrite()
Stepper stepper(aStepPin,MilliSecondDelay);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  pinMode(button_pin, INPUT_PULLUP);
  stepper.setNoOfSteps(180);     // Sorgt dafür, dass mit Einstieg in loop() 
                                 // zunächst 180 steps durchgeführt werden
}

void loop() {
  // put your main code here, to run repeatedly:
  if (stepper.stepped());          // Gibt solange true zurück, wie sich der Stepper bewegt   
  if (ButtonReleased()) {          // Funktion für die Taste am Pin button_pin, die einmal beim
                                   // Drücken reagiert und dann erst wieder nachdem mal losgelassen hat
    Serial.println("Released");
    stepper.setNoOfSteps(50);      // Dabei wird der Zähler auf 50 gesetzt und der Stepper stepped los ...
  }
}

// Prüft das Betätigen der Taste und entprellt mit einer Zeit von 50 ms
boolean ButtonReleased(){
  static unsigned long lastButtonUsedTime = 0;
  static byte lastButtonState = HIGH;
  static boolean wasLOW = false;                 // Verriegelt, damit der nächste Tastendruck erst 
  int Button = digitalRead(button_pin);          // nach dem Loslassen als true ausgegeben wird
  if (Button != lastButtonState) {
    lastButtonUsedTime = millis();
    lastButtonState = Button;
    wasLOW = (Button == LOW);
  }
  if (millis()-lastButtonUsedTime > 50 && wasLOW) {  // Hier ist die Verriegelung eingebaut
     wasLOW = false;                                 // indem wasLOW hier auf false geht
     return true;                                    // und erst wieder auf true gesetzt wird
  } else {                                           // wenn mindestens einmal zuvor der Button
     return false;                                   // im Zustand LOW war
  };   
}

Allerdings auch nur gegen Serial-Ausgaben getestet ...

Zum Einrichten eines weiteren Steppers braucht es dann nur die folgende Zeile:

const byte anotherStepPin = 12;

Stepper stepperNeu(anotherStepPin,MilliSecondDelay);  // Stepper-Pin und Verzögerung zwischen den digitalWrite()

Danach kann man mit

void setup(){
// ...
 stepperNew.setNoOfSteps(90); 
// ...
}

void loop(){
 // ...
  if (stepperNew.stepped());       
// ..:
}

den stepperNew um 90 Schritte bewegen lassen und natürlich über Knopfdruck bei geeigneter Programmierung weiteres veranlassen ...

1 Like

für den Fall das Du noch keine Lösung hast , oder es noch ganz verstanden hast. Dein Problem ist hier eigentlich nicht das delay das sind ja nur 1.6ms das Problem hier ist die for Schleife. 180*1.6ms dauert die in dem Fall ja. Wenn Du jetzt nicht 180 sondern 1800 Schritte fahren willst wird das noch schlimmer. Du könntest die for schleife auflösen und Deine Fahrbewegungen in eine Ablaufsteuerung (Schrittkette) packen und eine Zähler Variable mit laufen lassen für die Anzahl der Schritte. Deine Scheife ist dann der Loop. Dazu könntest Du dann das delay() noch durch millis() ersetzen. Das hört sich jetzt sicher ein bisschen kompliziert an. Du kannst ja aber mal folgendes versuchen. Der Motor dreht wenn der Schalter geschlossen ist immer eine bestimmte Anzahl Steps. Wenn der Schalter geöffnet wir bleibt er sofort stehen.

const byte btn = 2;
const byte stepPin = 3;
int count;
void setup() {
  // put your setup code here, to run once:
  pinMode(btn, INPUT_PULLUP);
  pinMode(stepPin, OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
  if (digitalRead(btn) == LOW && count < 1800) {
  count++;
  digitalWrite(stepPin, HIGH);
    delayMicroseconds(800);
    digitalWrite(stepPin, LOW);
    delayMicroseconds(800);
  }
  if (digitalRead(btn) == HIGH) count = 0;
}

Wenn Du das Prinzip dann verstanden hast lässt Du es gut sein und verwendest eine Lib. Micobahner hast Dir ja schon geschrieben warum.
Heinz

1 Like

Ich denke einfacher (nicht gleichbedeutend besser) wäre es, den Taster in der For-Schleife zusätzlich abzufragen. Somit ist man dann nicht mehr gefangen.