Using Proximity Sensor to Stop Motor

Hello all,

I am new to Arduino programming. For work, I am trying to create a program that turns a motor one way until a sensor goes hot. The motor should stop briefly, then begin going the other way until it hits a sensor again. That would complete a cycle. After a certain number of cycles, I would like the program to stop. I feel like I’m close but my program is not working as intended and I’m sure it’s a simple mistake that I’ve been unable to spot. The program seems to almost immediately jump to the last function, which stops the entire program. If I add a small delay (20 ms), then the sensor will not stop the motor and it just keeps spinning forward. Can anyone point me in the right direction? I have attached the program and I will copy it here:

PS: I am using an Arduino Uno and a 12V proximity sensor that senses magnets.


#define IN1 8
#define IN2 9
#define ENA 6

const byte sensor = 4; //Sensor will be on pin 4
const byte blue = 3; //LED will be on pin 3

byte motorSpeed = 60;

int counter = 0; //Variable to count number of cycles
int finish = 5; //Number of cycles until program is complete

void setup() { //Various pinmode assignments
pinMode(sensor,INPUT);
pinMode(ENA,OUTPUT);
pinMode(IN1,OUTPUT);
pinMode(IN2,OUTPUT);
pinMode(blue,OUTPUT);

// -----Program Start-----

digitalWrite(blue,LOW);
analogWrite(ENA,motorSpeed); //Starts the motor going forward
digitalWrite(IN1,HIGH);
digitalWrite(IN2,LOW);
delay(500);
forward();
}

// -----Program Functions-----

void forward(){
digitalWrite(blue,LOW);
analogWrite(ENA,motorSpeed);
digitalWrite(IN1,HIGH);
digitalWrite(IN2,LOW);

if(digitalRead(sensor) == HIGH){
forwardHalt();
}else{
forward();
}
}

void backward(){
digitalWrite(blue,LOW);
analogWrite(ENA,motorSpeed);
digitalWrite(IN1,LOW);
digitalWrite(IN2,HIGH);

if(digitalRead(sensor) == HIGH){
backHalt();
}else{
backward();
}
}

void forwardHalt(){
digitalWrite(blue,HIGH);
digitalWrite(ENA,LOW);
digitalWrite(IN1,LOW);
digitalWrite(IN2,LOW);
delay(5000);
backward();
}

void backHalt(){
digitalWrite(blue,HIGH);
digitalWrite(ENA,LOW);
digitalWrite(IN1,LOW);
digitalWrite(IN2,LOW);
delay(5000);
counter++;
if(counter == finish){
stopProgram();
}
forward();
}

void stopProgram(){
digitalWrite(ENA,LOW); //Stops motor
digitalWrite(IN1,LOW);
digitalWrite(IN2,LOW);
while(counter >= finish){
digitalWrite(blue,HIGH); //Turns on LED
}
}

void loop() {
//Empty because looping should all be done in Setup
}

componentTestVerticalTilt_B.ino (1.82 KB)

Please edit your post and insert code tags!

void forward(){
  digitalWrite(blue,LOW);
  analogWrite(ENA,motorSpeed);
  digitalWrite(IN1,HIGH);
  digitalWrite(IN2,LOW);

  if(digitalRead(sensor) == HIGH){
    forwardHalt();
    }else{
      forward();
    }
}

Let's analyze that function. It grounds the LED output, sets the motor PWM and set two other digital outputs (probably the direction selection). It then checks the sensor but the interesting branch is if the sensor wasn't activated yet. You're calling the same function inside the function. On the UNO this is done about a hundred thousand times a second. As every sub call puts the return address on the stack your stack is overflowing almost immediately and the Arduino drives crazy (handling non-predictable).

Your subroutine calls look like you're used to programming with gotos (or jumps) but as I already wrote a subroutine call stores the return address on the stack and the stack is just part of the RAM, the UNO has 2048 bytes of it. So either replace your subroutine calls by gotos (they still exist in C/C++ but are strongly deprecated) or rewrite it to have while loops and return when the next step is ready to be executed.

I am restructuring my code now. Will update you when I get back to work. Thanks for replying!

Going a little off topic here, sorry.

I'm having trouble visualising how a mechanism can travel in either of two directions but still hit the same sensor. (I'm seeing limit switches in my mind's eye and there would usually be one at each end; obviously my mind pic is wrong.)

Can you to satisfy my curiosity if and when you have a moment and give a pic of this device?


But that aside....

TheGrislyBear: I am restructuring my code now.

While you're doing that, you may want to rethink this:

void loop() {
  //Empty because looping should all be done in Setup
}

The looping really should be done in loop().

I have written a sketch for you using a delay()-less state machine approach, which is something you might like to read about here (Nick Gammon) and or here (Grumpy_Mike).

I'll post the sketch if you want it, but you may like to investigate further yourself first.

You may in any case want to read what Robin2 has to say about getting a sketch to do a whole load of things without using delay().

Here as a "teaser" is my serial output showing the changes between states, all delay()-less. I have the led on pin13 doing a blink without delay to prove there's no blocking. I had the halt set to 2 seconds, and finish = 2 for a quick test.

Motor starts "off" in setup() and only starts when a button is pressed.

.... motor controlled by a sensor ....
Compiler: 4.9.2, Arduino IDE: 10805
Created: 12:01:39, Aug  7 2019
C:\Users\xxx\Documents\Arduino\motor_and_sensor_630148\motor_and_sensor_630148.ino
setup() complete

Press the button to (re-)start the sequence... 
Switching to state GOING_FORWARD at 3257, switching to state HALTED_F at 5190
Switching to state GOING_BACKWARD at 5690, switching to state HALTED_B at 7014, 1 of 2 cycles complete
Switching to state GOING_FORWARD at 7515, switching to state HALTED_F at 8825
Switching to state GOING_BACKWARD at 9325, switching to state HALTED_B at 10706, 2 of 2 cycles complete
            ****FINISHED**** 
Switching to state IDLE at 11226
   
Press the button to (re-)start the sequence...

I will eventually have two sensors, but I am only using one for now just to see how it works with the code. I am manually moving the sensor to the magnet just for testing. I will check out those posts you directed me to. I already began restructuring the code, but I've never seen any code like yours before.

TheGrislyBear: but I've never seen any code like yours before.

If you mean what's in the "code" box of #4, then that's the debug screen output, not the program....

TheGrislyBear: I am manually moving the sensor to the magnet just for testing.

That's exactly what I was doing, except I used my hand and an IR sensor.

If you want me to post my state machine code (for one sensor), shout and I will do so.

Thanks. I will try to figure it out with the information you guys have provided. If I still need some assistance, I will definitely let you know. Thanks again!

Meltdown, do you think you could show me one of your functions? I think I need a little steering in the right direction.

TheGrislyBear:
Meltdown, do you think you could show me one of your functions?

Sure…

Well it’s not so much a “function” as a “full sketch”, and here it is below…

It’s a state machine, using switch…case which you should probably read up on.

It starts off in a state called IDLE waiting for a button, which moves it to a state GOING_FORWARD which does call a function called forward(). Note I have separated the logic of the states from the commands which control the motor. In GOING_FORWARD it will wait for the sensor, at which point it goes to the state HALTED_F which in turn invokes the function halt(). At the instant it leaves GOING_FORWARD, it makes a note of the time. That start time is then used delay()-lessly to keep it in HALTED_F the correct amount of time.

And so on…

It ends up back in IDLE by the way, with the motor off, waiting for the button to kick it off again.

The button is wired to ground from a digital pin.

//  https://forum.arduino.cc/index.php?topic=630148
// 7 august 2019
// this implementation by meltDown is a delay()-less state machine
// includes blink without delay pulse led 13
// includes button to (re-)start sequence from STATE_IDLE
// starts explicitly with motor off in setup() for safety

// check all the lines marked  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<

/* The requirement:
   I [TheGrislyBear] am trying to create a program that turns a motor one way until a sensor goes hot.
   The motor should stop briefly, then begin going the other way until it hits a sensor again.
   That would complete a cycle.
   After a certain number of cycles, I would like the program to stop.
*/

#define IN1 8
#define IN2 9
#define ENA 6
byte debug = 1; //set to 0 for no debug prints, or 1 to see  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<
bool messageHasDisplayed = false;

const byte sensor = 4;    //Sensor will be on pin 4
const byte blue = 3;     //LED will be on pin 3
const byte startPin = 5;

byte motorSpeed = 60;

int counter = 0;  //Variable to count number of cycles
int finish = 2;   //Number of cycles until program is complete <<<<<<<<<<<<<<<<<<<<<<<<<<<<<

//unsigned long currentMillis;
unsigned long previousMillisPulse;
int pulseInterval = 500; //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
byte pulseLed = 13;
bool pulseLedState = false;

enum {IDLE, GOING_FORWARD, GOING_BACKWARD, HALTED_F, HALTED_B} currentState = IDLE;
// enum just allocates integers from the left so ST_IDLE=0, ST_1 =1 etc
//    just makes it easier to have human relatable names in the switch..case later
unsigned long leftPreviousStateAtMillis;
int haltInterval = 500;  // remember to set this to real halt-time <<<<<<<<<<<<<<<<<<<<<<<<<<<<<

void setup()
{
  Serial.begin(9600);
  Serial.println(".... motor controlled by a sensor ....");
  Serial.print("Compiler: ");
  Serial.print(__VERSION__);
  Serial.print(", Arduino IDE: ");
  Serial.println(ARDUINO);
  Serial.print("Created: ");
  Serial.print(__TIME__);
  Serial.print(", ");
  Serial.println(__DATE__);
  Serial.println(__FILE__);

  //Various pinmode assignments
  pinMode(sensor, INPUT_PULLUP); //note meltDown's sensor is active low <<<<<<<<<<<<<<<<<<<<<<<<<<<<<
  pinMode(ENA, OUTPUT);
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(blue, OUTPUT);
  pinMode(pulseLed, OUTPUT);
  digitalWrite(pulseLed, pulseLedState);
  pinMode(startPin, INPUT_PULLUP);

  //start exlicitly with motor halted for safety
  halt();

  Serial.println("setup() complete");
  Serial.println(" ");

} //setup

void loop()
{
  doPulse();
  doStates();
} //loop

//here are the functions..

void doStates()
{
  switch (currentState)
  {
    case IDLE:
      //we only want the message to display once...
      //   but we don't want to block the sketch while we wait for the button
      if (!messageHasDisplayed)
      {
        Serial.println("Press the button to (re-)start the sequence... ");
        messageHasDisplayed = true;
      }

      if (!digitalRead(startPin)) //button is wired to ground, active low, press to start
      {
        if (debug) Serial.print("Switching to state GOING_FORWARD at ");
        if (debug) Serial.print(millis());
        currentState = GOING_FORWARD;
      }
      break;
      
    case GOING_FORWARD:
      forward();
      if (!digitalRead(sensor))
      {
        if (debug) Serial.print(", switching to state HALTED_F at ");
        if (debug) Serial.println(millis());
        currentState = HALTED_F;
        leftPreviousStateAtMillis = millis(); //gives us the start time for the halt
      }
      break;

    case GOING_BACKWARD:
      backward();
      if (!digitalRead(sensor))
      {
        if (debug) Serial.print(", switching to state HALTED_B at ");
        if (debug) Serial.print(millis());
        currentState = HALTED_B;
        leftPreviousStateAtMillis = millis(); //gives us the start time for the halt
      }
      break;

    case HALTED_F:
      halt();
      if (millis() - leftPreviousStateAtMillis >= haltInterval)
      {
        if (debug) Serial.print("Switching to state GOING_BACKWARD at ");
        if (debug) Serial.print(millis());
        currentState = GOING_BACKWARD;
      }
      break;

    case HALTED_B:
      halt();

      if (millis() - leftPreviousStateAtMillis >= haltInterval)
      {
        counter++;
        if (debug) Serial.print(", ");
        if (debug) Serial.print(counter);
        if (debug) Serial.print(" of ");
        if (debug) Serial.print(finish);
        if (debug)Serial.println(" cycles complete");
        if (counter == finish)
        {
          counter = 0; //unless you want to keep a running total <<<<<<<<<<<<<<<<<<<<<<<<
          currentState = IDLE;
          messageHasDisplayed = false;
          Serial.println("            ****FINISHED**** ");
          if (debug) Serial.print("Switching to state IDLE at ");
          if (debug) Serial.println(millis());
          if (debug) Serial.println("   ");
          return;
        }
        if (debug) Serial.print("Switching to state GOING_FORWARD at ");
        if (debug) Serial.print(millis());
        currentState = GOING_FORWARD;
      }
      break;
  }//switch
}//doStates

void doPulse()
{
  if (millis() - previousMillisPulse >= pulseInterval)
  {
    previousMillisPulse = millis();
    pulseLedState = !pulseLedState;
    digitalWrite(pulseLed, pulseLedState);
  }
}//doPulse



void forward()
{
  digitalWrite(blue, LOW);
  analogWrite(ENA, motorSpeed);
  digitalWrite(IN1, HIGH);
  digitalWrite(IN2, LOW);
}//forward

void backward()
{
  digitalWrite(blue, LOW);
  analogWrite(ENA, motorSpeed);
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, HIGH);
}//backward

void halt()
{
  digitalWrite(blue, HIGH);
  digitalWrite(ENA, LOW);
  digitalWrite(IN1, LOW);
  digitalWrite(IN2, LOW);
}//halt

Don’t be put off by the use of the ENUM. It’s just a simple way to give a friendly name to a number. So in the switch case, instead of 0 we can call it IDLE, etc.

Oh and to prove there's no blocking, the led on pin13 blinks delay()-lessly all the time.

This is a diagram showing the states and the transitions between them. The solid blob is the entry point.

Well that code certainly looks a lot different than what I had. I uploaded it and it works! The only thing I need to change is the fact that the motor moves when the sensor detects the magnet instead of stopping then the sensor goes high. Your example really helped me learn how to apply states beyond just a timer for one action. If I'm understanding this correctly, the arduino is calling the forward/backward function while also testing for the sensor signal?

Glad it works; I didn’t have a motor or driver handy so I tested it with leds :wink:

Yes to your question in the last para there. It fires up the correct function and leaves it alone. Then it waits for the sensor to trip and move things to the next state at which point the halt function kicks in.

That's pretty cool. I did not think the Arduino could do that. Thanks for all the help man!

That, to me, is the beauty of the switch...case "state machine" approach. In any state, do what needs to be done and then set up to look only at the triggers that will take you to the next state, which you do by setting the case name to the next one.

And a state diagram makes a good blue print for what needs to be done (although I will confess I drew it afterwards ;) )