RobTillaart / SHT31

Arduino library for the SHT31 temperature and humidity sensor

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Question on i2c error handling

spyder0069 opened this issue · comments

Do you have a suggested way of handling errors? I didn't see it in the examples.

Currently I am checking the status and if its less that FFFF then I am assuming I found the sensor.

I am using the sht31 on a probe that can be plugged or unplugged and I noticed that during an unplug I got invalid data as it probably interrupted the read stream. The humidity then showed 100 and the temp showed 1085. Is there a way to to check if the data read back is valid?

One other question. I saw in the code it mentions some differences when using a ESP32. I am using the ESP32. Do I need to do anything other than what is in the examples? Or do I have to set it somewhere in my code to tell the library I have a ESP32? Thanks for the help!

Do you have a suggested way of handling errors?

That is one of the requirements tensions with Arduino libraries, you want to make them reliable as possible and as small as possible. So sometimes I build it in a library, mostly on request of a user or if a sensor is less reliable.

reading the status should work more or less but it is intended only to work when there is connection.

I am using the sht31 on a probe that can be plugged or unplugged ...

So if I interpret your question, you want an isConnected() method, and maybe even some checks build in on the I2C communication. This latter is mostly implemented without any checking to keep footprint smaller.

I will create a branch with isCOnnected method and would ask you if you can verify it if it meets your needs.

I have a 0.2.3 update to do first - it are minor changes only

@spyder0069
Please verify the code in the branch isConnected #9
The function is straightforward and there is an example added

Is there a way to to check if the data read back is valid?

In the meanwhile I will check if the low level functions can do some more "connection checking"

Thank you for looking into this. I should be able to try the isConnected option later this afternoon.

I was looking through your code and it looks like there is crc in place. Would I use it something like this:

bool crcpassed;
crcpassed=sht.read();
if(crcpassed){
  //data is good go ahead 
  float t =sht.getTemperature();
}

I have two sensors that are separated using the same CLK but different SDA. I have it working with your code by using something similar to this

If(sensorID==0){  
  Wire.endTransmission(true);
  Wire.begin(sensor0,sensorCLK);
  Wire.setClock(100000);
  sht.begin(0x44);
  sht.read();
  float t =sht.getTemperature();
  float h=sht.getHumidity();
  Wire.endTransmission(true);
}else{
  Wire.endTransmission(true);
  Wire.begin(sensor1,sensorCLK);
  Wire.setClock(100000);
  sht.begin(0x44);
  sht.read();
  float t =sht.getTemperature();
  float h=sht.getHumidity();
  Wire.endTransmission(true);
}

Do you see any issues with using it like this? I do also have a i2c clock and lcd on a seperate bus which is why I end the transmissions and then change the pins with the wire.begin statements.

TIP: add cpp after the three backquotes and you get syntax highlighting (see your post above)

Yes you can do a CRC check on the data.

bool crcpassed = sht.read(false);  // use slow version which does a CRC check by setting the fast flag to false
if (crcpassed) 
{
  //data is good go ahead 
  float temperature = sht.getTemperature();
  display(temperature); 
}

I'm working on a 0.3.0 version with more checks for errors. Will make a new branch tonight so you can have a sneak preview.

Is it not easier to have two sensors with different address?

have a look at this snippet

uint8_t sensorSDA[2] = { 23, 24 }; // make an array out of it.
uint8_t sensorCLK

sht.begin(0x44, sensorSDA[sensorID], sensorCLK);   // This is the ESP32 version for begin which combines some stuff.
Wire.setClock(100000);
bool crcpassed = sht.read(false);  // force crc check.
if (crcpassed) 
{
  float temperature = sht.getTemperature();
  float h = sht.getHumidity();
  // process data
}
else
{
  // handle error
}
// etc...

Tip: spaces makes code more readable

One other question. I saw in the code it mentions some differences when using a ESP32. I am using the ESP32. Do I need to do anything other than what is in the examples? Or do I have to set it somewhere in my code to tell the library I have a ESP32? Thanks for the help!

The only difference for the ESP32 is the begin(addr, SD, CLK) call which is not available for the UNO
This call allows the ESP to set the SDA and CLK pins in a single begin() call.

You can see this in the .h and .cpp file of the library, see these lines in SHT31.h

#if defined(ESP8266) || defined(ESP32)
  bool begin(const uint8_t address, uint8_t dataPin, uint8_t clockPin);
#endif

This is called conditional compiling (don't know your level of prog skills)

"Is it not easier to have two sensors with different address?"

If you are asking about having a different i2c address like 0x45 and have them on the same bus then in my project that won't work. I have probes made up but they are identical and removable. On the same port I am also choosing to run a sht31 or a ds18b20 one wire probe. The sensors have to be hot swappable with just my code changing between them. I was running the adafruit library but I was seeing errors and having sht31 sensors stop responding. Plus now they have a separate bus library that has to be added and its pretty complex. Your approach seems to be having better results so far.

I did not know about the conditional compiling. That is above my knowledge. I will implement passing the sda/sck to the begin statement instead of changing it manually with the wire. Since I will be switching between the two is it appropriate to do the Wire.endTransmission(true); before the next begin (and is that the correct statement for that process)? I just wonder if I need to shut down any previous .begin before starting a new one. Not sure how the wire library stuff works.

Can you post your sketch? (Zip file)
I can have a look at it.

example_code.zip

Here is a snip of my routine handling the sensors.

TIP: Using CTRL-T in the arduino-IDE does autoformat the code making it more readable. (indents and spaces)

I'll have a look


does it work if the sensors stay connected?

a first partial rewrite

void RH_hardreset(byte portid, word resetdelay)
{
  enablemydebug = 1;

  mydebug("SHT31 #" + String(portid) + " hard reset for " + String(resetdelay));

  pinstate = digitalRead(sensorCLK);
  mydebug("sensorCLK state=" + String(pinstate));

  pinMode(sensorCLK, OUTPUT);

  int pin = sensor0;
  if (portid == 1) pin = sensor1;
  mydebug("pin=" + String(pin));

  pinstate = digitalRead(pin);
  mydebug("pinstate=" + String(pinstate));

  pinMode(pin, OUTPUT);
  digitalWrite(pin, LOW);
  if (RHhardresetLOG[portid] < 30000)  RHhardresetLOG[portid] ++;

  digitalWrite(sensorCLK, LOW);
  delay(resetdelay);

  pinMode(pin, INPUT);
  pinMode(sensorCLK, INPUT);
  delay(50);
  enablemydebug = 0;
}

I do not understand why you are toggling the CLK and SDA pins of the I2C bus? to reset it?
Better let the Wire class handle that, it is made for that, Just call Wire.begin() again - I think...

To be continued tomorrow.

Good tip. It organizes some of my code and some it messes up. To be honest I never expect anyone to look at my code but me. LoL. Currently every works. The CRC check is doing the trick. I've been unplugging and plugging the sensor in and other than the occasional esp message about the bus not liking it my retry routine recovers and gives good data. I just need to let it run and see how it does over time.

TIP:
I am actually running these with just 3 wires. I have a Schottky diode on the scl and feed that to a capacitor that powers the SHT31 so it works like parasite power. :^) I'm sure the heater won't work due to power but I don't have that implemented.

When using the adafruit sht library after 2 minutes to about 2 days I would sometimes have a sensor lockup. I have several of these projects running and so far I was only able to recreate it with two devices so I don't 100% have it narrowed down. Basically the sensor stopped responding and software resets didn't make a difference. I needed to cut power to the sensor. Since my project uses the parasite power I have to drop both io to ground and let the cap drain so that the voltage to the sensor goes below its operating threshold and it reboots. Then I can recover from there. Your library seems to be working better. So the RH_hardreset routine is an effort to bandaid what is happening until I can further determine if its something in the library, something with that particular sensor, or something with that particular device. So I am mid experimenting with all this. :^)

To be honest I never expect anyone to look at my code but me.

From experience, when you see your code in 3 months time you might need to read it twice (or more)

The CRC check is doing the trick.

So that solves the problem you started this issue for. Good to hear.

TIP:
I am actually running these with just 3 wires. I have a Schottky diode on the scl and feed that to a capacitor that powers the SHT31 so it works like parasite power. :^) I'm sure the heater won't work due to power but I don't have that implemented.

Clever trick, your background is Electrical Engineer?

Refactored a bit

  • moved log counter to top and added it to log
  • moved all CLK operation together
  • moved all DATAPIN operations together
  • added a **Wire,begin() ** at the end.
void RH_hardreset(byte portid, word resetdelay)
{
  enablemydebug = 1;

  // LOGGING
  if (RHhardresetLOG[portid] < 30000)  RHhardresetLOG[portid] ++;
  mydebug("SHT31 #" + String(portid) + " hard reset for " + String(resetdelay));
  mydebug("Count: " + String(RHhardresetLOG[portid]) );

  // CLOCK PIN
  pinstate = digitalRead(sensorCLK);
  mydebug("sensorCLK state=" + String(pinstate));
  pinMode(sensorCLK, OUTPUT);
  digitalWrite(sensorCLK, LOW);

  // DATA PIN
  int datapin = sensor0;
  if (portid == 1) datapin = sensor1;
  mydebug("datapin = " + String(datapin));
  pinstate = digitalRead(datapin);
  mydebug("datapin state = " + String(pinstate));
  pinMode(datapin, OUTPUT);
  digitalWrite(datapin, LOW);

  // DRAIN THE CAPACITOR
  delay(resetdelay);
 
  // RESTART WIRE BUS
  mydebug("Restart Wire bus");
  Wire.begin(0x44, datapin, sensorCLK);  // restart Wire bus might be more correct.

  enablemydebug = 0;
}

Idea:
What you might add is an analogPort to measure the voltage of the capacitor.
Then the system really can wait until the cap is empty (certain time below a certain voltage)

some snippet how I would have setup the other code,
split the large getRH() in some more functions that have a focussed function.
gives a lot less nested if statements

void getRH()
{
  bool done = false;
  int maxRetries = 3;
  int retries = 0;

  while (not done and (retries < maxRetries))
  {
    done = readRH();
    if (not done) RH_hardreset();
    retries++;
  }
  
  if (not done) // means an error
  {
    // handle error
  }
  else
  {
    // display values
  }
}


bool readRH()
{
  enablemydebug = 1;

  Wire.endTransmission(true);
  if (not sht.begin(0x44, datapin, sensorCLK))
  {
    // update errorCode
    return false;
  }
  Wire.setClock(100000);

  if (not sht.isConnected() ) 
  {
    // update errorCode
    mydebug("SHT31 #" + String(RHid) + " Connection: FAILED");
    return false;
  }

  bool crcpassed = sht.read(false);    // use slow version which does a CRC check by setting the fast flag to false
  if (not crcpassed)
  {
    // update errorCode
    mydebug("SHT31 #" + String(RHid) + " CRC: FAILED");
    return false;
  }

  float t = sht.getTemperature();
  mytempdigital[RHid] = t * 10;
  if (displaytype == 0) mytempdigital[RHid] = ((t * 1.8) + 32.0) * 10.0;

  float h = sht.getHumidity(); 
  if (h >= 100)                   // 100% IS A VALID VALUE, NOTE THE LIBRARY CANNOT RETUR VALUES > 100.  (sht31.cpp line 193)
  {
    errorcode[RHid] = 2;  //if a read error this usually is 100 or more so flag it as a failure
  };        
  myRHdigital[RHid] = h;

  mydebug("SHT31 #" + String(RHid) + " CRC: PASSED");
  mydebug("SHT31 #" + String(RHid) + " TEMP: " + String(mytempdigital[RHid] / 10) + "." +  String(mytempdigital[RHid] % 10));
  mydebug("SHT31 #" + String(RHid) + " HUMIDITY: " + String(myRHdigital[RHid]) + "%");

  enablemydebug = 0;
}

Everything worked well overnight. Thanks for your help and i will take a look at your suggestions.

"What you might add is an analogPort to measure the voltage of the capacitor."

Can't see the voltage behind the diode. Diode is necessary so that it doesn't cause problems with clk and so that you can run one clk to all sensors and they don't interfere. Time based holding of clock low works since all caps are going to drain close to the same amount of time. Just go on the longer side of the delay to be safe. :^)

Thanks for your time and if you haven't already I think its safe to commit the isConnected changes to your library. Have a great holiday!

I will merge the isConnected branch asap

Done, 0.2.4 released.