Multiple steppers including limit Switches does not work

I am currently working on a kinetic sculpture that is basically objects falling down on strings and being pulled up again in loops.
To reach specific locations I am using steppers to pull the objects. After some time a solenoid is used to release an object attached to a string and then it should be pulled up again by a stepper. The stepper runs constantly until a limit switch is activated. After that the loop should start all over again.
For now I got my code and hardware working for a single objects or two combined in the same loop. But I want them to work independently especially the limit switches should work for each object so they stop at the right height.

This is my code so far for two objects (end goal is the amount of around ten).

Can someone direct me in the right direction, please?


#include <AccelStepper.h>

AccelStepper StepperA(1, 26, 25);
AccelStepper StepperB(1, 13, 12);

#define limitSwitchA 4
#define limitSwitchB 5

#define solenoidA 8
#define solenoidB 9

int state = 0;

// ---------------------interval ---------------------

unsigned long Interval = 10000;


// ---------------------millis ---------------------
unsigned long currentMillis = 0;    // stores the value of millis() in each iteration of loop()
unsigned long previousMillis = 0;   // will store last time the steppers were updated



//================================================================================

void setup()
{
  // Set all default speeds and accelerations
  StepperA.setMaxSpeed(2000);
  StepperA.setAcceleration(1000);
  StepperA.setSpeed(2000);

  StepperB.setMaxSpeed(2000);
  StepperB.setAcceleration(1000);
  StepperB.setSpeed(2000);


  pinMode(limitSwitchA, INPUT_PULLUP);
  pinMode(limitSwitchB, INPUT_PULLUP);
  pinMode(solenoidA, OUTPUT);
  pinMode(solenoidB, OUTPUT);

  Serial.begin(115200);
}


//================================================================================

void loop()
{
  currentMillis = millis();    // capture the latest value of millis()
  // this is equivalent to noting the time from a clock

  run(); //call your function
  StepperA.runSpeed();// call the functions that do the work
  StepperB.runSpeed();
}

//========================================

void run() {


  if (currentMillis - previousMillis >= Interval) {

    while (digitalRead (limitSwitchA) == HIGH) {

      Serial.println ("up");
      StepperA.runSpeed ();

      Serial.println ("high");
      StepperA.runSpeed ();
      delay(6000 * 2);


      digitalWrite (solenoidA, LOW);
      delay (6000 * 2);
      Serial.println ("loose");
      digitalWrite (solenoidA, HIGH);

      Serial.println ("down");
      delay(6000);
      digitalWrite (solenoidA, LOW);
    }

    while (digitalRead (limitSwitchB) == HIGH) {

      Serial.println ("up");
      StepperB.runSpeed ();

      Serial.println ("high");
      StepperB.runSpeed ();
      delay(6000 * 2);


      digitalWrite (solenoidB, LOW);
      delay (6000 * 2);
      Serial.println ("loose");
      digitalWrite (solenoidB, HIGH);

      Serial.println ("down");
      delay(6000);
      digitalWrite (solenoidB, LOW);
    }

    previousMillis = currentMillis;

  }
}```

(Futhermore it is part of a greater concept and which is why I am using a stepper because I want to expand the concept of my project and will probably use certain locations to move to as well according to sensor inputs.)

One thing that bothers me is all the 12-second delays. What is THAT about?

You have the speed set to 2000. Do you really want to hit your limit switch at 2000 steps per second?

The basic logic of such an application is
running a loop at high speed continiously to be able to check for limit-switches all the time and with quick response and to switch ON / OFF step-pulse-creation controlled by boolean variables.

Threre are different approaches to create such a functionality.
What precision do you want to have?
Would 2 mm be enough? or does it have to be 0,01 mm?

Did you do real tests if your stepper-motors are able to start / stop at a constant step-pulse-frequency of 2000 pulses per second under load?

if the frequency is too high the stepper-motor just won't rotate.
If you want the objects to move pretty quick you might need acceleration decceration.

best regards Stefan

This was my code to test, that is why the delays are 12 seconds. (should have explained that...) Actually the Intervals will be much longer: between one to three minutes.

I tested it already and it works fine. The load is only around 10 -20 grams and in my tests the precision was working well in different speed settings.

So I guess I should work without the delay() using millis() instead and creating booleans to check if the different parts of loop are true and should be executed??

Hello
I´ve made an analyzis of used delay´s:

Line 74:       delay(6000 * 2);
	Line 78:       delay (6000 * 2);
	Line 83:       delay(6000);
	Line 94:       delay(6000 * 2);
	Line 98:       delay (6000 * 2);
	Line 103:       delay(6000);

Each delay will stopp the execution of the sketch for the selected time.
Your sketch needs a timer function. This function can be derived from the mother of all timers: IDE example BlinkWithoutDelay.

consider

enum { Off = HIGH, On = LOW };

#undef MyHW
#ifdef MyHW
# define limitSwitchA A1
# define limitSwitchB A2
# define solenoidA    13
# define solenoidB    12

class AccelStepper {
    byte pin;

  public:
    AccelStepper (int a, int b, int p)  {
        pin = p;
        pinMode (pin, OUTPUT);
        digitalWrite (pin, Off);
    };

    void setAcceleration (int x)   { };
    void setMaxSpeed     (int x)   { };
    void setSpeed        (int x)   { };
    void runSpeed        (void)    { digitalWrite (pin, On); };
    void stop            (void)    { digitalWrite (pin, Off); };
};

AccelStepper StepperA(1, 26, 10);
AccelStepper StepperB(1, 13, 11);

#else
# include <AccelStepper.h>
# define limitSwitchA 4
# define limitSwitchB 5
# define solenoidA 8
# define solenoidB 9

AccelStepper StepperA(1, 26, 25);
AccelStepper StepperB(1, 13, 12);
#endif

int state = 0;
// ---------------------interval ---------------------
unsigned long Interval = 10000;
// ---------------------millis ---------------------
unsigned long currentMillis = 0;    // stores the value of millis() in each iteration of loop()
unsigned long previousMillis = 0;   // will store last time the steppers were updated

//==============================================================================
void setup()
{
    // Set all default speeds and accelerations
    StepperA.setMaxSpeed(2000);
    StepperA.setAcceleration(1000);
    StepperA.setSpeed(2000);
    StepperB.setMaxSpeed(2000);
    StepperB.setAcceleration(1000);
    StepperB.setSpeed(2000);

    pinMode(limitSwitchA, INPUT_PULLUP);
    pinMode(limitSwitchB, INPUT_PULLUP);

    pinMode(solenoidA, OUTPUT);
    pinMode(solenoidB, OUTPUT);
    digitalWrite(solenoidA, Off);
    digitalWrite(solenoidB, Off);

    Serial.begin(115200);
}

// -----------------------------------------------------------------------------
void run (
    AccelStepper stepper,
    byte          pinLimitSwitch )
{

    if (digitalRead (pinLimitSwitch) == Off)
        stepper.runSpeed ();
    else
        stepper.stop ();
}

//==============================================================================
void
drop (
    byte pinSolenoid )
{
    digitalWrite (pinSolenoid, On);
    delay (500);
    digitalWrite (pinSolenoid, Off);
}

//==============================================================================
void loop()
{
    currentMillis = millis();    // capture the latest value of millis()

    if (currentMillis - previousMillis >= Interval) {
        drop (solenoidA);
        drop (solenoidB);

        previousMillis = currentMillis;
    }

    run (StepperA, limitSwitchA);
    run (StepperB, limitSwitchB);
}

I want to re-assure that I understand the required functionality right. So I describe it in my own words:

There are a certain number of objects.
Each object is tied to its own string
For each object there is a solenoid which is energised after a certain time. Energising the solenoid makes the object fall down until the falling is stopped when the string is taut.

After an amount of time that is bigger that the solenoid-wait-to-energise-time the stepper-motor starts winding up the string and leavers the object.

If the object reaches the limit-switch the steppermotor shall stop.

Object stays winded up until solenoid is energised again.
then repeat this cylce

This cycle should run for each object but completely independent from the other objects.

Is this a correct description?

Do you want to change the interval-times when dropping or winding start while your program is running?

should the count-down until the solenoid is energised to drop the object be started after the limit-switch is reached or at any time even in the middle of winding up?

Do you want the objects drop and wind-up in a certain pattern?

should there be interactivity between a visitor standing on front of your installation?

best regards Stefan

This looks promising, but I can not test it until tonight or tomorrow.

That's a perfect description for the basic plan.

The interval-times will be the same, so i have one consistent loop. But my goal is that the visitors can interact with the objects. For now I am thinking about sensors for the objects that trigger the overall loop and interfere with it.

For now my plan was to work on the basics, so I reach a smoothly running loop where I don't have to check if everything is working. After that I want to add the interactivity and different types of movements i.e. to drop or pull certain amounts and not always the whole way down/up.

@gcjr

the code you posted looks like a ready to use or almost ready to use code.
I haven't done anything yet with classes.

Would you mind explaining what you coded with the class through adding comments to all interesting lines inside the class?

Especially I'm wondering about the three parameters here

  public:
    AccelStepper (int a, int b, int p)  {
        pin = p;
        pinMode (pin, OUTPUT);
        digitalWrite (pin, Off);
    };

I have no idea what parameter "a" and parameter "b" are doing.

the names "a" and "b" don't explain anything.

From the numbers in creating the instances

AccelStepper StepperA(1, 26, 25);
AccelStepper StepperB(1, 13, 12);

I can guess "a" number of steppermotors?? does not really make sense
both instances have one stepper-motor

number in the meaning of motor 1, motor 2, motor 3
makes no sense too.
So what is the first parameter for?

second parameter might be a pin-number
but the definition uses only parameter "p" as a pin number.
So what is parameter "a" and parameter "b" for?

Just place-holders for future use?
No idea at all

I was asking "should the count-down until the solenoid makes the object drop
be started if the limit switch is triggered.
Julius-me did not answer this question.

The code that gcjr posted doesn't take care of that.
Dropping is done constantly if the time defined in variable interval has passed by.

Dropping the object assumes that the limit-switch changes his stage to "no-object near to limit-switch" and that this change can be used to start the stepper-motor to wind up.

best regards Stefan

this is a "stub" class i created to "simulate" the AccelStepper" class defined in AccelStepper.h and library.

i wouldn't normally create a class, i would normally just create a struct. both have "stub" (i.e. do nothing) routines allowing the code to be compiled and tested. notice that it doesn't do anything except turning on and off an LED.

the reason for creating a class instead of a struct is because of the constructor (there's probably a way to do this with a struct). the "int a, int b, int c" is just so that the function definition matches the one being called and i assume are pins connected to the stepper motor controller. i use one as the LED pin.

i'm not sure what is really desire and did something simple which is to drop periodically, which immediately starts the motor because the limit switch opens.

i leave it to julius to decide what to do: periodically or randomly drop/raise, leave pulled up or dropped a random period of time and i assume each string can be dropped independently. both drop() and run() can be called after a random period of time.

Hey, sorry for vanishing, I was busy with some other stuff and this had to wait sadly. but I am back on it. The code seems to work fine, but the solenoid is not turned on. But I am working on it now.
Everything is working independently which is amazing.

I just have some questions:

In the top you defined the solenoids for 13 and 12, and the switches to A1 and A2
then you change those pins. Same goes for the steppers.

Maybe I am not thinking correctly but is it because on top you defined them in a class and then reassign the pins after the #else ?

i;m using C preprocessor directives to make it easy to define values for my hardware as well as your by simply "undef" myHW

this is how i test the code i post

Hey,

so for now I got it working. I had to get rid of the delay, since it stopped all actions involved.

I am working now with this code:

enum { On = HIGH, Off = LOW };

#undef MyHW
#ifdef MyHW
# define limitSwitchA A1
# define limitSwitchB A2
# define solenoidA    13
# define solenoidB    12

class AccelStepper {
    byte pin;

  public:
    AccelStepper (int a, int b, int p)  {
      pin = p;
      pinMode (pin, OUTPUT);
      digitalWrite (pin, Off);
    };

    void setAcceleration (int x)   { };
    void setMaxSpeed     (int x)   { };
    void setSpeed        (int x)   { };
    void runSpeed        (void)    {
      digitalWrite (pin, On);
    };
    void stop            (void)    {
      digitalWrite (pin, Off);
    };
};

AccelStepper StepperA(1, 26, 10);
AccelStepper StepperB(1, 13, 11);

#else
# include <AccelStepper.h>
# define limitSwitchA 5
# define limitSwitchB 4
# define solenoidA 10
# define solenoidB 11

AccelStepper StepperA(1, 13, 12);
AccelStepper StepperB(1, 9, 8);
#endif


int state = 0;
// ---------------------interval ---------------------
unsigned long Interval = 15000;
// ---------------------millis ---------------------
unsigned long currentMillis = 0;    // stores the value of millis() in each iteration of loop()
unsigned long previousMillis = 0;   // will store last time the steppers were updated

//==============================================================================
void setup()
{
  // Set all default speeds and accelerations
  StepperA.setMaxSpeed(12000);
  StepperA.setAcceleration(10000);
  StepperA.setSpeed(12000);
  StepperB.setMaxSpeed(12000);
  StepperB.setAcceleration(10000);
  StepperB.setSpeed(12000);

  pinMode(limitSwitchA, INPUT_PULLUP);
  pinMode(limitSwitchB, INPUT_PULLUP);

  pinMode(solenoidA, OUTPUT);
  pinMode(solenoidB, OUTPUT);
  digitalWrite(solenoidA, Off);
  digitalWrite(solenoidB, Off);

  Serial.begin(115200);
}

// -----------------------------------------------------------------------------
void run (
  AccelStepper stepper,
  byte          pinLimitSwitch )
{

  if (digitalRead (pinLimitSwitch) == On) {
    stepper.runSpeed ();
  } else
    stepper.stop ();
}

//==============================================================================

void
drop (
  byte pinSolenoid,
  byte pinLimitSwitch
)
{

  Serial.println("drop");
  if (digitalRead (pinLimitSwitch) == Off) {
    digitalWrite (pinSolenoid, On);
  } else if (digitalRead(pinLimitSwitch) == On) {
    digitalWrite(pinSolenoid, Off);
  }
}


//==============================================================================
void loop()
{
  currentMillis = millis();    // capture the latest value of millis()

  if (currentMillis - previousMillis >= Interval) {
    drop (solenoidA, limitSwitchA);
    drop (solenoidB, limitSwitchB);

    dropStart (solenoidA, limitSwitchA);
    dropStart (solenoidB, limitSwitchB);


    previousMillis = currentMillis;
  }


  run (StepperA, limitSwitchA);
  run (StepperB, limitSwitchB);
}

But I am wondering now how to use different intervals, so the time the solenoid is on for the drop and the time it waits until it is on can be controlled better. I thought about using something like the blink without delay which is triggered through a boolean and tried something, but I guess the time interval in the void loop() stops it from being executed? Here is my tried code

void drop
(
  byte pinSolenoid,
  byte pinLimitSwitch
)
{

  Serial.println("drop");
  const unsigned long OnTime = 10000;           // milliseconds of on-time
  const unsigned long OffTime = 5000;          // milliseconds of off-time

  static int solenoidState = Off;
  static unsigned long duration = OnTime;
  static unsigned long lastTime;

  if ( millis() - lastTime < duration ) {
    return;
  }
  lastTime = millis();
  if (pinLimitSwitch == On && solenoidState == Off) {
    solenoidState = On;  // Turn it off
    duration = OffTime;
  }
  else {
    solenoidState = Off; //turn it on
    duration = OnTime;
  }
  digitalWrite(pinSolenoid, solenoidState);
}

If you want to switch different IO-pins in different intervals
you must replace all delay()'s with non-blocking timing based on function millis():
as an everyday example with easy to follow numbers
delay() is blocking. As long as the delay is "delaying" nothing else of the code can be executed.
Now there is a technique of non-blocking timing.
The basic principle of non-blocking timing is fundamental different from using delay()

You have to understand the difference first and then look into the code.

otherwise you might try to "see" a "delay-analog-thing" in the millis()-code which it really isn't
Trying to see a "delay-analog-thing" in millis() makes it hard to understand millis()
Having understood the basic principle of non-blocking timing based on millis() makes it easy to understand.

imagine baking a frosted pizza
the cover says for preparation heat up oven to 200°C
then put pizza in.
Baking time 10 minutes

You are estimating heating up needs 3 minutes
You take a look onto your watch it is 13:02 (snapshot of time)
You start reading the newspaper and from time to time looking onto your watch
watch shows 13:02. 13:02 - 13:02 = 0 minutes passed by not yet time
watch shows 13:03. 13:03 - 13:02 = 1 minute passed by not yet time
watch shows 13:04. 13:04 - 13:02 = 2 minutes passed by not yet time

watch shows 13:05 when did I start 13:02? OK 13:05 - 13:02 = 3 minutes time to put pizza into the oven

New basetime 13:05 (the snapshot of time)
watch 13:06 not yet time
watch 13:07 not yet time
watch 13:08 not yet time (13:08 - 13:05 = 3 minutes is less than 10 minutes
watch 13:09 not yet time
watch 13:10 not yet time
watch 13:11 not yet time
watch 13:12 not yet time
watch 13:13 not yet time
watch 13:14 not yet time (13:14 - 13:05 = 9 minutes is less than 10 minutes
watch 13:15 when did I start 13:05 OK 13:15 - 13:05 = 10 minutes time to eat pizza (yum yum)

You did a repeated comparing how much time has passed by
This is what non-blocking timing does

In the code looking at "How much time has passed by" is done

currentTime - startTime >= bakingTime

bakingTime is 10 minutes

13:06 - 13:05 = 1 minute >= bakingTime is false
13:07 - 13:05 = 2 minutes >= bakingTime is false
...
13:14 - 13:05 = 9 minutes >= bakingTime is false
13:15 - 13:05 = 10 minutes >= bakingTime is TRUE time for timed action!!

So your loop() is doing

void loop()
// doing all kinds of stuff like reading the newspaper

if (currentTime - previousTime >= period) {
previousTime = currentTime; // first thing to do is updating the snapshot of time
// time for timed action
}

best regards Stefan

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