Skip to content

Commit

Permalink
Added 3rd order polynomial linear compensation
Browse files Browse the repository at this point in the history
Added optinal 3rd order polynomial linear compensation to PS_Voltage
class to help improve ADC linear response.
  • Loading branch information
ogiewon committed Sep 1, 2017
1 parent 50b879e commit 6965c28
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 6 deletions.
87 changes: 82 additions & 5 deletions Arduino/libraries/ST_Anything/PS_Voltage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// defaults for this sensor are based on the device used during testing.
//
// Create an instance of this class in your sketch's global variable section
// For Example: st::PS_Voltage sensor1("voltage1", 120, 0, PIN_VOLTAGE, 0, 1023, 0, 5);
// For Example: st::PS_Voltage sensor1("voltage1", 120, 0, PIN_VOLTAGE, 0, 1023, 0.0, 5.0);
//
// st::PS_Voltage() constructor requires the following arguments
// - String &name - REQUIRED - the name of the object - must match the Groovy ST_Anything DeviceType tile name
Expand All @@ -31,6 +31,32 @@
//
// filteredValue = (filterConstant/100 * currentValue) + ((1 - filterConstant/100) * filteredValue)
//
//----------------------------------------------------------------------------------------------------------------------------------------------
// st::PS_Voltage() has a second constructor which includes a 3rd order polynomial compensation algorithm.
//
// Create an instance of this class in your sketch's global variable section
// For Example: static st::PS_Voltage sensor5(F("voltage1"), 5, 1, PIN_VOLTAGE_1, -40, 140, 0, 4095, 20, 75, -0.000000025934, 0.0001049656215, 0.9032840665333, 204.642825355678);
//
// The following arguments all all REQUIRED in order to use the Compensation Algorithm.
// - String &name - REQUIRED - the name of the object - must match the Groovy ST_Anything DeviceType tile name
// - long interval - REQUIRED - the polling interval in seconds
// - long offset - REQUIRED - the polling interval offset in seconds - used to prevent all polling sensors from executing at the same time
// - byte pin - REQUIRED - the Arduino Pin to be used as an analog input
// - double s_l - REQUIRED - first argument of Arduino map(s_l,s_h,m_l,m_h) function to scale the output
// - double s_h - REQUIRED - second argument of Arduino map(s_l,s_h,m_l,m_h) function to scale the output
// - double m_l - REQUIRED - third argument of Arduino map(s_l,s_h,m_l,m_h) function to scale the output
// - double m_h - REQUIRED - fourth argument of Arduino map(s_l,s_h,m_l,m_h) function to scale the output
// - byte numSamples - REQUIRED - number of analog readings to average per scheduled reading of the analog input
// - byte filterConstant - REQUIRED - Value from 5% to 100% to determine how much filtering/averaging is performed 100 = none, 5 = maximum
// - double Coeff1 - REQUIRED - 3rd order polynomial coefficient #1
// - double Coeff2 - REQUIRED - 3rd order polynomial coefficient #2
// - double Coeff3 - REQUIRED - 3rd order polynomial coefficient #3
// - double Coeff4 - REQUIRED - 3rd order polynomial coefficient #4
//
// 3rd order Plynomial Compensation Algorithm (useful for correcting non-linear analog to digital converters)
//
// CompensatedValue = Coeff1 * rawAnalogInput^3 + Coeff2 * rawAnalogInput^2 + Coeff3 * rawAnalogInput + Coeff4
//
// This class supports receiving configuration data from the SmartThings cloud via the ST App. A user preference
// can be configured in your phone's ST App, and then the "Configure" tile will send the data for all sensors to
// the ST Shield. For PollingSensors, this data is handled in the beSMart() function.
Expand All @@ -45,13 +71,15 @@
// 2017-08-18 Dan Ogorchock Modified to return floating point values instead of integer
// 2017-08-31 Dan Ogorchock Added oversampling optional argument to help reduce noisy signals
// 2017-08-31 Dan Ogorchock Added filtering optional argument to help reduce noisy signals
// 2017-09-01 Dan Ogorchock Added 3rd order polynomial nonlinear correction compensation
//
//
//******************************************************************************************
#include "PS_Voltage.h"

#include "Constants.h"
#include "Everything.h"
#include <math.h>

namespace st
{
Expand All @@ -70,7 +98,8 @@ namespace st
SENSOR_HIGH(s_h),
MAPPED_LOW(m_l),
MAPPED_HIGH(m_h),
m_nNumSamples(NumSamples)
m_nNumSamples(NumSamples),
m_bUseCompensation(false)
{
setPin(analogInputPin);

Expand All @@ -90,6 +119,40 @@ namespace st

}

//constructor - called in your sketch's global variable declaration section
PS_Voltage::PS_Voltage(const __FlashStringHelper *name, unsigned int interval, int offset, byte analogInputPin, double s_l, double s_h, double m_l, double m_h, int NumSamples, byte filterConstant, double Coeff1, double Coeff2, double Coeff3, double Coeff4) :
PollingSensor(name, interval, offset),
m_fSensorValue(-1.0),
SENSOR_LOW(s_l),
SENSOR_HIGH(s_h),
MAPPED_LOW(m_l),
MAPPED_HIGH(m_h),
m_nNumSamples(NumSamples),
m_dCoeff1(Coeff1),
m_dCoeff2(Coeff2),
m_dCoeff3(Coeff3),
m_dCoeff4(Coeff4),
m_bUseCompensation(true)
{
setPin(analogInputPin);

//check for upper and lower limit and adjust accordingly
if ((filterConstant <= 0) || (filterConstant >= 100))
{
m_fFilterConstant = 1.0;
}
else if (filterConstant <= 5)
{
m_fFilterConstant = 0.05;
}
else
{
m_fFilterConstant = float(filterConstant) / 100;
}

}


//destructor
PS_Voltage::~PS_Voltage()
{
Expand Down Expand Up @@ -123,11 +186,27 @@ namespace st
{
int i;
double tempValue = 0;
long tempAnalogInput = 0;

//implement oversampling / averaging
for (i = 0; i < m_nNumSamples; i++) {

tempValue += map_double(analogRead(m_nAnalogInputPin), SENSOR_LOW, SENSOR_HIGH, MAPPED_LOW, MAPPED_HIGH);
tempAnalogInput = analogRead(m_nAnalogInputPin);

if (m_bUseCompensation) {

//Serial.print(F("PS_Voltage::tempAnalogInput = "));
//Serial.print(tempAnalogInput);

tempAnalogInput = (m_dCoeff1 * pow(tempAnalogInput, 3)) + (m_dCoeff2 * pow(tempAnalogInput, 2)) + (m_dCoeff3 * tempAnalogInput) + m_dCoeff4;

//Serial.print(F(", PS_Voltage::tempAnalogInput (Compensated) = "));
//Serial.print(tempAnalogInput);
//Serial.println();

}

tempValue += map_double(tempAnalogInput, SENSOR_LOW, SENSOR_HIGH, MAPPED_LOW, MAPPED_HIGH);

//if (st::PollingSensor::debug)
//{
Expand All @@ -149,8 +228,6 @@ namespace st
{
m_fSensorValue = (m_fFilterConstant * tempValue) + (1 - m_fFilterConstant) * m_fSensorValue;
}

//m_fSensorValue = map_double(analogRead(m_nAnalogInputPin), SENSOR_LOW, SENSOR_HIGH, MAPPED_LOW, MAPPED_HIGH);

Everything::sendSmartString(getName() + " " + String(m_fSensorValue));
}
Expand Down
34 changes: 33 additions & 1 deletion Arduino/libraries/ST_Anything/PS_Voltage.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// defaults for this sensor are based on the device used during testing.
//
// Create an instance of this class in your sketch's global variable section
// For Example: st::PS_Voltage sensor1("voltage1", 120, 0, PIN_VOLTAGE, 0, 1023, 0, 5);
// For Example: st::PS_Voltage sensor1("voltage1", 120, 0, PIN_VOLTAGE, 0, 1023, 0.0, 5.0);
//
// st::PS_Voltage() constructor requires the following arguments
// - String &name - REQUIRED - the name of the object - must match the Groovy ST_Anything DeviceType tile name
Expand All @@ -31,6 +31,32 @@
//
// filteredValue = (filterConstant/100 * currentValue) + ((1 - filterConstant/100) * filteredValue)
//
//----------------------------------------------------------------------------------------------------------------------------------------------
// st::PS_Voltage() has a second constructor which includes a 3rd order polynomial compensation algorithm.
//
// Create an instance of this class in your sketch's global variable section
// For Example: static st::PS_Voltage sensor5(F("voltage1"), 5, 1, PIN_VOLTAGE_1, -40, 140, 0, 4095, 20, 75, -0.000000025934, 0.0001049656215, 0.9032840665333, 204.642825355678);
//
// The following arguments all all REQUIRED in order to use the Compensation Algorithm.
// - String &name - REQUIRED - the name of the object - must match the Groovy ST_Anything DeviceType tile name
// - long interval - REQUIRED - the polling interval in seconds
// - long offset - REQUIRED - the polling interval offset in seconds - used to prevent all polling sensors from executing at the same time
// - byte pin - REQUIRED - the Arduino Pin to be used as an analog input
// - double s_l - REQUIRED - first argument of Arduino map(s_l,s_h,m_l,m_h) function to scale the output
// - double s_h - REQUIRED - second argument of Arduino map(s_l,s_h,m_l,m_h) function to scale the output
// - double m_l - REQUIRED - third argument of Arduino map(s_l,s_h,m_l,m_h) function to scale the output
// - double m_h - REQUIRED - fourth argument of Arduino map(s_l,s_h,m_l,m_h) function to scale the output
// - byte numSamples - REQUIRED - number of analog readings to average per scheduled reading of the analog input
// - byte filterConstant - REQUIRED - Value from 5% to 100% to determine how much filtering/averaging is performed 100 = none, 5 = maximum
// - double Coeff1 - REQUIRED - 3rd order polynomial coefficient #1
// - double Coeff2 - REQUIRED - 3rd order polynomial coefficient #2
// - double Coeff3 - REQUIRED - 3rd order polynomial coefficient #3
// - double Coeff4 - REQUIRED - 3rd order polynomial coefficient #4
//
// 3rd order Plynomial Compensation Algorithm (useful for correcting non-linear analog to digital converters)
//
// CompensatedValue = Coeff1 * rawAnalogInput^3 + Coeff2 * rawAnalogInput^2 + Coeff3 * rawAnalogInput + Coeff4
//
// This class supports receiving configuration data from the SmartThings cloud via the ST App. A user preference
// can be configured in your phone's ST App, and then the "Configure" tile will send the data for all sensors to
// the ST Shield. For PollingSensors, this data is handled in the beSMart() function.
Expand All @@ -45,6 +71,7 @@
// 2017-08-18 Dan Ogorchock Modified to return floating point values instead of integer
// 2017-08-31 Dan Ogorchock Added oversampling optional argument to help reduce noisy signals
// 2017-08-31 Dan Ogorchock Added filtering optional argument to help reduce noisy signals
// 2017-09-01 Dan Ogorchock Added 3rd order polynomial nonlinear correction compensation
//
//
//******************************************************************************************
Expand All @@ -63,11 +90,16 @@ namespace st
double SENSOR_LOW, SENSOR_HIGH, MAPPED_LOW, MAPPED_HIGH;
int m_nNumSamples;
float m_fFilterConstant; //Filter constant % as floating point from 0.00 to 1.00
double m_dCoeff1, m_dCoeff2, m_dCoeff3, m_dCoeff4; //3rd order polynomial nonlinear correction compensation coefficients
bool m_bUseCompensation;

public:
//constructor - called in your sketch's global variable declaration section
PS_Voltage(const __FlashStringHelper *name, unsigned int interval, int offset, byte analogInputPin, double s_l=0, double s_h=1023, double m_l=0, double m_h=5000, int NumSamples=1, byte filterConstant = 100);

//constructor with 3rd order polynomial nonlinear correction compensation coefficients - called in your sketch's global variable declaration section
PS_Voltage(const __FlashStringHelper *name, unsigned int interval, int offset, byte analogInputPin, double s_l, double s_h, double m_l, double m_h, int NumSamples, byte filterConstant, double Coeff1, double Coeff2, double Coeff3, double Coeff4);

//destructor
virtual ~PS_Voltage();

Expand Down
Binary file added ESP32-Voltage-vs-ADC-Reading.xlsx
Binary file not shown.

0 comments on commit 6965c28

Please sign in to comment.