Limit Switch Timeout help

I'm starting a project that I will probably use an UNO initially then go with something smaller. It's a motor control project which will be controlling an reversible AC Gear motor using relays and there will be limit switches for Raise and Lower. I have determined the amount of time it takes to raise a small platform from it's lowered position and how long it takes to lower from the raised/resting position. So I need to make the code start a timer as soon as the command is given to either raise or lower which is one of two outputs to relays. So lets say to lower the platform it should take 10 seconds, I want it to look for the Lower limit switch to stop the motor, BUT if timer is greater then 12 seconds, to stop the motor and enable another output that will blink an error indicating LED. The same goes for raising the platform.

Hello Thinkster
Post your current sketch, well formated, with comments and in so called code tags "</>" and a schematic, not a Fritzy diagram, to see how we can help.
Have a nice day and enjoy coding in C++.
Дайте миру шанс

consider


const byte ButPin = A1;
const byte OutPin = LED_BUILTIN;

enum { Off = HIGH, On = LOW };

unsigned long msecLst;

#define  Timeout  2000

// -----------------------------------------------------------------------------
void loop (void)
{
    unsigned long msec = millis ();

    if (msecLst && (msec - msecLst) > Timeout)  {
        msecLst = 0;
        digitalWrite (OutPin, Off);
    }

    if (LOW == ButPin && 0 == msecLst)  {
        msecLst = msec ? msec : 1;
        digitalWrite (OutPin, On);
    }
}

// -----------------------------------------------------------------------------
void setup (void)
{
    Serial.begin (9600);

    pinMode (ButPin, INPUT_PULLUP);
    pinMode (OutPin, OUTPUT);
    digitalWrite (OutPin, Off);
}

Maybe these tutorials that deal with using millis() for timing will help.
Blink without delay().
Beginner's guide to millis().
Several things at a time.

Since this is preliminary, I don't have a sketch yet to post, but I will work on that and a flow chart.
Basically, this project would be similar to a gate opener logic as that uses (2) limit switches and mine uses (3) and instead of moving a gate left or right, mine is moving a platform up or down. If there are any good sketches or links to good gate opener projects, that would be helpful too..

You are asking for a somehow medium advanced functionality.
This means writing a code with such a functionality needs more than 10 lines of code.

Writing a code with such a functionality in one of multiple "professional" ways needs more than 50 lines of code.

Writing a code with such a functionality including explaining comments needs more than 100 lines

These are the reasons why this code has 184 lines.
This code is intended for two things:

  1. providing one solution
  2. as an experiment as follows

answering the question: Is it still easy to understand or is it overwhelming through all the details used in this code???

So I invite you (the thread-opener) to read this code and then to report

very honest

What your experience with reading this code is.

After reading it eventually building a test-circuitry and to test the code

void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__) ); // gets replaced by actual filename including path
  Serial.print( F("  compiled ") );
  Serial.print( F(__DATE__) ); // gets replaced by date at compiling
  Serial.print( F(" ") );
  Serial.println( F(__TIME__) ); // gets replaced by time at compiling
}


// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

const byte    OnBoard_LED = 13;

// declaring self-explaining names for input_pins
const byte buttonUp_pin   = 4;
const byte buttonDown_pin = 5;

const byte lowerLimitSwitch_pin = 6;
const byte upperLimitSwitch_pin = 7;

// declaring self-explaining names for output pins
const byte relayUp_pin   =  8;
const byte relayDown_pin =  9;
const byte errorLED_pin  = 10;

// input-pullup-resistors invert the logic
// +Vcc---pullup----IO-Pin-----switch----GND
// if the internal pullup-resistor is activated and switch/button is released
// the IO-pin is connected to +Vcc and senses +Vcc = HIGH
// if switch becomes closed the voltages drops across the pullup-resistor
// and the IO-pin senses LOW
// you can imagine this as if switch is closed the IO-pin has a BETTER connection
// to ground ==> the IO-pin senses ground


// define self-explaining names that make the code easier to read
#define pressed LOW
#define released HIGH

// declaring self-explaining names for constants used in the step-chain-function
const byte sc_WaitForButton   = 1;
const byte sc_StartMovingDown = 2;
const byte sc_WaitForDownPos  = 3;
const byte sc_StartMovingUp   = 4;
const byte sc_WaitForUpPos    = 5;
const byte sc_ErrorTimeOut    = 6;

byte stepNo; // variable to set step-chain-function to a certain step

unsigned long MyTimeOutTimer;            // Timer-variables MUST be of type unsigned long
unsigned long MyBlinkTimer;
const unsigned long MoveDownTimeOut = 12000;
const unsigned long MoveUpTimeOut   = 15000;


// function that makes the onBoard-LED blink to indicate microcontroller is running
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 setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  PrintFileNameDateTime();

  pinMode(buttonUp_pin,   INPUT_PULLUP);
  pinMode(buttonDown_pin, INPUT_PULLUP);

  pinMode(lowerLimitSwitch_pin, INPUT_PULLUP);
  pinMode(upperLimitSwitch_pin, INPUT_PULLUP);

  digitalWrite(relayUp_pin,   LOW);
  digitalWrite(relayDown_pin, LOW);
  digitalWrite(errorLED_pin,  LOW);

  pinMode(relayUp_pin,   OUTPUT);
  pinMode(relayDown_pin, OUTPUT);
  pinMode(errorLED_pin,  OUTPUT);
}


void myMotorStepChain() {

  // depending on the value the variable stepNo is set to
  // execute only that part of the code that belongs to that value
  // each value is represented by the NAME of a constant
  switch (stepNo) {

    case sc_WaitForButton:
      if (digitalRead(buttonUp_pin) == pressed) {
        // if button up is pressed
        stepNo = sc_StartMovingUp; // set new step
      }

      if (digitalRead(buttonDown_pin) == pressed) {
        // if button down is pressed
        stepNo = sc_StartMovingDown; // set new step
      }
      break; // immidiately jump down to END-OF-SWITCH


    case sc_StartMovingUp:
      digitalWrite(relayUp_pin, HIGH); // start motor for moving up
      MyTimeOutTimer = millis();      // store timestamp when moving up STARTED
      stepNo = sc_WaitForUpPos;       // set new step
      break; // immidiately jump down to END-OF-SWITCH


    case sc_WaitForUpPos:
      if (digitalRead(upperLimitSwitch_pin) == pressed) {
        // if switch is pressed => endposition reached change to
        digitalWrite(relayUp_pin, LOW); //switch off motor
        stepNo = sc_WaitForButton;     // idle mode waiting for the next button-press
      }

      if ( TimePeriodIsOver(MyTimeOutTimer, MoveUpTimeOut) ) {
        // if more time has passed by since motorstart than defined in variable MoveUpTimeOut
        digitalWrite(relayUp_pin, LOW); //switch off motor
        MyBlinkTimer = millis();
        stepNo = sc_ErrorTimeOut;      // set error-mode (blinking the LED)
      }
      break; // immidiately jump down to END-OF-SWITCH



    case sc_StartMovingDown:
      digitalWrite(relayDown_pin, HIGH); // start motor for moving up
      MyTimeOutTimer = millis();      // store timestamp when moving up STARTED
      stepNo = sc_WaitForDownPos;       // set new step
      break; // immidiately jump down to END-OF-SWITCH


    case sc_WaitForDownPos:
      if (digitalRead(lowerLimitSwitch_pin) == pressed) {
        // if switch is pressed => endposition reached change to
        digitalWrite(relayDown_pin, LOW); //switch off motor
        MyBlinkTimer = millis();
        stepNo = sc_WaitForButton;     // idle mode waiting for the next button-press
      }

      if ( TimePeriodIsOver(MyTimeOutTimer, MoveDownTimeOut) ) {
        // if more time has passed by since motorstart than defined in variable MoveDownTimeOut
        digitalWrite(relayDown_pin, LOW); //switch off motor
        MyBlinkTimer = millis();
        stepNo = sc_ErrorTimeOut;      // set error-mode (blinking the LED)
      }
      break; // immidiately jump down to END-OF-SWITCH


    case sc_ErrorTimeOut:
      if ( TimePeriodIsOver(MyBlinkTimer,200) ){
        //if more than 200 milliseconds have passed by
        static byte LED_state = digitalRead(errorLED_pin);
        LED_state = !LED_state; // invert LED-State (from LOW to HIGH / HIGH to LOW
        digitalWrite(errorLED_pin,LED_state);
      }
      break; // immidiately jump down to END-OF-SWITCH

  } // END-OF-SWITCH
}



void loop() {
  BlinkHeartBeatLED(OnBoard_LED, 100);

  myMotorStepChain();
}

best regards Stefan

Thank you for the code, I have looked it over and it looks quite useful! I will try to test it out next week in my workshop and report back the results.

OK, so I finally breadboarded this with LED's on the Outputs and Normally open Momentary pushbutton switches on the inputs to simulate commands and the limit switches. The code is well defined with comments, however after uploading it to an Arduino Uno, I only get the heartbeat LED blinking and no other functions. My switch legs are 5v from the internal pullup and the commons on all the switches do go to ground. Not sure where to go from here as I'm still learning Arduino code..

the most used debug-technique is serial output that prints to the serial monitor what is really going on.

I will add the serial output and then post it. This will take some time...
best regards Stefan

OK so here is a code-version with serial debug-output
While adding the debug-output I found the bug
variable "stepNo" was not initialised
this line of code was missing in void setup()

  stepNo = sc_WaitForButton; // initialise stepChain with that step it should start with

here is the new code-version

// MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START *

// a detailed explanation how these macros work is given in this tutorial
// https://forum.arduino.cc/t/comfortable-serial-debug-output-short-to-write-fixed-text-name-and-content-of-any-variable-code-example/888298

#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);
// usage: dbgi("2:my fixed text",myVariable,1000);
// myVariable can be any variable or expression that is defined in scope
// third parameter is the time in milliseconds that must pass by until the next time a
// Serial.print is executed
// end of macros dbg and dbgi
// MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END *


void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__) ); // gets replaced by actual filename including path
  Serial.print( F("  compiled ") );
  Serial.print( F(__DATE__) ); // gets replaced by date at compiling
  Serial.print( F(" ") );
  Serial.println( F(__TIME__) ); // gets replaced by time at compiling
}


// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

const byte    OnBoard_LED = 13;

// declaring self-explaining names for input_pins
const byte buttonUp_pin   = 4;
const byte buttonDown_pin = 5;

const byte lowerLimitSwitch_pin = 6;
const byte upperLimitSwitch_pin = 7;

// declaring self-explaining names for output pins
const byte relayUp_pin   =  8;
const byte relayDown_pin =  9;
const byte errorLED_pin  = 10;

// input-pullup-resistors invert the logic
// +Vcc---pullup----IO-Pin-----switch----GND
// if the internal pullup-resistor is activated and switch/button is released
// the IO-pin is connected to +Vcc and senses +Vcc = HIGH
// if switch becomes closed the voltages drops across the pullup-resistor
// and the IO-pin senses LOW
// you can imagine this as if switch is closed the IO-pin has a BETTER connection
// to ground ==> the IO-pin senses ground


// define self-explaining names that make the code easier to read
#define pressed LOW
#define released HIGH

// declaring self-explaining names for constants used in the step-chain-function
const byte sc_WaitForButton   = 1;
const byte sc_StartMovingDown = 2;
const byte sc_WaitForDownPos  = 3;
const byte sc_StartMovingUp   = 4;
const byte sc_WaitForUpPos    = 5;
const byte sc_ErrorTimeOut    = 6;

byte stepNo; // variable to set step-chain-function to a certain step

unsigned long MyTimeOutTimer;            // Timer-variables MUST be of type unsigned long
unsigned long MyBlinkTimer;
const unsigned long MoveDownTimeOut = 12000;
const unsigned long MoveUpTimeOut   = 15000;


// function that makes the onBoard-LED blink to indicate microcontroller is running
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 setup() {
  Serial.begin(115200);
  Serial.println("Setup-Start");
  PrintFileNameDateTime();

  pinMode(buttonUp_pin,   INPUT_PULLUP);
  pinMode(buttonDown_pin, INPUT_PULLUP);

  pinMode(lowerLimitSwitch_pin, INPUT_PULLUP);
  pinMode(upperLimitSwitch_pin, INPUT_PULLUP);

  digitalWrite(relayUp_pin,   LOW);
  digitalWrite(relayDown_pin, LOW);
  digitalWrite(errorLED_pin,  LOW);

  pinMode(relayUp_pin,   OUTPUT);
  pinMode(relayDown_pin, OUTPUT);
  pinMode(errorLED_pin,  OUTPUT);

  stepNo = sc_WaitForButton; // initialise stepChain with that step it should start with
}


void myMotorStepChain() {
  dbgi("1:", stepNo, 1000);

  // depending on the value the variable stepNo is set to
  // execute only that part of the code that belongs to that value
  // each value is represented by the NAME of a constant
  switch (stepNo) {

    case sc_WaitForButton:
      if (digitalRead(buttonUp_pin) == pressed) {
        // if button up is pressed
        stepNo = sc_StartMovingUp; // set new step
        dbg("sc_WaitForButton", digitalRead(buttonUp_pin));
      }

      if (digitalRead(buttonDown_pin) == pressed) {
        // if button down is pressed
        stepNo = sc_StartMovingDown; // set new step
        dbg("sc_WaitForButton", digitalRead(buttonDown_pin));
      }
      break; // immidiately jump down to END-OF-SWITCH


    case sc_StartMovingUp:
      digitalWrite(relayUp_pin, HIGH); // start motor for moving up
      MyTimeOutTimer = millis();      // store timestamp when moving up STARTED
      stepNo = sc_WaitForUpPos;       // set new step
      dbg("sc_StartMovingUp", digitalRead(relayUp_pin));
      break; // immidiately jump down to END-OF-SWITCH


    case sc_WaitForUpPos:
      if (digitalRead(upperLimitSwitch_pin) == pressed) {
        // if switch is pressed => endposition reached change to
        digitalWrite(relayUp_pin, LOW); //switch off motor
        stepNo = sc_WaitForButton;     // idle mode waiting for the next button-press
        dbg("sc_WaitForUpPos", digitalRead(upperLimitSwitch_pin));
      }

      dbgi("sc_WaitForUpPos", digitalRead(upperLimitSwitch_pin),500);
      if ( TimePeriodIsOver(MyTimeOutTimer, MoveUpTimeOut) ) {
        // if more time has passed by since motorstart than defined in variable MoveUpTimeOut
        digitalWrite(relayUp_pin, LOW); //switch off motor
        MyBlinkTimer = millis();
        stepNo = sc_ErrorTimeOut;      // set error-mode (blinking the LED)
      }
      break; // immidiately jump down to END-OF-SWITCH



    case sc_StartMovingDown:
      digitalWrite(relayDown_pin, HIGH); // start motor for moving up
      MyTimeOutTimer = millis();      // store timestamp when moving up STARTED
      stepNo = sc_WaitForDownPos;       // set new step
      dbg("sc_StartMovingDown", digitalRead(relayDown_pin));
      break; // immidiately jump down to END-OF-SWITCH


    case sc_WaitForDownPos:
      if (digitalRead(lowerLimitSwitch_pin) == pressed) {
        // if switch is pressed => endposition reached change to
        digitalWrite(relayDown_pin, LOW); //switch off motor
        MyBlinkTimer = millis();
        stepNo = sc_WaitForButton;     // idle mode waiting for the next button-press
        dbg("sc_WaitForDownPos", digitalRead(lowerLimitSwitch_pin));
      }

      dbgi("sc_WaitForDownPos", digitalRead(lowerLimitSwitch_pin),500);
      if ( TimePeriodIsOver(MyTimeOutTimer, MoveDownTimeOut) ) {
        // if more time has passed by since motorstart than defined in variable MoveDownTimeOut
        digitalWrite(relayDown_pin, LOW); //switch off motor
        MyBlinkTimer = millis();
        stepNo = sc_ErrorTimeOut;      // set error-mode (blinking the LED)
      }
      break; // immidiately jump down to END-OF-SWITCH


    case sc_ErrorTimeOut:
      if ( TimePeriodIsOver(MyBlinkTimer, 200) ) {
        //if more than 200 milliseconds have passed by
        static byte LED_state = digitalRead(errorLED_pin);
        LED_state = !LED_state; // invert LED-State (from LOW to HIGH / HIGH to LOW
        digitalWrite(errorLED_pin, LED_state);
      }
      break; // immidiately jump down to END-OF-SWITCH

  } // END-OF-SWITCH
}



void loop() {
  BlinkHeartBeatLED(OnBoard_LED, 100);

  myMotorStepChain();
}

best regards Stefan

1 Like

Thank you Stefan for your hard work and catching the bug. I will try out the code and let you know how it works. By the way, if I want to lower the frequency of the heartbeat, what do I change?

Thanks!
-Thinkster-

start analysing that lines of code that use the word BlinkHeartBeat

Your username is "Thinkster"
Start thinking. What do you think ?

best regards Stefan

Lol, you are right. Sorry I didn't look through the code at the bottom to realize it was there. I thought that it would be defined at the very beginning, so that's where I was looking.
Anyways, I just tested the code and all looks well so far! It behaves just as it should. This is a great start and I will try to add a few things. The debugging output shows just which step it's on so thank you for that debug update as well!

So as I mentioned, the code works good and I am testing with all momentary N/O pushbuttons. In addition to the "command" pushbuttons (buttonUp, buttonDown) what if I were to have a toggle switch whereas ON would send a constant +5V to a particular pin which would trigger the StartMovingDown step and OFF would send a constant 0V to that same pin which would trigger the StartMovingUp step AFTER xx seconds? I guess I should have a couple header pins with a jumper to send a LOW to another input to either enable or disable this state change trigger because it could conflict with my momentary pushbutton commands. I could build this in hardware to toggle, but for me it would be more challenging to do this all in code.

If it is a toggle-switch you would do state-change-detection
state-change.detection is done with a second variable oldState

if (oldtState != actualState) {
//execute code that should be executed on state-CHANGE
oldtState = actualState; // update oldState

if the IO-pins state changes from LOW-to-HIGH
set stepNo = StartMovingDown

if the IO-pins state changes from HIGH-to-LOW

  • initialise a timer-variable with actual millis() and set StepNo to a new step called wait_xx_seconds
    this new step does non-blocking timing

if TimePeriodIsOver
set stepNo = StartMovingUp

best regards Stefan

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.