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.cpp
- Committer:
- JacobBramley
- Date:
- 2013-08-11
- Revision:
- 0:f1a93e55feb5
File content as of revision 0:f1a93e55feb5:
// 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