The first long term measurement I made, to test the behaviour of the sensor over a longer time range was a failure. After the five days with the device introduced in this post, the readings made absolute no sense.

plant-sensor-data

The sensor was not moved in the flower pot and the plant was once watered at the begin of the measurement. While it looked promising at the begin, the frequency suddenly went down again, which was very irritating. I am still investigating how this could happen.

To get closer to the real measurement of the final plant watering sensor, I started a new approach.

lucky-resistor-1

I soldered a header to one of the LED pads on a fully assembled plant sensor. Next I changed the device for the measurements.

lucky-resistor-2

It is the previous design, using a Adafruit Feather M0 as controller, with the Adalogger FeatherWing  on top.

lucky-resistor-3

Next I added a small prototype board with matching headers for the Feather system and on top the OLED display.

I actually just added an optocoupler to receive signals from the plant watering sensor, but keep it completely insulated. To be able to compare two plant watering sensors, I prepared two inputs. For the first tests, I just like to repeat the long term measurements with one sensor.

A new firmware transfers the current values for the frequency measurement and the battery measurement using pulse length coding as used in the IrDA protocol. Here I use a very slow encoding, using a 1ms pulse for zeros and 2ms pulse for ones.

Code

The reason for this slow encoding is to use interrupts on the receiving side, without much impact to the rest of the code. There is plenty of time between the signal changes of this very slow encoding to process the bits and do all the other work. The plant sensor sends a signal every ~10s with 32bits, so there is no communication for a long time between this bursts.

Here the relevant code segments for the SAM D21 (Feather M0) to receive the bits from the plant sensor using an interrupt.

First I setup a timer to measure the time passed between the interrupts. The method setupCounter() initialises and starts the timer and the method readCounter() reads the current value of the timer.

void setupCounter()
{
  PM->APBCMASK.bit.TCC2_ = 1;
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN|GCLK_CLKCTRL_GEN_GCLK0|GCLK_CLKCTRL_ID(GCM_TCC2_TC3);
  while (GCLK->STATUS.bit.SYNCBUSY) {}

  counter->CTRLA.bit.SWRST = 1;
  while (counter->SYNCBUSY.bit.SWRST == 1) {}
  counter->CTRLA.bit.ENABLE = 0;
  counter->CTRLA.bit.PRESCALER = TCC_CTRLA_PRESCALER_DIV256_Val;
  counter->CTRLA.bit.ENABLE = 1;
  while (counter->SYNCBUSY.bit.ENABLE == 1) {}
}

uint16_t readCounter()
{
  counter->CTRLBSET.bit.CMD = TCC_CTRLBSET_CMD_READSYNC_Val;
  while (counter->SYNCBUSY.bit.COUNT == 1) {}
  const uint16_t value = counter->COUNT.bit.COUNT;
  return value;
}

The interrupt is setup using the attachInterupt() methods from the Arduino library. A method onInputChange() is called each time the state on one of the input pins changes. The logic in this method is very simple, it just checks how much time passed between a down/up signal change and shifts the bit into the variable gInputShiftValueA.

void setup()
{
  // ...
  pinMode(11, INPUT_PULLUP);
  pinMode(12, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(11), onInputChange, CHANGE);
  attachInterrupt(digitalPinToInterrupt(12), onInputChange, CHANGE);
  // ...
}

bool gLastInputStateA = false;
uint16_t gLastInputTimeA = 0;
volatile uint32_t gInputShiftValueA = 0;
volatile uint8_t gInputShiftCountA = 0;

void onInputChange()
{
  const uint16_t longBitDelay = 300;
  const bool inputStateA = digitalRead(11);
  if (inputStateA != gLastInputStateA) {
    gLastInputStateA = inputStateA;
    const uint16_t currentTime = readCounter();
    const uint16_t timeDifference = (currentTime - gLastInputTimeA);
    gLastInputTimeA = currentTime;
    if (inputStateA) {
      gInputShiftValueA <<= 1;               if (timeDifference > 300) {
        gInputShiftValueA |= 1;
      }
      gInputShiftCountA += 1;
    }
  }
  // ...
}

In the method readCurrentFrequency() I check if there are enough bits received, check the initial byte for the right value and convert the other bytes to the expected values.

void readCurrentFrequency()
{
  uint32_t inputShiftValueA = 0;
  uint8_t inputShiftCountA = 0;
  noInterrupts();
  if (gInputShiftCountA >= 32) {
    if (gInputShiftCountA == 32) {
      inputShiftCountA = gInputShiftCountA;
      inputShiftValueA = gInputShiftValueA;
    }
    gInputShiftCountA = 0;
    gInputShiftValueA = 0;
  }
  interrupts();
  if (inputShiftCountA == 32 && (inputShiftValueA >> 24) == 0x01) {
    gDeviceFrequencyA = ((inputShiftValueA >> 16) & 0xff);
    gDeviceBatteryA = ((inputShiftValueA >> 8) & 0xff) | ((inputShiftValueA & 0xff) << 8);
  }
}

Please note, I removed all comments from the code segments above to make them more compact for this article.

If you have questions, miss some information or just have any feedback, feel free to add a comment below.