diff --git a/code/parasite/lib/parasite/parasite/adc.cpp b/code/parasite/lib/parasite/parasite/adc.cpp index e1a4ad9..ec6bda3 100644 --- a/code/parasite/lib/parasite/parasite/adc.cpp +++ b/code/parasite/lib/parasite/parasite/adc.cpp @@ -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(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(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))}; } diff --git a/code/parasite/lib/parasite/parasite/adc.h b/code/parasite/lib/parasite/parasite/adc.h index df05c3e..1ba5de7 100644 --- a/code/parasite/lib/parasite/parasite/adc.h +++ b/code/parasite/lib/parasite/parasite/adc.h @@ -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_; }; diff --git a/code/parasite/src/main.cpp b/code/parasite/src/main.cpp index e0f52b6..c0dc126 100644 --- a/code/parasite/src/main.cpp +++ b/code/parasite/src/main.cpp @@ -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 diff --git a/resources.md b/resources.md index 8d85305..5d6ffdc 100644 --- a/resources.md +++ b/resources.md @@ -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)