Turn-off delay with millis() via two Ultrasonic Range detectors

Hello from Germany dear forum users,

I just created an account out of despair, after reading the rules at How to use this forum - please read. - Installation & Troubleshooting - Arduino Forum

I literally searched almost 3.5 hours for a solution, no library and no website seemed to have an answer for a simple thing.

I always had my problems with the millis() function, but everytime I thought I understood it to 100%, I was thrown back when I started a new project with another use of millis.

Long story short: What I wanted to build is a simple light detection for my house and garage.
So 2 lamps shall be installed inside of 3 garages. A third lamp will be attached outside to light up when I bring out the garbage.

I'll be using one ultrasonic sensor for both of the garages and another one outside.

Whenever I come into the garage from one of the doors, the ultrasonic sensor detects something and should light up the light for a certain amount of time (called "intervallgarage" in my code). Same thing should happen when the sensor outside detects me and turns the light on and waits for "intervallmuell" ("Müll" is "garbage" in German).

The Code:

const int triggergarage = 2;
const int echogarage    = 3;
const int lampe2        = 12;
const int lampe3        = 11;
long dauergarage        = 0;
long entfernunggarage   = 0;

const int triggermuell  = 4;
const int echomuell     = 5;
const int lampe1        = 13;
long dauermuell         = 0;
long entfernungmuell    = 0;

unsigned long previousMillismuell = 0;  
unsigned long previousMillisgarage = 0;    

const long intervallmuell = 2000;
const long intervallgarage = 120000;

void setup() {

  Serial.begin(9600);
  pinMode(lampe1, OUTPUT);
  pinMode(lampe2, OUTPUT);
  pinMode(lampe3, OUTPUT);
  pinMode(triggermuell, OUTPUT);
  pinMode(echomuell, INPUT);

  pinMode(triggergarage, OUTPUT);
  pinMode(echogarage, INPUT);
}

void loop() {
muell();
}

void muell(){



digitalWrite(triggermuell, LOW);
delay(5);
digitalWrite(triggermuell, HIGH);
delay(10);
digitalWrite(triggermuell, LOW);
dauermuell = pulseIn(echomuell, HIGH);
entfernungmuell = (dauermuell/2) * 0.03432;

if (entfernungmuell >= 2 && entfernungmuell <= 70)
{
unsigned long currentMillismuell = millis();
digitalWrite(lampe1, HIGH);
Serial.println(currentMillismuell);
}

if (currentMillismuell - previousMillismuell >= intervallmuell){
    digitalWrite(lampe1, LOW);
    previousMillismuell = currentMillismuell;
}
}

void garage(){

digitalWrite(triggergarage, LOW); 
delay(5); 
digitalWrite(triggergarage, HIGH); 
delay(10);
digitalWrite(triggergarage, LOW);
dauergarage = pulseIn(echogarage, HIGH); 
entfernunggarage = (dauergarage/2) * 0.03432;

if (entfernunggarage >= 2 && entfernunggarage <= 300) 
{}

}

You can ignore everything that has to do with garage yet, it's just a duplication of the (yet not working) code above for the garbagelight. What happens if I use my code is following:

1.) Ultrasonic Detector sends sound - nothing is within the range of 2-70cm - LED stays off - so far so good.

2.) Ultrasonic Detector sends sound- I'm inside the measuring range - LED goes on - also okay.

But what I noticed, is that the counter when the LED should go on is running permanently, that means when I trigger the sensor at the currentTime of 1000ms, it just lights up the LED for the rest of the intervall, not the full intervall. That also means in a very unlucky case, the lamp is on for just a fracture of a second. The lamp should stay on for the given time (which is unfortunately called interval, because I started the sketch inside of the blink without delay example).

It's not about using the wrong or right sensors for this project, just about the function, it could've also been a push button or whatever else gives me an input.

Thank you very very much :slight_smile:

Try this:
Make this global >>>>----> unsigned long currentMillismuell

if (entfernungmuell >= 2 && entfernungmuell <= 70)
{
//unsigned long currentMillismuell = millis();  //remove this line 
digitalWrite(lampe1, HIGH);
Serial.println(currentMillismuell);
}
void loop() 
{
  currentMillismuell = millis();  //add this line (wife is calling me)
  muell();
}

I think this is a bit mixed up

if (entfernungmuell >= 2 && entfernungmuell <= 70)
{
    unsigned long currentMillismuell = millis();
    digitalWrite(lampe1, HIGH);
    Serial.println(currentMillismuell);
}

if (currentMillismuell - previousMillismuell >= intervallmuell){
    digitalWrite(lampe1, LOW);
    previousMillismuell = currentMillismuell;
}
}

What you call currentMillismuell should probably be startMuellMillis to because it is the start of the timing period

and then the code should be

if (entfernungmuell >= 2 && entfernungmuell <= 70)
{
    static unsigned long startMuellMillis = millis();
    digitalWrite(lampe1, HIGH);
    Serial.println(currentMillismuell);
}

if (millis() - startMuellMillis >= intervallmuell){
    digitalWrite(lampe1, LOW);
}
}

Note the use of "static" so the value is retained from one iteration to the next.

I am assuming that the test if (entfernungmuell >= 2 && entfernungmuell <= 70) can only be true once. If that is not correct then you also need a variable that records the fact that the timing is in progress so that startMuelMillis is not continually updated.

The demo Several Things at a Time may help to explain the technique for using millis()

...R

Hey Robin2 an larryd,

thank you both very much for taking the time to get into my problem and posting a solution.

Unfortunately, I've already tried both ways before today, so making the unsigned long global @larryd compiled the code without a problem, but just didn't work on a breadboard circuit.
The starttime of counter wasn't affected by this solution and the old problem with the remaining time persists.

And @Robin2, as I mentioned now, I've even tried it with this method, not in that exact way but with the static function. Feels good to know that I've tried something that could work.
What it gives me out when I want to compile the code (as already many many times today) is:

Detection_for_light:55: error: 'startMuellMillis' was not declared in this scope
 if (millis() - startMuellMillis >= intervallmuell){
                ^
exit status 1
'startMuellMillis' was not declared in this scope

Even though it's static now, it won't transfer to the next if statement.

I just answered here before I remembered to take a look on your links @Robin2, sorry for that.
I will do it now :slight_smile:

Thank you both.

Working on the code as you originally posted - which does not compile.

First of all, not very important, I did notice you have trigger high for 10 milliseconds, where 10 microseconds is enough (use delaymicroseconds() instead). You lose almost 15 ms there, an unnecessary slow-down.

The function garage() is never called in the sketch, so that won't do anything.

You declare currentMillismuell and assign a value to it on line 52, inside the if block, so it goes out of scope the moment you leave that block. That indeed gives a compiler error.

You need one variable and one constant for your timing. A startMillis and an interval.

Basically your code should look a bit like this (I left out the setup() part and all other pin declarations):

unsigned long int startMillis; // make it unsigned! Or you may get problems at high millis().
int interval 2000; // the 2-second interval.

loop() {
  muell();
}

bool muell() {
  digitalWrite(triggermuell, LOW);
  delayMicroseconds(5);
  digitalWrite(triggermuell, HIGH);
  delayMicroseconds((10);
  digitalWrite(triggermuell, LOW);
  dauermuell = pulseIn(echomuell, HIGH);
  entfernungmuell = (dauermuell / 2) * 0.03432;
  if (entfernungmuell >= 2 && entfernungmuell <= 70) startMillis = millis();
  if (millis() - interval > startMillis) digitalWrite(lampe1, HIGH); // light on
  else digitalWrite(lampe1, LOW); // light off
  return;
}

ALL timing related variables should be declared "unsigned long", and you need an "=" sign.

int interval 2000; // the 2-second interval.

Should be:

unsigned long interval=2000; // the 2-second interval.

jremington:
ALL timing related variables should be declared "unsigned long", and you need an "=" sign.

int interval 2000; // the 2-second interval.

Should be:

unsigned long interval=2000; // the 2-second interval.

It's a constant, why does it matter? I understand the need for anything that's going to be used to store a millis() value (that of course should be a 32-bit unsigned int) but not the others, in this case a 16-bit signed int should be perfectly fine for the value of 2000. It saves 2 bytes of the very small variable space.

By the way, when on the ESP8266 an unsigned int is 32-bit so on that platform it doesn't matter if you use a long or an int... those variable sizes are platform/compiler dependent!

It's a constant, why does it matter?

"Constant" has nothing to do with it.

Declaring all time-related variables and constants as unsigned long is good practice, as it helps beginners avoid problems due to false assumptions.

For example, the following type of error crops up every few days on the forum:

unsigned long interval = 100*1000; //"100 second" time interval, except when it is not

Hello and thanks again to all of you,

special thanks to wvmarle, your code was working after inverting the values of the lamps.
Means the lamp was on if nothing was detected and off for the interval when something was in the range.

The next problem that I'm facing is, that if I have both bools, so bool muell() and bool garage() in my loop, the lamp connected to the garbage sensor starts to flicker and doesn't stay on for the wanted time. Same for the garagesensor. How can that be? Is it possible that there could be a kind of interaction between both of the ultrasonic sensors?

I thought it's maybe caused by the (even though insignificant on the first glance) delay, which was already minimized by using delayMicroseconds for the ultrasonic ping and echo. Any way to find a solution for this problem?

Thank you all very much again, a very helpful and nice community :slight_smile:

Something else that I noticed is that, if I increase the interval from 2000ms (which was for test purposes so that I did't have to wait that long to see a result on the breadboard) to 25000ms/25s (which will be the real value for this), the lamps won't go on before those 25 seconds passed by at first.
Something like a start-up time, since the 25 seconds are part of the comparison in this row:

if (millis() - intervallgarage > startMillisgarage) digitalWrite(lampe1, LOW); // light on

Am I wrong if I say that it makes sense so far, because if the program just started and has a millis() value of let's say 2000ms, you would subtract 25000ms from those 2000ms, which will give a negative value and since we're using an unsigned, it just won't give anything out.
So it can't even compare if the value is greater than startMillisgarage before the 25 seconds have expired at first?

Edit:

Another problem I faced is, that if I have both functions in seperate bools, the measuring won't work properly anymore. Means, that if the loop goes into bool muell(); and then into bool garage();, nothing will happen when you get in the range of the ultrasonic detector. I solved this by just putting both of the old bools into a single bool called measure().

bool measure()
 digitalWrite(triggermuell, LOW);
  delayMicroseconds(5);
  digitalWrite(triggermuell, HIGH);
  delayMicroseconds(10);
  digitalWrite(triggermuell, LOW);
  dauermuell = pulseIn(echomuell, HIGH);
  entfernungmuell = (dauermuell / 2) * 0.03432;
  if (entfernungmuell >= 2 && entfernungmuell <= 70) startMillismuell = millis();
  if (millis() - intervallmuell > startMillismuell) digitalWrite(lampe1, LOW); // off
  else digitalWrite(lampe1, HIGH); // on
  return;

  digitalWrite(triggergarage, LOW); 
  delayMicroseconds(5); 
  digitalWrite(triggergarage, HIGH); 
  delayMicroseconds(10);
  digitalWrite(triggergarage, LOW);
  dauergarage = pulseIn(echogarage, HIGH); 
  entfernunggarage = (dauergarage/2) * 0.03432;
  if (entfernunggarage >= 2 && entfernunggarage <= 70) startMillisgarage = millis();
  if (millis() - intervallgarage > startMillisgarage) {digitalWrite(lampe2, LOW); digitalWrite(lampe3, LOW);}
  else digitalWrite(lampe2, HIGH); digitalWrite(lampe3, HIGH); // on
  return;

This also solves the flickering problem of the lamp but I have no logical explanation for that.
Maybe someone of you?

Thanks

Am I wrong if I say that it makes sense so far, because if the program just started and has a millis() value of let's say 2000ms, you would subtract 25000ms from those 2000ms, which will give a negative value and since we're using an unsigned, it just won't give anything out

That is incorrect. In unsigned long integer arithmetic, 2000 - 25000 = 4294944296.

Since the function measure() unconditionally returns after execution of this statement:

  return;

the code following that return statement is never executed.

Note that the term "bool" in "bool measure()" is the type of value that the function returns (a boolean value). Since your function measure() does not return any value, the return value type should be declared "void" instead.

Hey jremington,

thank you very much for your reply, I also stumbled upon those informations after analyzing the code wvmarle gave me. Always glad to learn something new!

The sketch is already running fine so far, except the speed of the processing of the ultrasonic sensor, if more than one sensor is used. I already "speeded" up the sketch by using

PORTB |= _BV(AVR-Pin);

for digitalWrite(Pin, 1); and digitalWrite(Pin, 0); but it still seems to be slow somehow, there is a kind of a delay where an object is not recognized in front of the sensor, but after a second or so it will be recognized. Have to find a solution for this, because the light should light up anytime, not only when I get in the measuring process the right time.

A cleaned up (unnecessary int's were removed and I replaced the German words with the Englisch equivalents for the sake of simplicity) version of the code is here:

const int triggergarage = 2;
const int echogarage    = 3;
const int lampe2        = 12;
const int lampe3        = 11;

long durationgarage        = 0;
long distancegarage   = 0;

const int triggertrash  = 4;
const int echotrash     = 5;
const int lampe1        = 13;

long durationtrash         = 0;
long distancetrash    = 0;

unsigned long int startMillistrash;
unsigned long int startMillisgarage; 

const long intervalltrash = 2000;
const long intervallgarage = 5000;

void setup() {

  Serial.begin(9600);
  pinMode(lampe1, OUTPUT);
  pinMode(lampe2, OUTPUT);
  pinMode(lampe3, OUTPUT);
  pinMode(triggertrash, OUTPUT);
  pinMode(echotrash, INPUT);

  pinMode(triggergarage, OUTPUT);
  pinMode(echogarage, INPUT);
}

void loop() {
//trash();
//garage();
measure();
}

bool trash() {
  digitalWrite(triggertrash, LOW);
  delayMicroseconds(5);
  digitalWrite(triggertrash, HIGH);
  delayMicroseconds(10);
  digitalWrite(triggertrash, LOW);
  durationtrash = pulseIn(echotrash, HIGH);
  distancetrash = (durationtrash / 2) * 0.03432;
  if (distancetrash >= 2 && distancetrash <= 70) startMillistrash = millis();
  if (millis() - intervalltrash > startMillistrash) PORTB &= ~_BV(PB5); // light off
  else PORTB |= _BV(PB5); // light on
  return;
}

bool garage(){
  digitalWrite(triggergarage, LOW); 
  delayMicroseconds(5); 
  digitalWrite(triggergarage, HIGH); 
  delayMicroseconds(10);
  digitalWrite(triggergarage, LOW);
  durationgarage = pulseIn(echogarage, HIGH); 
  distancegarage = (durationgarage/2) * 0.03432;
  if (distancegarage >= 2 && distancegarage <= 70) startMillisgarage = millis();
  if (millis() - intervallgarage > startMillisgarage) { PORTB &= ~_BV(PB4); PORTB &= ~_BV(PB3); } // light on
  else {PORTB |= _BV(PB4); PORTB |= _BV(PB3); } // light off
  return;
}

void measure(){
  digitalWrite(triggertrash, LOW);
  delayMicroseconds(5);
  digitalWrite(triggertrash, HIGH);
  delayMicroseconds(10);
  digitalWrite(triggertrash, LOW);
  durationtrash = pulseIn(echotrash, HIGH);
  distancetrash = (durationtrash / 2) * 0.03432;
  if (distancetrash >= 2 && distancetrash <= 70) startMillistrash = millis();
  if (millis() - intervalltrash > startMillistrash) PORTB &= ~_BV(PB5); // light off
  else PORTB |= _BV(PB5); // light on

  digitalWrite(triggergarage, LOW); 
  delayMicroseconds(5); 
  digitalWrite(triggergarage, HIGH); 
  delayMicroseconds(10);
  digitalWrite(triggergarage, LOW);
  durationgarage = pulseIn(echogarage, HIGH); 
  distancegarage = (durationgarage/2) * 0.03432;
  if (distancegarage >= 2 && distancegarage <= 70) startMillisgarage = millis();
  if (millis() - intervallgarage > startMillisgarage) { PORTB &= ~_BV(PB4); PORTB &= ~_BV(PB3); } // light on
  else {PORTB |= _BV(PB4); PORTB |= _BV(PB3); } // light off
}

The two bool functions garage() and trash() were just put outside the brackets, I left them in the code because I don't know yet if the void measure() without return; or the bool with the return; is better and why, since both versions are working fine, except the speed issue above.

Thanks and good night from Germany.

ariama:
The next problem that I'm facing is, that if I have both bools, so bool muell() and bool garage() in my loop, the lamp connected to the garbage sensor starts to flicker and doesn't stay on for the wanted time. Same for the garagesensor. How can that be? Is it possible that there could be a kind of interaction between both of the ultrasonic sensors?

Please define "flicker".

The code as I gave it, is supposed to reset the interval every time something is detected in range, so the lights stay on for the interval duration after the last detection.

It is possible for these sensors to interact, that one picks up the echoes of pings from the other. To stop this from happening you have to add a delay of maybe 10-20 ms. Whether that's the case in your situation depends on the relative locations of the sensors.

wvmarle:
Please define "flicker".

That problem is solved. The lamp (or on the breadboard the LED) stayed on for the given time, but had a cyclic flicker in an interval of 500ms. Like it would loos it's energy for a fraction of a millisecond. Nothing really heavy, but noticeable.

wvmarle:
It is possible for these sensors to interact, that one picks up the echoes of pings from the other.

The two sensors will work independently of one another, as said, one sensor will be inside my garage and the other one will be outside where the garbage is. So it's very unlikely that they will interfere each other.

So everything is solved and working fine, thank you for that! The processing speed is just my next problem and I have absolutely no idea how to find a way to improve the code for that.

One sensor is working fine and reacts immediately (LED) when I hold my hand in front of it.
If I have two of them and I hold my hand in front of one, the LED doesn't light up immediately, you have to stay in front of the sensor for ≈ 1 second. But in practice, I will maybe pass the sensor quickly if I have to get in the garage at night for an emergency (just an example). I don't want to stay in front of the sensor to "get" me at first.

Or is the ultrasonic sensor maybe not good for a very fast processing if I'm using multiple (more than one) of them?

Thank you!

Post your latest code and maybe we can find something.

ariama:

const int triggergarage = 2;

const int echogarage    = 3;
const int lampe2        = 12;
const int lampe3        = 11;

long durationgarage        = 0;
long distancegarage  = 0;

const int triggertrash  = 4;
const int echotrash    = 5;
const int lampe1        = 13;

long durationtrash        = 0;
long distancetrash    = 0;

unsigned long int startMillistrash;
unsigned long int startMillisgarage;

const long intervalltrash = 2000;
const long intervallgarage = 5000;

void setup() {

Serial.begin(9600);
  pinMode(lampe1, OUTPUT);
  pinMode(lampe2, OUTPUT);
  pinMode(lampe3, OUTPUT);
  pinMode(triggertrash, OUTPUT);
  pinMode(echotrash, INPUT);

pinMode(triggergarage, OUTPUT);
  pinMode(echogarage, INPUT);
}

void loop() {
//trash();
//garage();
measure();
}

bool trash() {
  digitalWrite(triggertrash, LOW);
  delayMicroseconds(5);
  digitalWrite(triggertrash, HIGH);
  delayMicroseconds(10);
  digitalWrite(triggertrash, LOW);
  durationtrash = pulseIn(echotrash, HIGH);
  distancetrash = (durationtrash / 2) * 0.03432;
  if (distancetrash >= 2 && distancetrash <= 70) startMillistrash = millis();
  if (millis() - intervalltrash > startMillistrash) PORTB &= ~_BV(PB5); // light off
  else PORTB |= _BV(PB5); // light on
  return;
}

bool garage(){
  digitalWrite(triggergarage, LOW);
  delayMicroseconds(5);
  digitalWrite(triggergarage, HIGH);
  delayMicroseconds(10);
  digitalWrite(triggergarage, LOW);
  durationgarage = pulseIn(echogarage, HIGH);
  distancegarage = (durationgarage/2) * 0.03432;
  if (distancegarage >= 2 && distancegarage <= 70) startMillisgarage = millis();
  if (millis() - intervallgarage > startMillisgarage) { PORTB &= ~_BV(PB4); PORTB &= ~_BV(PB3); } // light on
  else {PORTB |= _BV(PB4); PORTB |= _BV(PB3); } // light off
  return;
}

void measure(){
  digitalWrite(triggertrash, LOW);
  delayMicroseconds(5);
  digitalWrite(triggertrash, HIGH);
  delayMicroseconds(10);
  digitalWrite(triggertrash, LOW);
  durationtrash = pulseIn(echotrash, HIGH);
  distancetrash = (durationtrash / 2) * 0.03432;
  if (distancetrash >= 2 && distancetrash <= 70) startMillistrash = millis();
  if (millis() - intervalltrash > startMillistrash) PORTB &= ~_BV(PB5); // light off
  else PORTB |= _BV(PB5); // light on

digitalWrite(triggergarage, LOW);
  delayMicroseconds(5);
  digitalWrite(triggergarage, HIGH);
  delayMicroseconds(10);
  digitalWrite(triggergarage, LOW);
  durationgarage = pulseIn(echogarage, HIGH);
  distancegarage = (durationgarage/2) * 0.03432;
  if (distancegarage >= 2 && distancegarage <= 70) startMillisgarage = millis();
  if (millis() - intervallgarage > startMillisgarage) { PORTB &= ~_BV(PB4); PORTB &= ~_BV(PB3); } // light on
  else {PORTB |= _BV(PB4); PORTB |= _BV(PB3); } // light off
}

This was my latest code that worked. Thanks!

  if (distancetrash >= 2 && distancetrash <= 70) startMillistrash = millis();
  if (millis() - intervalltrash > startMillistrash) PORTB &= ~_BV(PB5); // light off
  else PORTB |= _BV(PB5); // light on

Several things to say about these lines (last time I was just focussed on the millis() part :)).

  1. you're using direct port writes - not good for readability. It's faster but for your application that's irrelevant.
  2. you're hardcoding pin numbers here, instead of using the definitions from before.
  3. variable names are commonly started with lowercase, then camelCased. Function names start with capital and are CamelCased.
  4. the millis() is reversed from normal practice.
  5. you switch off the light during the interval, on when the interval is over.

Before the rewrite, compare it to this:

  if (distancegarage >= 2 && distancegarage <= 70) startMillisgarage = millis();
  if (millis() - intervallgarage > startMillisgarage) { PORTB &= ~_BV(PB4); PORTB &= ~_BV(PB3); } // light on
  else {PORTB |= _BV(PB4); PORTB |= _BV(PB3); } // light off

Here you switch on and off the light reversed: on during the interval, off afterwards.

Anyway, hereby my version of these lines, with comments. Those PORT assignments are too hard to decipher; I just assume HIGH is light on and LOW is light off but that may be reversed depending on your driver circuits.

  if (distanceTrash >= 2 && distanceTrash <= 70) {
    startMillisTrash = millis();  // Set startMillisTrash every time something is in range.
    digitalWrite(lampe1, HIGH);   // Light on when something is in range.
  }
  if (millis() - startMillisTrash > intervallTrash) digitalWrite(lampe1, LOW); // Light off when the interval has passed.

Style is important, it helps make code readable. Not just for others, but for yourself as well when you come back to it next year.