Implement ADC correction for the soil sensor for a range of input voltages

This commit is contained in:
rbaron 2021-02-19 10:34:18 +01:00
parent 5b3c6a471d
commit fb48ad7da7
4 changed files with 67 additions and 29 deletions

View file

@ -23,6 +23,37 @@ constexpr double kBattDividerR1 = 1470;
constexpr double kBattDividerR2 = 470;
constexpr double kBattDividerFactor =
(kBattDividerR1 + kBattDividerR2) / kBattDividerR2;
constexpr double kDiodeDrop = 0.6;
// We need the sensor to behave well as the battery discharge. I plan on
// using a CR2032 coin cell, whose voltage ranges from roughly 3.0 (charged)
// to 2.0V (discharged). This is the input range for Vdd, so all of our
// analog-to-digital (ADC) measurements are somewhat relative to this varying
// voltage. The soil sensor itself is at heart an RC circuit: it charges to
// 0.63Vcc within a time constant (RC). More importantly, within a cycle, the
// final charge is proportional to Vcc. If we sample this with our ADC, and use
// a sampling mode that is relative to Vcc (AR_VDD4 here), the final voltage
// should "cancel out", keeping the sampled value constant as we vary Vcc. In
// other words, in theory, the raw value returned from the ADC should remain
// constant as I keep the sensor in the same state and vary the input voltage.
// In practice, I'm seeing a small drift in the raw sampled values. This could
// be due to some capacitance or lower transistor response times in the fast
// discharge circuit.
// I captured some (Vcc, raw adc sample) pairs and did a linear regression to
// correct for these values when the sensor is out in the air and in the water.
// This should mitigate the drifting issue enough.
// In reality, I don't think leaving this correction out would be a big deal. I
// expect the battery to discharge over a period of a few months, and I'd expect
// plants to be watered with the period of days/couple of weeks. This drift
// wouldn't be noticeable in that time scale, but since we can do better, why
// not?
// Some assumptions for these hardcoded values:
// - Sampling resolution of 10 bits!
// - Input (vdd) is in the range 2.0 - 3.0V
double GetRawValAir(double vdd) { return vdd * 94.29 + 308.95; }
double GetRawValWater(double vdd) { return vdd * 36.02 - 57.1; }
} // namespace
double BatteryMonitor::Read() {
@ -33,15 +64,22 @@ double BatteryMonitor::Read() {
return kBattDividerFactor * v_in;
}
soil_reading_t SoilMonitor::Read() {
soil_reading_t SoilMonitor::Read(double vdd) {
analogOversampling(kOversampling);
// Set up the analog reference to be VDD. This allows us to cancel out
// the effect of the battery discharge across time, since the RC circuit
// also depends linearly on VDD.
analogReference(AR_VDD4);
int raw = analogRead(pin_);
double percentage =
static_cast<double>(raw - air_val_) / (water_val_ - air_val_);
double v_out_corr = (vdd * raw) / 1023 + 0.6;
int raw_corr = v_out_corr / vdd * 1023;
double air = GetRawValAir(vdd);
double water = GetRawValWater(vdd);
double percentage = static_cast<double>(raw - air) / (water - air);
// Serial.printf(
// "vdd: %f, raw: %d, raw_corr: %d, v_out_corr: %f, percentage: %f\n", vdd,
// raw, raw_corr, v_out_corr, 100 * percentage);
return {raw, std::max(0.0, std::min(1.0, percentage))};
}

View file

@ -19,15 +19,11 @@ struct soil_reading_t {
class SoilMonitor {
public:
explicit SoilMonitor(int air_val, int water_val, int pin)
: air_val_(air_val), water_val_(water_val), pin_(pin) {}
explicit SoilMonitor(int pin) : pin_(pin) {}
soil_reading_t Read();
soil_reading_t Read(double vdd);
private:
// Referencce values for estimating the soil moisture percentage.
const int air_val_, water_val_;
// Pin the analog signal is connected to.
const int pin_;
};

View file

@ -15,8 +15,6 @@ constexpr int kSoilAnalogPin = 21; // P0.31, AIN7
constexpr int kBattAnalogPin = 15; // P0.05, AIN3
constexpr int kDischargeEnablePin = 16; // P0.30;
constexpr double kPWMFrequency = 500000;
constexpr int kSoilMonitorAirVal = 680;
constexpr int kSoilMonitorWaterVal = 60;
// parasite::SquareWaveGenerator square_wave_generator(kPWMFrequency, kPWMPin);
@ -24,8 +22,7 @@ const parasite::MACAddr kMACAddr = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
parasite::BLEAdvertiser advertiser(kMACAddr);
parasite::BatteryMonitor batt_monitor(kBattAnalogPin);
parasite::SoilMonitor soil_monitor(kSoilMonitorAirVal, kSoilMonitorWaterVal,
kSoilAnalogPin);
parasite::SoilMonitor soil_monitor(kSoilAnalogPin);
SoftwareTimer timer;
@ -54,25 +51,24 @@ void updateAdvertisingData(parasite::BLEAdvertiser* advertiser,
void timer_cb(TimerHandle_t timer_handle) {
parasite::SquareWaveGenerator square_wave_generator(kPWMFrequency, kPWMPin);
digitalToggle(kLED1Pin);
square_wave_generator.Start();
digitalWrite(kDischargeEnablePin, HIGH);
parasite::soil_reading_t soil_reading = soil_monitor.Read();
// Serial.printf("Moisture val: %d, %f%%\n", soil_reading.raw,
// 100 * soil_reading.parcent);
square_wave_generator.Stop();
digitalWrite(kDischargeEnablePin, LOW);
// digitalToggle(kLED1Pin);
double battery_voltage = batt_monitor.Read();
// Serial.printf("Batt voltage: %f\n", battery_voltage);
Serial.printf("Batt voltage: %f\n", battery_voltage);
digitalWrite(kDischargeEnablePin, HIGH);
square_wave_generator.Start();
delay(10);
parasite::soil_reading_t soil_reading = soil_monitor.Read(battery_voltage);
Serial.printf("Moisture val: %d, %f%%\n", soil_reading.raw,
100 * soil_reading.parcent);
square_wave_generator.Stop();
digitalWrite(kDischargeEnablePin, LOW);
updateAdvertisingData(&advertiser, soil_reading, battery_voltage);
// Keep adversiting for 1 second.
delay(1000);
delay(500);
advertiser.Stop();
@ -81,10 +77,14 @@ void timer_cb(TimerHandle_t timer_handle) {
void setup() {
Serial.begin(9600);
pinMode(kLED1Pin, OUTPUT);
// pinMode(kLED1Pin, OUTPUT);
pinMode(kDischargeEnablePin, OUTPUT);
timer.begin(2000, timer_cb, /*timerID=*/nullptr, /*repeating=*/true);
// digitalWrite(kDischargeEnablePin, HIGH);
// parasite::SquareWaveGenerator square_wave_generator(kPWMFrequency,
// kPWMPin); square_wave_generator.Start();
timer.begin(1000, timer_cb, /*timerID=*/nullptr, /*repeating=*/true);
timer.start();
// Suspend the loop task. Under the hood this is a freeRTOS task set up

View file

@ -140,9 +140,13 @@ Vcc = 2.0 => Moisture Air: 547; Hand: 23
Possible issues:
* Fast discharging circuit is not fully open with lower voltage?
* Weird capacitance stored in the transitor messes up the quick switching?
* Time it takes to overcome the 0.6V diode drop in the output circuit?
Possible fixes:
* Hack a software interpolation
* Measure and correct it via a software interpolation
For now I'm implementing the software fix that seems to work well in practice. The measurements and regression coefficients are in [this spreadsheet](https://docs.google.com/spreadsheets/d/1F6rtkCX7626tzZff0NANZVknawb_U4uS38zjyy6ZPEE/edit#gid=1442651809).
# Battery
* [CR2032 datasheet](https://data.energizer.com/pdfs/cr2032.pdf)