arduino2/libraries/Sensirion-master/Sensirion.cpp
2020-11-06 13:17:55 +01:00

401 lines
13 KiB
C++

/* ========================================================================== */
/* Sensirion.cpp - Library for Sensirion SHT1x & SHT7x family temperature */
/* and humidity sensors */
/* Created by Markus Schatzl, November 28, 2008 */
/* Released into the public domain */
/* */
/* Revised (v1.1) by Carl Jackson, August 4, 2010 */
/* Rewritten (v2.0) by Carl Jackson, December 10, 2010 */
/* See README.txt file for details */
/* ========================================================================== */
/******************************************************************************
* Includes
******************************************************************************/
#include "Sensirion.h"
// Wiring Core Includes
#if ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
extern "C" {
// AVR LibC Includes
#include <stddef.h>
#include <stdint.h>
#include <math.h>
}
/******************************************************************************
* Definitions
******************************************************************************/
// Sensirion command definitions: adr command r/w
const uint8_t MEAS_TEMP = 0x03; // 000 0001 1
const uint8_t MEAS_HUMI = 0x05; // 000 0010 1
const uint8_t STAT_REG_W = 0x06; // 000 0011 0
const uint8_t STAT_REG_R = 0x07; // 000 0011 1
const uint8_t SOFT_RESET = 0x1e; // 000 1111 0
// Status register writable bits
const uint8_t SR_MASK = 0x07;
// getByte flags
const bool noACK = false;
const bool ACK = true;
// Temperature & humidity equation constants
const float D1 = -40.1; // for deg C @ 5V
const float D2h = 0.01; // for deg C, 14-bit precision
const float D2l = 0.04; // for deg C, 12-bit precision
// const float C1 = -4.0000; // for V3 sensors
// const float C2h = 0.0405; // for V3 sensors, 12-bit precision
// const float C3h = -2.8000E-6; // for V3 sensors, 12-bit precision
// const float C2l = 0.6480; // for V3 sensors, 8-bit precision
// const float C3l = -7.2000E-4; // for V3 sensors, 8-bit precision
const float C1 = -2.0468; // for V4 sensors
const float C2h = 0.0367; // for V4 sensors, 12-bit precision
const float C3h = -1.5955E-6; // for V4 sensors, 12-bit precision
const float C2l = 0.5872; // for V4 sensors, 8-bit precision
const float C3l = -4.0845E-4; // for V4 sensors, 8-bit precision
const float T1 = 0.01; // for V3 and V4 sensors
const float T2h = 0.00008; // for V3 and V4 sensors, 12-bit precision
const float T2l = 0.00128; // for V3 and V4 sensors, 8-bit precision
/******************************************************************************
* Constructors
******************************************************************************/
Sensirion::Sensirion(uint8_t dataPin, uint8_t clockPin) {
// Initialize private storage for library functions
_pinData = dataPin;
_pinClock = clockPin;
_presult = NULL; // No pending measurement
_stat_reg = 0x00; // Sensor status register default state
// Initialize CLK signal direction
// Note: All functions exit with CLK low and DAT in input mode
pinMode(_pinClock, OUTPUT);
// Return sensor to default state
resetConnection(); // Reset communication link with sensor
putByte(SOFT_RESET); // Send soft reset command
}
/******************************************************************************
* User functions
******************************************************************************/
// All-in-one (blocking): Returns temperature, humidity, & dewpoint
uint8_t Sensirion::measure(float *temp, float *humi, float *dew) {
uint16_t rawData;
uint8_t error;
if (error = measTemp(&rawData))
return error;
*temp = calcTemp(rawData);
if (error = measHumi(&rawData))
return error;
*humi = calcHumi(rawData, *temp);
*dew = calcDewpoint(*humi, *temp);
return 0 ;
}
// Initiate measurement. If blocking, wait for result
uint8_t Sensirion::meas(uint8_t cmd, uint16_t *result, bool block) {
uint8_t error, i;
#ifdef CRC_ENA
_crc = bitrev(_stat_reg & SR_MASK); // Initialize CRC calculation
#endif
startTransmission();
if (cmd == TEMP)
cmd = MEAS_TEMP;
else
cmd = MEAS_HUMI;
if (error = putByte(cmd))
return error;
#ifdef CRC_ENA
calcCRC(cmd, &_crc); // Include command byte in CRC calculation
#endif
// If non-blocking, save pointer to result and return
if (!block) {
_presult = result;
return 0;
}
// Otherwise, wait for measurement to complete with 720ms timeout
i = 240;
while (digitalRead(_pinData)) {
i--;
if (i == 0)
return S_Err_TO; // Error: Timeout
delay(3);
}
error = getResult(result);
return error;
}
// Check if non-blocking measurement has completed
// Non-zero return indicates complete (with or without error)
uint8_t Sensirion::measRdy(void) {
uint8_t error = 0;
if (_presult == NULL) // Already done?
return S_Meas_Rdy;
if (digitalRead(_pinData) != 0) // Measurement ready yet?
return 0;
error = getResult(_presult);
_presult = NULL;
if (error)
return error; // Only possible error is S_Err_CRC
return S_Meas_Rdy;
}
// Get measurement result from sensor (plus CRC, if enabled)
uint8_t Sensirion::getResult(uint16_t *result) {
uint8_t val;
#ifdef CRC_ENA
val = getByte(ACK);
calcCRC(val, &_crc);
*result = val;
val = getByte(ACK);
calcCRC(val, &_crc);
*result = (*result << 8) | val;
val = getByte(noACK);
val = bitrev(val);
if (val != _crc) {
*result = 0xFFFF;
return S_Err_CRC;
}
#else
*result = getByte(ACK);
*result = (*result << 8) | getByte(noACK);
#endif
return 0;
}
// Write status register
uint8_t Sensirion::writeSR(uint8_t value) {
uint8_t error;
value &= SR_MASK; // Mask off unwritable bits
_stat_reg = value; // Save local copy
startTransmission();
if (error = putByte(STAT_REG_W))
return error;
return putByte(value);
}
// Read status register
uint8_t Sensirion::readSR(uint8_t *result) {
uint8_t val;
uint8_t error = 0;
#ifdef CRC_ENA
_crc = bitrev(_stat_reg & SR_MASK); // Initialize CRC calculation
#endif
startTransmission();
if (error = putByte(STAT_REG_R)) {
*result = 0xFF;
return error;
}
#ifdef CRC_ENA
calcCRC(STAT_REG_R, &_crc); // Include command byte in CRC calculation
*result = getByte(ACK);
calcCRC(*result, &_crc);
val = getByte(noACK);
val = bitrev(val);
if (val != _crc) {
*result = 0xFF;
error = S_Err_CRC;
}
#else
*result = getByte(noACK);
#endif
return error;
}
// Public reset function
// Note: Soft reset returns sensor status register to default values
uint8_t Sensirion::reset(void) {
_stat_reg = 0x00; // Sensor status register default state
resetConnection(); // Reset communication link with sensor
return putByte(SOFT_RESET); // Send soft reset command & return status
}
/******************************************************************************
* Sensirion data communication
******************************************************************************/
// Write byte to sensor and check for acknowledge
uint8_t Sensirion::putByte(uint8_t value) {
uint8_t mask, i;
uint8_t error = 0;
pinMode(_pinData, OUTPUT); // Set data line to output mode
mask = 0x80; // Bit mask to transmit MSB first
for (i = 8; i > 0; i--) {
digitalWrite(_pinData, value & mask);
PULSE_SHORT;
digitalWrite(_pinClock, HIGH); // Generate clock pulse
PULSE_LONG;
digitalWrite(_pinClock, LOW);
PULSE_SHORT;
mask >>= 1; // Shift mask for next data bit
}
pinMode(_pinData, INPUT); // Return data line to input mode
#ifdef DATA_PU
digitalWrite(_pinData, DATA_PU); // Restore internal pullup state
#endif
digitalWrite(_pinClock, HIGH); // Clock #9 for ACK
PULSE_LONG;
if (digitalRead(_pinData)) // Verify ACK ('0') received from sensor
error = S_Err_NoACK;
PULSE_SHORT;
digitalWrite(_pinClock, LOW); // Finish with clock in low state
return error;
}
// Read byte from sensor and send acknowledge if "ack" is true
uint8_t Sensirion::getByte(bool ack) {
uint8_t i;
uint8_t result = 0;
for (i = 8; i > 0; i--) {
result <<= 1; // Shift received bits towards MSB
digitalWrite(_pinClock, HIGH); // Generate clock pulse
PULSE_SHORT;
result |= digitalRead(_pinData); // Merge next bit into LSB position
digitalWrite(_pinClock, LOW);
PULSE_SHORT;
}
pinMode(_pinData, OUTPUT);
digitalWrite(_pinData, !ack); // Assert ACK ('0') if ack == 1
PULSE_SHORT;
digitalWrite(_pinClock, HIGH); // Clock #9 for ACK / noACK
PULSE_LONG;
digitalWrite(_pinClock, LOW); // Finish with clock in low state
PULSE_SHORT;
pinMode(_pinData, INPUT); // Return data line to input mode
#ifdef DATA_PU
digitalWrite(_pinData, DATA_PU); // Restore internal pullup state
#endif
return result;
}
/******************************************************************************
* Sensirion signaling
******************************************************************************/
// Generate Sensirion-specific transmission start sequence
// This is where Sensirion does not conform to the I2C standard and is
// the main reason why the AVR TWI hardware support can not be used.
// _____ ________
// DATA: |_______|
// ___ ___
// SCK : ___| |___| |______
void Sensirion::startTransmission(void) {
digitalWrite(_pinData, HIGH); // Set data register high before turning on
pinMode(_pinData, OUTPUT); // output driver (avoid possible low pulse)
PULSE_SHORT;
digitalWrite(_pinClock, HIGH);
PULSE_SHORT;
digitalWrite(_pinData, LOW);
PULSE_SHORT;
digitalWrite(_pinClock, LOW);
PULSE_LONG;
digitalWrite(_pinClock, HIGH);
PULSE_SHORT;
digitalWrite(_pinData, HIGH);
PULSE_SHORT;
digitalWrite(_pinClock, LOW);
PULSE_SHORT;
// Unnecessary here since putByte always follows startTransmission
// pinMode(_pinData, INPUT);
}
// Communication link reset
// At least 9 SCK cycles with DATA=1, followed by transmission start sequence
// ______________________________________________________ ________
// DATA: |_______|
// _ _ _ _ _ _ _ _ _ ___ ___
// SCK : __| |__| |__| |__| |__| |__| |__| |__| |__| |______| |___| |______
void Sensirion::resetConnection(void) {
uint8_t i;
digitalWrite(_pinData, HIGH); // Set data register high before turning on
pinMode(_pinData, OUTPUT); // output driver (avoid possible low pulse)
PULSE_LONG;
for (i = 0; i < 9; i++) {
digitalWrite(_pinClock, HIGH);
PULSE_LONG;
digitalWrite(_pinClock, LOW);
PULSE_LONG;
}
startTransmission();
}
/******************************************************************************
* Helper Functions
******************************************************************************/
// Calculates temperature in degrees C from raw sensor data
float Sensirion::calcTemp(uint16_t rawData) {
if (_stat_reg & LOW_RES)
return D1 + D2l * (float) rawData;
else
return D1 + D2h * (float) rawData;
}
// Calculates relative humidity from raw sensor data
// (with temperature compensation)
float Sensirion::calcHumi(uint16_t rawData, float temp) {
float humi;
if (_stat_reg & LOW_RES) {
humi = C1 + C2l * rawData + C3l * rawData * rawData;
humi = (temp - 25.0) * (T1 + T2l * rawData) + humi;
} else {
humi = C1 + C2h * rawData + C3h * rawData * rawData;
humi = (temp - 25.0) * (T1 + T2h * rawData) + humi;
}
if (humi > 100.0) humi = 100.0;
if (humi < 0.1) humi = 0.1;
return humi;
}
// Calculates dew point in degrees C
float Sensirion::calcDewpoint(float humi, float temp) {
float k;
k = log(humi/100) + (17.62 * temp) / (243.12 + temp);
return 243.12 * k / (17.62 - k);
}
#ifdef CRC_ENA
// Calculate CRC for a single byte
void Sensirion::calcCRC(uint8_t value, uint8_t *crc) {
const uint8_t POLY = 0x31; // Polynomial: x**8 + x**5 + x**4 + 1
uint8_t i;
*crc ^= value;
for (i = 8; i > 0; i--) {
if (*crc & 0x80)
*crc = (*crc << 1) ^ POLY;
else
*crc = (*crc << 1);
}
}
// Bit-reverse a byte (for CRC calculations)
uint8_t Sensirion::bitrev(uint8_t value) {
uint8_t i;
uint8_t result = 0;
for (i = 8; i > 0; i--) {
result = (result << 1) | (value & 0x01);
value >>= 1;
}
return result;
}
#endif