Code Issue: Is my averaging function acting on the right data?

Running into a problem with a new version of my code. I’ve included it all below, but I’m fairly certain it has to do with the function “thermistor_temp_calc”.

I have a number of thermistors in my system that I take readings from (see pool_temp and heat_temp functions). They store multiple pin voltages in an array and then pass the array pointer to this function to average the readings and convert the readings into a temperature and pass it back.

I fixed one bug with this function by adding the size of the array (# of samples) to the arguments (previously, it was incorrectly always assuming it was always the same value).

On the right side of the graph, you can see the old code results (prior to breaking the task of averaging and calculating into a separate function). In the middle (between 5/29 12:00 and 5/28 12:00) was due to the function not knowing the correct length of the array and presumably adding erroneous data to the average. After fixing that error, however, I’m still seeing some pretty erratic readings, as well as readings that are 2 degrees higher than they should be. I’m wondering if my averaging function is still somehow getting erroneous data into it that’s throwing my averages off.

Original code is essentially the same as this: https://www.hackster.io/gusgonnet/pool-temperature-monitor-5331f2

My Code:

//**************************************************************************************
// Author: Eric Spaeth
// Contact: Eric.Spaeth@gmail.com
//**************************************************************************************

int _version = 0.0;

#include <math.h>
#include "application.h"

// LOW TEMP:   https://www.amazon.com/gp/product/B01MZ6Y336
// HIGH TEMP:  https://www.amazon.com/gp/product/B00TGQDHPY
// ULTRASONIC: https://www.amazon.com/gp/product/B01MU0XG51

// the nominal resistance of the thermistor
#define lowT_resNOMINAL 10000
#define hiT_resNOMINAL 100000
// temp. for nominal resistance (almost always 25 C)
#define lowT_tempNOMINAL 25
#define hiT_tempNOMINAL 25
// The beta coefficient of the thermistor (usually 3000-4000)
#define lowT_bCO 3950
#define hiT_bCO 3950
// the value of the 'other' resistor
#define lowT_RESISTOR 10000
#define hiT_RESISTOR 100000
// how many samples to take and average, more takes longer
// but measurement is 'smoother'
#define tempSAMPLES 250
#define heatSAMPLES 50
#define surfSAMPLES 10

//measure the temperature every POOL_READ_INTERVAL msec
#define POOL_READ_INTERVAL 60000
#define HEAT_READ_INTERVAL 1000

#define HEAT_THRESHOLD 250  // Temperature at which heater is "on"
#define HEAT_WINDOW 600000  // Window over which to calculate heater on % 10min=600,000ms
const int heatDutySAMPLES = HEAT_WINDOW/HEAT_READ_INTERVAL;
int heatSampleINDEX = 0;   // keeps track of array placement of next sample for rolling average
int heatSampleARRAY[heatDutySAMPLES]={0};   // Storage array for whether heater is on/off at a given time
int heatDUTY;               // final output of heater duty cycle in #on/1000

unsigned long pool_interval = 0;
unsigned long heat_interval = 0;
int samples_0[tempSAMPLES];
int samples_1[tempSAMPLES];
int samples_2[tempSAMPLES];
int samples_3[heatSAMPLES];
int samples_4[surfSAMPLES];
int heat_DUTY[heatDutySAMPLES];

int lowT_THERMISTOR_0 = A0; // Pool Water, Sensor 0
int lowT_THERMISTOR_1 = A1; // Pool Water, Sensor 1
int lowT_THERMISTOR_2 = A2; // Pool Room Air, Sensor 2
int hiT_THERMISTOR_3 = A3;  // Heater Exhaust, Senosr 3

float surf_distance[surfSAMPLES];   // distance of pool surface to sensor
float maxFILL = 0;                  // distance from Ultrasonic Sensor to pool max fill line [mm]
int pool_ULTRASONIC_4 = A4;         // Ultrasonic Pool Level, Sensor 4
int pool_ULTRA_TRIG_0 = D0;         // Ultrasonic Pool, Trigger Pin 0

//this is coming from http://www.instructables.com/id/Datalogging-with-Spark-Core-Google-Drive/?ALLSTEPS
char pool_temperature_str0[64]; //String to store the sensor data - Pool Water Sensor 1
char pool_temperature_str1[64]; //String to store the sensor data - Pool Water Sensor 2
char pool_temperature_str2[64]; //String to store the sensor data - Pool Room Air Sensor 1
char pool_temperature_str3[64]; //String to store the sensor data - Heater Exhaust Sensor 1
char pool_depth_str4[64]; //String to store the sensor data - Ultrasonic Pool Level Sensor 1
char heat_duty_str5[64];  // String to sore the heater duty cycle
char pool_temperature_ifttt[64];



void setup() {

    STARTUP(WiFi.selectAntenna(ANT_EXTERNAL));
    Particle.publish("device starting", "Version: " + String(_version), 60, PRIVATE);

    pool_interval = 0;
    heat_interval = 0;
    Particle.function("status", status);
    pinMode(lowT_THERMISTOR_0, INPUT);
    pinMode(lowT_THERMISTOR_1, INPUT);
    pinMode(lowT_THERMISTOR_2, INPUT);
    pinMode(hiT_THERMISTOR_3,  INPUT);
    pinMode(pool_ULTRASONIC_4, INPUT);
    pinMode(pool_ULTRA_TRIG_0, OUTPUT);
    //google sheets will get this variable
    //the name of this varriable CANNOT be longer than 12 characters
    //https://docs.particle.io/reference/firmware/photon/#particle-variable-
    Particle.variable("pool_tmp0", pool_temperature_str0, STRING);
    Particle.variable("pool_tmp1", pool_temperature_str1, STRING);
    Particle.variable("pool_tmp2", pool_temperature_str2, STRING);
    Particle.variable("pool_tmp3", pool_temperature_str3, STRING);
    Particle.variable("pool_dpth0",pool_depth_str4, STRING);
    Particle.variable("heat_duty0",heat_duty_str5, STRING);

}

void loop() {
    //measure the temperature and depth right away after a start and every POOL_READ_INTERVAL msec after that
    
    if( (millis() - pool_interval >= POOL_READ_INTERVAL) or (pool_interval==0) ) {
        pool_temp();
        //pool_depth();
        pool_interval = millis();
        }
    if( (millis() - heat_interval >= HEAT_READ_INTERVAL) or (heat_interval==0) ) {
        heat_temp();
        heat_interval = millis();
        
        heatSampleINDEX++;
        if(heatSampleINDEX==heatDutySAMPLES){
            heatSampleINDEX = 0;
        }
    }
    

}

/*******************************************************************************
 * Function Name  : status
 * Description    : this function gets called for the sake of pushing the temperature to your phone
 * Return         : 0
 *******************************************************************************/
int status(String args)
{
 //this triggers a recipe in IFTTT
 Particle.publish("pool_temp", pool_temperature_ifttt, 60, PRIVATE);
 return 0;
}

/*******************************************************************************
 * Function Name  : pool_temp_calc
 * Description    : averages voltage array from a thermistor pin to a temperature in F
 * Return         : temperature (F)
 *******************************************************************************/
float thermistor_temp_calc(int *sample_array, int size, bool type)
{
    int i=0;
    int resNOM  = 0;
    int tempNOM = 0;
    int bCO     = 0;
    int resist  = 0;
    // if bool is 0, lowT thermistor, else hiT thermistor
    if (type=0){
        resNOM  = lowT_resNOMINAL;
        tempNOM = lowT_tempNOMINAL;
        bCO     = lowT_bCO;
        resist  = lowT_RESISTOR;
    }else{
        resNOM  = hiT_resNOMINAL;
        tempNOM = hiT_tempNOMINAL;
        bCO     = hiT_bCO;
        resist  = hiT_RESISTOR;
    }
    // average the array of voltage readings to a single value
    float average = 0;
    for (i=0; i< size; i++) {
        average += sample_array[i];
    }
    average /= size;
    
    // convert the averaged value to resistance
    average = resist / ((4095 / average)  - 1);
    
    // calculate the Temperature and convert to F
    float steinhart;
    steinhart = log(average / resNOM);          // ln(R/Ro)
    steinhart /= bCO;                           // 1/B * ln(R/Ro)
    steinhart += 1.0 / (tempNOM + 273.15);      // + (1/To)
    steinhart = (1.0 / steinhart) - 273.15;     // Invert, convert to C
    steinhart = (steinhart * 9.0)/ 5.0 + 32.0;  // convert to F
    return steinhart;
}

int pool_depth()
{
    int i;
    for (i=0; i< surfSAMPLES; i++){
        // Send signal to JSN_SR04T to take a reading
        digitalWrite(pool_ULTRA_TRIG_0,LOW);
        delayMicroseconds(2);
        digitalWrite(pool_ULTRA_TRIG_0, HIGH);
        delayMicroseconds(10);
        digitalWrite(pool_ULTRA_TRIG_0,LOW);
        
        float soundSPEED = 345970;   // Speed of Sound at 75°F in [mm/s]
        float pulse_time=pulseIn(pool_ULTRASONIC_4, HIGH);   // total reflection time of soundwave
        pulse_time /= 2;            // time for sound to hit the surface (half total time)
        pulse_time /= 1e6;          // convert from microseconds to seconds
        surf_distance[i] = pulse_time*soundSPEED;   // convert time to distance
        delay (100);
    }
    
    float poolFILL = 0;
    for (i=0; i< surfSAMPLES; i++){
        poolFILL += surf_distance[i];
    }
    poolFILL /= surfSAMPLES;
    poolFILL -= maxFILL;
    
    char poolFillChar0[32];
    sprintf(poolFillChar0,"%0d", (int)poolFILL);
    sprintf(pool_depth_str4, "%s", poolFillChar0);
    return 0;
}

/*******************************************************************************
 * Function Name  : pool_temp
 * Description    : read the value of the thermistor, convert it to degrees and store it in pool_temperature_str
 * Return         : 0
 *******************************************************************************/
int pool_temp()
{
    unsigned short i=0;

    // take N samples in a row, with a slight delay
    for (i=0; i< tempSAMPLES; i++) {
        samples_0[i] = analogRead(lowT_THERMISTOR_0);
        samples_1[i] = analogRead(lowT_THERMISTOR_1);
        samples_2[i] = analogRead(lowT_THERMISTOR_2);
        delay(50);
    }
    
    // // average all the samples out
    // float temp_0=0;
    // for (i=0; i< tempSAMPLES; i++) {
    //     temp_0 += samples_0[i];
    // }
    // temp_0 /= tempSAMPLES;
    
    // float temp_1 = 0;
    // for (i=0; i< tempSAMPLES; i++) {
    //     temp_1 += samples_1[i];
    // }
    // temp_1 /= tempSAMPLES;
    
    // float temp_2 = 0;
    // for (i=0; i< tempSAMPLES; i++) {
    //     temp_2 += samples_2[i];
    // }
    // temp_2 /= tempSAMPLES;
    
    // Send voltage samples to function to average and convert to temp in F
    float temp_0;
    temp_0 = thermistor_temp_calc(samples_0,tempSAMPLES,0);
    float temp_1;
    temp_1 = thermistor_temp_calc(samples_1,tempSAMPLES,0);
    float temp_2;
    temp_2 = thermistor_temp_calc(samples_2,tempSAMPLES,0);

    // get decimal values for string conversion

    char ascii[32];
    int temp_0_dec = (temp_0 - (int)temp_0) * 100;
    int temp_1_dec = (temp_1 - (int)temp_1) * 100;
    int temp_2_dec = (temp_2 - (int)temp_2) * 100;
    
    // clean decimal data for negative temperatures
    temp_0_dec = abs(temp_0_dec);
    temp_1_dec = abs(temp_1_dec);
    temp_2_dec = abs(temp_2_dec);
    
    sprintf(ascii,"%0d.%d", (int)temp_0, temp_0_dec);
    Particle.publish("pool_temp_dashboard", ascii, 60, PRIVATE);

    char tempInChar0[32];
    sprintf(tempInChar0,"%0d.%d", (int)temp_0, temp_0_dec);
    
    char tempInChar1[32];
    sprintf(tempInChar1,"%0d.%d", (int)temp_1, temp_1_dec);
    
    char tempInChar2[32];
    sprintf(tempInChar2,"%0d.%d", (int)temp_2, temp_2_dec);
    

    //Write temperature to string, google sheets will get this variable
    sprintf(pool_temperature_str0, "%s", tempInChar0);
    sprintf(pool_temperature_str1, "%s", tempInChar1);
    sprintf(pool_temperature_str2, "%s", tempInChar2);

    //this variable will be published by function status()
    sprintf(pool_temperature_ifttt, "%s", tempInChar0);

    return 0;
}

int heat_temp()
{
    unsigned short i=0;
    
    // take N samples in a row, with a slight delay
    for (i=0; i< heatSAMPLES; i++) {
        samples_3[i] = analogRead(hiT_THERMISTOR_3);
        delay(10);
    }
    
    // float temp_3=0;
    // for (i=0; i< heatSAMPLES; i++) {
    //     temp_3 += samples_3[i];
    // }
    // temp_3 /= heatSAMPLES;

    // Send voltage samples to function to average and convert to temp in F
    float temp_3;
    temp_3 = thermistor_temp_calc(samples_3,heatSAMPLES,1);
    
    // get decimal values for string conversion

    char ascii[32];
    int temp_3_dec = (temp_3 - (int)temp_3) * 100;
    
    // clean decimal data for negative temperatures
    temp_3_dec = abs(temp_3_dec);

    char tempInChar3[32];
    sprintf(tempInChar3,"%0d.%d", (int)temp_3, temp_3_dec);
    
    //Write temperature to string, google sheets will get this variable
    sprintf(pool_temperature_str3, "%s", tempInChar3);
    
    int heatSTATE = 0;
    if(temp_3>HEAT_THRESHOLD){
        heatSTATE = 1;
    }
    
    heatSampleARRAY[heatSampleINDEX] = heatSTATE;
    
    // Calculate the # of samples heater is on.
    heatDUTY=0;
    for (i=0; i< heatDutySAMPLES; i++) {
        heatDUTY += heatSampleARRAY[i];
    }
    
    // Convert to #/1000 (2 decimal percent with no decimal) [(sum/samples)*100*100 = sum*100*100/samples]
    heatDUTY*=10000;
    heatDUTY/=heatDutySAMPLES;
    
    char heatDutyChar0[32];
    sprintf(heatDutyChar0,"%0d", (int)heatDUTY);
    sprintf(heat_duty_str5, "%s", heatDutyChar0);
    return 0;
}

Here’s my fritzing circuit diagram, too. The left thermistor (purple leads) is the high temp thermistor (100k) in the exhaust stream. The other 3 are waterproof thermistors (10k), 2 in the pool water, 1 in the pool room air. Ignore the ultrasonic rangefinder. It’s not active or installed yet.

@eric.spaeth, quick note. This line needs to be OUTSIDE of any function, ideally after your #include statements:

STARTUP(WiFi.selectAntenna(ANT_EXTERNAL));

One problem I see in thermistor_temp_calc() is that you declare type as a boolean but (tried to) test against zero. Ideally, you test against true or false. This statement:

    if (type=0){

will SET type to zero and always be true! I suspect you wanted the line to be:

    if (!type) {   // if type is false

Something else you can do is define a two dimensional array for your lowT and hiT values and use type (which you would change to an int) as an index into the array.

Re: Antenna startup macro
I had tried that originally, but as the first code line and compiling was giving me errors. After the #includes works fine. Interestingly enough though, my external antenna is functioning already, but I’ve moved it as suggested

Re: boolean
That’s a good catch, and I like the array solution as it would make it flexible if I add more thermistors ever.

Uploaded new code now and should have indication if it’s fixed in an hour or so.

relevant new code segments:

// Index 0: lowT thermistor, Index 1: hiT thermistor
const int resNOMINAL[]={10000,100000};
const int tempNOMINAL[]={25,25};
const int therm_bCO[]={3950,3950};
const int seriesRESISTOR[]={10000,100000};
.....
float thermistor_temp_calc(int *sample_array, int size, int type)
{
    int i=0;
    int resNOM  = 0;
    int tempNOM = 0;
    int bCO     = 0;
    int resist  = 0;
    
    resNOM  = resNOMINAL[type];
    tempNOM = tempNOMINAL[type];
    bCO     = therm_bCO[type];
    resist  = seriesRESISTOR[type];
    
    // if bool is 0, lowT thermistor, else hiT thermistor
    // if (type==0){
    //     resNOM  = lowT_resNOMINAL;
    //     tempNOM = lowT_tempNOMINAL;
    //     bCO     = lowT_bCO;
    //     resist  = lowT_RESISTOR;
    // }else{
    //     resNOM  = hiT_resNOMINAL;
    //     tempNOM = hiT_tempNOMINAL;
    //     bCO     = hiT_bCO;
    //     resist  = hiT_RESISTOR;
    // }
    
    // average the array of voltage readings to a single value
    float average = 0;
    for (i=0; i< size; i++) {
        average += sample_array[i];
    }
    average /= size;
    
    // convert the averaged value to resistance
    average = resist / ((4095 / average)  - 1);
    
    // calculate the Temperature and convert to F
    float steinhart;
    steinhart = log(average / resNOM);          // ln(R/Ro)
    steinhart /= bCO;                           // 1/B * ln(R/Ro)
    steinhart += 1.0 / (tempNOM + 273.15);      // + (1/To)
    steinhart = (1.0 / steinhart) - 273.15;     // Invert, convert to C
    steinhart = (steinhart * 9.0)/ 5.0 + 32.0;  // convert to F
    return steinhart;
}

That's because antenna settings are "sticky" so you only need to set it once. However, the same applies to resetting to INTERNAL!

You could also use an array of structs for your values, allowing you to ditch your local variables.

If I had any idea what that meant. :wink:

Update on the data acquisition: it's looking much smoother and more like what is expected. Thanks for your help!

// declare the structure with the thermistor parameters
struct therms {
    int resNOMINAL;
    int tempNOMINAL;
    int therm_bCO;
    int seriesRESISTOR;
};

// create an array of those structures
therms thermistors[2];

// initialize the struct for each array member (thermistor type)
thermistors[0] = {10000, 25, 3950, 10000};  // lowT
thermistors[1] = {100000, 25, 3950, 100000};  // hiT

then in your code:

    // convert the averaged value to resistance
    average = thermistors[type].seriesRESISTOR / ((4095 / average)  - 1);
    
    // calculate the Temperature and convert to F
    float steinhart;
    steinhart = log(average / thermistors[type].resNOM);    // ln(R/Ro)
    steinhart /= thermistors[type].therm_bCO;               // 1/B * ln(R/Ro)
    steinhart += 1.0 / (thermistors[type].resNOMINAL+ 273.15);  // + (1/To)
    steinhart = (1.0 / steinhart) - 273.15;     // Invert, convert to C
    steinhart = (steinhart * 9.0)/ 5.0 + 32.0;  // convert to F

:wink:

3 Likes

I get the following error messages when attempting to use this code:

poolcontrolv0.ino:29:1: error: 'thermistors' does not name a type
 thermistors[0] = {10000, 25, 3950, 10000};  // lowT
 ^
poolcontrolv0.ino:30:1: error: 'thermistors' does not name a type
 thermistors[1] = {100000, 25, 3950, 100000};  // hiT
 ^
poolcontrolv0.ino:178:15: error: 'resNOMINAL' was not declared in this scope
     resNOM  = resNOMINAL[type];

These lines,

thermistors[0] = {10000, 25, 3950, 10000};  // lowT
thermistors[1] = {100000, 25, 3950, 100000};  // hiT

should be in setup() if you define thermistors like @peekay123 showed .

Alternately, you could define thermistors like this,

therms thermistors[] = {{10000, 25, 3950, 10000}, {100000, 25, 3950, 100000}};
3 Likes