uses BBC micro:bit to measure and display indoor air quality using Bosch BME680 and/or Sensirion SGP30

Dependencies:   microbit

uses Bosch BME680 and/or Sensirion SGP30 sensors to measure indor air quality

sensors should be connected to BBC micro:bit using i2c

commands are received and data is being sent using uBit / nordic radio protocol

display ---

last line always indicates: - first dot: bme680 detected - second dot: sgp30 detected - third dot: sgp 30 setting humidity/temperature - fourth dor: sgp30 measuring - fith dot: bme680 measuring

the detect dots should be in a stable state (not blinking) the measuring dots should be blinking (constant light means: measurement failed)

if only one bme680 is present: - first 3 lines indicate gas resistence (air quality / more dots == worse quality) - fourth line indicates humidity level

if only sgp30 is present: - first two lines indicate SGP30 VOC level - third and fourth line indicate sgp30 CO2 level

if both sensors are present: - first line indicates SGP30 VOC level - second line line indicates sgp30 CO2 level - third line indicates bme680 gas resistence (air quality) - fourth line indicates bme 680 humidity level

buttons - B display state, switches betweeen - full bright - low light - display off

AB reset sgp30 baseline in non volatile storage

data logging -- during measurements the minimum and mximum values for each measured value (temperature, air pressure, humidity,gas resistance, VOC, CO2) are being stored in non volatile storage those (and the last measurement results) are being shown when btn A has been pressed

Committer:
jsa1969
Date:
Wed Feb 13 20:21:38 2019 +0000
Revision:
44:67a19da5f269
Parent:
42:ce269f988ac8
Child:
46:2fed2865a0f3
more bme tweaking

Who changed what in which revision?

UserRevisionLine numberNew contents of line
jsa1969 2:544117df8c65 1 #include "sgp30.h"
jsa1969 2:544117df8c65 2 #include "physics.h"
jsa1969 2:544117df8c65 3
jsa1969 2:544117df8c65 4 // the i2c address
jsa1969 2:544117df8c65 5 #define SGP30_I2CADDR_DEFAULT 0x58 ///< SGP30 has only one I2C address
jsa1969 2:544117df8c65 6
jsa1969 2:544117df8c65 7 // commands and constants
jsa1969 2:544117df8c65 8 #define SGP30_FEATURESET 0x0020 ///< The required set for this library
jsa1969 2:544117df8c65 9 #define SGP30_CRC8_POLYNOMIAL 0x31 ///< Seed for SGP30's CRC polynomial
jsa1969 2:544117df8c65 10 #define SGP30_CRC8_INIT 0xFF ///< Init value for CRC
jsa1969 2:544117df8c65 11 #define SGP30_WORD_LEN 2 ///< 2 bytes per word
jsa1969 2:544117df8c65 12
jsa1969 2:544117df8c65 13 Sgp30::Sgp30(I2cCallbacks *callbacks) {
jsa1969 2:544117df8c65 14 _callbacks = callbacks;
jsa1969 2:544117df8c65 15 _i2caddr = SGP30_I2CADDR_DEFAULT <<1;
jsa1969 2:544117df8c65 16 }
jsa1969 2:544117df8c65 17
jsa1969 2:544117df8c65 18 bool Sgp30::begin() {
jsa1969 2:544117df8c65 19 uint8_t command[2];
jsa1969 2:544117df8c65 20 command[0] = 0x36;
jsa1969 2:544117df8c65 21 command[1] = 0x82;
jsa1969 2:544117df8c65 22 if (!readWordFromCommand(command, 2, 10, serialnumber, 3)) {
jsa1969 2:544117df8c65 23 return false;
jsa1969 2:544117df8c65 24 }
jsa1969 2:544117df8c65 25
jsa1969 2:544117df8c65 26 uint16_t featureset;
jsa1969 2:544117df8c65 27 command[0] = 0x20;
jsa1969 2:544117df8c65 28 command[1] = 0x2F;
jsa1969 2:544117df8c65 29 if (! readWordFromCommand(command, 2, 10, &featureset, 1)) {
jsa1969 2:544117df8c65 30 return false;
jsa1969 2:544117df8c65 31 }
jsa1969 2:544117df8c65 32
jsa1969 2:544117df8c65 33 //Serial.print("Featureset 0x"); Serial.println(featureset, HEX);
jsa1969 2:544117df8c65 34 if (featureset != SGP30_FEATURESET) {
jsa1969 2:544117df8c65 35 return false;
jsa1969 2:544117df8c65 36 }
jsa1969 2:544117df8c65 37
jsa1969 2:544117df8c65 38 if (! IAQinit()) {
jsa1969 2:544117df8c65 39 return false;
jsa1969 2:544117df8c65 40 }
jsa1969 2:544117df8c65 41
jsa1969 2:544117df8c65 42 return true;
jsa1969 2:544117df8c65 43 }
jsa1969 2:544117df8c65 44
jsa1969 15:8bb5c580d453 45 bool Sgp30::IAQmeasure() {
jsa1969 2:544117df8c65 46 uint8_t command[2];
jsa1969 2:544117df8c65 47 command[0] = 0x20;
jsa1969 2:544117df8c65 48 command[1] = 0x08;
jsa1969 2:544117df8c65 49 uint16_t reply[2];
jsa1969 2:544117df8c65 50 if (! readWordFromCommand(command, 2, 12, reply, 2)) {
jsa1969 2:544117df8c65 51 return false;
jsa1969 2:544117df8c65 52 }
jsa1969 2:544117df8c65 53 TVOC = reply[1];
jsa1969 2:544117df8c65 54 eCO2 = reply[0];
jsa1969 2:544117df8c65 55 return true;
jsa1969 2:544117df8c65 56 }
jsa1969 2:544117df8c65 57
jsa1969 15:8bb5c580d453 58 bool Sgp30::test() {
jsa1969 5:9a04d6d0c2ff 59 uint8_t command[2];
jsa1969 5:9a04d6d0c2ff 60 command[0] = 0x20;
jsa1969 5:9a04d6d0c2ff 61 command[1] = 0x32;
jsa1969 6:fc6f2f08724b 62 uint16_t reply;
jsa1969 6:fc6f2f08724b 63 if (! readWordFromCommand(command, 2, 220, &reply, 1)) {
jsa1969 5:9a04d6d0c2ff 64 return false;
jsa1969 5:9a04d6d0c2ff 65 }
jsa1969 5:9a04d6d0c2ff 66
jsa1969 6:fc6f2f08724b 67 return reply==0xD400;
jsa1969 5:9a04d6d0c2ff 68 }
jsa1969 15:8bb5c580d453 69 bool Sgp30::setHumidity(const uint32_t rel_humidity, const uint32_t temperature) {
jsa1969 2:544117df8c65 70 return setHumidity(Physics::absHumidity(rel_humidity, temperature));
jsa1969 2:544117df8c65 71 }
jsa1969 2:544117df8c65 72
jsa1969 15:8bb5c580d453 73 bool Sgp30::IAQinit() {
jsa1969 2:544117df8c65 74 uint8_t command[2];
jsa1969 2:544117df8c65 75 command[0] = 0x20;
jsa1969 2:544117df8c65 76 command[1] = 0x03;
jsa1969 2:544117df8c65 77 return readWordFromCommand(command, 2, 10);
jsa1969 2:544117df8c65 78 }
jsa1969 2:544117df8c65 79
jsa1969 15:8bb5c580d453 80 bool Sgp30::setHumidity(const uint32_t absolute_humidity) {
jsa1969 2:544117df8c65 81 if (absolute_humidity > 256000) {
jsa1969 2:544117df8c65 82 return false;
jsa1969 2:544117df8c65 83 }
jsa1969 2:544117df8c65 84
jsa1969 2:544117df8c65 85 if (absolute_humidity == _absolute_humidity) {
jsa1969 2:544117df8c65 86 return true;
jsa1969 2:544117df8c65 87 }
jsa1969 2:544117df8c65 88
jsa1969 2:544117df8c65 89 uint16_t ah_scaled = (uint16_t)(((uint64_t)absolute_humidity * 256 * 16777) >> 24);
jsa1969 2:544117df8c65 90 uint8_t command[5];
jsa1969 2:544117df8c65 91 command[0] = 0x20;
jsa1969 2:544117df8c65 92 command[1] = 0x61;
jsa1969 2:544117df8c65 93 command[2] = ah_scaled >> 8;
jsa1969 2:544117df8c65 94 command[3] = ah_scaled & 0xFF;
jsa1969 2:544117df8c65 95 command[4] = generateCRC(command+2, 2);
jsa1969 2:544117df8c65 96
jsa1969 2:544117df8c65 97 if (readWordFromCommand(command, 5, 10)) {
jsa1969 2:544117df8c65 98 _absolute_humidity = absolute_humidity;
jsa1969 2:544117df8c65 99 return true;
jsa1969 2:544117df8c65 100 } else {
jsa1969 2:544117df8c65 101 return false;
jsa1969 2:544117df8c65 102 }
jsa1969 2:544117df8c65 103 }
jsa1969 2:544117df8c65 104
jsa1969 15:8bb5c580d453 105 bool Sgp30::readWordFromCommand(const uint8_t command[], const uint8_t commandLength, const uint16_t delayms, uint16_t *readdata, const uint8_t readlen)
jsa1969 2:544117df8c65 106 {
jsa1969 44:67a19da5f269 107 int lock = _callbacks->acquireLock();
jsa1969 44:67a19da5f269 108 if (_callbacks->write(_i2caddr, command, commandLength, lock) != MICROBIT_OK) {
jsa1969 44:67a19da5f269 109 _callbacks->releaseLock();
jsa1969 2:544117df8c65 110 return false;
jsa1969 2:544117df8c65 111 }
jsa1969 2:544117df8c65 112
jsa1969 42:ce269f988ac8 113 _callbacks->delay_ms_relaxed(delayms);
jsa1969 2:544117df8c65 114
jsa1969 44:67a19da5f269 115 if (readlen == 0) {
jsa1969 44:67a19da5f269 116 _callbacks->releaseLock();
jsa1969 2:544117df8c65 117 return true;
jsa1969 44:67a19da5f269 118 }
jsa1969 2:544117df8c65 119
jsa1969 2:544117df8c65 120 uint8_t replylen = readlen * (SGP30_WORD_LEN +1);
jsa1969 2:544117df8c65 121 uint8_t replybuffer[replylen];
jsa1969 44:67a19da5f269 122 if (_callbacks->read(_i2caddr,replybuffer,replylen, lock) != MICROBIT_OK) {
jsa1969 44:67a19da5f269 123 _callbacks->releaseLock();
jsa1969 2:544117df8c65 124 return false;
jsa1969 2:544117df8c65 125 }
jsa1969 44:67a19da5f269 126
jsa1969 44:67a19da5f269 127 _callbacks->releaseLock();
jsa1969 2:544117df8c65 128 for (uint8_t i=0; i<readlen; i++) {
jsa1969 2:544117df8c65 129 uint8_t crc = generateCRC(replybuffer+i*3, 2);
jsa1969 2:544117df8c65 130 if (crc != replybuffer[i * 3 + 2])
jsa1969 2:544117df8c65 131 return false;
jsa1969 2:544117df8c65 132 // success! store it
jsa1969 2:544117df8c65 133 readdata[i] = replybuffer[i*3];
jsa1969 2:544117df8c65 134 readdata[i] <<= 8;
jsa1969 2:544117df8c65 135 readdata[i] |= replybuffer[i*3 + 1];
jsa1969 2:544117df8c65 136 }
jsa1969 2:544117df8c65 137
jsa1969 2:544117df8c65 138 return true;
jsa1969 2:544117df8c65 139 }
jsa1969 2:544117df8c65 140
jsa1969 15:8bb5c580d453 141 uint8_t Sgp30::generateCRC(const uint8_t *data, const uint8_t datalen) {
jsa1969 2:544117df8c65 142 // calculates 8-Bit checksum with given polynomial
jsa1969 2:544117df8c65 143 uint8_t crc = SGP30_CRC8_INIT;
jsa1969 2:544117df8c65 144
jsa1969 2:544117df8c65 145 for (uint8_t i=0; i<datalen; i++) {
jsa1969 2:544117df8c65 146 crc ^= data[i];
jsa1969 2:544117df8c65 147 for (uint8_t b=0; b<8; b++) {
jsa1969 2:544117df8c65 148 if (crc & 0x80)
jsa1969 2:544117df8c65 149 crc = (crc << 1) ^ SGP30_CRC8_POLYNOMIAL;
jsa1969 2:544117df8c65 150 else
jsa1969 2:544117df8c65 151 crc <<= 1;
jsa1969 2:544117df8c65 152 }
jsa1969 2:544117df8c65 153 }
jsa1969 2:544117df8c65 154 return crc;
jsa1969 2:544117df8c65 155 }