Jacob Bramley / sht7x
Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers sht7x.cpp Source File

sht7x.cpp

00001 // Copyright (c) 2013 Jacob Bramley
00002 // 
00003 // Permission is hereby granted, free of charge, to any person obtaining a copy
00004 // of this software and associated documentation files (the "Software"), to deal
00005 // in the Software without restriction, including without limitation the rights
00006 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
00007 // copies of the Software, and to permit persons to whom the Software is
00008 // furnished to do so, subject to the following conditions:
00009 // 
00010 // The above copyright notice and this permission notice shall be included in
00011 // all copies or substantial portions of the Software.
00012 // 
00013 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
00014 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
00015 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
00016 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
00017 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
00018 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
00019 // SOFTWARE.
00020 
00021 #define __STC_LIMIT_MACROS
00022 #include <stdint.h>
00023 #include <stddef.h>
00024 #include <new>
00025 #include <math.h>
00026 #include <limits>
00027 #include "mbed.h"
00028 #include "sht7x.h"
00029 
00030 // For some reason, Mbed's stdint.h doesn't define INT64_MIN.
00031 #ifndef INT64_MIN
00032 #define INT64_MIN (-0x8000000000000000)
00033 #endif
00034 
00035 #ifdef TARGET_LPC11U24
00036 #define SLEEP() sleep()
00037 #else
00038 #define SLEEP() /* Busy-wait */
00039 #endif
00040 
00041 namespace sht7x {
00042 
00043 // ==== Public interface. ====
00044 
00045 SHT7x::SHT7x(PinName sck, PinName sda)
00046     : sck_(DigitalOut(sck)), sda_(DigitalInOut(sda)) {
00047   sck_state_ = PIN_INVALID;
00048   sda_state_ = PIN_INVALID;
00049   state_ = STATE_UNKNOWN;
00050 
00051   // The default sensor configuration, according to the datasheet.
00052   status_.precision = PRECISION_HIGH;
00053   status_.otp = OTP_ON;
00054   status_.heater = HEATER_OFF;
00055   status_.battery = BATTERY_GT_2_47;
00056 
00057   // Configure the pins, but do nothing else at this point.
00058   sda_.mode(OpenDrain);
00059   sda_.output();
00060   this->set_sda(PIN_FREE);
00061   this->set_sck(PIN_0);
00062 }
00063 
00064 
00065 bool SHT7x::reset() {
00066   if (!check_state(STATE_UNKNOWN, STATE_SLEEP, STATE_COMMS_ERROR)) {
00067     return false;
00068   }
00069   state_ = STATE_RESETTING;
00070 
00071   // If we're trying to reset the sensor, it's likely that the communications
00072   // channel has got into a weird state. We need it up and running in order to
00073   // send the reset command.
00074   reset_comms();
00075 
00076   command(CMD_RESET);
00077   state_ = STATE_UNKNOWN;
00078 
00079   status_.precision = PRECISION_HIGH;
00080   status_.otp = OTP_ON;
00081   status_.heater = HEATER_OFF;
00082   status_.battery = BATTERY_GT_2_47;
00083 
00084   return true;
00085 }
00086 
00087 
00088 bool SHT7x::initialize() {
00089   if (!check_state(STATE_UNKNOWN)) {
00090     return false;
00091   }
00092   state_ = STATE_INITIALIZING;
00093 
00094   // Wait for the sensor to turn on.
00095   // There is no external notification that the sensor is ready, so a timer
00096   // is the only option here.
00097   wait_us(time_reset());
00098 
00099   state_ = STATE_SLEEP;
00100 
00101   // Make sure that the cached status byte is up to date.
00102   return status();
00103 }
00104 
00105 
00106 bool SHT7x::configure(Precision precision, OTP otp, Heater heater) {
00107   if (!check_state(STATE_SLEEP)) {
00108     return false;
00109   }
00110   state_ = STATE_SETTING_CONFIGURATION;
00111 
00112   // Overlay the new status on the existing one.
00113   if (precision != PRECISION_KEEP)  { status_.precision = precision; }
00114   if (otp != OTP_KEEP)              { status_.otp = otp; }
00115   if (heater != HEATER_KEEP)        { status_.heater = heater; }
00116 
00117   // Write the status byte.
00118   command(CMD_WRITE_STATUS);
00119   put_byte(encode_status_byte());
00120   get_ack();
00121 
00122   // TODO: It's not obvious how to get a CRC byte from this command. Is it
00123   // necessary to read the status back again?
00124 
00125   state_ = STATE_SLEEP;
00126   return true;
00127 }
00128 
00129 
00130 bool SHT7x::status() {
00131   if (!check_state(STATE_SLEEP)) {
00132     return false;
00133   }
00134 
00135   // Read the status register to populate the cached values.
00136   command(CMD_READ_STATUS);
00137   uint32_t raw = get_byte();
00138   put_ack(0);
00139   uint32_t crc = get_byte();
00140   put_ack(1);
00141 
00142   // We must decode the status byte before checking the CRC because the status
00143   // register is used to initialize the CRC check.
00144   decode_status_byte(raw);
00145 
00146   if (check_crc(CMD_READ_STATUS, raw, crc)) {
00147     state_ = STATE_SLEEP;
00148   } else {
00149     state_ = STATE_COMMS_ERROR;
00150   }
00151 
00152   return true;
00153 }
00154 
00155 
00156 bool SHT7x::status(Status * & status) {
00157   if (this->status()) {
00158     status = &status_;
00159     return true;
00160   }
00161   return false;
00162 }
00163 
00164 
00165 bool SHT7x::measure(Temp & temp, uint32_t mvdd) {
00166   if (!check_state(STATE_SLEEP)) {
00167     return false;
00168   }
00169   state_ = STATE_MEASURING_TEMPERATURE;
00170 
00171   // TODO: Wait for sda low, rather than a timer.
00172   command(CMD_READ_TEMP);
00173   wait_us(time_reading_temperature());
00174 
00175   uint32_t raw = get_byte() << 8;
00176   put_ack(0);
00177   raw |= get_byte() << 0;
00178   put_ack(0);
00179   uint32_t crc = get_byte();
00180   put_ack(1);
00181 
00182   if (check_crc(CMD_READ_TEMP, raw, crc)) {
00183     new (&temp) Temp(bits_required_temperature(), raw, mvdd);
00184     state_ = STATE_SLEEP;
00185 
00186     // Make sure that the cached status byte is up to date.
00187     // In particular, the Vdd voltage detection is updated after a measurement.
00188     return status();
00189   }
00190 
00191   new (&temp) Temp();
00192   state_ = STATE_COMMS_ERROR;
00193   return false;
00194 }
00195 
00196 
00197 bool SHT7x::measure(Hum & hum, Temp const * temp) {
00198   if (!check_state(STATE_SLEEP)) {
00199     return false;
00200   }
00201   state_ = STATE_MEASURING_HUMIDITY;
00202 
00203   // TODO: Wait for sda low, rather than a timer.
00204   command(CMD_READ_HUM);
00205   wait_us(time_reading_humidity());
00206 
00207   // Two bytes are sent even for 8-bit humidity readings.
00208   uint32_t raw = get_byte() << 8;
00209   put_ack(0);
00210   raw |= get_byte() << 0;
00211   put_ack(0);
00212   uint32_t crc = get_byte();
00213   put_ack(1);
00214 
00215   if (check_crc(CMD_READ_HUM, raw, crc)) {
00216     new (&hum) Hum(bits_required_humidity(), raw, temp);
00217     state_ = STATE_SLEEP;
00218 
00219     // Make sure that the cached status byte is up to date.
00220     // In particular, the Vdd voltage detection is updated after a measurement.
00221     return status();
00222   }
00223 
00224   new (&hum) Hum();
00225   state_ = STATE_COMMS_ERROR;
00226   return false;
00227 }
00228 
00229 
00230 // ==== Internal methods. ====
00231 
00232 // Construct a status register value, suitable for writing with
00233 // CMD_WRITE_STATUS, from cached values.
00234 uint32_t SHT7x::encode_status_byte() const {
00235   uint8_t value = 0;
00236   value |= (status_.precision == PRECISION_LOW  ) ? (0x01) : (0x00);
00237   value |= (status_.otp       == OTP_OFF        ) ? (0x02) : (0x00);
00238   value |= (status_.heater    == HEATER_ON      ) ? (0x04) : (0x00);
00239   value |= (status_.battery   == BATTERY_LT_2_47) ? (0x40) : (0x00);
00240   return value;
00241 }
00242 
00243 
00244 // Update the cached status values with a status register value obtained from
00245 // the sensor using CMD_READ_STATUS.
00246 void SHT7x::decode_status_byte(uint32_t value) {
00247   status_.precision = (value & 0x01) ? PRECISION_LOW   : PRECISION_HIGH;
00248   status_.otp       = (value & 0x02) ? OTP_OFF         : OTP_ON;
00249   status_.heater    = (value & 0x04) ? HEATER_ON       : HEATER_OFF;
00250   status_.battery   = (value & 0x40) ? BATTERY_LT_2_47 : BATTERY_GT_2_47;
00251 }
00252 
00253 
00254 inline uint32_t SHT7x::bits_required_temperature() const {
00255   switch (status_.precision) {
00256     default:
00257     case PRECISION_HIGH:  return 14;
00258     case PRECISION_LOW:   return 12;
00259   }
00260 }
00261 
00262 
00263 inline uint32_t SHT7x::bits_required_humidity() const {
00264   switch (status_.precision) {
00265     default:
00266     case PRECISION_HIGH:  return 12;
00267     case PRECISION_LOW:   return 8;
00268   }
00269 }
00270 
00271 
00272 inline uint32_t SHT7x::time_atom() const {
00273   return 1;
00274 }
00275 
00276 
00277 inline uint32_t SHT7x::time_reset() const {
00278   return 11000;
00279 }
00280 
00281 
00282 inline uint32_t SHT7x::time_reading_temperature() const {
00283   switch (status_.precision) {
00284     default:
00285     case PRECISION_HIGH:  return 320000;  // 320ms, 14 bits
00286     case PRECISION_LOW:   return 80000;   //  80ms, 12 bits
00287   }
00288 }
00289 
00290 
00291 inline uint32_t SHT7x::time_reading_humidity() const {
00292   switch (status_.precision) {
00293     default:
00294     case PRECISION_HIGH:  return 80000;   //  80ms, 12 bits
00295     case PRECISION_LOW:   return 20000;   //  20ms, 8 bits
00296   }
00297 }
00298 
00299 
00300 void SHT7x::start() {
00301   uint32_t const t = time_atom();
00302 
00303   set_sda(PIN_1); wait_us(t);
00304   set_sck(PIN_1); wait_us(t);
00305   set_sda(PIN_0); wait_us(t);
00306   set_sck(PIN_0); wait_us(t);
00307                   wait_us(t);
00308   set_sck(PIN_1); wait_us(t);
00309   set_sda(PIN_1); wait_us(t);
00310   set_sck(PIN_0); wait_us(t);
00311 }
00312 
00313 
00314 void SHT7x::put_bit(int b) {
00315   uint32_t const t = time_atom();
00316 
00317   set_sda(b);     wait_us(t);
00318   set_sck(PIN_1); wait_us(t);
00319                   wait_us(t);
00320   set_sck(PIN_0); wait_us(t);
00321 }
00322 
00323 
00324 int SHT7x::get_bit() {
00325   uint32_t const t = time_atom();
00326 
00327   set_sda(PIN_FREE); wait_us(t);
00328   set_sck(PIN_1);    wait_us(t);
00329   int b = sda_;      wait_us(t);
00330   set_sck(PIN_0);    wait_us(t);
00331 
00332   return b;
00333 }
00334 
00335 
00336 // The protocol uses three different ACKs: One input ACK and two output ACKs. A
00337 // high output ACK usually ends a transmission. A low output ACK usually
00338 // acknowledges a byte as part of a transmission. Refer to the SHT7x datasheet
00339 // for details.
00340 void SHT7x::put_ack(int b) {
00341   uint32_t const t = time_atom();
00342 
00343   set_sda(b);        wait_us(t);
00344   set_sck(PIN_1);    wait_us(t);
00345                      wait_us(t);
00346   set_sck(PIN_0);    wait_us(t);
00347   set_sda(PIN_FREE); wait_us(t);
00348 }
00349 
00350 
00351 void SHT7x::get_ack() {
00352   uint32_t const t = time_atom();
00353 
00354   set_sda(PIN_FREE); wait_us(t);
00355   set_sck(PIN_1);    wait_us(t);
00356                      wait_us(t);
00357   set_sck(PIN_0);    wait_us(t);
00358 }
00359 
00360 
00361 void SHT7x::put_byte(uint32_t byte) {
00362   // TODO: Unroll this.
00363   for (uint32_t i = 0; i < 8; i++) {
00364     put_bit((byte >> (7-i)) & 1);
00365   }
00366 }
00367 
00368 
00369 uint32_t SHT7x::get_byte() {
00370   uint32_t byte = 0;
00371   // TODO: Unroll this.
00372   for (uint32_t i = 0; i < 8; i++) {
00373     byte = (byte << 1) | get_bit();
00374   }
00375   return byte;
00376 }
00377 
00378 
00379 inline int SHT7x::get_sda() {
00380   return sda_.read();
00381 }
00382 
00383 
00384 inline void SHT7x::set_sda(int pin) {
00385   set_sda((pin) ? (PIN_1) : (PIN_0));
00386 }
00387 
00388 
00389 inline void SHT7x::set_sda(pin_State pin) {
00390   if (sda_state_ != pin) {
00391     sda_ = (pin == PIN_0) ? (0) : (1);
00392     sda_state_ = pin;
00393   }
00394 }
00395 
00396 
00397 inline void SHT7x::set_sck(int pin) {
00398   set_sck((pin) ? (PIN_1) : (PIN_0));
00399 }
00400 
00401 
00402 inline void SHT7x::set_sck(pin_State pin) {
00403   if (sck_state_ != pin) {
00404     sck_ = (pin == PIN_0) ? (0) : (1);
00405     sck_state_ = pin;
00406   }
00407 }
00408 
00409 
00410 void SHT7x::reset_comms() {
00411   for (uint32_t i = 0; i < 9; i++) {
00412     put_bit(1);
00413   }
00414 }
00415 
00416 
00417 void SHT7x::command(Command command) {
00418   start();
00419   put_byte(static_cast<uint32_t>(command));
00420   get_ack();
00421 }
00422 
00423 
00424 bool SHT7x::check_state(State s0) {
00425   State state = state_;
00426   return state == s0;
00427 }
00428 
00429 
00430 bool SHT7x::check_state(State s0, State s1) {
00431   State state = state_;
00432   return (state == s0) || (state == s1);
00433 }
00434 
00435 
00436 bool SHT7x::check_state(State s0, State s1, State s2) {
00437   State state = state_;
00438   return (state == s0) || (state == s1) || (state == s2);
00439 }
00440 
00441 
00442 int SHT7x::payload_size(Command command) {
00443   switch (command) {
00444     case CMD_READ_TEMP:
00445     case CMD_READ_HUM: return 2;
00446     case CMD_READ_STATUS:
00447     case CMD_WRITE_STATUS: return 1;
00448     case CMD_RESET:
00449     default: return 0;
00450   }
00451 }
00452 
00453 
00454 bool SHT7x::check_crc(Command command, uint32_t data, uint32_t crc_in) {
00455   int const data_bits = payload_size(command) * 8;
00456   int const bits = data_bits + 8;
00457 
00458   data |= command << data_bits;
00459 
00460   // The Sensirion application note describes an algorithm which reverses the
00461   // bits in the status byte to form the initial CRC, performs the CRC
00462   // calculation, then reverses the bits again at the end. This CRC
00463   // implementation operates in reverse, so there is no need to reverse the CRC.
00464 
00465   // TODO: Optimize this further, if possible.
00466 
00467   // The CRC is initialized with the status byte.
00468   uint32_t crc = encode_status_byte();
00469   for (int bit = bits - 1; bit >= 0; bit--) {
00470     // Eor bit 0 of the CRC with the top bit of the data to determine the value
00471     // to feed back into the CRC.
00472     uint32_t result = ((data >> bit) ^ crc) & 1;
00473     // Bit 0 contains that value that must be fed back into the CRC at bit
00474     // positions 7, 3 and 2, so use it to construct a bit mask. These bits
00475     // correspond to positions 0, 4 and 5 in the reversed-CRC implementation.
00476     uint32_t invert = (result << 7) | (result << 3) | (result << 2);
00477     crc = (crc >> 1) ^ invert;
00478   }
00479 
00480   return crc == crc_in;
00481 }
00482 
00483 
00484 // ==== Readings. ====
00485 
00486 // Shift 'v' right by 'shift' and perform signed rounding.
00487 static inline int64_t shift_round(int64_t v, uint32_t shift) {
00488   uint64_t u = static_cast<uint64_t>(v);
00489 
00490   if (shift == 0) {
00491     return v;
00492   }
00493 
00494   // Rounding (with ties away from zero).
00495   // Positive:
00496   //  i += bit(shift-1)
00497   // Negative:
00498   //  i += bit(shift-1) - 1
00499   // We can simply take the value of bit 31 and subtract the value of the
00500   // sign bit, then add the result to the truncated reading.
00501   // Note that this relies on C99-style division, where negative values are
00502   // truncated towards 0.
00503   int64_t i = (v / (1LL << shift));
00504   int64_t s = (u >> 63) & 1;
00505   int64_t h = (u >> (shift - 1)) & 1;
00506 
00507   return i + h - s;
00508 }
00509 
00510 
00511 // ---- Temperature ----
00512 
00513 // -- Floating point. --
00514 
00515 float Temp::get_d1_f() const {
00516   // The following data-points are provided in the datasheet:
00517   //  Vdd     d1
00518   //  5       -40.1
00519   //  4       -39.8
00520   //  3.5     -39.7
00521   //  3       -39.6
00522   //  2.5     -39.4
00523   // The relationship between Vdd and d1 is not linear. However, the
00524   // deviation from a linear relationship is probably a result of aliasing
00525   // rather than anything else. The following relationship provides a linear
00526   // response that looks like a reasonable best-fit for the specified points:
00527   //  Vdd     d1
00528   //  5       -40.1
00529   //  2.5     -39.43
00530   //
00531   // d1 = -0.268 * Vdd - 38.76
00532   return -0.000268 * mvdd - 38.76;
00533 }
00534 
00535 
00536 float Temp::get_f() const {
00537   float d1 = get_d1_f();
00538   float d2;
00539   switch (get_bits()) {
00540     case 12:  d2 = 0.04f;     break;
00541     case 14:  d2 = 0.01f;     break;
00542     default:  return std::numeric_limits<float>::quiet_NaN();
00543   }
00544   return d1 + d2 * get_raw();
00545 }
00546 
00547 
00548 // -- Fixed point. --
00549 
00550 int64_t Temp::get_d1_q32() const {
00551   // See notes in get_d1_f() for justification of this formula.
00552   // d1 = -0.268 * Vdd - 38.76
00553   return -0x0011904bLL * mvdd - 0x26c28f5c28LL;
00554 }
00555 
00556 
00557 int64_t Temp::get_q32() const {
00558   int64_t d1 = get_d1_q32();
00559   int64_t d2;
00560   switch (get_bits()) {
00561     case 12:  d2 = 0xa3d70a3LL;   break;  // 0.04, q32
00562     case 14:  d2 = 0x28f5c28LL;   break;  // 0.01, q32
00563     default:  return 0;
00564   }
00565   return d1 + d2 * get_raw();
00566 }
00567 
00568 
00569 int32_t Temp::get_fixed(int fbits) const {
00570   return shift_round(get_q32(), 32-fbits);
00571 }
00572 
00573 
00574 // ---- Humidity ----
00575 
00576 // -- Floating point. --
00577 
00578 float Hum::get_f() const {
00579   float c1 = -2.0468f;
00580   float c2;
00581   float c3;
00582   float t1 = 0.01f;
00583   float t2;
00584   float r;
00585   float raw = static_cast<float>(get_raw());
00586   switch (get_bits()) {
00587     case 8:
00588       c2 = 0.5872f;
00589       c3 = -4.0845E-4f;
00590       t2 = 0.00128f;
00591       break;
00592     case 12:
00593       c2 = 0.0367f;
00594       c3 = -1.5955E-6f;
00595       t2 = 0.00008f;
00596       break;
00597     default:
00598       return std::numeric_limits<float>::quiet_NaN();
00599   }
00600   r = c1 + (c2 * raw) + (c3 * (raw * raw));
00601   if (temp) {
00602     r += (temp->get_f() - 25.0f)*(t1+t2*raw);
00603   }
00604   // Clamp as specified in the datasheet.
00605   if (r < 0.0f) {
00606     return 0.0f;
00607   }
00608   if (r > 99.0f) {
00609     return 100.0f;
00610   }
00611   return r;
00612 }
00613 
00614 
00615 // -- Fixed point. --
00616 
00617 int64_t Hum::get_q32() const {
00618   int64_t c1 = -0x20bfb15b5LL;    // -2.0468
00619   int64_t c2;
00620   int64_t c3;
00621   int64_t t1 = 0x28f6LL;          // 0.01, q20
00622   int64_t t2;
00623   int64_t r;
00624   int64_t raw = static_cast<int64_t>(get_raw());
00625   switch (get_bits()) {
00626     case 8:
00627       c2 = 0x9652bd3cLL;  // 0.5872f;
00628       c3 = -0x1ac4a7LL;   // -4.0845E-4f;
00629       t2 = 0x53eLL;       // 0.00128f, q20;
00630       break;
00631     case 12:
00632       c2 = 0x9652bd3LL;   // 0.0367f;
00633       c3 = -0x1ac4LL;     // -1.5955E-6f;
00634       t2 = 0x54LL;        // 0.00008f, q20;
00635       break;
00636     default:
00637       return INT64_MIN;
00638   }
00639   // The 'c' coefficients are all in q32 format. The raw value is in q0
00640   // (integer) format, so r will be in q32 format after this operation.
00641   r = c1 + (c2 * raw) + (c3 * (raw * raw));
00642   if (temp) {
00643     int64_t co = t1 + (t2 * raw);   // q20
00644     int64_t t = temp->get_q32() - (25LL << 32);
00645     r += shift_round(co * t, 20);
00646   }
00647   // Clamp as specified in the datasheet.
00648   if (r < 0LL) {
00649     return 0LL;
00650   }
00651   if (r > (99LL<<32)) {
00652     return 100LL<<32;
00653   }
00654   return r;
00655 }
00656 
00657 
00658 int32_t Hum::get_fixed(int fbits) const {
00659   return shift_round(get_q32(), 32-fbits);
00660 }
00661 
00662 
00663 } // namespace sht7x