// low = 3K
// mid = 300K
// high = 16K
enum class Rating : uint8_t { low, mid, high, none };
struct RangeToPin {
const uint8_t pin[3];
const uint32_t low[2];
const uint32_t mid[2];
const uint32_t high[2];
};
// The range values are converted to unsigned long.
// They represent the measured value of type float with 3 decimal places.
constexpr RangeToPin led {
{9, 10, 11},
{200000, 1800999},
{1801000, 2700999},
{2701000, 5000000}
};
// Resistor pin 4 3K
// 5 300K
// 6 16K
constexpr RangeToPin ppm {
{4, 5, 6},
{0, 2000999},
{2001000, 3000999},
{3001000, 5000000}
};
constexpr uint8_t ORPpin = A0; // Sensor connected to A0
template <typename T, uint8_t MAX_SIZE> class Readings {
public:
uint8_t getIdx() { return idx; };
const void setValue(T val) {
values[idx] = val;
if (++idx >= maxIdx) idx = 0;
}
T getAverage() {
T tmp = 0;
for (auto value : values) { tmp += value; }
return tmp / maxIdx;
}
private:
const uint8_t maxIdx {MAX_SIZE};
T values[MAX_SIZE];
uint8_t idx {0};
};
Readings<float, 5> readings;
int ORPReading;
float ORPVoltage;
float ORPSalinity;
float predictSalinity(float voltage) {
float ppm = 0;
// Calibration points array
float calibrationPoints[][2] = {
{0.00, 0 }, // Assuming 0 ppm at 0 V
{0.44985, 0.5 },
{2.76, 80 },
{2.97, 90 },
{3.5657, 100 },
{3.6377, 1000}, // 3 k ohms range
{3.8550, 2000}, // Assuming 2000 ppm at 3.4 V
// Continue with additional points as needed
{4.21190, 3000}, // 300k ohms range(Most accurate range)
{4.3256, 4000}, // 16 k ohms resistance range
{4.8079, 5000},
};
int numPoints = sizeof(calibrationPoints) / sizeof(calibrationPoints[0]);
// Find where the voltage fits within the calibration points
for (int i = 0; i < numPoints - 1; i++) {
if (voltage >= calibrationPoints[i][0] && voltage < calibrationPoints[i + 1][0]) {
// Perform linear interpolation
float slope = (calibrationPoints[i + 1][1] - calibrationPoints[i][1]) /
(calibrationPoints[i + 1][0] - calibrationPoints[i][0]);
ppm = calibrationPoints[i][1] + slope * (voltage - calibrationPoints[i][0]);
break;
}
}
return ppm;
}
float readAndCalculateSalinity() {
ORPReading = analogRead(ORPpin);
ORPVoltage = ORPReading * (5.0 / 1023.0);
return predictSalinity(ORPVoltage);
}
//
// Check range -> return level low, mid or high
//
Rating checkRange(float measuredValue, const RangeToPin &rtp) {
uint32_t compare = measuredValue * 1000; // Convert to unsigend long
Rating result;
if (compare >= rtp.low[0] && compare <= rtp.low[1]) {
result = Rating::low;
} else if (compare >= rtp.mid[0] && compare <= rtp.mid[1]) {
result = Rating::mid;
} else if (compare >= rtp.high[0] && compare <= rtp.high[1]) {
result = Rating::high;
} else {
result = Rating::none;
}
return result;
}
//
// Switch the pins (Resistor and/or led) according to the given value (low, mid or high)
//
void switchPins(Rating val, const RangeToPin &rtp) {
for (auto pin : rtp.pin) {
digitalWrite(pin, LOW); // Reset LEDs before setting
}
switch (val) {
case Rating::low: [[fallthrough]];
case Rating::mid: [[fallthrough]];
case Rating::high: digitalWrite(rtp.pin[static_cast<uint8_t>(val)], HIGH); break;
default: break; // out of range, do nothig.
}
}
//
// Main program
//
void setup() {
Serial.begin(9600);
for (auto pin : led.pin) { pinMode(pin, OUTPUT); }
for (auto pin : ppm.pin) { pinMode(pin, OUTPUT); }
// Start with the middle range resistor
digitalWrite(ppm.pin[static_cast<uint8_t>(Rating::mid)], HIGH);
}
void loop() {
static Rating oldLevelResistor {Rating::none};
static Rating oldLevelLed {Rating::none};
ORPSalinity = readAndCalculateSalinity();
readings.setValue(ORPSalinity);
// readings.incIdx();
float averageSalinity = readings.getAverage();
Rating levelResistor = checkRange(ORPSalinity, ppm);
Rating levelLed = checkRange(ORPSalinity, led);
if (oldLevelResistor != levelResistor) {
oldLevelResistor = levelResistor;
switchPins(levelResistor, ppm);
}
if (oldLevelLed != levelLed) {
oldLevelLed = levelLed;
switchPins(levelLed, led);
}
Serial.println("=====================");
Serial.print("Raw Reading: ");
Serial.println(ORPReading);
Serial.print("Voltage: ");
Serial.println(ORPVoltage, 4);
Serial.print("Predicted Salinity (PPM): ");
Serial.println(ORPSalinity);
Serial.print("Average Salinity (PPM): ");
Serial.println(averageSalinity);
Serial.print("Active Resistor: ");
switch (levelResistor) {
case Rating::low: Serial.println("3K ohms"); break;
case Rating::mid: Serial.println("300K ohms"); break;
case Rating::high: Serial.println("16K ohms"); break;
default: break;
}
delay(1000);
}
to Sensor
Visualizes which resistor
is switched on.