Check for how long a sensor is inactive

Good afternoon,

I want to detect for how long a sensor is inactive,
what is the suitable function to do this ?

Can I use only >>>> millis();

Appreciate early reply

millis is suitable. You initiate a timer at the beginning of your program, in the setup for instance and calculate the time when the sensor becomes active

In the header
unsigned long timer = 0;

In the setup, at the end
timer = millis();

And in the loop
if (digitalRead (sensorPin)) timer = millis() - timer;

This last test may depend on your sensor...

if the sensor is 'active', record the time (millis() or micros()).

Subtract the recorded time from the current time to get the number of milliseconds or microseconds that the sensor has been inactive (the time since it was last active).

Thank you for the reply, it was really helpful.

I need some assistance in a related manner also.

I am connecting four force sensors to Arduino,
and I need to check if any of the sensor was inactive for more than 10sec,
as a result I will turn ON a corresponding LED.

sensor:
http://bildr.org/2012/11/force-sensitive-resistor-arduino/

The code works well when connecting two force sensors only,
and the interval variables shows the communicative time correctly.

When I add the third sensor, the internal variables reset for all after it reach 10000,
and none of the LEDs work.

//From the article: http://bildr.org/2012/11/force-sensitive-resistor-arduino

/////////////
int interval_S1=0;
int pre_time_S1=0;
int time_S1=0;

int interval_S2=0;
int pre_time_S2=0;
int time_S2=0;

int interval_S3=0;
int pre_time_S3=0;
int time_S3=0;


int interval_S4=0;
int pre_time_S4=0;
int time_S4=0;



void setup()
{
  Serial.begin(9600);
  pinMode(3, OUTPUT);   
  pinMode(4, OUTPUT); 
  pinMode(5, OUTPUT);   
  pinMode(6, OUTPUT); 
  pinMode(A0, INPUT);   
  pinMode(A1, INPUT);  
  pinMode(A2, INPUT);   
  pinMode(A3, INPUT); 
 
}

void loop(){
  
short FSR1Reading = analogRead(A0); 
short FSR2Reading = analogRead(A1);
short FSR3Reading = analogRead(A2);
short FSR4Reading = analogRead(A3);



 if  (FSR1Reading <50)  { delay(500); time_S1 = millis();}
 else { pre_time_S1 =time_S1;}
 
 
  if  (FSR2Reading <50)  { time_S2 = millis();}
 else { pre_time_S2 =time_S2;}
 
 
  if  (FSR3Reading <50)  { time_S3 = millis(); Serial.print("S3=");Serial.println(FSR3Reading); }
  else { pre_time_S3 =time_S3;}
 
 
  if  (FSR4Reading <50)  { time_S4 = millis();}
 else { pre_time_S4 =time_S4;}
 

   
   interval_S1 = abs (time_S1 - pre_time_S1); 
   Serial.print("Interval_S1=");  Serial.println(interval_S1);
   
   interval_S2 = abs (time_S2 - pre_time_S2); 
   Serial.print("Interval_S2=");  Serial.println(interval_S2);
   
   interval_S3 = abs (time_S3 - pre_time_S3); 
   Serial.print("Interval_S3=");  Serial.println(interval_S3);
   
   //interval_S4 = abs (time_S4 - pre_time_S4); 
   //Serial.print("Interval_S4=");  Serial.println(interval_S4);
   
   
   
   
   if (interval_S1 > 10000) { digitalWrite(3, HIGH);}
   else {digitalWrite(3, LOW); }
   
   if (interval_S2 > 10000) { digitalWrite(4, HIGH);}
   else {digitalWrite(4, LOW); }
   
   if (interval_S3 > 10000) { digitalWrite(5, HIGH);}
   else {digitalWrite(5, LOW); }
   
   //if (interval_S4 > 10000) { digitalWrite(6, HIGH);}
   //else {digitalWrite(6, LOW); }
   


}

When you use millis() you can be 1 extra milli off just due to how the millis counter works.

The low 8 bits only ever returns 250 values. It skips 6 every 1/4 second. So when you subtract the Start from the End, if there are an odd number of skipped values in the lower 8 bits between those times the time difference will be off by 1 ms.

If it matters and the longest interval is at most around 70 minutes, use micros() for timing. Those are good to the next 4 us, a much smaller margin of error.

BTW, if your code doesn't block then you will need micros to see how long since the sensor was read, 1 milli should cover a few dozen reads.

GoForSmoke:
When you use millis() you can be 1 extra milli off just due to how the millis counter works.

The low 8 bits only ever returns 250 values. It skips 6 every 1/4 second. So when you subtract the Start from the End, if there are an odd number of skipped values in the lower 8 bits between those times the time difference will be off by 1 ms.

If it matters and the longest interval is at most around 70 minutes, use micros() for timing. Those are good to the next 4 us, a much smaller margin of error.

BTW, if your code doesn't block then you will need micros to see how long since the sensor was read, 1 milli should cover a few dozen reads.

Can you explain it in a simpler way ?

I tired to replace millis(); with micros(); but it did not work

You do know that 1 milli is 1000 micros?

I can't see your code to tell why whatever you did did not work, only guess.
I use micros() a lot without problems.

I looked at your last posted code and

int interval_S1=0;
int pre_time_S1=0;
int time_S1=0;

interval_S1 = abs (time_S1 - pre_time_S1);

You need to use unsigned integers for time calculations. Unsigned subtraction will always get time across rollover right with the same equation that works when it's not.

You need to use unsigned long to time intervals > 65535 micros (65.535 mills) long.
Unsigned long micros() can measure intervals over 70 minutes long.

And you should use an array of structs or 3 arrays for your interval, pre-time and time variables just to save your own personal time.

First you should remove the delay unless you really need it (but I don't see why you would)

delay(500);

Second, you should use unsigned long instead of int variables:

unsigned longinterval_S1=0;
unsigned long pre_time_S1=0;
unsigned long time_S1=0;
byte FSRReading[4};

Last, you could simplify your code by using arrays, because it does 4 times the same thing.

unsigned long interval[4] = {0};
unsigned long pre_time[4] = {0};
unsigned long time_S[4] = {0};
byte pins[4] = {3,4,5,6};

In the setup:

for (int i=0;i<4;i++) pinMode(pins[i], OUTPUT);

and in the loop:

FSRReading[0] = analogRead(A0); 
FSRReading[1] = analogRead(A1);
FSRReading[2] = analogRead(A2);
FSRReading[3] = analogRead(A3);

and

for (int i = 0; i<4; i++) {
   if  (FSRReading[i] <50) time_S[i] = millis(); else pre_time[i] =time_S[i];
   interval[i] = time_S[i] - pre_time[i]; // <-- you don't need abs here
   if (interval[i] > 10000) digitalWrite(pins[i], HIGH); else digitalWrite(pins[i], LOW);
}

(not tested)

One caveat there, this is from the AVR chip datasheets.

When switching between analog input pins there needs to be settling time before you get a good read. The reccomendation in the datasheet is to read twice and only use the 2nd read.

This will give much better reads.

analogRead( A0 );
FSRReading[0] = analogRead(A0);
analogRead( A1 );
FSRReading[1] = analogRead(A1);
analogRead( A2 );
FSRReading[2] = analogRead(A2);
analogRead( A3 );
FSRReading[3] = analogRead(A3);

I changed all the variables for the time calculations into unsigned long,
but the result is the same.

The interval variables are still reset to zero after it reach 10000.

When I changed the threshold I am comparing with to 20000,
all interval variables are reset after that new threshold.

The same code works perfectly with two force sensors only,
when I add the third one, I get this reset problem.

The test works with any two sensors. For example, in the first test, I tried S1 and S2 only, and I did not encounter the problem. When I repeat the test with sensor 3 and 4, I also did not encounter the problem. However, if I add one more sensor, the problem appear!

lesept:
First you should remove the delay unless you really need it (but I don't see why you would)

delay(500);

I am using the delay, so I have time to read at the serial monitor.

I think the interval is reset because of this :

if  (FSRReading[i] <50) time_S[i] = millis(); else pre_time[i] =time_S[i];

Maybe there are moments when the reading passes over the threshold (50) for only one read and this resets the interval. You should do for example 10 readings and compute the average which you would compare to the threshold.

I repeated this test three times, still interval variables are reset after it reach the threshold "1000".

I attached the test image.

10000 micros is 10 ms. Were the values resetting that fast when you used millis()?

For the printing.... if you only print twice a second events measured 50+ times a second, well.. errrr.......

const unsigned long printEvery = 500000; // half a second in micros
unsigned long lastPrint;

....... 

void loop()
{
  if ( micros() - lastPrint >= printEvery )  // this small independent task prints twice a second without blocking
  {
    lastprint += printEvery;
    Serial.println( value to print );  
  }  // every block like this in void loop() is a task, if you don't block they can run smoothly together

...........
}

Does this work?

// If any of the sensors are inactive for more than 10sec, I will turn ON a corresponding LED.
// If a sensor becomes active again I will turn off the corresponding LED

// Since the sensors are independent, use arrays to make it easier to change the number of sensors
const byte SensorCount = 4;
const byte FSRPins[SensorCount] = {A0, A1, A2, A3};
const byte LEDPins[SensorCount] = {3, 4, 5, 6};
unsigned long FSRLastActiveTime[SensorCount]; // Globals are initialized to zero

void setup()
{
  Serial.begin(115200);  // 9600 baud was FAST... back in 1975!

  for (byte i = 0; i < SensorCount; i++)
  {
    pinMode(LEDPins[i], OUTPUT);
    pinMode(FSRPins[i], INPUT);
  }
}

void loop()
{
  unsigned long currentMillis = millis();

  for (byte i = 0; i < SensorCount; i++)
  {
    if (analogRead(FSRPins[i] < 50))
    {
      FSRLastActiveTime[i] = currentMillis;
    }

    if (FSRLastActiveTime[i] != 0 && currentMillis - FSRLastActiveTime[i] >= 10000)
    {
      digitalWrite(LEDPins[i], HIGH);
      FSRLastActiveTime[i] = 0;
    }
    else
    {
      digitalWrite(LEDPins[i], LOW);
    }
  }
}

Kind of a side point but can I make a suggestion about that code, John?

That for-loop has an analog read in it that changes pins every read. Nothing to die over but there maybe should be 2 reads and only save the second per sensor. And that has each pass through the for-loop at over 200 micros.

That's no crime at all even if you scale up to 16 sensors. But suppose you want to add a button to the sketch for something else, maybe a STOP button? It will only get read after that (yes, blocky) for-loop is done.

So you take out the for-loop. You define a byte global as sensorIndex to do what i does in the loop and you increment and limit the index at the end of void loop(). Now the sensors get read in turn and your sketch can grow with everything getting a responsive turn.

For processes that get too long I reccomend using state machines to break them up if necessary just so other tasks get run time.

This is less blocking:

// If any of the sensors are inactive for more than 10sec, I will turn ON a corresponding LED.
// If a sensor becomes active again I will turn off the corresponding LED

// Since the sensors are independent, use arrays to make it easier to change the number of sensors
const byte SensorCount = 4;
const byte FSRPins[SensorCount] = {A0, A1, A2, A3};
const byte LEDPins[SensorCount] = {3, 4, 5, 6};
unsigned long FSRLastActiveTime[SensorCount]; // Globals are initialized to zero
byte i;

void setup()
{
  Serial.begin(115200);  // 9600 baud was FAST... back in 1975!

  for (i = 0; i < SensorCount; i++)
  {
    pinMode(LEDPins[i], OUTPUT);
    pinMode(FSRPins[i], INPUT);
  }
  i = 0; // index for a roll your own for-loop() below
}

void loop()
{
  unsigned long currentMillis = millis();

    if (analogRead(FSRPins[i] < 50))
    {
      FSRLastActiveTime[i] = currentMillis;
    }

    if (FSRLastActiveTime[i] != 0 && currentMillis - FSRLastActiveTime[i] >= 10000)
    {
      digitalWrite(LEDPins[i], HIGH);
      FSRLastActiveTime[i] = 0;
    }
    else
    {
      digitalWrite(LEDPins[i], LOW);
    }
  
  if ( ++i >= SensorCount )    i = 0;
}

But come to think of it, Serial services are only run when void loop() ends, before it restarts? Is that only serial out, it prints between loops? IIRC 115200 baud takes 86.8 us to send a char, do analog reads make serial a bit more asynch?

GoForSmoke:
Kind of a side point but can I make a suggestion about that code, John?

Sure. Go ahead.

GoForSmoke:
That for-loop has an analog read in it that changes pins every read. Nothing to die over but there maybe should be 2 reads and only save the second per sensor. And that has each pass through the for-loop at over 200 micros.

It is my understanding from the datasheet that "The ADC is optimized for analog signals with an output impedance of approximately 10 k or less. If such a source is used, the sampling time will be negligible." For higher impedance signals it might be necessary to double-sample but I try to stay with lower impedance signals.

GoForSmoke:
That's no crime at all even if you scale up to 16 sensors. But suppose you want to add a button to the sketch for something else, maybe a STOP button? It will only get read after that (yes, blocky) for-loop is done.

At 200 microseconds per iteration and up to 16 iterations (on a MEGA) I think the maximum of 3.2 milliseconds between pushing a button and having the code react would be very hard to notice.

GoForSmoke:
So you take out the for-loop. You define a byte global as sensorIndex to do what i does in the loop and you increment and limit the index at the end of void loop(). Now the sensors get read in turn and your sketch can grow with everything getting a responsive turn.

If 3.2 milliseconds (on a fully-utilized MEGA, 800 microseconds in this case) is not responsive enough then unrolling the loop would be an excellent choice for making it more responsive.

GoForSmoke:
But come to think of it, Serial services are only run when void loop() ends, before it restarts? Is that only serial out, it prints between loops? IIRC 115200 baud takes 86.8 us to send a char, do analog reads make serial a bit more asynch?

Serial I/O is mostly interrupt driven. The only thing that happens between calls to 'loop()' is a call to serialEventRun() which checks for .available() on each of the hardware serial ports and, if so, calls serialEvent() if the user has declared that function.

The 2 reads isn't for double sampling. It is because of settling time when switching from one analog pin to another. The datasheet reccomends reading twice and only saving the second read.

If each read has sufficient process time before the next then switching the pin before running the process could give the ADC circuitry enough time to settle and not need 2 reads to get 1 good read.

As far as serial, the input buffer must be maintained by interrupt or many of the Example Sketches could not work. But AFAIK the output buffer gets sent inbetween the end of one loop() and the start of the next. There's even a function we can redefine but I forget the name.

Sure reading 16 in 3.2 ms is not humanly noticeable but whether that time happens in one pass through loop() or many is what I'm trying to point out as blocking or not and what that means to adding future features.

One thing I will point out, if 3.2 ms blocking is no problem then is there any problem reading and processing one sensor's data per pass then letting other non-analog-read tasks get a run knowing that might take 3.3-3.5 ms to do the whole set?

I tried to show how a sketch can be less blocking and stating why. It makes code that's easier to add to. Without blocking I don't interrupts for most things. With blocking I'm more likely to need interrupts just because polling won't be fast enough.

GoForSmoke:
The 2 reads isn't for double sampling. It is because of settling time when switching from one analog pin to another. The datasheet reccomends reading twice and only saving the second read.

I have not yet found that in the 328P datasheet (https://www.microchip.com/wwwproducts/en/ATmega328P). Could you tell me what section it is in? The closest text I could find is in 23.5.2 ADC Voltage Reference. It says "The first ADC conversion result after switching reference voltage source may be inaccurate, and the user is advised to discard this result." but that has to do with changing reference voltage, not changing input pin.

GoForSmoke:
But AFAIK the output buffer gets sent in between the end of one loop() and the start of the next. There's even a function we can redefine but I forget the name.

I assure you that, on the AVR-based Arduinos, the serial output is interrupt driven. I've read the core code that does it. Only if interrupts are disabled and the output buffer is full does Serial.write() poll the UART status.

It's easy enough to test:

void loop()
{
  Serial.println("If this text shows up, the output must happen before loop() ends.");
  while(1){}
}

The text shows up for me on my Arduino UNO so it can't be being held until loop() returns.