Arduino + millis + steps for relay control randomly crashes

Hi, I have a project to control a 12V motor. At the moment I have a setup that generally works sometimes.

Sometimes it runs for hours sometimes minutes and sometime just a step or two. Doesn't freeze or lock up the same way too — at times you can press the button and it start again (sequence status LED is off), other times the power needs to be turned off as the button is unresponsive (sequence status LED is on). Doesn't freeze at the same step. Only consistency is that it seems to be happening during the transition from one step to another, not mid way. Tried a Nano and an Uno board and it's the same.

Hardware:

Arduino Nano or Uno (used both)
Two 5V single channel relays (wired up like a car power window circuit to change polarity of motor)
3 LED's (status led and two others connected the relay to show motor direction)
Push button switch - tried a variety of buttons (internal pull up)
12V Motor
12V power supply (10A, much more than this needs)
Buck converter for 5V

Arduino is running a trigger wire to the relays through a diode which at first I thought solved the issue, only to still have it happening. It's a bit tricky figuring this out as it is very random. Relays are powered through 5V, NOT from the arduino. Motor is powered through separate 12V. Only two standard red LED's are ever on at the same time.

Requirements: Motor needs to rotate one direction for a specified time, pause, rotate the other direction, pause and then start the sequence again. This needs to be able to continue an infinite amount of times until the button is pressed to turn off the motor. When the button is pressed during the sequence the motor needs to stop instantly. The next time the button is pressed the sequence needs to start from the beginning.


#define RELAYONE 11
#define RELAYTWO 10
#define SW1_PIN 9
#define LEDSTAT 8

const unsigned long RELAY_ON_INTERVAL = 9000;
const unsigned long PAUSE_INTERVAL = 500;
const unsigned long MOTOR_OFF_INTERVAL = 500; /* not using this */
const unsigned long SPARE_INTERVAL = 750; /* not using this */
unsigned long startMillis;

// true: RELAY signal is ON
// false: RELAY signal is OFF
bool isRELAY = false;

// Tracks the current steps
uint8_t steps = 0;

// Holds the current state
bool relay_one_state = false;
bool relay_two_state = false;
bool led_state = false;

// This is for button debouncing
bool currState = HIGH;
bool prevState = HIGH;
bool buttonState = HIGH;
unsigned long debounceStart = 0;
unsigned long debounceDuration = 50;


void setup() {
  pinMode(RELAYONE, OUTPUT);
  pinMode(RELAYTWO, OUTPUT);
  pinMode(SW1_PIN, INPUT_PULLUP);
  pinMode(LEDSTAT, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  /*
   *  Here is the breakdown of the steps:
   *  0:  RELAYONE is on (high)
   *  1:  off
   *  2:  RELAYTWO is on (high)
   *  3:  off
   */

  // Logic for the RELAY
  //  Pseudo code:
  //    if RELAY is ON, check the current steps
  //      if steps is 1 and 3
  //        send PAUSE_INTERVAL
  //      if steps is 0
  //        send RELAY_ON_INTERVAL
  //      if steps is 2
  //        send RELAY_ON_INTERVAL
  //  The duration of each signal is achieve using
  //    the millis() function
  //    For example:
  //      if ( ( millis() - startMillis ) >= RELAY_ON_INTERVAL ) {
  //        steps++;  
  //        startMillis = millis(); // save the start time
  //      }
  //    The ( millis() - startMillis ) is basically the elapse time
  //      since the startMillis is recorded
  //      so that ( ( millis() - startMillis ) >= RELAY_ON_INTERVAL ) is 
  //      basically for checking if the required interval is achieve.
  //      If it becomes true, increment the counter and record the time
  //      and so on
  if (isRELAY) {
    
    if        ( ( steps == 1  ) ||
                ( steps == 3  ) ) {
      Serial.println("pause");
      relay_one_state = false;
      relay_two_state = false;
      if ( ( millis() - startMillis ) >= PAUSE_INTERVAL ) {
        steps++;  
        startMillis = millis(); // save the start time
      }
    } else if ( ( steps == 0 )) {
      Serial.println("step 0");
      relay_one_state = true;
      relay_two_state = false;
      led_state = true;
      if ( ( millis() - startMillis ) >= RELAY_ON_INTERVAL ) {
        // signal interval is complete
        // move to the next step
        steps++;  
        startMillis = millis(); // save the start time
      }
    } else if ( ( steps == 2  ) ) {
      Serial.println("step 2");
      relay_one_state = false;
      relay_two_state = true;
      if ( ( millis() - startMillis ) >= RELAY_ON_INTERVAL ) {
        steps++;  
        startMillis = millis(); // save the start time
      }                  
    } else {
      relay_one_state = false;
      relay_two_state = false;
      
      if ( ( millis() - startMillis ) >= MOTOR_OFF_INTERVAL ) {
        steps++;  
        startMillis = millis(); // save the start time
      } 
    }
    // if the steps reach steps 5, go back to steps 0
    if (steps > 4) steps = 0;
  }

  // Update the output signal
  digitalWrite(RELAYONE, relay_one_state);
    digitalWrite(RELAYTWO, relay_two_state);
    digitalWrite (LEDSTAT, led_state);

  /*
   *  This is for debouncing the tactile switch
   */
  currState = digitalRead(SW1_PIN);
  if (currState != prevState) {
    debounceStart = millis();
  }
  
  if ((millis() - debounceStart) > debounceDuration) {
    if (currState != buttonState) {
      buttonState = currState;
      if (currState == LOW) {
        if ( isRELAY ) {
          // currently sending motor sequence, now turn it off
          isRELAY = false;
          Serial.println("off");
          relay_one_state = false;
          relay_two_state = false;
          led_state = false;
        } else {
          // currently motor sequence is off, now turn it on
          isRELAY = true;
          startMillis = millis();
          Serial.println("on");
          steps = 0;
        }
      }
    }
  }
  prevState = currState;

  // Do other stuffs here, without blocking

}

Show a schematic and include whatever you are using for power.(no fritzing thanks)

2 Likes

Welcome to the forum.

Beside a schematic, can you also show a photo of your project ?
You can draw with a pen the schematic on a piece of paper and make a photo of it.

This project is perfect to try in a simulation. Because there are no grounding problems or sparks from motors or inductive glitches from relays and there is unlimited power.
Your project in Wokwi simulation:

I don't know what the sketch should do and if I have to press the button. Can you try it ?

Can you upgrade your code ? It will make it easier for you (and for us).

The debouncing should be piece of code on its own. It should not interact with the other code. There are libraries for debouncing, you can use a function or you can put the code in the loop(), everything is okay, but it should stand on its own.

There is a variable 'steps', would it be possible to call it 'state' ?
You do "steps++", but can you give it the number of the next step ? For example: "steps = 2".
Can you give each step its own piece of code ? Don't combine 1 and 3.
would it be possible to replace the if-else-if with a switch-case ?
You use "if (isRELAY) {", but that breaks the logic of the code. Would it be possible to remove that and give it its own idle state ?
Then have a look at this: https://majenko.co.uk/blog/our-blog-1/the-finite-state-machine-26
It is the same as you have. There is a fancy word for it: Finite State Machine.

1 Like

I'll try to draw something up.

Schematic - I'll try to draw something.

Thanks for the wokwi suggestion. I had never heard of it before. You do have to press the button for it to start going through the steps/states.

I'm still trying to learn coding (I only used to do a bit of HTML when I was younger) so I'm slowly going through your suggestions trying to understand how to implement it all! This was put together from some stuff I found online. I'll see if I can get it cleaned up.


Here's a schematic I drew up in Illustrator. Thought it would be a little easier to understand through vectors rather than my messy scribbles on paper. In terms of trouble shooting I've tried to add different value capacitors to the Nano, diodes (1N4001as that's what I have on hand) in various places and I tried looking into snubber setups (non AC) on the motor side of the relays but haven't found anything I've been able to properly implement.

Using a 12V10A PSU that came with the motor. Links below.

Motor (12V 27RPM + PSU + PWM control
Relays (5V)
Buck converter

I had a go at making the code a bit easier to follow (see below).

I'll have to have another go at this. I tried a couple of things but I kept breaking it.

Changed steps to state. When I tried to use state = x in place of state = ++ it didn't seem to run. My understanding is ++ is just counting up by 1. All states now have their own code and are in sequence order.

Don't quite understand these ones yet. Will see what I can figure out.


#define RELAYONE 11
#define RELAYTWO 10
#define SW1_PIN 9
#define LEDSTAT 8
#define TESTLED 13

const unsigned long RELAY_ON_INTERVAL = 9000;
const unsigned long PAUSE_INTERVAL = 500;
unsigned long startMillis;

// true: RELAY signal is ON
// false: RELAY signal is OFF
bool isRELAY = false;

// Tracks the current state
uint8_t state = 0;

// Holds the current state
bool relay_one_state = false;
bool relay_two_state = false;
bool led_state = false;

// This is for button debouncing
bool currState = HIGH;
bool prevState = HIGH;
bool buttonState = HIGH;
unsigned long debounceStart = 0;
unsigned long debounceDuration = 50;


void setup() {
  pinMode(RELAYONE, OUTPUT);
  pinMode(RELAYTWO, OUTPUT);
  pinMode(SW1_PIN, INPUT);
  pinMode(LEDSTAT, OUTPUT);
  pinMode(TESTLED, OUTPUT);
  Serial.begin(9600);
}

void loop() {

  
  /*
   *  Here is the breakdown of the state:
   *  0:  RELAYONE is on (high)
   *  1:  off
   *  2:  RELAYTWO is on (high)
   *  3:  off
   */

  // Logic for the RELAY
  //  Pseudo code:
  //    if RELAY is ON, check the current state
  //      if state is 1 and 3
  //        send PAUSE_INTERVAL
  //      if state is 0
  //        send RELAY_ON_INTERVAL
  //      if state is 2
  //        send RELAY_ON_INTERVAL
  //      else
  //        send a low signals
  //  The duration of each signal is achieve using
  //    the millis() function
  //    For example:
  //      if ( ( millis() - startMillis ) >= RELAY_ON_INTERVAL ) {
  //        state++;  
  //        startMillis = millis(); // save the start time
  //      }
  //    The ( millis() - startMillis ) is basically the elapse time
  //      since the startMillis is recorded
  //      so that ( ( millis() - startMillis ) >= RELAY_ON_INTERVAL ) is 
  //      basically for checking if the required interval is achieve.
  //      If it becomes true, increment the counter and record the time
  //      and so on
  if (isRELAY) {
    
    if        (( state == 0  )) {
      Serial.println("relay_one");
      relay_one_state = true;
      relay_two_state = false;
      if ( ( millis() - startMillis ) >= RELAY_ON_INTERVAL ) {
        state++;  
        startMillis = millis(); // save the start time
      }
    }
      else if (( state == 1 )) {
      Serial.println("pause");
      relay_one_state = false;
      relay_two_state = false;
      led_state = true;
      if ( ( millis() - startMillis ) >= PAUSE_INTERVAL ) {
        state++;  
        startMillis = millis(); // save the start time
      }
    }
      else if (( state == 2  )) {
      Serial.println("relay_two");
      relay_one_state = false;
      relay_two_state = true;
      if ( ( millis() - startMillis ) >= RELAY_ON_INTERVAL ) {
        state++;  
        startMillis = millis(); // save the start time
      }
    }
      else if (( state == 3 )) {
      Serial.println("pause");
      relay_one_state = false;
      relay_two_state = false;
      led_state = true;
      if ( ( millis() - startMillis ) >= PAUSE_INTERVAL ) {
        state++;  
        startMillis = millis(); // save the start time
      }            
    }
    

    // if the state reach state 5, go back to state 0
    if (state > 4) state = 0;
  }

  // Update the output signal
  digitalWrite(RELAYONE, relay_one_state);
    digitalWrite(RELAYTWO, relay_two_state);
    digitalWrite (LEDSTAT, led_state);


  
  if ((millis() - debounceStart) > debounceDuration) {
    if (currState != buttonState) {
      buttonState = currState;
      if (currState == LOW) {
        if ( isRELAY ) {
          // currently sending motor sequence, now turn it off
          isRELAY = false;
          Serial.println("off");
          relay_one_state = false;
          relay_two_state = false;
          led_state = false;
        } else {
          // currently motor sequence is off, now turn it on
          isRELAY = true;
          startMillis = millis();
          Serial.println("on");
          state = 0;
        }
      }
    }
  }
  prevState = currState;

  // Do other stuffs here, without blocking

    /*
   *  This is for debouncing the tactile switch
   */
  currState = digitalRead(SW1_PIN);
  if (currState != prevState) {
    debounceStart = millis();
  }

    /*
    *  This is for testing to see if the Arduino has crashed. Onbard LED should blink
    */
    digitalWrite(TESTLED,millis()%500>250);

}

Use variables.
You could, for example, read the button and do the debouncing at the begin of the loop(). You can put the result in variables, and use the variable in the rest of the loop().

void loop()
{
  bool buttonPressed = false;         // default: not pressed
  
  // Code to debounce the button
  if ... debounce ... millis ... something ...
  {
    buttonPressed = true;   // the debounce code found a valid button press
  }

  // Rest of the code
  case IDLE:
    if (buttonPressed)
    {
       ...
    }
}

Have you seen that page of the "Finite State Machine". It is just a fancy word, your sketch already has it. A "Finite State Machine" is a programming technique that programmers know. The time you spend to study that page is time well used.
And you can say to others: "Your sketch does not have a Finite State Machine ? pfffft" :rofl:

I tried to draw on the picture how it is connected with the Arduino relays and the two switches, but I don't understand it :cold_sweat:
If you buy such cheap relays of 10A, then they can be used for 0.5A (if you are lucky).

Cool. I'll have another go.

Yes, I read through that page and have booked marked to for further study.

It's a bit confusing with the extra toggles in the schematic, but it's basically the same way of reversing polarity that is iften found in power window circuits. Eg:
sddefault

Here's mine without the toggles:

Thank you. The Arduino with relays as a H-bridge is the part I understood.

Hey, nice job. You mightn't need the diodes in series with the output pins driving the relay boards. The relay board and the LED with series current limiting resistor can both just be connected to the pin.

I said that to someone just yesterday, minus the "pfffft". But my expression carried the message.

a7

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