Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
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
Generated on Wed Jul 13 2022 20:23:36 by
1.7.2