Interface library for the Sensirion SHT7x series humidity and temperature sensors.

This was developed and tested on the LPC1768 board.

Currently, it doesn't work for LPC11U24. I'm investigating why.

sht7x.h

Committer:
JacobBramley
Date:
2013-08-11
Revision:
0:f1a93e55feb5

File content as of revision 0:f1a93e55feb5:

// Copyright (c) 2012-2013 Jacob Bramley
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#include <stdint.h>
#include "mbed.h"

#ifndef SHT7X_SHT7X_H
#define SHT7X_SHT7X_H

namespace sht7x {

//! The Reading class holds a generic reading.
//!
//! This class is used by the Temp and Hum classes, and cannot be instantiated
//! by itself.
class Reading {
 public:
  //! Return the raw reading value.
  //!
  //! Only the low-order bits are relevant. Use get_bits() to determine how many
  //! bits are part of the reading.
  inline uint32_t get_raw() const {
    return (is_valid()) ? reading : 0;
  }

  //! Return the number of bits in the reading.
  inline uint32_t get_bits() const {
    return bits;
  }

  //! Check the validity of the reading.
  //!
  //! Only the default constructor produces an invalid reading, so this can be
  //! used to determine whether or not a reading was taken.
  inline bool is_valid() const {
    return (bits > 0);
  }

 protected:
  Reading(uint32_t bits, uint32_t reading)
      : bits(bits), reading(reading) {}

  // The default constructor creates an invalid reading.
  Reading() : bits(0) {}

 private:
  uint32_t bits;
  uint32_t reading;
};

//! The Temp class holds a temperature reading.
class Temp : public Reading {
 public:
  //! Initialize a Temp object with a raw reading.
  //!
  //! @param bits
  //!  The number of significant bits in the reading.
  //!
  //! @param reading
  //!  The raw reading taken from the SHT7x.
  //!
  //! @param mvdd
  //!  The supply voltage.
  //!
  //!  The coefficients used for the temperature calculation depend upon the
  //!  supply voltage to the sensor. Specifying this voltage allows for the
  //!  reading to be adjusted. The adjustment is applied to all of the get_*()
  //!  methods except get_raw().
  Temp(uint32_t bits, uint32_t reading, uint32_t mvdd = mvdd_default)
      : Reading(bits, reading), mvdd(mvdd) {}

  //! Initialize an empty object.
  //!
  //! The resulting object's is_valid() function will return false. It should
  //! be passed into SHT7x::measure() to get a valid reading.
  Temp() : Reading() {}

  //! Return the reading (float).
  float get_f() const;

  //! Return the reading as a 64-bit signed value with 32 fractional bits.
  //!
  //! The rounding mode is round-to-nearest, with ties away from zero.
  int64_t get_q32() const;            // Fixed-point (q32)

  //! Return the reading as a 32-bit signed value.
  //!
  //! @param fbits
  //!  The number of fractional bits desired in the result.
  //!
  //! The rounding mode is round-to-nearest, with ties away from zero.
  int32_t get_fixed(int fbits) const; // Fixed-point (q0-q32)

  //! Return the reading as an integer.
  //!
  //! The rounding mode is round-to-nearest, with ties away from zero.
  int32_t get_i() const {             // Integer
    return get_fixed(0);
  }

  //! Default supply voltage setting.
  //!
  //! This setting is the recommended supply voltage according to the
  //! datasheet.
  static uint32_t const mvdd_default = 3300;

 private:
  uint32_t mvdd;

  // The 'd1' coefficient used for the temperature calculation depend upon
  // the supply voltage to the sensor. These methods calculate and return the
  // proper coefficient, based on mvdd.
  float get_d1_f() const;
  int64_t get_d1_q32() const;
};

//! The Hum class holds a humidity reading.
class Hum : public Reading {
 public:
  //! Initialize a Hum object with a raw reading.
  //!
  //! @param bits
  //!  The number of significant bits in the reading.
  //!
  //! @param reading
  //!  The raw reading taken from the SHT7x.
  //!
  //! @param temp
  //!  A recent temperature reading.
  //!
  //!  The humidity reading is dependant on temperature. By providing a Temp, it
  //!  will be used to adjust humidity readings accordingly.
  //!
  //!  Note that the Temp object is not copied, so it must remain in scope at
  //!  least until the last Hum::get_*() call (other than get_raw()).
  Hum(uint32_t bits, uint32_t reading, Temp const * temp = NULL)
      : Reading(bits, reading), temp(temp) {}

  //! Initialize an empty object.
  //!
  //! The resulting object's is_valid() function will return false. It should be
  //! passed into SHT7x::measure() to get a valid reading.
  Hum() : Reading() {}

  //! Return the reading (float).
  float get_f() const;

  //! Return the reading as a 64-bit signed value with 32 fractional bits.
  //!
  //! The rounding mode is round-to-nearest, with ties away from zero.
  int64_t get_q32() const;            // Fixed-point (q32)

  //! Return the reading as a 32-bit signed value.
  //!
  //! @param fbits
  //!  The number of fractional bits desired in the result.
  //!
  //! The rounding mode is round-to-nearest, with ties away from zero.
  int32_t get_fixed(int fbits) const; // Fixed-point (q0-q32)

  //! Return the reading as an integer.
  //!
  //! The rounding mode is round-to-nearest, with ties away from zero.
  int32_t get_i() const {             // Integer
    return get_fixed(0);
  }

 private:
  Temp const * temp;
};

enum Precision {
  PRECISION_HIGH = 0x0,
  PRECISION_LOW = 0x1,
  PRECISION_KEEP = -1
};

enum OTP {
  OTP_ON = 0x0,
  OTP_OFF = 0x2,
  OTP_KEEP = -1
};

enum Heater {
  HEATER_OFF = 0x0,
  HEATER_ON = 0x4,
  HEATER_KEEP = -1
};

enum Battery {
  BATTERY_GT_2_47 = 0x00,
  BATTERY_LT_2_47 = 0x40
};

struct Status {
  Precision precision;
  OTP otp;
  Heater heater;
  Battery battery;
};

enum State {
  STATE_UNKNOWN,
  STATE_SLEEP,
  STATE_RESETTING,
  STATE_INITIALIZING,
  STATE_SETTING_CONFIGURATION,
  STATE_GETTING_CONFIGURATION,
  STATE_MEASURING_TEMPERATURE,
  STATE_MEASURING_HUMIDITY,
  STATE_COMMS_ERROR
};

//! Sensirion SHT7x Interface Class
//! 
//! This class interfaces with a Sensirion SHT7x series humidity and temperature
//! sensor. The SHT7x series implement a protocol similar to I²C, but
//! unfortunately not similar enough that the LPC I²C peripheral can communicate
//! directly. This implementation uses GPIO.
//! 
//! Other Sensirion sensors may also be compatible, but this code has only been
//! tested with SHT71 and SHT75.
//! 
//! The SHT7x interface functions in this class will return 'false' to indicate
//! a fault. A fault can be caused by one of the following events:
//! 
//!   - The class is in the wrong state. After a reset, it is necessary to call
//!     initialize() before any communication with the sensor.
//!   - A communications error is detected, most likely in the CRC check.
//!     - The CRC byte sent from the sensor takes the status byte into account,
//!       so any CRC check will fail if the low-voltage detection bit has
//!       changed since it the status byte was last read.
//! 
//! There is no way to determine the cause of a fault. The suggested
//! error-handling routine is as follows:
//! 
//!   - Optionally, call status() to correct problems where the low-voltage
//!     detection bit in the status byte has changed. This should not be
//!     necessary if the power supply is known to be stable.
//!   - If that fails, call reset() and then initialize() to force a reset.
//!     - This operation is relatively slow since initialize() takes 11ms.
//! 
//! The class is not thread-safe and interface functions are not re-entrant.
//! 
//! The datasheet can (as of early 2012) be found here:
//!  http://www.sensirion.com/en/pdf/product_information/Datasheet-humidity-sensor-SHT7x.pdf
//! 
//! Example:
//! @code
//! #include "mbed.h"
//! #include "sht7x.h"
//! 
//! bool do_measure(sht7x::SHT7x & sensor) {
//!   sht7x::Temp temp;
//!   sht7x::Hum hum;
//! 
//!   if (!sensor.measure(temp, 5000)) {
//!     printf("Couldn't measure temperature.\n");
//!     return false;
//!   }
//! 
//!   if (!sensor.measure(hum, &temp)) {
//!     printf("Couldn't measure humidity.\n");
//!     return false;
//!   }
//! 
//!   printf("Temperature: %.3f degrees C.\n", temp.get_f());
//!   printf("Humidity: %.3f%% relative humidity.\n", hum.get_f());
//!   
//!   return true;
//! }
//! 
//! int main() {
//!   Serial serial(USBTX, USBRX);
//!   serial.baud(115200);
//!   serial.format(8, Serial::None, 1);
//! 
//!   sht7x::SHT7x sensor(p27, p28);
//!   // Set up communications with the sensor.
//!   sensor.initialize();
//! 
//!   printf("\n======== SHT7x Example ========\n\n");
//!   printf("==== Configuration: PRECISION_HIGH, OTP_ON ====\n");
//!   sensor.configure(sht7x::PRECISION_HIGH, sht7x::OTP_ON);
//! 
//!   while (1) {
//!     if (!do_measure(sensor)) {
//!       printf("==== Resetting sensor... ====\n");
//!       sensor.reset();
//!       sensor.initialize();
//!       continue;
//!     }
//!     wait(3);
//!   }
//! }
//! @endcode
//! 
//! TODO: It might be possible to use I²C for the majority of communications,
//! but switch the pin mapping to GPIO to wait for a reading. (This seems to be
//! the only part that is not sufficiently I²C-compliant.) Is it worth
//! investigating this?
class SHT7x {
 public:
  //! Construct a new SHT7x interface object.
  //!
  //! The constructor does not communicate with the sensor in any way, but it
  //! assigns and configures the specified input and output pins.
  SHT7x(PinName sck, PinName sda);

  //! Reset the sensor and restore its configuration to default values.
  //!
  //! Note that it should not normally be necessary to call this method manually
  //! because the sensor resets automatically when turned on.
  //!
  //! After calling reset, the sensor will be in the power-on state, so it is
  //! necessary to call initialize before issuing any other commands.
  bool reset();

  //! Initialize the sensor and communications channel.
  //!
  //! This takes 11ms, and must be done manually before using the sensor for the
  //! first time after a hard or soft reset. For this reason, it is advisable to
  //! call initialize() in advance, rather than on-demand.
  bool initialize();

  //! Configure the sensor's status byte.
  //!
  //! @param precision
  //!  High-precision readings take longer to measure than low-precision
  //!  readings. As a result, they will probably require more energy.
  //!
  //! @param otp
  //!  By default, the sensor will reload OTP calibration data before each
  //!  measurement. It is possible to disable this mechanism. However, the
  //!  datasheet is not clear about the consequences of this, except that
  //!  disabling it can take about 10ms off the measurement time.
  //!
  //! @param heater
  //!  The heater can be used to verify the operation of the sensor. When the
  //!  heater is activated, the temperature reading should increase and the
  //!  humidity reading should decrease. Note that the heater should not be
  //!  left on permanently because it could cause damage. The datasheet is not
  //!  clear about how long it can be left on for, however.
  //!
  //! Setting any of the values to *_KEEP will cause the existing values to
  //! remain unchanged.
  bool configure(Precision precision,
                 OTP otp = OTP_KEEP,
                 Heater heater = HEATER_KEEP);

  //! Retrieve the sensor's status byte.
  bool status(Status * & status);

  //! Take a temperature measurement.
  //!
  //! @param temp
  //!  The sht7x::Temp object to populate.
  //!
  //! @param mvdd
  //!  The supply voltage to the sensor. This is used to adjust the sensor
  //!  reading as specified by the datasheet. The datasheet recommends using a
  //!  3.3V supply voltage for maximum accuracy.
  //!
  //! The temperature reading can vary by approximately 0.8 degrees across the
  //! specified voltage range (2.4-5.5V). If this accuracty is sufficient, or
  //! if you only want to read the raw data, you may ignore mvdd.
  bool measure(Temp & temp, uint32_t mvdd = Temp::mvdd_default);


  //! Take a humidity measurement.
  //!
  //! @param hum
  //!  The sht7x::Hum object to populate.
  //!
  //! @param temp
  //!  A temperature reading. This is used to adjust the humidity reading as
  //!  specified by the datasheet.
  //!
  //! The humidity reading can vary by approximately 2% relative humidity
  //! across the operating temperature and humidity ranges. If this accuracy is
  //! sufficient, or if you only want to read the raw data, you may ignore temp.
  bool measure(Hum & hum, Temp const * temp = NULL);

 private:
  // Private types and fields.

  enum Command {
    CMD_READ_TEMP       = 0x03,
    CMD_READ_HUM        = 0x05,
    CMD_READ_STATUS     = 0x07,
    CMD_WRITE_STATUS    = 0x06,
    CMD_RESET           = 0x1e
  };

  // Pin states, as driven by the microprocessor. Because sda is open-drain,
  // its real value may differ in the PIN_1 and PIN_FREE cases.
  enum pin_State {
    PIN_0               = 0x0,
    PIN_1               = 0x1,
    PIN_FREE            = 0x2,
    PIN_INVALID         = 0x3
  };

  // Communications pins.
  DigitalOut sck_;
  DigitalInOut sda_;
  pin_State sck_state_;
  pin_State sda_state_;

  // Sensor (local) state.
  State state_;

  // Cached status register bits.
  Status status_;

  // Utilities
  uint32_t encode_status_byte() const;
  void     decode_status_byte(uint32_t value);

  uint32_t bits_required_temperature() const;
  uint32_t bits_required_humidity() const;

  uint32_t time_atom() const;
  uint32_t time_reset() const;
  uint32_t time_reading_temperature() const;
  uint32_t time_reading_humidity() const;

  // Communications
  void start();

  void put_bit(int b);
  int  get_bit();

  void put_ack(int b);
  void get_ack();

  void     put_byte(uint32_t value);
  uint32_t get_byte();

  int  get_sda();
  void set_sda(int pin);
  void set_sda(pin_State pin);
  void set_sck(int pin);
  void set_sck(pin_State pin);

  // Utility methods.
  void reset_comms();
  void command(Command command);
  bool check_state(State s0);
  bool check_state(State s0, State s1);
  bool check_state(State s0, State s1, State s2);
  static int payload_size(Command command);
  bool status();

  // Error handling.
  bool check_crc(Command command, uint32_t data, uint32_t crc);
};

} // namespace sht7x

#endif // SHT7X_SHT7X_H