How to make actuators extend and retract in sequence while being interruptible by a switch

I just began learning Arduino a month ago so bear with me for my etiquette infractions!

Ultimately, I am trying to create a table whose legs sequentially raise up and down (via 4x linear actuators) until a user inserts a coin into the table (via coin acceptor switch) which interrupts the loop for X seconds and afterwards returns to what it was doing. So far, I've modified the blinkwithoutdelay example code to the point where I can get the light to stop blinking for X seconds after a coin is inserted, but I'm having a hard time getting this code to operate the actuators.

I have a separate file that allows me to operate the actuators once sequentially when a coin is inserted, which feels great, but isn't getting me what I need since I can't use the delay function in the final code except for the result of a coin insertion.

How might I go about further modifying the blinkwithoutdelay code to allow operation of the actuators? Here is the code as it exists now, warts and all.

modified blinkwithoutdelay

// constants won't change. Used here to set a pin number:
const int ledPin = LED_BUILTIN;  // the number of the LED pin

// Variables will change:
int ledState = LOW;  // ledState used to set the LED

// Generally, you should use "unsigned long" for variables that hold time
// The value will quickly become too large for an int to store
unsigned long previousMillis = 0;  // will store last time LED was updated

// constants won't change:
const long interval = 1000;  // interval at which to blink (milliseconds)

#define button 2; //coin acceptor
int coinState = 0; //button is not pressed?
int pushButton = 2; // coin signal input

#define motor1in1 9 //motor drive forward
#define motor1in2 10 //motor drive reverse

int rotDirection = 0;

void setup() {
  // set the digital pin as output:
  pinMode(ledPin, OUTPUT);
  pinMode(pushButton, INPUT);
  pinMode(motor1in1, OUTPUT);
  pinMode(motor1in2, OUTPUT);
  digitalWrite(motor1in1, LOW);
  digitalWrite(motor1in2, HIGH);
}

void loop() {
  int coinState = digitalRead(pushButton);
  if (coinState == HIGH){ // the signal wire is always HIGH, so coin insertion will give me a LOW state
    unsigned long currentMillis = millis();

  // here is where you'd put code that needs to be running all the time.

  // check to see if it's time to blink the LED; that is, if the difference
  // between the current time and last time you blinked the LED is bigger than
  // the interval at which you want to blink the LED.
  

  if (currentMillis - previousMillis >= interval) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;

    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW) {
      ledState = HIGH;
    } else {
      ledState = LOW;
    }
    
    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
  }else { //when signal wire is LOW
    delay(5000); //amount of time coin disables the loop for
  }
}

here is the actuator driving code

#define enA 11 //enable pwm
#define motor1in1 9 //motor drive forward
#define motor1in2 10 //motor drive reverse
#define motor2in1 7 //motor drive forward
#define motor2in2 8 //motor drive reverse
#define button 2 //coin acceptor

const long interval = 1000;  // how many miliseconds to drive a motor for
unsigned long previousMillis = 0;  // will store last time motor was updated

int rotDirection = 0;
int pressed = false;

void setup() {
  pinMode(enA, OUTPUT); //not currently used
  pinMode(motor1in1, OUTPUT);
  pinMode(motor1in2, OUTPUT);
  pinMode(motor2in1, OUTPUT);
  pinMode(motor2in2, OUTPUT);
  pinMode(button, INPUT); 
  // Set initial rotation direction
  digitalWrite(motor1in1, LOW);
  digitalWrite(motor1in2, HIGH);
  digitalWrite(motor2in1, LOW);
  digitalWrite(motor2in2, HIGH);
}

void loop() {
  //int potValue = analogRead(A0); // Read potentiometer value
  //int pwmOutput = map(potValue, 0, 1023, 0 , 255); // Map the potentiometer value from 0 to 255
  //analogWrite(enA, pwmOutput); // Send PWM signal to L298N Enable pin

  // Read button - Debounce
  if (digitalRead(button) == true) {
    pressed = !pressed;
  }
  while (digitalRead(button) == true);
  delay(10);

  // If button is pressed - change rotation direction
  if (pressed == true  & rotDirection == 0) 
  {
    digitalWrite(motor1in1, HIGH);
    digitalWrite(motor1in2, LOW);
    rotDirection = 1;
    delay(1000);
    digitalWrite(motor1in1, LOW);
    digitalWrite(motor1in2, HIGH);
    rotDirection = 0;
    delay(1000);
    digitalWrite(motor2in1, HIGH);
    digitalWrite(motor2in2, LOW);
    rotDirection = 1;
    delay(1000);
    digitalWrite(motor2in1, LOW);
    digitalWrite(motor2in2, HIGH);
    rotDirection = 0;
    delay(1000);
  }
  // If button is pressed - change rotation direction
  //if (pressed == false & rotDirection == 1) 
  {
   // digitalWrite(in1, LOW);
   // digitalWrite(in2, HIGH);
   // rotDirection = 0;
   // delay(20);
  }
}

The actuators (motorlin1, motorlin2) look like they are extended for one second, then retracted for one second, and repeat one more time. That sounds like "legs raise up and down"... what does the light have to do with the motors or the interrupt?

Here is a simulation of driving a motor with a timer. The basic function is 1. check a timer 2. step the motor.

YES, the action in the code is essentially what I'm looking to do, I'll be modulating the number of seconds once I have everything installed. The light is there just as a visual indicator that the loop and coin interrupt are working properly.

The motor timer code looks cool, how might I modify this for my application?

It may help if you spend some time searching for finite state machine. Once mastered, it is a very powerful way to do many things within your code. Briefly, you will have several states: GOING_UP, GOING_DOWN, WAITING.

Every time through loop, you check your inputs and based on what state you are in, decide to continue in the current state or change to a new state.

  • The table is GOING_UP. You check the coin acceptor. If a coin is present, you note the time and transition to WAITING. Next time through loop(), you see if you have waited long enough. If not, do nothing, if yes, transition into GOING_UP (and note the start time again). If you have been GOING_UP long enough, transition to GOING_DOWN, etc.

There are many examples and frameworks that can help you get started. You just need to fill in what you want to be doing in each state.

The motor simulation "steps" the motor ONLY at 2000 microseconds ("interval = 2" means 2 milliseconds = 2k microseconds). You can do anything non-blocking inside the simulation, and the motor will step only at 2ms. You can add your event by creating another "interval2" and making your event happen when "interval2" time arrives.

look this over

#define enA 11 //enable pwm
#define motor1in1 9 //motor drive forward
#define motor1in2 10 //motor drive reverse
#define motor2in1 7 //motor drive forward
#define motor2in2 8 //motor drive reverse
#define button 2 //coin acceptor

int rotDirection = 0;

unsigned long PausePeriod = 5000;
unsigned long Period = 1000;
unsigned long period = Period;
unsigned long msec0;

int stateLast;
int state = 1;

void loop ()
{
    unsigned long msec = millis ();
    if (msec - msec0 >= period)  {
        msec0 += period;

        switch (state)  {
        case 0:         // paused
            period = Period;
            state  = stateLast;
            break;
        case 1:
            digitalWrite (motor1in1, HIGH);
            digitalWrite (motor1in2, LOW);
            period = Period;
            state++;
            break;

        case 2:
            digitalWrite (motor1in1, LOW);
            digitalWrite (motor1in2, HIGH);
            state++;
            break;

        case 3:
            digitalWrite (motor2in1, HIGH);
            digitalWrite (motor2in2, LOW);
            state++;
            break;

        case 4:
            digitalWrite (motor2in1, LOW);
            digitalWrite (motor2in2, HIGH);
            state = 1;
            break;
        }
        rotDirection = 0 == rotDirection ? 1 : 0;
        stateLast    = state;
    }

    if (LOW == digitalRead(button))  {
        period = PausePeriod;
        state  = 0;
    }
}

void setup () {
    pinMode (enA, OUTPUT); //not currently used
    pinMode (motor1in1, OUTPUT);
    pinMode (motor1in2, OUTPUT);
    pinMode (motor2in1, OUTPUT);
    pinMode (motor2in2, OUTPUT);
    pinMode (button, INPUT_PULLUP);

    // Set initial rotation direction
    digitalWrite (motor1in1, LOW);
    digitalWrite (motor1in2, HIGH);
    digitalWrite (motor2in1, LOW);
    digitalWrite (motor2in2, HIGH);
}

It works, thank you!!! :exploding_head:

I've got to add in the other two motors now but this is incredible.

It seems to need a little refinement, two things at the moment:
-if I insert a coin while one of the motors is HIGH it remains HIGH for the duration of the pause, fully extending the actuator and slowly going back to fully retracted over many cycles. if i catch it on LOW there's no problem because I want all motors to be LOW during the pause state. Seems like I can achieve this just by plugging in the digitalWrite s from case 2 and 4 into case 0, correct?
-it seems to register pauses when no coin is inserted, this is a situation to invoke the Debounce?

Sounds like you might need to make some changes to the given code when you detect a coin to make sure the motors are in the proper state. It would seem like modifying this bit of code would be good:

    if (LOW == digitalRead(button))  {
        period = PausePeriod;
        state  = 0;
        // put motors in desired state
    }

this shouldn't be possible if the pin is configured as INPUT_PULLUP

@gcjr

you are a very experienced code-developper:

Why do you use hard-coded numbers in the switch-case-break-statement?

using
const byte selfExplaining_Name = ...; would show a much better way of coding

and after this step presenting the same code with enum. In this way the
const byte would be the bridge and explanation to enum

because this is a "Sequencer", a type of state machine that moves sequentially from first to last and back to first. state++ emphasizes this behavior and this type of state machine. I believe that using symbols for the states would actually make the code less readable and more difficult to maintain.

would it really be more readable if you used symbols like State1 or FirstState. would you really change all the symbols if you added a new state so that State3 now does what State4 use to do? would you create a State2A?

what about this approach that doesn't use a switch statement where you expect "self-explaining" symbol names

const byte PinBut = A1;
const byte PinMots [] = { 10, 11, 12, 13 };
const int  Nmots = sizeof(PinMots);

unsigned long PausePeriod = 5000;
unsigned long Period = 1000;

enum { Off = HIGH, On = LOW };

struct State {
    byte          motor [Nmots];
    unsigned long period;
}
state [] = {
    {{ On,  Off, Off, Off }, Period },
    {{ Off, On,  Off, Off }, Period },
    {{ Off, Off, On,  Off }, Period },
    {{ Off, Off, Off, On  }, Period },
};
const int Nstate = sizeof(state)/sizeof(State);

int idx;
int idxLast;

int rotDirection = 0;

unsigned long period = Period;
unsigned long msec0;

// -----------------------------------------------------------------------------
void loop ()
{
    unsigned long msec = millis ();
    if (msec - msec0 >= period)  {
        msec0 += period;

        State *p = &state [idx];
        for (int n = 0; n < Nmots; n++)
            digitalWrite (PinMots [n], p->motor [n]);

        if (Nstate <= ++idx)
            idx = 0;

        period       = p->period;
        rotDirection = 0 == rotDirection ? 1 : 0;
    }

    if (LOW == digitalRead(PinBut))  {
        for (int n = 0; n < Nmots; n++)
            digitalWrite (PinMots [n], Off);
        period = PausePeriod;
    }
}

void setup () {
    pinMode (PinBut, INPUT_PULLUP);

    for (int n = 0; n < Nmots; n++)  {
        pinMode      (PinMots [n], OUTPUT);
        digitalWrite (PinMots [n], Off);
    }
}

you asked "why"

Yes and you explained it. Thank you.

Of course not. A self-explaining name does explain what does the maschine do.

"FirstState" would only explain: it is the first step of something
Not sure if I conclude right
from these defines

I assume

means drive motor1 forward
so the self-explaining constant of this state would be
motor1Forward
.
.

motor1Reverse
.
.

motor2Forward
.
.

motor2Reverse

And then the names of the states explain what the machine is really doing

There is no remembering nescessary what was state 1 ??? .... .... minutes later
you remember it or you have looked up in in same manual
Ah ! motor1 forward

or

you would have to go through the code and the wiring:
there is Pin1 of the motordriver if pin 1 is high and pin 2 is low this means plus on this pole of the motor minus there and then the motor turns ....

or

having a real test to observe what the motor / actuator is really doing

and this self-explaining name says it just spot-on
read the code a single time and then knowing what happends

since there don't need to be states at all, why should the states describe what the code is doing, shouldn't there be meaningfully names functions

To me the small number of lines of code make it discussable if it is really an advantage to put two lines of code into functions.

so is there a need to provide some selfExplaining_Name?

Thanks y'all! Especially @gcjr
Here's a link to the operation of the finished piece- https://youtube.com/shorts/f6s5RFRFPXM?feature=share

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