Help with mechatronics project, using while() loop

Hello, so I'm having some difficulty with setup.
Basically, I have an Arduino Mega with an IR sensor input, and 8 motor outputs. I am connecting an Andriod phone to the Mega, serially (via usb).
The Andriod app is sending 49's - 56's when a button (1-8) is pressed on the app (and sending -1 when it's not pressed/idle).

I wish to press a button on the app, let's say number 1, and have the motor#1 turn on when I put my hand under the IR sensor (the reading is "0"). Putting my hand under the IR sensor might be 20 seconds after I select the #1 button on the app.

I'm sort of new at coding. How can I tell the Arduino to know when a serial number (49 in this case) has been sent and wait to turn on a motor after the IR sensor notifies my hand. I've been trying to use a WHILE loop, but it doesn't seem to work. It keeps turning on motor on and off (loop keeps running) and it doesn't print anything on the serial monitor, regardless if the sensor is reading 0 or 1.
Could someone guide me with what functions or loops I could use? Thanks.

int Andr = 0;
int ok = 0;
int sensor=1;

void setup() {
  Serial.begin(9600);
  pinMode(2, INPUT);   // input reading of IR sensor
  pinMode(3, OUTPUT);  // output to motor1 (Face Wash)
  pinMode(4, OUTPUT);  // output to motor2 (Toner)
  pinMode(5, OUTPUT);  // output to motor3 (Serum)
  pinMode(6, OUTPUT);  // output to motor4 (Moisturizer)
  pinMode(7, OUTPUT);  // output to motor5
  pinMode(8, OUTPUT);  // output to motor6
  pinMode(9, OUTPUT);  // output to motor7
  pinMode(10, OUTPUT); // output to motor8
  pinMode(12, INPUT);  // input from Andriod App
  pinMode(LED_BUILTIN, OUTPUT);
 
}


void loop() {

int Andr = Serial.read();
  delay(10);
 

   int sensor = digitalRead(2); // read sensor input from digital input (pin 2)
   Serial.print(sensor);
   delay(10); // wait 10 ms

  
    
   if (Andr == 49) {
   while (sensor == 0) {
   int sensor= digitalRead(2);
   Serial.print(sensor);
   digitalWrite(3,HIGH); //turns on motor1 if hand is under sensor and when Andriod sends a signal "1" meaning the Face Wash product choice
   delay(250);
   digitalWrite(3,LOW);
   delay(5000);
   }
 
   } 

 if (Andr == 50) {
   while (sensor == 0) {
   int sensor= digitalRead(2);
   Serial.print(sensor);
   digitalWrite(4,HIGH); //turns on motor2
   delay(250);
   digitalWrite(4,LOW);
   delay(5000);
   }
 
   } 


 if (Andr == 51) {
   while (sensor == 0) {
   int sensor= digitalRead(2);
   Serial.print(sensor);
   digitalWrite(5,HIGH); //turns on motor3
   delay(250);
   digitalWrite(5,LOW);
   delay(5000);
   }
 
   } 

 if (Andr == 52) {
   while (sensor == 0) {
   int sensor= digitalRead(2);
   Serial.print(sensor);
   digitalWrite(6,HIGH); //turns on motor4
   delay(250);
   digitalWrite(6,LOW);
   delay(5000);
   }
 
   } 

 if (Andr == 53) {
   while (sensor == 0) {
   int sensor= digitalRead(2);
   Serial.print(sensor);
   digitalWrite(7,HIGH); //turns on motor5
   delay(250);
   digitalWrite(7,LOW);
   delay(5000);
   }
 
   } 

 if (Andr == 54) {
   while (sensor == 0) {
   int sensor= digitalRead(2);
   Serial.print(sensor);
   digitalWrite(8,HIGH); //turns on motor6
   delay(250);
   digitalWrite(8,LOW);
   delay(5000);
   }
 
   } 

 if (Andr == 55) {
   while (sensor == 0) {
   int sensor= digitalRead(2);
   Serial.print(sensor);
   digitalWrite(9,HIGH); //turns on motor7
   delay(250);
   digitalWrite(9,LOW);
   delay(5000);
   }
 
   } 

 if (Andr == 56) {
   while (sensor == 0) {
   int sensor= digitalRead(2);
   Serial.print(sensor);
   digitalWrite(10,HIGH); //turns on motor8
   delay(250);
   digitalWrite(10,LOW);
   delay(5000);
   }

   } 



}

Firstly, test whether there is any serial data to read using Serial.available()
If there is then read it into a char variable and subtract 49 from it.
EDIT - should be
If there is then read it into a char variable and subtract 48 from it

Test whether it is in the range 0 to 7
If it is in range then use the value to set that level of a boolean array to true to flag receipt of the digit and save the value of millis() to a second array of unsigned longs

Using the arrays you can now determine whether a digit was received or not and how long ago

NOTE THE EDIT ABOVE

Won't whatever "good" value of Andr (49 etc) have been overwritten by the "bad" -1, since it will be sending -1 as soon as you let go the button??

I wonder if you need an if() to only store the serial reads when they're not -1, so Andr always contains the last "good" input.

Robin2's serial tutorial separates the capture and use of input by using 2 functions recv... and show... which strikes me as a wise way to work; it also has a flag for newdata.

In the recv... function you can test the value and decline if not in range, then in show... (which I sometimes rename to use...) you do what you want with it.

Separating the receipt and use of data is a sound principle, I reckon.

Are you actually trying to test the time between the receipt of a number and the handgesture, to limit it to 20s or was the 20s just you saying the hand gesture does not immediately follow the arrival of the number?

UKHelibob suggests storing the arrival time I think, but I'm not sure that's what the ask is. (Maybe though...)

You need to store the gesture time once, that's what starts all the motors whose numbers arrived and whose array values are 1, and then turn them all off 250ms later. (At least, the given code uses 250 across the board: the time for each motor to stay on could be in an array too, if they ever need to be different.)

Once (edit)the gesture has been detected, zero all the values a motor has been turn off, zero its value in the "number arrived array", ready for next time.

Is any overlap required? If motors 1,2 and 5 are running and new values 3,4 and 7 arrive, will a new gesture kick off 3, 4 and 7 before 1, 2 and 5 are done? That would necessitate storing the (edit) arrival start times....

What's the delay(5000) for?

I wonder if you need an if() to only store the serial reads when they're not -1, so Andr always contains the last "good" input.

Taken care of by testing whether there is an serial data available so the program will never read -1 due to there being no serial input available

Separating the receipt and use of data is a sound principle, I reckon.

I agree wholeheartedly. There will also need to be a function to reset the boolean to false when the required period has elapsed. My earlier advice was aimed at collecting the information to act on. An array of structs holding current state and time of entry to that state would be neater but I didn't want to overwhelm the OP with too many new ideas

12Stepper:
Are you actually trying to test the time between the receipt of a number and the handgesture, to limit it to 20s or was the 20s just you saying the hand gesture does not immediately follow the arrival of the number?

UKHelibob suggests storing the arrival time I think, but I'm not sure that's what the ask is. (Maybe though...)

You need to store the gesture time once, that's what starts all the motors whose numbers arrived and whose array values are 1, and then turn them all off 250ms later. (At least, the given code uses 250 across the board: the time for each motor to stay on could be in an array too, if they ever need to be different.)

Once (edit)the gesture has been detected, zero all the values a motor has been turn off, zero its value in the "number arrived array", ready for next time.

Is any overlap required? If motors 1,2 and 5 are running and new values 3,4 and 7 arrive, will a new gesture kick off 3, 4 and 7 before 1, 2 and 5 are done? That would necessitate storing the (edit) arrival start times....

What's the delay(5000) for?

Correct, the "good" value gets overwritten quickly. I will check out the serial tutorial, thanks! So, yes I need to "store" value.

I was just trying to say the hand gesture does not immediately follow the number arrival. I don't think i need to store the times since the hand gesture will one be done once and for one motor.

the delay(5000) was for previous testing. I don't need it now.

The code below might give some ideas. I just input characters from the serial monitor.

It starts with the motor pins in an array, and then puts 0's in another array eligibleToRun[].

If it gets valid input 0-7 (arrays work from 0 not 1....) it accepts that and makes the corresponding eligibleToRun slot a 1. Then prints out the eligibleToRun[] array so far.

Output:

pinMode()-ing 8 motors/pins:
0/3 1/4 2/5 3/6 4/7 5/8 6/9 7/10  
<Arduino is ready>
This just in ... q, that's no good
This just in ... 9, that's no good
This just in ... 5, that's OK
00000100 
This just in ... 1, that's OK
01000100 
This just in ... 8, that's no good
This just in ... 7, that's OK
01000101

Code:

// https://forum.arduino.cc/index.php?topic=650798
// based on robin2 Example 1 - Receiving single characters
// https://forum.arduino.cc/index.php?topic=396450

char receivedChar;
boolean newData = false;

byte motorPins[] = {3, 4, 5, 6, 7, 8, 9, 10};
const byte howManyMotors = sizeof(motorPins) / sizeof(motorPins[0]);
byte eligibleToRun[howManyMotors] = {0, 0, 0, 0, 0, 0, 0, 0};

void setup() {
  Serial.begin(9600);
  Serial.print("pinMode()-ing "); Serial.print(howManyMotors); Serial.println(" motors/pins:");
  for (byte i = 0; i < howManyMotors; i++)
  {
    pinMode(motorPins[i], OUTPUT);
    Serial.print(i); Serial.print("/"); Serial.print(motorPins[i]); Serial.print(" ");
  }
  Serial.println(" ");
  Serial.println("<Arduino is ready>");
}

void loop()
{
  recvAndValidateOneChar();
  useNewData();
}

void recvAndValidateOneChar()
{
  if (Serial.available() > 0)
  {
    receivedChar = Serial.read();
    Serial.print("This just in ... ");
    Serial.print(receivedChar);
    if (receivedChar >= '0' && receivedChar <= '7')
    {
      Serial.println(", that's OK");
      newData = true;
    }
    else
    {
      Serial.println(", that's no good");
    }

  }
}//recvAndValidateOneChar

void useNewData()
{
  if (newData == true)
  {
    eligibleToRun[receivedChar - 48] = 1;
    for (byte i = 0; i < howManyMotors; i++)
    {
      Serial.print(eligibleToRun[i]);
    }
    Serial.println(" ");
    newData = false;
  }
}

ERRATUM:

I had a brain fart, and made the motors inputs. Changed to:

pinMode(motorPins[i], OUTPUT);

Next step is to use the gesture to kick off the motors with 1's...

Thank you so much for the help! I will test out the code and play around with it.

ERRATUM:

I had a brain fart, and made the motors inputs. Should be:

pinMode(motorPins[i], OUTPUT);

I did a bit more, so now have a sketch that runs the eligible motors (well lights up leds actually) for the required time, then turns them off and resets the eligibilities.

I'll post it if you want it, or you may want to continue with no spoiler :wink:

12Stepper:
I did a bit more, so now have a sketch that runs the eligible motors (well lights up leds actually) for the required time, then turns them off and resets the eligibilities.

I'll post it if you want it, or you may want to continue with no spoiler :wink:

:slight_smile: it won't hurt to take a look at it. Let's see it

12Stepper:
I did a bit more, so now have a sketch that runs the eligible motors (well lights up leds actually) for the required time, then turns them off and resets the eligibilities.

I'll post it if you want it, or you may want to continue with no spoiler :wink:

I was looking over the code. does the void useNewData() function take into consideration the IR sensor (input 2)? It can be digitally read, where 0 means there is a hand under it and it can initiate the motor. And I don't see the input in the void setup(). Should it be?

samuelmoy:
I was looking over the code. does the void useNewData() function take into consideration the IR sensor (input 2)? It can be digitally read, where 0 means there is a hand under it and it can initiate the motor. And I don't see the input in the void setup(). Should it be?

In #6 I said:

12Stepper:
The code below might give some ideas. I just input characters from the serial monitor.

It starts with the motor pins in an array, and then puts 0's in another array eligibleToRun[].

If it gets valid input 0-7 (arrays work from 0 not 1....) it accepts that and makes the corresponding eligibleToRun slot a 1. Then prints out the eligibleToRun[] array so far.

All that version does is accept input and stick it in array.

12Stepper:
I did a bit more, so now have a sketch that runs the eligible motors (well lights up leds actually) for the required time, then turns them off and resets the eligibilities.

samuelmoy:
:slight_smile: it won't hurt to take a look at it. Let's see it

That would be this one...

The previous version's loop() only had:

void loop()
{
  recvAndValidateOneChar();
  useNewData();
}

... whereas the new version's is:

void loop()
{
  proofOfLife();
  recvAndValidateOneChar();
  useNewData();
  checkForGesture();
  checkIfTimeToStop();
}//loop

Here's the new code:

// https://forum.arduino.cc/index.php?topic=650798
// based on robin2 Example 1 - Receiving single characters
// https://forum.arduino.cc/index.php?topic=396450

char receivedChar;
boolean newData = false;

byte motorPins[] = {3, 4, 5, 6, 7, 8, 9, 10};
const byte howManyMotors = sizeof(motorPins) / sizeof(motorPins[0]);
byte eligibleToRun[howManyMotors] = {0, 0, 0, 0, 0, 0, 0, 0};
byte gesturePin = 2;
bool gestureState;
bool gestureStatePrev;
int motorsRunFor = 1000; //made longer than 250 else too short to see

unsigned long startedAtMillis;
bool running = false;

//the proof of life led
int proofOfLifeLedInterval = 500;
unsigned long previousMillisProofOfLife;
bool proofOfLifeState = false;

void setup() 
{
  Serial.begin(9600);
  Serial.print("pinMode()-ing "); Serial.print(howManyMotors); Serial.println(" motors/pins:");
  for (byte i = 0; i < howManyMotors; i++)
  {
    pinMode(motorPins[i], OUTPUT);
    Serial.print(i); Serial.print("/"); Serial.print(motorPins[i]); Serial.print(" ");
  }
  Serial.println(" ");
  pinMode(gesturePin, INPUT_PULLUP);
  gestureState = digitalRead(gesturePin);
  gestureStatePrev = gestureState;
  //initialise proof of life led
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, proofOfLifeState);
  Serial.println("<Arduino is ready>");
}

void loop()
{
  proofOfLife();
  recvAndValidateOneChar();
  useNewData();
  checkForGesture();
  checkIfTimeToStop();
}//loop

void proofOfLife()
{
  if (millis() - previousMillisProofOfLife >= proofOfLifeLedInterval)
  {
    previousMillisProofOfLife = millis();
    proofOfLifeState = !proofOfLifeState;
    digitalWrite(LED_BUILTIN, proofOfLifeState);
  }
} //proofoflife

void recvAndValidateOneChar()
{
  if (Serial.available() > 0)
  {
    receivedChar = Serial.read();
    Serial.print("This just in ... ");
    Serial.print(receivedChar);
    if (receivedChar >= '0' && receivedChar <= '7')
    {
      Serial.println(", that's OK");
      newData = true;
    }
    else
    {
      Serial.println(", that's no good");
    }
  }
}//recvAndValidateOneChar

void useNewData()
{
  if (newData == true)
  {
    eligibleToRun[receivedChar - 48] = 1; 
    for (byte i = 0; i < howManyMotors; i++)
    {
      Serial.print(eligibleToRun[i]);
    }
    Serial.println(" ");
    newData = false;
  }
}//useNewData

void checkForGesture()
{
  gestureState = digitalRead(gesturePin);

  if (gestureState != gestureStatePrev)
  {
    if (gestureState == LOW)  // changed to pressed
    {
      Serial.println("Starting eligible motors");
      startedAtMillis = millis();
      running = true;
      //start the eligible motors:
      for (byte i = 0; i < howManyMotors; i++)
      {
        digitalWrite(motorPins[i], eligibleToRun[i]);
      }

    }
    // Delay a little bit to avoid bouncing
    delay(50);
  }
  // save the current state as the last state, for next time through the loop
  gestureStatePrev = gestureState;
}//checkForGesture

void checkIfTimeToStop()
{
  if (running && millis() - startedAtMillis >= motorsRunFor)
  {
    running = false;
    //stop the motors and reset eligibility
    for (byte i = 0; i < howManyMotors; i++)
    {
      digitalWrite(motorPins[i], LOW);
      eligibleToRun[i] = 0;
    }
    
    Serial.println("Motors are stopped");
    
    for (byte i = 0; i < howManyMotors; i++)
    {
      Serial.print(eligibleToRun[i]);
    }
    Serial.println(" ");
  }
}//checkIfTimeToStop

Thanks for this!

Why do I get this in the serial monitor when I just type one number in? It's printing as if I sent a space.

This just in ... 0, that's OK
10000000 
This just in ... 
, that's no good

You probably need to set the line ending to No line ending.

line end and baud.GIF