Time spent in a function

Hello All,

I need some help grasping with the concept/example "several things happening at same time". Let's say I have multiple functions being called inside void loop(), one of the functions, is taking a temperature reading every 30 seconds. Because I am using the Nano ESP32, the ADC sucks and so I am using a digital smoothing filter (averaged out over 8 to 16 readings)...From my testing, it looks like the processor/MCU is only spending micro/milli seconds in that function and only passing through that function 1 once every 30 seconds.

And so therefore, the smoothing digital filter will take very long to gather enough readings to "average" out the noise properly.. And kind of useless? I should be taking readings as often/fast as possible instead of every 30seconds? Am i correct in this assumption?

OR

When a function is called... the processor passes hundred of thousands/millions of times through the function when its called every 30 seconds? and so the averaging/smoothing will work. I'm struggling with processor speed and what that means I think?? LOL, i'm sorry

int LED = 13;
int tempProbe = A0;
unsigned long lastPass = 0;
unsigned long startTemp = 0;
unsigned long elapsedTemp = 0;
unsigned int counterTemp = 0;
int reading;
unsigned int filteredReading;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  Serial.println("Hello, Nano ESP32!");
  pinMode(LED, OUTPUT);
  pinMode(tempProbe, INPUT);
}

void loop() {
 
  ledFlash();
  
  if (millis() - lastPass >= 30000) {
    tempReading();
    lastPass = millis();
  }
  
}

void tempReading() {
  startTemp = micros();
  reading = analogRead(tempProbe); 
  reading = analogRead(tempProbe);
  filteredReading = ( filteredReading*15 + reading)/16;
  counterTemp ++;
  elapsedTemp = micros() - startTemp;
  Serial.println(filteredReading);
  Serial.println(elapsedTemp);
  Serial.println(counterTemp);
}

void ledFlash() {
  digitalWrite(LED, !digitalRead(LED));
  delay(1000);
}

This code is just a simple example i put together for my testing and curiosity.

Thanks

No, only once, every 30 seconds in your code.

No, it executes once.

And you are on the right track with respect to getting smoothed or average values, it would be better maybe to call the function more frequently.

Use the same timing mechanism but just make it happen more often.

For averaging or smoothing, I suggest taking a look at "leaky integration", a form of low pass filtering that can be quite easy to implement and in the right circumstances be just the ticket.

I'll add a link here in a minute.

a7

Depends on what you are measuring the temperature of. Generally, ambient temperatures only change very slowly, so little chance of missing a sudden change.

Here's a post in a thread that may be of interest: simulation, post and the hole thread maybe.

HTH

a7

That is correct

How often it is called depends on the value of the period that you are waiting before calling the function again, ie 30000 milliseconds in your code

Reduce that number, or better still make it a variable or a #define, to increase the frequency with which it is called

Having said that, because of the delay() in your ledFlash() function the maximum rate that you could currently call tempReading() would be once per second. You could even remove the check on elapsed time and call tempReading() every time through loop(), ie once per second as currently written

These are all great answers, thank you so much for the quick help.

@alto777 Thank you for the link. I will play with this over the holidays, if the kids allow me some down time :wink:

@PaulRB This will be for measuring hottub water, which as you've stated, changes very slowly BUT, the ADC on this ESP32 has wild swings! and filtering seems to be helping.

@UKHeliBob I am of course no expert and did not realize a delay() in a function as such would still cause the loop() to stall. BUT it makes sense now that I think of it and look at it properly. I never use delay() in my programs for house projects BUT this was a helpful comment that taught me.

Without playing with this "leaky integration" yet.. One thing comes to mind.... it may be of help to also scrap/throw away, min. and max outliers when calculating the average? some more thinking to do.

Thanks again all for helping me to understand this concept...

Matt

I've heard that the ESP32 ADC is not great, in that it is not very linear, and of course all ADC have some random noise. But "wild swings" should not be happening. You may have a circuit fault or some interference. Describe the circuit and environment in more detail and perhaps we can suggest possible causes.

You can literally make a career out of it.

There's a piece of code floating around that takes ten readings, sorts them so it can throw out the one (or was it two) highest and lowest among them, then straight up averages the remaining samples.

I'm sure the author was very proud, and it probably works, as in it does exactly as I described. I can't say if it is worth the trouble, every case is special in some way. You may want to keep an open mind and look into a few different tricks ppl have come up with.

Found it:

for(int i=0;i<10;i++)       //Get 10 sample value from the sensor for smooth the value
  { 
    buf[i]=analogRead(SensorPin);
    delay(10);
  }
  for(int i=0;i<9;i++)        //sort the analog from small to large
  {
    for(int j=i+1;j<10;j++)
    {
      if(buf[i]>buf[j])
      {
        temp=buf[i];
        buf[i]=buf[j];
        buf[j]=temp;
      }
    }
  }
  avgValue=0;
  for(int i=2;i<8;i++)                      //take the average value of 6 center sample
    avgValue+=buf[i];
  float phValue=(float)avgValue/1024/6; //convert the analog into millivolt
  phValue=3.5*phValue;  //convert the millivolt into pH value
  

It uses delay() to take ten samples in 0.1 seconds. It could be written to not block and accomplish the same result. I only post it because I marvel at it when it pops up.

But I think others have made the point. The water temperature doesn't need to be measured very often, and property implemented there should be no wild or spurious readings.

a7

So sorry! I should have used a better word than “wild”… it isn't that bad BUT far worse than I am used to from other projects and Arduino boards, Adafruit ADC breakouts like the ADSxxxx…

I will admit, this is my first time measuring water temps at volumes like this and maybe this is all normal??… when observing the temperature read out, it is not uncommon to see the water temp “swing” +/- 0.25 - 0.50 of a degree Fahrenheit from the average real temp. Maybe this is normal for 2000 litres of water to fluctuate this much do to convection and placement of sensor (which is on the wall of the tub)? These swings can happen when heating the water or when just sitting there not being heated...

I wish i was at home,,, I do have hand drawings of what I did. Taking pictures of what I have would also help but not feasible at this point as the hottub is buried in snow :grimacing:

There is not much room in a hot tub for wiring and interconnections and one source of interference is possibly due to the fact the Nano ESP32 board is in close proximity (8inches) to the power conductors. The power conductors do supply 240Volts @ 30 amps @ 60Hz (when heating)?? I would imagine that would be quite an magnetic field ?? I do not know if this interferes with ADC inputs though?

Anyways, I really do appreciate the help and do not want to come off as rude BUT feel like I am using the original topic to now ask for help with a complete separate topic. That was not my intention and the project as is, is satisfactory... and the original question was answered so kindly

You see now that functions, when called, execute to completion just once, then return. Of course a function could all by itself use a loop of some kind and do thousands of things.

In the non-blocking "two things at once" world it is desirable to have functions that execute quickly, so everyone gets a crack at the precious resource which is processor time. And everything can indeed seem to be happening at the same time. Humans are so sluggish...

But your misconception may have been borne (or born) of some reading you did.

There are two places to control how often a function executes.

In the first place, as in your loop() calls to tempReading(), it is the caller that decides to do that every 30000 milliseconds. tempReading() does its thing when called.

In the second place, the caller, loop() in this case, would just call tempReading(), and that called function would be in control of whether or not to take a reading. Since it could be called thouasnds of times a second, usually it would be deciding that the time had not arrived.

The timing pattern is identical:

void tempReading() {
  if (millis() - lastPass >= 30000) {
    lastPass = millis();

    reading = analogRead(tempProbe); 
    reading = analogRead(tempProbe);
    filteredReading = ( filteredReading*15 + reading)/16;
  }
}

and that limit is removed in the loop(), it just calls tempReading() frequently.

I usually turn that kind of test inside out:

void tempReading() {
  if (millis() - lastPass < 30000)   // time to?
    return;                          // nope, we outta here

  lastPass = millis();

  reading = analogRead(tempProbe); 
  reading = analogRead(tempProbe);
  filteredReading = ( filteredReading*15 + reading)/16;
}

In a tiny sketch it makes little difference where the timing is done. When you get to something even a bit more complicate, the loop() might be calling many such timed functions. If you do the timing in the loop(), it can get cluttered fast and impede reading, viz:

void loop() {
{
  if (millis() - lastPass1 >= 50) {
    checkTheSwitches();
    lastPass1 = millis();
  }

  if (millis() - lastPass2 >= 1000) {
    readTheSensors();
    lastPass2 = millis();
  } 

 if (millis() - lastPass3 >= 28800) {
    feedTheCat();
    lastPass3 = millis();
  }
}

This contrived example might be better if the timing were done in the called functions:

void loop() {
{
    checkTheSwitches();
    readTheSensors();
    feedTheCat();
}

where most of the time all three functions just return without having needed to do anything. Each function would manage its own lastPass timer, which could be hidden from other (mis)usage.

When (not if) you get to using finite state machines you will see it is very common to just keep giving lower level functions an opportunity to advance whatever process they have been desinged to handle, at a pace you can adjust for each of them.

The first thing I look at in a sketch posted here is the loop() function. Then I check to see how delay() is used and lastly for the use of millis(). If I can read the loop and make sense of it, and there are no uses of delay() and only one or two calls to millis() I already know something about the code and the writer.


After all this, I noticed

  filteredReading = ( filteredReading*15 + reading)/16;

which is exactly a - wait for it - leaky integrator! Your filteredReading will be a low pass filtered version of your raw signal. So whoever wrote that meant for that function to be called regularly, and for filteredValue to be the one that informs the logic of you process.

a7

It shouldn't be far worse. Maybe slightly worse, in ways you would only notice by comparing the readings of the ESP32's ADC with other Arduino like Uno or external ADC while using the same sensors in the same situation at the same moment.

Generally I would expect the larger the volume of water, the more stable the temperature would be. But I guess in a hot tub, you do have pumps and heaters running that would cause streams of warmer or cooler water to form, and they would move about randomly, causing genuine temperature fluctuations at the sensor, at least until some time has passed and the whole body of water has reached a stable temperature.

So maybe the temperature fluctuations you experienced are genuine and not really due to the ESP32's slightly poorer ADC.

Taking a reading every second over 5 minutes to get 300 readings should give a reasonably steady and consistent average reading, no matter what algorithm you use to take that average.

A sidenote: I assume you use the measurement to switch a heater on and off. In your example you measure every 30 seconds, followed by a low-pass filter with a time constant of 16 times that period. I think it is unlikely that after switching on the heater it will switch off within 16 x 30 seconds = 8 minutes. Are you sure the water won’t reach uncomfortable temperatures in that time?
In general: the faster you switch, the more wear on the relays. Slower switching means greater swings in temperature.
It’s a balancing act to find the best timescale.

You knew it was not me who came up with this!? How?? LOL I Acquired it through my readings here as the years pass and the lovely people who help on this forum. (JR Remington and Rob Tillart I believe i owe for this).

The “filteredReading” is definitely what informs the logic.

@stitech i think you are right that it would possibly overshoot and have terrible temp management @30seconds polling. The code i posted was an arbitrary example for me to get clarity on how function calls work. I have no idea why i decided to use 30secs? :man_shrugging:t2:

The timing of the measurements and adjustments should be on the order of 10x faster than the time it takes the heater to react. If the heater reacts too fast compared to the measurements, you'll get wide swings, and you could slow the response down with a lower-power heater.

A control system should balance between the measurements and the system response, and there really isn't enough data to say what's really going on. Take some data to figure out how fast things change:

If you turn the hot tub on high, it may take more than 5 minutes to make a significant change. Take enough data to see how the system reacts.

I could see the raw readings easily bouncing around 0.25° depending on the probe, and how the probe is positioned relative to the surface, any air jets, and the turbulence in the tub due to the jets. The 15:1 smoothing/lowpass filtered data should be much less noisy than the raw data.

If you turned the heaters on 100%, how hot would it get in 10-20 minutes?

Op might also calculate the response of his system...

P/(Rho*Cp*V) 

will give a temperature rise per second.
If you want overshoot to be less than 1 degC, you can calculate the max on time if the heater.
If that is 1 s. Your heater is too big (or your pool is too small), if it is two days, it is too small...

Thanks for the comments, There is no proportional control on this, just old school ON and OFF. when a call for heat is initialized, Depending on ambient outside air conditions, It takes anywhere from 25-35minutes to raise roughly 2000 litres 2degrees Fahrenheit @ 5.5kW heater input. if i turn the pump on Hi speed during heating, the heat transfer is much quicker and this 2degree rise happens in 15-20 minutes instead.

The water temp usually falls back down the 2degrees in a very consistent ~6hours before another call for heat is initiated.

This of course is all based on ambient air where I live at the current moment … sitting around 0 celsius, we get -30celsius and +30celsius through the year and these numbers will change :wink::face_with_spiral_eyes:

You can do proportional control by using slow PWM, for example perhaps over a 5 or 10 minute period. When the water is well below the desired temperature the heater is on most or all the time. Close to the desired temperature the heater is on for a short time then off for a longer time.

Chances are that when the heater is ON the temperature is not uniform across the container. You may want to stir the water, use more than one temp sensor and get an average, or slow down the heating using PWM as @PerryBebbington suggests…

The intention was to have proportional control but winter was coming fast up here when i rebuilt the controller a month or so ago and just needed to get it done. LOL. I have no idea how I will pursue proportional next year (if at all) but imagine some sort of SSR , that can accept PWM or 4-20mA as an input reference??