Conductivity via Analog Electrical Resistance topic

Classes for measuring conductivity using a simple analog voltage divider.

Introduction

This is for a very basic conductivity circuit built with a single resistor and an old power cord. DC power is briefly supplied across the power cord causing the water to act as one of the resistors on a voltage divider. Knowing the voltage of the other resistor in the divider, we can calculate to resistance from the water (and then its electrical conductivity) based on the drop in volage across the divider.

For this to work, the power across the circuit MUST be turned off between readings. If the power to the circuit is left on the water will become polarized and the values will not be valid. The water temperature (if used) must be suplied separately for a calculation.

The Circuit

One pole of the power cord wire is connected to the ground of the main logger board. The other pole is connected to the sensor power supply via a resistor of known resistance (R1) and then to an analog pin to measure the voltage.

So the circuit is:

1Vin (sensor power) --- R1 --- power cord --- Vout
2 |
3 |
4 water between prongs (Rwater)
5 |
6 |
7 ground

The above diagram and the calculations assume the reistance of the analog pins themselves on the Arduino is negligible.

Calculating the Conductivity

First, we need to convert the bit reading of the ADC into volts based on the range of the ADC (1 bit more than the resolution):

\[meas\_voltage = \frac{analog\_ref\_voltage * raw\_adc\_bits}{ANALOG\_EC\_ADC\_RANGE}\]

Assuming the voltage of the ADC reference is the same as that used to power the EC resistor circuit we can replace the reference voltage with the sensor power voltage:

\[meas\_voltage = \frac{sensor\_power\_voltage * raw\_adc\_bits}{ANALOG\_EC\_ADC\_RANGE}\]

Now we can calculate the resistance of the water, knowing the resistance of the resistor we put in the circuit and the voltage drop:

\[R_{water\_ohms} = \frac{meas\_voltage * R_{series\_ohms}}{sensor\_power\_voltage - meas\_voltage}\]

Combining the above equations and doing some rearranging, we get:

\[R_{water\_ohms} = \frac{R_{series\_ohms}}{\frac{ANALOG\_EC\_ADC\_RANGE}{raw\_adc\_bits} - 1}\]

The conductivity is then the inverse of the resistance - multiplied by a measured cell constant and a 10^6 conversion to µS/cm.

\[water\_conductivity = \frac{1000000}{R_{water\_ohms} * sensor_{EC\_Konst}}\]

The real cell constant will vary based on the size of the "cell" - that is, the size of the plug on the power cord. You can calculate the cell constant for each power cord sensor you use following the calibration program.

For one AC Power Cord 12t with male IEC 320-C8 connector the cell constant was 2.88.

References

Build flags

Sensor Constructor

AnalogElecConductivity::AnalogElecConductivity(int8_t powerPin, int8_t dataPin, float Rseries_ohms = 499, float sensorEC_Konst = 1.0, uint8_t measurementsToAverage = 1)

Construct a new AnalogElecConductivity object.

Parameters
powerPin The port pin providing power to the EC probe. Needs to be switched, and assumed to be same V as the dataPin's ADC.
dataPin

The processor ADC port pin to read the voltage from the EC probe. Not all processor pins can be used as analog pins. Those usable as analog pins generally are numbered with an "A" in front of the number

  • ie, A1.
Rseries_ohms The resistance of the resistor series (R) in the line; optional with default value of 499.
sensorEC_Konst The cell constant for the sensing circuit; optional with default value of 2.88 - which is what has been measured for a typical standard sized lamp-type plug.
measurementsToAverage The number of measurements to average; optional with default value of 1.


Example Code

The analog electrical conductivity sensor is used in the menu a la carte example.

1#include <sensors/AnalogElecConductivity.h>
2
3const int8_t ECpwrPin = A4; // Power pin (-1 if continuously powered)
4const int8_t ECdataPin1 = A0; // Data pin (must be an analog pin, ie A#)
5
6// Create an Analog Electrical Conductivity sensor object
7AnalogElecConductivity analogEC_phy(ECpwrPin, ECdataPin1);
8
9// Create a conductivity variable pointer for the analog sensor
10Variable* analogEc_cond = new AnalogElecConductivity_EC(
11 &analogEC_phy, "12345678-abcd-1234-ef00-1234567890ab");
12
13// Create a calculated variable for the temperature compensated conductivity
14// (that is, the specific conductance). For this example, we will use the
15// temperature measured by the Maxim DS18 saved as ds18Temp several sections
16// above this. You could use the temperature returned by any other water
17// temperature sensor if desired. **DO NOT** use your logger board temperature
18// (ie, from the DS3231) to calculate specific conductance!
19float calculateAnalogSpCond(void) {
20 float spCond = -9999; // Always safest to start with a bad value
21 float waterTemp = ds18Temp->getValue();
22 float rawCond = analogEc_cond->getValue();
23 float temperatureCoef = 0.019;
24 // ^^ Linearized temperature correction coefficient per degrees Celsius.
25 // The value of 0.019 comes from measurements reported here:
26 // Hayashi M. Temperature-electrical conductivity relation of water for
27 // environmental monitoring and geophysical data inversion. Environ Monit
28 // Assess. 2004 Aug-Sep;96(1-3):119-28.
29 // doi: 10.1023/b:emas.0000031719.83065.68. PMID: 15327152.
30 if (waterTemp != -9999 && rawCond != -9999) {
31 // make sure both inputs are good
32 spCond = rawCond / (1 + temperatureCoef * (waterTemp - 25.0));
33 }
34 return spCond;
35}
36
37// Properties of the calculated variable
38// The number of digits after the decimal place
39const uint8_t analogSpCondResolution = 0;
40// This must be a value from http://vocabulary.odm2.org/variablename/
41const char* analogSpCondName = "specificConductance";
42// This must be a value from http://vocabulary.odm2.org/units/
43const char* analogSpCondUnit = "microsiemenPerCentimeter";
44// A short code for the variable
45const char* analogSpCondCode = "anlgSpCond";
46// The (optional) universallly unique identifier
47const char* analogSpCondUUID = "12345678-abcd-1234-ef00-1234567890ab";
48
49// Finally, Create the specific conductance variable and return a pointer to it
50Variable* analogEc_spcond = new Variable(
51 calculateAnalogSpCond, analogSpCondResolution, analogSpCondName,
52 analogSpCondUnit, analogSpCondCode, analogSpCondUUID);

Classes

class AnalogElecConductivity
Class for the analog [Electrical Conductivity monitor](Conductivity via Analog Electrical Resistance)
class AnalogElecConductivity_EC
The Variable sub-class used for electrical conductivity measured using an analog pin connected to electrodes submerged in the medium.

Sensor Variable Counts

The number of variables that can be returned by the analog conductivity sensor

#define ANALOGELECCONDUCTIVITY_NUM_VARIABLES = 1
Sensor::_numReturnedValues; we only get one value from the analog conductivity sensor.
#define ANALOGELECCONDUCTIVITY_INC_CALC_VARIABLES = 0
Sensor::_incCalcValues; we don't calculate any additional values though we recommend users include a temperature sensor and calculate specific conductance in their own program.

Configuration Defines

Defines to help configure the range and resolution of the home-made conductivity sensor depending on the processor and ADC in use.

#define ANALOG_EC_ADC_RESOLUTION = 10
Default resolution (in bits) of the voltage measurement.
#define ANALOG_EC_ADC_MAX = ((1 <<
The maximum possible value of the ADC - one less than the resolution shifted up one bit.
#define ANALOG_EC_ADC_RANGE = (1 <<
The maximum possible range of the ADC - the resolution shifted up one bit.
#define ANALOG_EC_ADC_REFERENCE_MODE = DEFAULT
The voltage reference mode for the processor's ADC.
#define ANALOG_EC_ADC_REFERENCE_MODE = AR_DEFAULT
The voltage reference mode for the processor's ADC.
#define RSERIES_OHMS_DEF = 499
The default resistance (in ohms) of the measuring resistor. This should not be less than 300 ohms when measuring EC in water.
#define SENSOREC_KONST_DEF = 1.0
Cell Constant For EC Measurements.

Sensor Timing

The timing for analog conductivity via resistance.

#define ANALOGELECCONDUCTIVITY_WARM_UP_TIME_MS = 2
Sensor::_warmUpTime_ms; giving 2ms for warm-up.
#define ANALOGELECCONDUCTIVITY_STABILIZATION_TIME_MS = 1
Sensor::_stabilizationTime_ms; we give just a tiny delay for stabilization.
#define ANALOGELECCONDUCTIVITY_MEASUREMENT_TIME_MS = 0
Sensor::_measurementTime_ms; we assume the analog voltage is measured instantly.

Electrical Conductance

The electrical conductance variable from a home-made analog sensor.

AnalogElecConductivity_EC::AnalogElecConductivity_EC(AnalogElecConductivity* parentSense, const char* uuid = "", const char* varCode = "anlgEc")

Construct a new AnalogElecConductivity_EC object.

Parameters
parentSense The parent AnalogElecConductivity providing the result values.
uuid A universally unique identifier (UUID or GUID) for the variable; optional with the default value of an empty string.
varCode A short code to help identify the variable in files; optional with a default value of "anlgEc".

#define ANALOGELECCONDUCTIVITY_EC_RESOLUTION = 1
Decimals places in string representation; EC should have 1.
#define ANALOGELECCONDUCTIVITY_EC_VAR_NUM = 0
Sensor variable number; EC is stored in sensorValues[0].
#define ANALOGELECCONDUCTIVITY_EC_VAR_NAME = "electricalConductivity"
Variable name in ODM2 controlled vocabulary; "electricalConductivity".
#define ANALOGELECCONDUCTIVITY_EC_UNIT_NAME = "microsiemenPerCentimeter"
Variable unit name in ODM2 controlled vocabulary; "microsiemenPerCentimeter" (µS/cm)
#define ANALOGELECCONDUCTIVITY_EC_DEFAULT_CODE = "anlgEc"
Default variable short code; "anlgEc".

Define documentation

#define ANALOGELECCONDUCTIVITY_NUM_VARIABLES = 1

Sensor::_numReturnedValues; we only get one value from the analog conductivity sensor.


#define ANALOGELECCONDUCTIVITY_INC_CALC_VARIABLES = 0

Sensor::_incCalcValues; we don't calculate any additional values though we recommend users include a temperature sensor and calculate specific conductance in their own program.


#define ANALOG_EC_ADC_RESOLUTION = 10

Default resolution (in bits) of the voltage measurement.

The default for all boards is 10, use a build flag to change this, if necessary.


#define ANALOG_EC_ADC_MAX = ((1 <<

The maximum possible value of the ADC - one less than the resolution shifted up one bit.


#define ANALOG_EC_ADC_RANGE = (1 <<

The maximum possible range of the ADC - the resolution shifted up one bit.


#define ANALOG_EC_ADC_REFERENCE_MODE = DEFAULT

The voltage reference mode for the processor's ADC.

For an AVR board, this must be one of:

  • DEFAULT: the default built-in analog reference of 5 volts (on 5V Arduino boards) or 3.3 volts (on 3.3V Arduino boards)
  • INTERNAL: a built-in reference, equal to 1.1 volts on the ATmega168 or ATmega328P and 2.56 volts on the ATmega32U4 and ATmega8 (not available on the Arduino Mega)
  • INTERNAL1V1: a built-in 1.1V reference (Arduino Mega only)
  • INTERNAL2V56: a built-in 2.56V reference (Arduino Mega only)
  • EXTERNAL: the voltage applied to the AREF pin (0 to 5V only) is used as the reference.

If not set on an AVR board DEFAULT is used.

For the best accuracy, use an EXTERNAL reference with the AREF pin connected to the power supply for the EC sensor.

For a SAMD board, this must be one of:

  • AR_DEFAULT: the default built-in analog reference of 3.3V
  • AR_INTERNAL: a built-in 2.23V reference
  • AR_INTERNAL1V0: a built-in 1.0V reference
  • AR_INTERNAL1V65: a built-in 1.65V reference
  • AR_INTERNAL2V23: a built-in 2.23V reference
  • AR_EXTERNAL: the voltage applied to the AREF pin is used as the reference

If not set on an SAMD board AR_DEFAULT is used.

For the best accuracy, use an EXTERNAL reference with the AREF pin connected to the power supply for the EC sensor.


#define ANALOG_EC_ADC_REFERENCE_MODE = AR_DEFAULT

The voltage reference mode for the processor's ADC.

For an AVR board, this must be one of:

  • DEFAULT: the default built-in analog reference of 5 volts (on 5V Arduino boards) or 3.3 volts (on 3.3V Arduino boards)
  • INTERNAL: a built-in reference, equal to 1.1 volts on the ATmega168 or ATmega328P and 2.56 volts on the ATmega32U4 and ATmega8 (not available on the Arduino Mega)
  • INTERNAL1V1: a built-in 1.1V reference (Arduino Mega only)
  • INTERNAL2V56: a built-in 2.56V reference (Arduino Mega only)
  • EXTERNAL: the voltage applied to the AREF pin (0 to 5V only) is used as the reference.

If not set on an AVR board DEFAULT is used.

For the best accuracy, use an EXTERNAL reference with the AREF pin connected to the power supply for the EC sensor.

For a SAMD board, this must be one of:

  • AR_DEFAULT: the default built-in analog reference of 3.3V
  • AR_INTERNAL: a built-in 2.23V reference
  • AR_INTERNAL1V0: a built-in 1.0V reference
  • AR_INTERNAL1V65: a built-in 1.65V reference
  • AR_INTERNAL2V23: a built-in 2.23V reference
  • AR_EXTERNAL: the voltage applied to the AREF pin is used as the reference

If not set on an SAMD board AR_DEFAULT is used.

For the best accuracy, use an EXTERNAL reference with the AREF pin connected to the power supply for the EC sensor.


#define RSERIES_OHMS_DEF = 499

The default resistance (in ohms) of the measuring resistor. This should not be less than 300 ohms when measuring EC in water.


#define SENSOREC_KONST_DEF = 1.0

Cell Constant For EC Measurements.

This should be measured following the calibration example on https://hackaday.io/project/7008-fly-wars-a-hackers-solution-to-world-hunger/log/24646-three-dollar-ec-ppm-meter-arduino.

Mine was around 2.9 with plugs being a standard size they should all be around the same. If you get bad readings you can use the calibration script and fluid to get a better estimate for K. Default to 1.0, and can be set at startup.


#define ANALOGELECCONDUCTIVITY_MEASUREMENT_TIME_MS = 0

Sensor::_measurementTime_ms; we assume the analog voltage is measured instantly.

It's not really quite instantly, but it is very fast and the time to measure is included in the read function. On ATmega based boards (UNO, Nano, Mini, Mega), it takes about 100 microseconds (0.0001 s) to read an analog input, so the maximum reading rate is about 10,000 times a second.


#define ANALOGELECCONDUCTIVITY_EC_RESOLUTION = 1

Decimals places in string representation; EC should have 1.

Range of 0-3V3 with 10bit ADC - resolution of 0.003 = 3 µS/cm.


#define ANALOGELECCONDUCTIVITY_EC_VAR_NUM = 0

Sensor variable number; EC is stored in sensorValues[0].


#define ANALOGELECCONDUCTIVITY_EC_VAR_NAME = "electricalConductivity"

Variable name in ODM2 controlled vocabulary; "electricalConductivity".


#define ANALOGELECCONDUCTIVITY_EC_UNIT_NAME = "microsiemenPerCentimeter"

Variable unit name in ODM2 controlled vocabulary; "microsiemenPerCentimeter" (µS/cm)


#define ANALOGELECCONDUCTIVITY_EC_DEFAULT_CODE = "anlgEc"

Default variable short code; "anlgEc".