Stay inside a if loop until a task is complete

I create a code where the ultrasonic sensor is detected and a timer begins. However, the timer does not work every second as it should do, and the loop exits when the sensor value is no longer in the threshold.

#include <Servo.h>
Servo servotest;
int trigPin = 2;
int echoPin = 3;
long distance;
long duration;
unsigned long prevMillis;
int count = 20;


void setup()
{
  servotest.attach(1);
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  servotest.write(0);
  servotest.write(0);
  Serial.begin(9600);
  prevMillis = millis();

}

void loop() {
  ultrasonic();
  delay(500);
  unsigned long curMillis = millis();

  if (distance <= 5) {
    while (true) {

      delay(3000);
      servotest.write(90);
      delay(2000);

      if (curMillis - prevMillis >= 1000) {
        count -= 1;
        Serial.println(count);
        prevMillis += 1000;
      }
      if ((count == 0)||distance <= 5)) {

        Serial.println("countended");

      }
    }
  }
  else {
    Serial.println("elseended");
  }
}

void ultrasonic() {
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  duration = pulseIn(echoPin, HIGH);
  distance = duration * 0.034 / 2;
}

I have attempted to implement a while(true) loop after the if loop and this a solution but I am not sure if it is correct. With the timer, despite the interval being set at 1 second, the output takes place every 5 and also randomly stops. What is the cause of the error(s) and what are the solutions.

Right away, without going any further, I can tell you that mixing delay() and millis() in the same program is usually a huge disaster.

millis timers rely on the code looping very rapidly. If you have a delay in your code then the code can only loop very slowly as during the delay your uC just takes a wee break. Use your millis timing for all 'delays' except perhaps any that are needed in setup which only runs once. See the tutorials in the stickies at the top of this section.

And IF is not a loop - it is just a test. Allow the loop() function to do the repetition.

...R

what do you mean 'stops'? millis refers to the arduinos internal timer which starts when you switch it on and stops when you switch it off. For timing stuff in your code you simply check the current number of millis since the arduino was turned on and then check again to see if your desired time has elapsed. look at the links already advised. They are at the top of this section as a sticky

here: Demonstration code for several things at the same time - Project Guidance - Arduino Forum

pmagowan:
millis refers to the arduinos internal timer which starts when you switch it on and stops when you switch it off.

NO. millis() is started by the Arduino init code shortly after the processor comes out of reset, and it runs until the processor is again reset, or some software deliberately turns off the timer, which there is NO good reason to ever do. "you" should NEVER turn the timer on or off, just leave it be, let it run, and use it for doing your timing, as it continues to run.

RayLivingston:
NO. millis() is started by the Arduino init code shortly after the processor comes out of reset, and it runs until the processor is again reset, or some software deliberately turns off the timer, which there is NO good reason to ever do. "you" should NEVER turn the timer on or off, just leave it be, let it run, and use it for doing your timing, as it continues to run.

I think we misunderstand one another. By switch it off I mean the arduino. i.e. millis starts from when you switch your arduino on (more or less) and stops when you switch your arduino off. My point, which is the same as yours is that you should only reference millis, not mess with the actual gubbins below the hood.

ultrao:
@pmagowan A countdown timer which uses milis instead of delays. If a sensor's reading (or some other criteria) is a certain value, then the countdown timer stops at the value it is . Not the internal timer itself.

You can make any timer you like referencing millis and 'start' and 'stop' it when you like. eg

if you want a simple timer for 500ms you can set a variable to equal millis

unsigned long time = millis();

when you do this all you are saying to the uC is to make a variable give it the value of the current number of milliseconds since you turned your arduino on.

you now want to check if 500ms has passed since this time so every time the code loops you check the current value of millis and subtract the time variable to see if the remainder is >=500

You have an if statement in your code that does this for the interval 1000.

you can do this as many times as you like and activate a counter so counter=1 is 500ms, counter=2 is 1000ms etc. You can reset your counter at any time by simply coding counter=0.

You can use a sensor or any other input to reset your counter.

Think about it like a stop watch. It keeps ticking under the hood even when you set a timer or stop your timer.

Your requirements aren't very clear.
What start the timer?
What stops it?
How many times/for how long do ou want it to run?

I think you want to do some work every second, as long as the distance <= 5 for a maximum of 20 iterations.

unsigned long previousMillis = 0;
const long interval = 1000;
bool timerRunning = false;
int count = 0;
long distance;

void loop() {

  ultrasonic();
  
  unsigned long currentMillis = millis();

  if (distance <= 5) {
    //Start timer running if not already running
    if (timerRunning == false) {
      timerRunning = true;
      previousMillis = currentMillis - interval;
      count = 20;
    }
  }
  else
  {
    //Stop timer running
    timerRunning = false;
  }

  //Timer running and work to do and timer popped
  if (timerRunning && count > 0 && currentMillis - previousMillis >= interval) {
    //Do something every second while distance <= 5 for 20 iterations
    previousMillis += interval;
    count--;
  }
}

You have been given a code-example by pcbbc,
now it is your turn to understand the basic principle.
Without understanding it you will have to come back every 15 minutes to ask for the next modification of your code.
Not very satisfying nor for us nor for you

best regards Stefan
any newbee can apply the most professional habit from the first line of code they write on their own:
add only ONE thing at a time. Test/debug that ONE thing until that ONE thing works reliable - repeat.
The sad thing is: only the REAL professionals write code this way.

ultrao:
This is the rough flow:

  1. If distance >5
  2. move motor and wait 3 seconds
  3. after 3 seconds start the 20 second timer
  4. continue until the 20 second timer runs out or distance > 5
  5. reset

So think about the different chunks of code:

  1. distance sensor code
  2. motor code
  3. Timer code

Your distance code only has to change a state variable as you have only 2 states (dist >5, dist<5). You can write a function for your distance sensors and have it set this state as in the true false example given to you by pcbbc

Your motor code is up to you and can be a separate function.

The timer code is also given to you by pcbbc and can be a function all of its own just quietly ticking away a counter up to 20.

These functions can all work on their own and be called in loop. That way you have control over the individual parts and can adjust or add as necessary. You can use the same timer for anything else you want to do also. It is simply a matter of style. I like the chunk method of creating functions because it is iterative and if you give them good names that make the main code easy to read.

ultrao:
This is the rough flow:

  1. If distance >5
  2. move motor and wait 3 seconds
  3. after 3 seconds start the 20 second timer
  4. continue until the 20 second timer runs out or distance > 5
  5. reset

Ordering these as a list of numbered points is not very helpful. It doesn’t say which bits should be executed when condition 1 is met.
20 seconds is not a multiple of 3, if indeed that is what we are repeating.
Reset what?
Since the condition to start (> 5) is the same as the condition to end, I’m not at all sure how that works either.

(Pseudo code of operations)

if (distance >5) {

move servo
wait 3 seconds
start a 20 second timer
}
if (count = 0 or distance <5 ) {
stop 20 second timer
reset
}

You recognise that both your if states will be true at the same time? Your timer will start and then stop less than a millisecond later

Typo. Also, when I ran the program the code stopped at 19 seconds, and did not progress from that stage.

You are very poor at explaining things without requiring significant work on the part of those trying to help you.

imagine if I said:
"on another note your problem is resolved on another thread"!

(Pseudo code of operations)

if (distance >5) {

move servo
wait 3 seconds
start a 20 second timer - to time and do what?
}
if (count = 0 or distance <5 ) { - count of what? count is never changed
stop 20 second timer
reset - reset what?
}

(Pseudo code of operations)
//count is the timer's variable, which starts at 20 and is reduced by -1 every second when timer initiated
int count = 20
if (distance >5) {

move servo
wait 3 seconds
start a 20 second countdown timer, which reduces the "count" variable value by -1 every second.
}
if (count = 0 or distance <5 ) {
stop 20 second timer
reset the timer
}

why not make your count 23 if you are wanting to wait for 3 additional seconds. Your logic is poor

pmagowan:
why not make your count 23 if you are wanting to wait for 3 additional seconds. Your logic is poor

Your assumption might be poor too..

we could assume that's because OP wants to keep the servo at least 3 seconds at the 90° position regardless of what happens to the distance.


OK I'll give it a try to what I understood.

Servo is at 0° which is the IDLE state. that's where you start at first.

if you read a distance that is greater than 5 then you enter a new state (PAUSED) where you move the servo to 90° and unconditionally wait for 3 seconds there regardless of what happens on the sensor

When the 3 seconds have elapsed, you enter a new state (WAITING). You will return to the IDLE state (and move the servo back to 0°) either when the distance falls below 5 or 20 seconds have elapsed, whatever happens first.

it could be depicted with this state machine:

the code would look something like this (typed here so probably some typos)

#include <Servo.h>
Servo servotest;

int trigPin = 2;
int echoPin = 3;

enum : byte {IDLE, PAUSED, WAITING} currentState = IDLE;

unsigned long chrono;

double distance(bool forcePing = false) {
  static unsigned long lastPing;
  static double d = 0;  // remember last distance read

  if (forcePing || (millis() - lastPing >= 40)) { // don't relaunch a ping if previous one was less than 40ms ago
    digitalWrite(trigPin, LOW);
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);
    d = pulseIn(echoPin, HIGH) * 0.034 / 2.0;
    lastPing = millis();
  }
  return d;
}

void setup()
{
  Serial.begin(115200);

  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  servotest.attach(1);
  servotest.write(0);
  distance(true); // force read the distance the first time 
}

void loop() {
  switch (currentState) {
    case IDLE:
      // we are IDLE, check if the distance is above 5
      if (distance() > 5) {
        servotest.write(90);    // move Servo
        chrono = millis();      // record time
        currentState = PAUSED;  // ready to wait for 3 sec
      }
      break;

    case PAUSED:
      // waiting for 3 seconds unconditionally
      if (millis() - chrono >= 3000ul) {
        chrono = millis();      // record time
        currentState = WAITING; 
      }
      break;

    case WAITING:
      // check if distance falls below 5 or 20 seconds have elapsed, whatever happens first
      if ((distance() < 5) || (millis() - chrono >= 20000ul)) {
        servotest.write(0);     // move Servo
        currentState = IDLE;
      }
      break;
  }
}