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.
Revision 0:f1a93e55feb5, committed 2013-08-11
- 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