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;
}