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.

Files at this revision

API Documentation at this revision

Comitter:
JacobBramley
Date:
Sun Aug 11 18:11:05 2013 +0000
Commit message:
First Mbed release of the Sensirion SHT7x interface library.

Changed in this revision

sht7x.cpp Show annotated file Show diff for this revision Revisions of this file
sht7x.h Show annotated file Show diff for this revision Revisions of this file
diff -r 000000000000 -r f1a93e55feb5 sht7x.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sht7x.cpp	Sun Aug 11 18:11:05 2013 +0000
@@ -0,0 +1,663 @@
+// Copyright (c) 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.
+
+#define __STC_LIMIT_MACROS
+#include <stdint.h>
+#include <stddef.h>
+#include <new>
+#include <math.h>
+#include <limits>
+#include "mbed.h"
+#include "sht7x.h"
+
+// For some reason, Mbed's stdint.h doesn't define INT64_MIN.
+#ifndef INT64_MIN
+#define INT64_MIN (-0x8000000000000000)
+#endif
+
+#ifdef TARGET_LPC11U24
+#define SLEEP() sleep()
+#else
+#define SLEEP() /* Busy-wait */
+#endif
+
+namespace sht7x {
+
+// ==== Public interface. ====
+
+SHT7x::SHT7x(PinName sck, PinName sda)
+    : sck_(DigitalOut(sck)), sda_(DigitalInOut(sda)) {
+  sck_state_ = PIN_INVALID;
+  sda_state_ = PIN_INVALID;
+  state_ = STATE_UNKNOWN;
+
+  // The default sensor configuration, according to the datasheet.
+  status_.precision = PRECISION_HIGH;
+  status_.otp = OTP_ON;
+  status_.heater = HEATER_OFF;
+  status_.battery = BATTERY_GT_2_47;
+
+  // Configure the pins, but do nothing else at this point.
+  sda_.mode(OpenDrain);
+  sda_.output();
+  this->set_sda(PIN_FREE);
+  this->set_sck(PIN_0);
+}
+
+
+bool SHT7x::reset() {
+  if (!check_state(STATE_UNKNOWN, STATE_SLEEP, STATE_COMMS_ERROR)) {
+    return false;
+  }
+  state_ = STATE_RESETTING;
+
+  // If we're trying to reset the sensor, it's likely that the communications
+  // channel has got into a weird state. We need it up and running in order to
+  // send the reset command.
+  reset_comms();
+
+  command(CMD_RESET);
+  state_ = STATE_UNKNOWN;
+
+  status_.precision = PRECISION_HIGH;
+  status_.otp = OTP_ON;
+  status_.heater = HEATER_OFF;
+  status_.battery = BATTERY_GT_2_47;
+
+  return true;
+}
+
+
+bool SHT7x::initialize() {
+  if (!check_state(STATE_UNKNOWN)) {
+    return false;
+  }
+  state_ = STATE_INITIALIZING;
+
+  // Wait for the sensor to turn on.
+  // There is no external notification that the sensor is ready, so a timer
+  // is the only option here.
+  wait_us(time_reset());
+
+  state_ = STATE_SLEEP;
+
+  // Make sure that the cached status byte is up to date.
+  return status();
+}
+
+
+bool SHT7x::configure(Precision precision, OTP otp, Heater heater) {
+  if (!check_state(STATE_SLEEP)) {
+    return false;
+  }
+  state_ = STATE_SETTING_CONFIGURATION;
+
+  // Overlay the new status on the existing one.
+  if (precision != PRECISION_KEEP)  { status_.precision = precision; }
+  if (otp != OTP_KEEP)              { status_.otp = otp; }
+  if (heater != HEATER_KEEP)        { status_.heater = heater; }
+
+  // Write the status byte.
+  command(CMD_WRITE_STATUS);
+  put_byte(encode_status_byte());
+  get_ack();
+
+  // TODO: It's not obvious how to get a CRC byte from this command. Is it
+  // necessary to read the status back again?
+
+  state_ = STATE_SLEEP;
+  return true;
+}
+
+
+bool SHT7x::status() {
+  if (!check_state(STATE_SLEEP)) {
+    return false;
+  }
+
+  // Read the status register to populate the cached values.
+  command(CMD_READ_STATUS);
+  uint32_t raw = get_byte();
+  put_ack(0);
+  uint32_t crc = get_byte();
+  put_ack(1);
+
+  // We must decode the status byte before checking the CRC because the status
+  // register is used to initialize the CRC check.
+  decode_status_byte(raw);
+
+  if (check_crc(CMD_READ_STATUS, raw, crc)) {
+    state_ = STATE_SLEEP;
+  } else {
+    state_ = STATE_COMMS_ERROR;
+  }
+
+  return true;
+}
+
+
+bool SHT7x::status(Status * & status) {
+  if (this->status()) {
+    status = &status_;
+    return true;
+  }
+  return false;
+}
+
+
+bool SHT7x::measure(Temp & temp, uint32_t mvdd) {
+  if (!check_state(STATE_SLEEP)) {
+    return false;
+  }
+  state_ = STATE_MEASURING_TEMPERATURE;
+
+  // TODO: Wait for sda low, rather than a timer.
+  command(CMD_READ_TEMP);
+  wait_us(time_reading_temperature());
+
+  uint32_t raw = get_byte() << 8;
+  put_ack(0);
+  raw |= get_byte() << 0;
+  put_ack(0);
+  uint32_t crc = get_byte();
+  put_ack(1);
+
+  if (check_crc(CMD_READ_TEMP, raw, crc)) {
+    new (&temp) Temp(bits_required_temperature(), raw, mvdd);
+    state_ = STATE_SLEEP;
+
+    // Make sure that the cached status byte is up to date.
+    // In particular, the Vdd voltage detection is updated after a measurement.
+    return status();
+  }
+
+  new (&temp) Temp();
+  state_ = STATE_COMMS_ERROR;
+  return false;
+}
+
+
+bool SHT7x::measure(Hum & hum, Temp const * temp) {
+  if (!check_state(STATE_SLEEP)) {
+    return false;
+  }
+  state_ = STATE_MEASURING_HUMIDITY;
+
+  // TODO: Wait for sda low, rather than a timer.
+  command(CMD_READ_HUM);
+  wait_us(time_reading_humidity());
+
+  // Two bytes are sent even for 8-bit humidity readings.
+  uint32_t raw = get_byte() << 8;
+  put_ack(0);
+  raw |= get_byte() << 0;
+  put_ack(0);
+  uint32_t crc = get_byte();
+  put_ack(1);
+
+  if (check_crc(CMD_READ_HUM, raw, crc)) {
+    new (&hum) Hum(bits_required_humidity(), raw, temp);
+    state_ = STATE_SLEEP;
+
+    // Make sure that the cached status byte is up to date.
+    // In particular, the Vdd voltage detection is updated after a measurement.
+    return status();
+  }
+
+  new (&hum) Hum();
+  state_ = STATE_COMMS_ERROR;
+  return false;
+}
+
+
+// ==== Internal methods. ====
+
+// Construct a status register value, suitable for writing with
+// CMD_WRITE_STATUS, from cached values.
+uint32_t SHT7x::encode_status_byte() const {
+  uint8_t value = 0;
+  value |= (status_.precision == PRECISION_LOW  ) ? (0x01) : (0x00);
+  value |= (status_.otp       == OTP_OFF        ) ? (0x02) : (0x00);
+  value |= (status_.heater    == HEATER_ON      ) ? (0x04) : (0x00);
+  value |= (status_.battery   == BATTERY_LT_2_47) ? (0x40) : (0x00);
+  return value;
+}
+
+
+// Update the cached status values with a status register value obtained from
+// the sensor using CMD_READ_STATUS.
+void SHT7x::decode_status_byte(uint32_t value) {
+  status_.precision = (value & 0x01) ? PRECISION_LOW   : PRECISION_HIGH;
+  status_.otp       = (value & 0x02) ? OTP_OFF         : OTP_ON;
+  status_.heater    = (value & 0x04) ? HEATER_ON       : HEATER_OFF;
+  status_.battery   = (value & 0x40) ? BATTERY_LT_2_47 : BATTERY_GT_2_47;
+}
+
+
+inline uint32_t SHT7x::bits_required_temperature() const {
+  switch (status_.precision) {
+    default:
+    case PRECISION_HIGH:  return 14;
+    case PRECISION_LOW:   return 12;
+  }
+}
+
+
+inline uint32_t SHT7x::bits_required_humidity() const {
+  switch (status_.precision) {
+    default:
+    case PRECISION_HIGH:  return 12;
+    case PRECISION_LOW:   return 8;
+  }
+}
+
+
+inline uint32_t SHT7x::time_atom() const {
+  return 1;
+}
+
+
+inline uint32_t SHT7x::time_reset() const {
+  return 11000;
+}
+
+
+inline uint32_t SHT7x::time_reading_temperature() const {
+  switch (status_.precision) {
+    default:
+    case PRECISION_HIGH:  return 320000;  // 320ms, 14 bits
+    case PRECISION_LOW:   return 80000;   //  80ms, 12 bits
+  }
+}
+
+
+inline uint32_t SHT7x::time_reading_humidity() const {
+  switch (status_.precision) {
+    default:
+    case PRECISION_HIGH:  return 80000;   //  80ms, 12 bits
+    case PRECISION_LOW:   return 20000;   //  20ms, 8 bits
+  }
+}
+
+
+void SHT7x::start() {
+  uint32_t const t = time_atom();
+
+  set_sda(PIN_1); wait_us(t);
+  set_sck(PIN_1); wait_us(t);
+  set_sda(PIN_0); wait_us(t);
+  set_sck(PIN_0); wait_us(t);
+                  wait_us(t);
+  set_sck(PIN_1); wait_us(t);
+  set_sda(PIN_1); wait_us(t);
+  set_sck(PIN_0); wait_us(t);
+}
+
+
+void SHT7x::put_bit(int b) {
+  uint32_t const t = time_atom();
+
+  set_sda(b);     wait_us(t);
+  set_sck(PIN_1); wait_us(t);
+                  wait_us(t);
+  set_sck(PIN_0); wait_us(t);
+}
+
+
+int SHT7x::get_bit() {
+  uint32_t const t = time_atom();
+
+  set_sda(PIN_FREE); wait_us(t);
+  set_sck(PIN_1);    wait_us(t);
+  int b = sda_;      wait_us(t);
+  set_sck(PIN_0);    wait_us(t);
+
+  return b;
+}
+
+
+// The protocol uses three different ACKs: One input ACK and two output ACKs. A
+// high output ACK usually ends a transmission. A low output ACK usually
+// acknowledges a byte as part of a transmission. Refer to the SHT7x datasheet
+// for details.
+void SHT7x::put_ack(int b) {
+  uint32_t const t = time_atom();
+
+  set_sda(b);        wait_us(t);
+  set_sck(PIN_1);    wait_us(t);
+                     wait_us(t);
+  set_sck(PIN_0);    wait_us(t);
+  set_sda(PIN_FREE); wait_us(t);
+}
+
+
+void SHT7x::get_ack() {
+  uint32_t const t = time_atom();
+
+  set_sda(PIN_FREE); wait_us(t);
+  set_sck(PIN_1);    wait_us(t);
+                     wait_us(t);
+  set_sck(PIN_0);    wait_us(t);
+}
+
+
+void SHT7x::put_byte(uint32_t byte) {
+  // TODO: Unroll this.
+  for (uint32_t i = 0; i < 8; i++) {
+    put_bit((byte >> (7-i)) & 1);
+  }
+}
+
+
+uint32_t SHT7x::get_byte() {
+  uint32_t byte = 0;
+  // TODO: Unroll this.
+  for (uint32_t i = 0; i < 8; i++) {
+    byte = (byte << 1) | get_bit();
+  }
+  return byte;
+}
+
+
+inline int SHT7x::get_sda() {
+  return sda_.read();
+}
+
+
+inline void SHT7x::set_sda(int pin) {
+  set_sda((pin) ? (PIN_1) : (PIN_0));
+}
+
+
+inline void SHT7x::set_sda(pin_State pin) {
+  if (sda_state_ != pin) {
+    sda_ = (pin == PIN_0) ? (0) : (1);
+    sda_state_ = pin;
+  }
+}
+
+
+inline void SHT7x::set_sck(int pin) {
+  set_sck((pin) ? (PIN_1) : (PIN_0));
+}
+
+
+inline void SHT7x::set_sck(pin_State pin) {
+  if (sck_state_ != pin) {
+    sck_ = (pin == PIN_0) ? (0) : (1);
+    sck_state_ = pin;
+  }
+}
+
+
+void SHT7x::reset_comms() {
+  for (uint32_t i = 0; i < 9; i++) {
+    put_bit(1);
+  }
+}
+
+
+void SHT7x::command(Command command) {
+  start();
+  put_byte(static_cast<uint32_t>(command));
+  get_ack();
+}
+
+
+bool SHT7x::check_state(State s0) {
+  State state = state_;
+  return state == s0;
+}
+
+
+bool SHT7x::check_state(State s0, State s1) {
+  State state = state_;
+  return (state == s0) || (state == s1);
+}
+
+
+bool SHT7x::check_state(State s0, State s1, State s2) {
+  State state = state_;
+  return (state == s0) || (state == s1) || (state == s2);
+}
+
+
+int SHT7x::payload_size(Command command) {
+  switch (command) {
+    case CMD_READ_TEMP:
+    case CMD_READ_HUM: return 2;
+    case CMD_READ_STATUS:
+    case CMD_WRITE_STATUS: return 1;
+    case CMD_RESET:
+    default: return 0;
+  }
+}
+
+
+bool SHT7x::check_crc(Command command, uint32_t data, uint32_t crc_in) {
+  int const data_bits = payload_size(command) * 8;
+  int const bits = data_bits + 8;
+
+  data |= command << data_bits;
+
+  // The Sensirion application note describes an algorithm which reverses the
+  // bits in the status byte to form the initial CRC, performs the CRC
+  // calculation, then reverses the bits again at the end. This CRC
+  // implementation operates in reverse, so there is no need to reverse the CRC.
+
+  // TODO: Optimize this further, if possible.
+
+  // The CRC is initialized with the status byte.
+  uint32_t crc = encode_status_byte();
+  for (int bit = bits - 1; bit >= 0; bit--) {
+    // Eor bit 0 of the CRC with the top bit of the data to determine the value
+    // to feed back into the CRC.
+    uint32_t result = ((data >> bit) ^ crc) & 1;
+    // Bit 0 contains that value that must be fed back into the CRC at bit
+    // positions 7, 3 and 2, so use it to construct a bit mask. These bits
+    // correspond to positions 0, 4 and 5 in the reversed-CRC implementation.
+    uint32_t invert = (result << 7) | (result << 3) | (result << 2);
+    crc = (crc >> 1) ^ invert;
+  }
+
+  return crc == crc_in;
+}
+
+
+// ==== Readings. ====
+
+// Shift 'v' right by 'shift' and perform signed rounding.
+static inline int64_t shift_round(int64_t v, uint32_t shift) {
+  uint64_t u = static_cast<uint64_t>(v);
+
+  if (shift == 0) {
+    return v;
+  }
+
+  // Rounding (with ties away from zero).
+  // Positive:
+  //  i += bit(shift-1)
+  // Negative:
+  //  i += bit(shift-1) - 1
+  // We can simply take the value of bit 31 and subtract the value of the
+  // sign bit, then add the result to the truncated reading.
+  // Note that this relies on C99-style division, where negative values are
+  // truncated towards 0.
+  int64_t i = (v / (1LL << shift));
+  int64_t s = (u >> 63) & 1;
+  int64_t h = (u >> (shift - 1)) & 1;
+
+  return i + h - s;
+}
+
+
+// ---- Temperature ----
+
+// -- Floating point. --
+
+float Temp::get_d1_f() const {
+  // The following data-points are provided in the datasheet:
+  //  Vdd     d1
+  //  5       -40.1
+  //  4       -39.8
+  //  3.5     -39.7
+  //  3       -39.6
+  //  2.5     -39.4
+  // The relationship between Vdd and d1 is not linear. However, the
+  // deviation from a linear relationship is probably a result of aliasing
+  // rather than anything else. The following relationship provides a linear
+  // response that looks like a reasonable best-fit for the specified points:
+  //  Vdd     d1
+  //  5       -40.1
+  //  2.5     -39.43
+  //
+  // d1 = -0.268 * Vdd - 38.76
+  return -0.000268 * mvdd - 38.76;
+}
+
+
+float Temp::get_f() const {
+  float d1 = get_d1_f();
+  float d2;
+  switch (get_bits()) {
+    case 12:  d2 = 0.04f;     break;
+    case 14:  d2 = 0.01f;     break;
+    default:  return std::numeric_limits<float>::quiet_NaN();
+  }
+  return d1 + d2 * get_raw();
+}
+
+
+// -- Fixed point. --
+
+int64_t Temp::get_d1_q32() const {
+  // See notes in get_d1_f() for justification of this formula.
+  // d1 = -0.268 * Vdd - 38.76
+  return -0x0011904bLL * mvdd - 0x26c28f5c28LL;
+}
+
+
+int64_t Temp::get_q32() const {
+  int64_t d1 = get_d1_q32();
+  int64_t d2;
+  switch (get_bits()) {
+    case 12:  d2 = 0xa3d70a3LL;   break;  // 0.04, q32
+    case 14:  d2 = 0x28f5c28LL;   break;  // 0.01, q32
+    default:  return 0;
+  }
+  return d1 + d2 * get_raw();
+}
+
+
+int32_t Temp::get_fixed(int fbits) const {
+  return shift_round(get_q32(), 32-fbits);
+}
+
+
+// ---- Humidity ----
+
+// -- Floating point. --
+
+float Hum::get_f() const {
+  float c1 = -2.0468f;
+  float c2;
+  float c3;
+  float t1 = 0.01f;
+  float t2;
+  float r;
+  float raw = static_cast<float>(get_raw());
+  switch (get_bits()) {
+    case 8:
+      c2 = 0.5872f;
+      c3 = -4.0845E-4f;
+      t2 = 0.00128f;
+      break;
+    case 12:
+      c2 = 0.0367f;
+      c3 = -1.5955E-6f;
+      t2 = 0.00008f;
+      break;
+    default:
+      return std::numeric_limits<float>::quiet_NaN();
+  }
+  r = c1 + (c2 * raw) + (c3 * (raw * raw));
+  if (temp) {
+    r += (temp->get_f() - 25.0f)*(t1+t2*raw);
+  }
+  // Clamp as specified in the datasheet.
+  if (r < 0.0f) {
+    return 0.0f;
+  }
+  if (r > 99.0f) {
+    return 100.0f;
+  }
+  return r;
+}
+
+
+// -- Fixed point. --
+
+int64_t Hum::get_q32() const {
+  int64_t c1 = -0x20bfb15b5LL;    // -2.0468
+  int64_t c2;
+  int64_t c3;
+  int64_t t1 = 0x28f6LL;          // 0.01, q20
+  int64_t t2;
+  int64_t r;
+  int64_t raw = static_cast<int64_t>(get_raw());
+  switch (get_bits()) {
+    case 8:
+      c2 = 0x9652bd3cLL;  // 0.5872f;
+      c3 = -0x1ac4a7LL;   // -4.0845E-4f;
+      t2 = 0x53eLL;       // 0.00128f, q20;
+      break;
+    case 12:
+      c2 = 0x9652bd3LL;   // 0.0367f;
+      c3 = -0x1ac4LL;     // -1.5955E-6f;
+      t2 = 0x54LL;        // 0.00008f, q20;
+      break;
+    default:
+      return INT64_MIN;
+  }
+  // The 'c' coefficients are all in q32 format. The raw value is in q0
+  // (integer) format, so r will be in q32 format after this operation.
+  r = c1 + (c2 * raw) + (c3 * (raw * raw));
+  if (temp) {
+    int64_t co = t1 + (t2 * raw);   // q20
+    int64_t t = temp->get_q32() - (25LL << 32);
+    r += shift_round(co * t, 20);
+  }
+  // Clamp as specified in the datasheet.
+  if (r < 0LL) {
+    return 0LL;
+  }
+  if (r > (99LL<<32)) {
+    return 100LL<<32;
+  }
+  return r;
+}
+
+
+int32_t Hum::get_fixed(int fbits) const {
+  return shift_round(get_q32(), 32-fbits);
+}
+
+
+} // namespace sht7x
diff -r 000000000000 -r f1a93e55feb5 sht7x.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sht7x.h	Sun Aug 11 18:11:05 2013 +0000
@@ -0,0 +1,478 @@
+// 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