Reading the value of a sensor every X millis (best practise?)

Hi all,

looking at the huge amount of examples to read and present the value of a sensor every X milliseconds, I've found that 99% of the time the suggested code is something like:

void loop() {
   [...]
   int valueA = readValue(sensorA);
   [...]
   delay(1000); //let's say we want to read the sensor every one second (1.000 millis)
}

This is a quick'n'dirty solution that is fine if we want to test the sensor but for sure it doesn't match the requirement. A second scenario (the missing 1% of the examples) takes in consideration the duration of the cycle itself:

void loop(){
   _startTime = micros();
   [...]
   int valueA = readValue(sensorA);
   [...]
   _endTime = micros()
   delay(1000-(_endTime -_startTime));
}

That sounds better but we can see at least two issues here:

  1. using delayMicroseconds() function could be a problem in case we need to manage interrupts;
  2. often the time we spend doing nothing (delay) is greater than the time we actually do something. This is not a real 'problem' but we could use that time to do something useful.

To solve the first problem we can manage the 'delay' in another way:

[...]
#define CYCLEDURATION 1000000  // 1.000.000 micros = 1 second
[...]
void loop(){
   unsigned int _currentMicros = micros();
   if(_currentMicros - _previousMicros > CYCLEDURATION) {

}
}

You won't find other solutions on this forum than using micros() or millis() :wink: Our famous 'Blink without delay' example - proper way to do 'dalays' .

Sorry for the uncompleted post (accidentally pressed TAB): here the second part :sweat_smile:

From the last scenario:

[...]
#define CYCLEDURATION 1000000  // 1.000.000 micros = 1 second
[...]
void loop(){
   unsigned int _currentMicros = micros();
   if(_currentMicros - _previousMicros > CYCLEDURATION) {
      _previousMicros = _currentMicros;
      [...]
      int valueA = read(sensorsA);
      [...]
   }
}

Again even here we are actually loosing time doing nothing. In this particular case we could use that time to calculate a more accurate value of the sensor using (for instance) the average of a certain number of samples:

[...]
#define CYCLEDURATION 1000000  // 1.000.000 micros = 1 second
[...]
int _sumValueA = 0;
unsigned long _samplesNumber = 0;
unsigned int _previousMicros = micros();  //Added to be sure we will not enter the IF (we want at least a sample of the sensor)
void loop(){
   unsigned int _currentMicros = micros();
   if(_currentMicros - _previousMicros > CYCLEDURATION) {
      _previousMicros = _currentMicros;
      [...]
      int valueA = _sumValueA/_samplesNumber;
      _sumValueA = 0;
      _samplesNumber = 0;
      [...]
   } else {
      _sumValueA += read(sensorsA);
      _samplesNumber++;
   }
}

In my project, I'd like to read the value of a set of sensors and present them every one second.
The time I spend in the IF statement is about 300-350 milliseconds so I can spend the rest of the second collecting samples from the sensors. Every time I enter the ELSE statement it takes something like 30 milliseconds so at the end of the second I can provide a set of values based on 22 samples (average value).

As you can see looking at the IF condition, we could exceed the CYCLEDURATION to collect the last sample from the sensors, in my case the average value is 15ms. We could eliminate this (in case we really need to be as much accurate as the Arduino can be) just adding another IF statement based on the average time used to collect samples in order to catch the moment when the last sample is taken and call delay() function only in that case.

@waski: yes, "blink without delay" is fine in case of executing an action every x millis, but I was trying to use the rest of the time collecting samples from the sensor in order to have a more stable value. This way (consider the second post) I can collect as much samples I can.

Your missing the point. you use "blink without" to decide when to print the valuse and do what ever you want with the rest of the time.

Mark

Just to be clear (sorry if I was not before), the post would be focused on "best practise to read a sensor's value every x milliseconds" not on "best practise to run code every x milliseconds".
I'm not missing the point: I agree with you that the IF statement is the same, but it is used to do something different.

In the 'blink without delay' code, the main action is defined into the IF branch and we don't have any ELSE branch because the target is to run a piece of code once every X micros and we don't want to do anything else during the rest of the time - because we just want to make a led blinking.

In my code, I want to manage the value of a sensor (or set of sensors) and I'm trying to find a way to automatically collect as much samples as possible and then use a function to get a stable and reasonable value (in my case I used the average but we could use a more complex function). This must be done every X microseconds.

Blink without does NOT there to show how to blink a LED its there to show you how to do something every x mill seconds.!

Mark

don't have any ELSE branch because the target is to run a piece of code once every X micros and we don't want to do anything else during the rest of the time - because we just want to make a led blinking.

This is not true. In the rest of the code you can do whatever you want as fast as possible .

Look at this :

const int numReadings = 10;

int readings[numReadings];      // the readings from the analog input
int index = 0;                               // the index of the current reading
int total = 0;                             // the running total
int average = 0;                    // the average

int inputPin = A0;

void setup()
{
               
  // initialize all the readings to 0: 
  for (int thisReading = 0; thisReading < numReadings; thisReading++)
    readings[thisReading] = 0;          
}

void loop() {
  // subtract the last reading:
  total= total - readings[index];         
  // read from the sensor:  
  readings[index] = analogRead(inputPin); 
  // add the reading to the total:
  total= total + readings[index];       
  // advance to the next position in the array:  
  index = index + 1;                    

  // if we're at the end of the array...
  if (index >= numReadings)              
    // ...wrap around to the beginning: 
    index = 0;                           

  // calculate the average:
  average = total / numReadings;

Then, you can use "blink without" to print or use this averaged value . If you need smoother results - just increase the number of readings : numReadings .

In the 'blink without delay' code, the main action is defined into the IF branch and we don't have any ELSE branch

That's because an else is not needed. The logic is not to turn the LED on or off at the right time OR do something else, it is to to turn the LED on or off at the right time then ALWAYS do something else. Having said that, there could also be conditions on whether the something else happens, but that is independent of turning the LED on or off or leaving it in its current state.

I was trying to use the rest of the time

There is no "rest of the time".... that doesn't make sense. Seems you're thinking "delay" still, where yes you delay for say 1 second where the system sits and does nothing. But in the "without delay" case, that's not what happens.

This video may help,

max_on_air:
Sorry for the uncompleted post (accidentally pressed TAB): here the second part :sweat_smile:

::snip::

From the last scenario:

In my project, I'd like to read the value of a set of sensors and present them every one second.
The time I spend in the IF statement is about 300-350 milliseconds so I can spend the rest of the second collecting samples from the sensors. Every time I enter the ELSE statement it takes something like 30 milliseconds so at the end of the second I can provide a set of values based on 22 samples (average value).

There must be something wrong with your math. 300-350 milliseconds is an ETERNITY for a processor running at 16 MHz. You can run a couple of dozen lines of code in a few microseconds. A microsecond is a thousand milliseconds.

If your sampling interval is once per second, why are you having problems collecting data that fast? Are you saying you want to collect your readings many times a second, and then average and store them once a second? That should be doable as well, unless you need millisecond level timing.

As you can see looking at the IF condition, we could exceed the CYCLEDURATION to collect the last sample from the sensors, in my case the average value is 15ms. We could eliminate this (in case we really need to be as much accurate as the Arduino can be) just adding another IF statement based on the average time used to collect samples in order to catch the moment when the last sample is taken and call delay() function only in that case.
[/quote]

DuncanC:
A microsecond is a thousand milliseconds.

whoops

waski:
Then, you can use "blink without" to print or use this averaged value . If you need smoother results - just increase the number of readings : numReadings .

For sure your implementation will work in the most of the cases, but I see two problems if you collect the samples this way:

  1. the first times you run your code you will not have the same number of samples to be used in the average function, in other words the first N values (where N = numReadings-1) will not be accurate as the others - but this is a minor problem in case you can accept a sort of 'stabilization period'

  2. in case the value of your sensor changes really fast, you will have a real problem because you are making the average with the last N values that have been read several seconds before. This is not a real problem if you are measuring the temperature but it could be a problem if your sensor is measuring the space distance between it and an obstacle

DuncanC:
There must be something wrong with your math. 300-350 milliseconds is an ETERNITY for a processor running at 16 MHz. You can run a couple of dozen lines of code in a few microseconds.

Yeah, it'a an eternity to read a sensor value, but I'm doing also other things.
Actually I'm managing 5 measures, 4 outputs, a bidirectional communication with the upper layer (via the mailbox provided with the Arduino YUN) and other few things. I just wanted to give a reasonable example of the timing in a real working case :slight_smile:

max_on_air:

DuncanC:
There must be something wrong with your math. 300-350 milliseconds is an ETERNITY for a processor running at 16 MHz. You can run a couple of dozen lines of code in a few microseconds.

Yeah, it'a an eternity to read a sensor value, but I'm doing also other things.
Actually I'm managing 5 measures, 4 outputs, a bidirectional communication with the upper layer (via the mailbox provided with the Arduino YUN) and other few things. I just wanted to give a reasonable example of the timing in a real working case :slight_smile:

You need to do a better job of describing it then.

Describe the tasks you want performed and the sequence and timing for preforming those tasks. Be clear and descriptive. You know what you want to do, but we don't.

It sounds like you want to collect sensor data quite often, average that data, then send it to another device once a second? Is that other device the Linux chip on your YUN (The "upper layer").

DuncanC:

max_on_air:

DuncanC:
There must be something wrong with your math. 300-350 milliseconds is an ETERNITY for a processor running at 16 MHz. You can run a couple of dozen lines of code in a few microseconds.

Yeah, it'a an eternity to read a sensor value, but I'm doing also other things.
Actually I'm managing 5 measures, 4 outputs, a bidirectional communication with the upper layer (via the mailbox provided with the Arduino YUN) and other few things. I just wanted to give a reasonable example of the timing in a real working case :slight_smile:

You need to do a better job of describing it then.

Describe the tasks you want performed and the sequence and timing for preforming those tasks. Also indicate about how long each sub-task will take. Show the math you are using to determine the total amount of time needed for a full cycle

Be clear and descriptive. You know what you want to do, but we don't.

It sounds like you want to collect sensor data quite often, average that data, then send it to another device once a second? Is that other device the Linux chip on your YUN (The "upper layer").

1) the first times you run your code you will not have the same number of samples to be used in the average function, in other words the first N values (where N = numReadings-1) will not be accurate as the others - but this is a minor problem in case you can accept a sort of 'stabilization period'

That's why i suggested using "blink without" to print/use your averaged value. Lets say you want 10 samples. The whole loop() takes 5ms . So after 50ms, you'll have 10 samples ready to use/ print . Just use millis() to measure how long it takes for a loop() to complete, and make it print/use averaged value every ( numOfReadings * loop_time ) . Your code will work offcourse, but saying that with "blink without" you can't do other things "in background", is not true :slight_smile:

Im using this sort of approach to read Cozir CO2 sensor. Main loop() is verry long and full of "blocking code " and even that, 120 readings to average is done really fast.

I think we are landed on the same high level code now - or at least we are very close.
Anyway, I didn't say that "with "blink without" you can't do other things in background", I agree this is not true :slight_smile: