Apollon77 / I2CSoilMoistureSensor

Simple Arduino Library for the I2C Soil Moisture Sensor version from Chirp (https://github.com/Miceuz/i2c-moisture-sensor)
MIT License
73 stars 27 forks source link

After calling sensor.sleep(), can't read value #8

Closed simonrobb closed 7 years ago

simonrobb commented 7 years ago

I'm using the library on an ESP8266 (Wemos D1 mini) connected to three sensors, each addressed individually.

After calling sensor.sleep() on one of the sensors, it returns 4294967295 for subsequent capacitance readings. The other two sensors continue to operate normally for the next reading, however on further readings all sensors give that same value of 4294967295 (and -0.1 for temperature).

At this point the board can't be reset using the onboard reset button. I can power cycle the board and it will start up again, but now the sensor which was put to sleep doesn't respond to I2C messages, and all three sensors return the bad value for capacitance.

What am I missing here? Is there a method to wake the sensor after sleeping?

Apollon77 commented 7 years ago

Do you have more infos? Your code, details to the used sensors (like FW versions and such), which library version you use? ...

Normally after using sleep you must send a "reset" to wake the sensors up again

simonrobb commented 7 years ago

I'll try using .reset() after sleeping and see if that makes a difference. I was following the example code which doesn't have a reset call. I'll update that code if it works.

The sensors are all running FW 2.3, and are addressed 0x21, 0x22, and 0x23. My code looks like this:

#include <ESP8266WiFi.h>
#include <Printers.h>
#include <XBee.h>
#include <Wire.h>
#include <I2CSoilMoistureSensor.h>
#include <SoftwareSerial.h>

#define I2C_SCL_PIN D3
#define I2C_SDA_PIN D4

#define XBEE_RX_PIN D5      // Connected to XBee's TX pin
#define XBEE_TX_PIN D6      // Connected to XBee's RX pin
#define XBEE_SLEEP_PIN D7   // Connected to XBee's sleep pin (9)

const unsigned int SH = 0x0013a200;
const unsigned int SL = 0x40de2039;
const unsigned int sleepTime = 30 * 60 * 1000;

I2CSoilMoistureSensor topSensor(0x21);
I2CSoilMoistureSensor middleSensor(0x22);
I2CSoilMoistureSensor bottomSensor(0x23);

SoftwareSerial XBeeSerial(XBEE_RX_PIN, XBEE_TX_PIN); // RX, TX

// Create an XBee object at the top of your sketch
XBee xbee = XBee();

ZBTxStatusResponse txStatus = ZBTxStatusResponse();

void setup() {
  // Set up debug serial console
  Serial.begin(9600);

  // Set up I2C interface
  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);

  // Set up xbee serial
  XBeeSerial.begin(9600);
  xbee.setSerial(XBeeSerial);

  // Configure onboard indicator led pin
  pinMode(BUILTIN_LED, OUTPUT);

  // Configure xbee sleep pin
  /* Serial.print("Configuring GPIO");
  Serial.print(XBEE_SLEEP_PIN);
  Serial.println(" as XBee sleep pin...");
  pinMode(XBEE_SLEEP_PIN, OUTPUT);
  digitalWrite(XBEE_SLEEP_PIN, LOW); */

  // Initialise sensors
  topSensor.begin();
  middleSensor.begin();
  bottomSensor.begin();
  delay(1000); // Give some time to sensors to boot up

  Serial.println("Top Sensor");
  Serial.print("Address: ");
  Serial.println(topSensor.getAddress(),HEX);
  Serial.print("Firmware version: ");
  Serial.println(topSensor.getVersion(),HEX);
  Serial.println();
  Serial.println();

  Serial.println("Middle Sensor");
  Serial.print("Address: ");
  Serial.println(middleSensor.getAddress(),HEX);
  Serial.print("Firmware version: ");
  Serial.println(middleSensor.getVersion(),HEX);
  Serial.println();
  Serial.println();

  Serial.println("Bottom Sensor");
  Serial.print("Address: ");
  Serial.println(bottomSensor.getAddress(),HEX);
  Serial.print("Firmware version: ");
  Serial.println(bottomSensor.getVersion(),HEX);
  Serial.println();
  Serial.println();
}

void loop() {
  // Top sensor
  while (topSensor.isBusy()) delay(50);
  unsigned int topCapacitance = topSensor.getCapacitance();
  int topTemperature = topSensor.getTemperature();
  topSensor.sleep();

  // Middle sensor
  while (middleSensor.isBusy()) delay(50);
  unsigned int middleCapacitance = middleSensor.getCapacitance();

  // Bottom sensor
  while (bottomSensor.isBusy()) delay(50);
  unsigned int bottomCapacitance = bottomSensor.getCapacitance();

  Serial.print("Soil Capacitance: ");
  Serial.print("top = ");
  Serial.print(topCapacitance);
  Serial.print(", middle = ");
  Serial.print(middleCapacitance);
  Serial.print(", bottom = ");
  Serial.print(bottomCapacitance);
  Serial.println();

  Serial.print("Temperature: ");
  Serial.print("top = "); 
  Serial.print(topTemperature/(float)10);
  Serial.println();
  Serial.println();

  // Create an array for the data to send
  uint8_t payload[] = {
    (uint8_t)(topTemperature >> 8), 
    (uint8_t)(topTemperature >> 0),
    (uint8_t)(topCapacitance >> 8), 
    (uint8_t)(topCapacitance >> 0),
    (uint8_t)(middleCapacitance >> 8), 
    (uint8_t)(middleCapacitance >> 0),
    (uint8_t)(bottomCapacitance >> 8), 
    (uint8_t)(bottomCapacitance >> 0)
    }; 

  // Specify the address of the remote XBee (this is the SH + SL)
  XBeeAddress64 addr64 = XBeeAddress64(SH, SL);

  // Create a TX Request
  ZBTxRequest zbTx = ZBTxRequest(addr64, payload, sizeof(payload));

  // Send data
  xbee.send(zbTx);

  delay(sleepTime);
}
simonrobb commented 7 years ago

I've just tried again, by resetting and sleeping the bottom sensor in every loop(). This is the output I received:

Soil Capacitance: top = 330, middle = 333, bottom = 333
Temperature: top = 26.50

Soil Capacitance: top = 331, middle = 333, bottom = 333
Temperature: top = 26.50

Soil Capacitance: top = 329, middle = 334, bottom = 65535
Temperature: top = 26.50

Soil Capacitance: top = 329, middle = 333, bottom = 333
Temperature: top = 26.50

Soil Capacitance: top = 329, middle = 333, bottom = 65535
Temperature: top = 26.50

Soil Capacitance: top = 329, middle = 333, bottom = 333
Temperature: top = 26.50

Soil Capacitance: top = 329, middle = 333, bottom = 4294967295
Temperature: top = 26.50

Soil Capacitance: top = 4294967295, middle = 4294967295, bottom = 4294967295
Temperature: top = -0.10

@Miceuz This is starting to seem like it is more of a hardware issue than one with the library - have you come across anything like this in your testing/development?

Miceuz commented 7 years ago

@simonrobb can you paste your code for this particular case? 4294967295 is all '1' for 32 bit value. This means, sensor is not responding at all. Do you get the same result if you just have one sensor on the bus? Does everything work fine without using sleep()?

In general, I2C address match will wakeup the sensor from sleep, so no reset should be necessary, but maybe wakeup takes some time and that throws I2C master off track.

BTW, how long is your wiring? You have pullups? Can you have a look at waveform on scope?

Another thing to try - slow down the I2C bus speed.

I'm really busy these days till mid April, so it would be really helpful if you could check all those things for me.

simonrobb commented 7 years ago

Thanks for the reply @Miceuz. I've looked into a few of these things today.

The sensors work fine when not using the sleep function. My SCL/SDA lines are going into pins D3/D4 of the Wemos D1 mini, which have pullups built in.

I've tested using one, two and three sensors. The number doesn't appear to make any difference. However, when I switch out the top and bottom sensors for new ones, there haven't been any problems. So it could be at the hardware level.

I observed no problems while mocking up the unit on a breadboard - using jumper wires only. So that's a good question regarding the wire length - the distance to the further sensor is around 2 metres. I understand that might be an issue. I've tried slowing the bus speed using Wire.setClock(40000) to try and get some extra range but it's difficult to confirm if that's had any effect. I'm still seeing the problem, in any case.

I may try using a CAT5 cable instead of the basic wire I'm currently using.

One last thing - after the problem occurs and I soft reboot the Wemos, I get the following when I tune into the Wemos' boot baud rate:

Fatal exception (0): 
epc1=0x40100003, epc2=0x00000000, epc3=0x00000000, excvaddr=0x00000000, depc=0x00000000

The code I've used for testing today:

#include <ESP8266WiFi.h>
#include <Printers.h>
#include <XBee.h>
#include <Wire.h>
#include <I2CSoilMoistureSensor.h>
#include <SoftwareSerial.h>

#define I2C_SCL_PIN D3
#define I2C_SDA_PIN D4

const unsigned int sleepTime = 10 * 1000;

I2CSoilMoistureSensor topSensor(0x21);
I2CSoilMoistureSensor middleSensor(0x22);
I2CSoilMoistureSensor bottomSensor(0x23);

ZBTxStatusResponse txStatus = ZBTxStatusResponse();

void setup() {
  // Set up debug serial console
  Serial.begin(9600);

  // Set up I2C interface, lower the clock speed for extra range
  Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
  Wire.setClock(40000);

  // Configure onboard indicator led pin
  pinMode(BUILTIN_LED, OUTPUT);

  // Initialise sensors
  topSensor.begin();
  middleSensor.begin();
  bottomSensor.begin();
  delay(1000); // Give some time to sensors to boot up

  Serial.println("Top Sensor");
  Serial.print("Address: ");
  Serial.println(topSensor.getAddress(),HEX);
  Serial.print("Firmware version: ");
  Serial.println(topSensor.getVersion(),HEX);
  Serial.println();
  Serial.println();

  Serial.println("Middle Sensor");
  Serial.print("Address: ");
  Serial.println(middleSensor.getAddress(),HEX);
  Serial.print("Firmware version: ");
  Serial.println(middleSensor.getVersion(),HEX);
  Serial.println();
  Serial.println();

  Serial.println("Bottom Sensor");
  Serial.print("Address: ");
  Serial.println(bottomSensor.getAddress(),HEX);
  Serial.print("Firmware version: ");
  Serial.println(bottomSensor.getVersion(),HEX);
  Serial.println();
  Serial.println();

  /* Serial.println("Setting sensor address to 0x23");
  if (bottomSensor.setAddress(0x23,true))
    Serial.println("... DONE");
  else
    Serial.println("... ERROR"); */
}

void loop() {
  // Wake sensors from sleep
  topSensor.resetSensor();
  middleSensor.resetSensor();
  bottomSensor.resetSensor();
  delay(1000); // Give some time to sensors to boot up

  // Take readings
  while (topSensor.isBusy()) delay(50);
  unsigned int topCapacitance = topSensor.getCapacitance();
  int topTemperature = topSensor.getTemperature();

  while (middleSensor.isBusy()) delay(50);
  unsigned int middleCapacitance = middleSensor.getCapacitance();

  while (bottomSensor.isBusy()) delay(50);
  unsigned int bottomCapacitance = bottomSensor.getCapacitance();

  // Put sensors to sleep
  topSensor.sleep();
  middleSensor.sleep();
  bottomSensor.sleep();

  Serial.print("Soil Capacitance: ");
  Serial.print("top = ");
  Serial.print(topCapacitance);
  Serial.print(", middle = ");
  Serial.print(middleCapacitance);
  Serial.print(", bottom = ");
  Serial.print(bottomCapacitance);
  Serial.println();

  Serial.print("Temperature: ");
  Serial.print("top = "); 
  Serial.print(topTemperature/(float)10);
  Serial.println();
  Serial.println();

  delay(sleepTime);
}
Miceuz commented 7 years ago

Hi @simonrobb, thanks for the report.

Might be the wiring. Using cat5 or just twisting the wire might improve things significantly. Do you have a scope to look at the waveform? Maybe attiny441 in sleep mode has some different requirements for the incoming signals than in active mode and it just does not recognize the address. Or goes into some forever loop...

Another thing, the fact that Wemos goes the way of the dodo is really strange. Maybe Wemos I2C library does not handle problems on the bus properly. Sensor does not answer for once because of the long wire, this throws Wemos I2C driver off track. Can you try this on Arduino Uno?

krikk commented 7 years ago

i think i did have the same problem and i think i solved it:

if you send the sensor to sleep you have to "wake" it before getting any reading... example code: first call after sleep:

topSensor.getVersion(); // could be any call i think this is just for waking the sensor
delay(1000); //give him time to get awake
topSensor.getTemperature();
Miceuz commented 7 years ago

It should wake up in much shorter time. 17 microseconds should be enough according to the datasheet.

This might be related to the fact that the MCU is being operated outside of it's specification - 16MHz at 3.3V is too fast, but I did not notice any problems before, as usually functions related to writing flash and eeprom memory fail first.

krikk commented 7 years ago

ok 1000 ms delay was a bit high, i reduced it to 20 ms and it also works...

Miceuz commented 7 years ago

@krikk can you try reducing it to 1ms?

krikk commented 7 years ago

works also with 1ms :)

krikk commented 7 years ago

..but i noticed something strange: if i do a i2c scan (search for available devices) while the soilmoisturesensor is in sleep mode, it scans correctly until the device id of the soilmoisturesensor, but then i get: Unknown error at address 0x48

...after this the whole i2c bus is "crashed" and does only recovery with powercycling the whole setup

Miceuz commented 7 years ago

@krikk thanks for your contribution. What platform are you using to do the scan? What voltage is used to power the sensor?

simonrobb commented 7 years ago

@krikk thanks for the tip; I did have a call to sensor.begin() in place until @Miceuz advised earlier in the thread it probably wasn't necessary. It didn't seem to make any difference when it was there.

The issue you're seeing now sounds very similar to what I have, in that the sensor is non-responsive, then the entire I2C bus goes down, and requires a hard reset to get it working again.

simonrobb commented 7 years ago

@Miceuz sorry I've been unable to try on an Uno yet - I do have one at my workshop, but haven't been there in the last week. As to the scope, I can probably get one but I've never used one before. Is there anything in particular that I should look for?

Miceuz commented 7 years ago

@simonrobb the fact that entire I2C bus is non responsive is really suspicious. This means that either esp8266 I2C library is not robus enough or that the sensor hogs down the bus by dragging it down. Can you check with scope or multimeter the voltages on SCL and SDA lines - are they pulled low or high? The best would be if you could get the bus to hang, then stop all the activity on it and then measure the voltages.

krikk commented 7 years ago

i did my tests on a wemos d1 R2, a esp8266 board, the code: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P047_i2c-soil-moisture-sensor.ino#L116

powersupply, 3.3v directly attached to board, board is powered with usb

...i will try to measure the voltage, but i only have a cheap multimeter no oscilloscope

acbap commented 7 years ago

I am working on a similar setup (d1 mini connected to 3 or more sensors w/6ft cables), and having the same issues. I figured I have overestimated what I could do with cable length and number of sensors, but it is hard to give up on it, considering it has worked for up to 40mins without crashing. After seeing this thread, I added Wire.setClock(40000), which seems to help. Also, playing with other tricks mentioned in this thread didn't hurt.

I have very little (no) experience or diagnostic tools, so I am eagerly following this.

krikk commented 7 years ago

so did check the voltage of SCL and SDA lines when the error occures:

test with sleep mod ENABLED: SCL and SDA are normally on 3.3 V, i read this with my multimeter, but if the sensor is in "sleep" mode and i do a i2c scan (search for available devices) the voltage of the SDA line suddenly drops to 0.0V, SCL line stays on 3.3 V, if i disable sleep mode and do the same, SDA stays at 3.3 V

Miceuz commented 7 years ago

So, I finally got around to test this. The bug you were experiencing was caused by a combination of clock stretching from the sensor side and default settings in the ESP8266 I2C library.

When Attiny441 receives I2C address that it recognises, it drags the SCL line low and then goes into the wakeup sequence, stretching the clock line until it is ready to service request. It appears to be taking about 2ms to do that.

Default ESP8266 I2C library waits for 230us before sending garbage into the bus. This throws Attiny441 off as it releases clock line and keeps SDA line down waiting for ACK clock pulse. A timeout definitely would help in the sensor side, but the problem can be easily mitigated on the master side, it enough to specify clock stretching timeout long enough: Wire.setClockStretchLimit(2500);

My test sketch that illustrates the problem and the remedy:

#include <I2CSoilMoistureSensor.h>

I2CSoilMoistureSensor topSensor(0x20);
#include <Wire.h>

void setup() {
  Serial.begin(9600);

  Wire.begin();
  Wire.setClock(40000);
  Wire.setClockStretchLimit(2500);  

  topSensor.begin();
  delay(1000); // Give some time to sensors to boot up

  Serial.println("Top Sensor");
  Serial.print("Address: ");
  Serial.println(topSensor.getAddress(),HEX);
  Serial.print("Firmware version: ");
  Serial.println(topSensor.getVersion(),HEX);
  Serial.println();
  Serial.println();

}

uint32_t ts=0;
void loop() {
  int topTemperature = topSensor.getTemperature();

  delay(5000);
  ts = micros();
  Wire.beginTransmission(0x20);
  Wire.endTransmission();
  Serial.print("Nonsleeping scan took ");
  Serial.println(micros() - ts);
  delay(5000);

  topSensor.sleep();

  delay(5000);
  ts = micros();
  Wire.beginTransmission(0x20);
  int t = Wire.endTransmission();
  Serial.print("Sleeping scan took ");
  Serial.println(micros() - ts);
  Serial.print("finished with code: ");
  Serial.println(t);

  delay(5000);

}
simonrobb commented 7 years ago

@Miceuz Thanks so much for this. I've incorporated it into my code and I'll keep a close eye on the issue. Since it's intermittent, it's going to be difficult for me to say it's fixed 100% - but if we don't see the bug come up in the next few weeks of my testing let's call it fixed. It certainly makes sense (with my limited understanding of I2C).

Thanks again for the help.

Apollon77 commented 7 years ago

Should I incorporate something of this into my library or in the README ?! What you think? To be honest, you lost me a bit somewhere in the discussion :-)

Miceuz commented 7 years ago

@simonrobb I think this is really fixing the issue. @Apollon77 I'm not sure if Wire.setClockStretchLimit(2500) is a standard call in all Arduino Wire libraries, if so, it wouldn't hurt to add it to the begin() function. Or at least add it with guards for ESP8266 environment.

Apollon77 commented 7 years ago

Then I would add it more to the README because I explicitely decided to have the Wire-initializsation outside of the class exactly to allow the users to handle such specific stuff in their code without need to change the library ...

derSeddy commented 7 years ago

I recently ordered the rugged version of Miceuz's sensor, which comes with a 1m cable. I was able to communicate with the sensor using my Wemos D1 mini esp8266, but some values where very odd. For example the temperature read fine but only if it was above 255, else I would get 255 or 127. Miceuz pointed me in the right direction when he asked me to tinker with the suggestions in this thread. I was able to solve the issue by slowing down the bus speed with Wire.setClock(x), in my case I chose 50000. But here is the twist: In the current master of the esp8266 libraries, Wire.setClock([40000 or 50000]) does not decrease the clock speed. The lowest possible clock speed is 100000, which is already the default value. I don't know why some people got the impression that this change would help. The problem lies in the implementation of Wire.setClock (actually the underlying twi_setClock), which will set the delay timings based on a predefined list of possible timings. I added a definition for 50000 and created a merge request for the change, see https://github.com/esp8266/Arduino/pull/3401. I suggest that we wait until the merge request is merged and then add a hint to the readme that for longer cables decreasing the bus speed is recommended.

simonrobb commented 7 years ago

@derSeddy good pick up. I didn't test setting the clock speed in isolation, so I'd guess the reason the fix worked was because of the line Wire.setClockStretchLimit(2500) as @Miceuz suggested. He is better positioned to comment on that than me.

Further to that, clock stretching was most definitely the issue, and I've since moved away from the ESP8266 in favour of micropython running on an ESP32. I had the same problems which were immediately fixed by lowering the clock speed.

lukecyca commented 6 years ago

I experienced the same problem on an ESP8266. I tried with Wire.setClockStretchLimit(2500) and still experienced the problem. However, with Wire.setClockStretchLimit(4000) it worked correctly.