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:
Sun Jan 16 15:42:13 2022 +0000
Revision:
49:bbb506b58e6e
Parent:
46:2fed2865a0f3
Child:
52:112eaa5282c6
support switching light levels via btn B

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 49:bbb506b58e6e 69
jsa1969 49:bbb506b58e6e 70 bool Sgp30::getIAQBaseline(uint16_t *eco2_base,
jsa1969 49:bbb506b58e6e 71 uint16_t *tvoc_base) {
jsa1969 49:bbb506b58e6e 72 uint8_t command[2];
jsa1969 49:bbb506b58e6e 73 command[0] = 0x20;
jsa1969 49:bbb506b58e6e 74 command[1] = 0x15;
jsa1969 49:bbb506b58e6e 75 uint16_t reply[2];
jsa1969 49:bbb506b58e6e 76 if (!readWordFromCommand(command, 2, 10, reply, 2))
jsa1969 49:bbb506b58e6e 77 return false;
jsa1969 49:bbb506b58e6e 78 *eco2_base = reply[0];
jsa1969 49:bbb506b58e6e 79 *tvoc_base = reply[1];
jsa1969 49:bbb506b58e6e 80 return true;
jsa1969 49:bbb506b58e6e 81 }
jsa1969 49:bbb506b58e6e 82
jsa1969 49:bbb506b58e6e 83 bool Sgp30::setIAQBaseline(uint16_t eco2_base, uint16_t tvoc_base) {
jsa1969 49:bbb506b58e6e 84 uint8_t command[8];
jsa1969 49:bbb506b58e6e 85 command[0] = 0x20;
jsa1969 49:bbb506b58e6e 86 command[1] = 0x1e;
jsa1969 49:bbb506b58e6e 87 command[2] = tvoc_base >> 8;
jsa1969 49:bbb506b58e6e 88 command[3] = tvoc_base & 0xFF;
jsa1969 49:bbb506b58e6e 89 command[4] = generateCRC(command + 2, 2);
jsa1969 49:bbb506b58e6e 90 command[5] = eco2_base >> 8;
jsa1969 49:bbb506b58e6e 91 command[6] = eco2_base & 0xFF;
jsa1969 49:bbb506b58e6e 92 command[7] = generateCRC(command + 5, 2);
jsa1969 49:bbb506b58e6e 93
jsa1969 49:bbb506b58e6e 94 return readWordFromCommand(command, 8, 10);
jsa1969 49:bbb506b58e6e 95 }
jsa1969 49:bbb506b58e6e 96
jsa1969 15:8bb5c580d453 97 bool Sgp30::setHumidity(const uint32_t rel_humidity, const uint32_t temperature) {
jsa1969 2:544117df8c65 98 return setHumidity(Physics::absHumidity(rel_humidity, temperature));
jsa1969 2:544117df8c65 99 }
jsa1969 2:544117df8c65 100
jsa1969 15:8bb5c580d453 101 bool Sgp30::IAQinit() {
jsa1969 2:544117df8c65 102 uint8_t command[2];
jsa1969 2:544117df8c65 103 command[0] = 0x20;
jsa1969 2:544117df8c65 104 command[1] = 0x03;
jsa1969 2:544117df8c65 105 return readWordFromCommand(command, 2, 10);
jsa1969 2:544117df8c65 106 }
jsa1969 2:544117df8c65 107
jsa1969 15:8bb5c580d453 108 bool Sgp30::setHumidity(const uint32_t absolute_humidity) {
jsa1969 2:544117df8c65 109 if (absolute_humidity > 256000) {
jsa1969 2:544117df8c65 110 return false;
jsa1969 2:544117df8c65 111 }
jsa1969 2:544117df8c65 112
jsa1969 2:544117df8c65 113 if (absolute_humidity == _absolute_humidity) {
jsa1969 2:544117df8c65 114 return true;
jsa1969 2:544117df8c65 115 }
jsa1969 2:544117df8c65 116
jsa1969 2:544117df8c65 117 uint16_t ah_scaled = (uint16_t)(((uint64_t)absolute_humidity * 256 * 16777) >> 24);
jsa1969 2:544117df8c65 118 uint8_t command[5];
jsa1969 2:544117df8c65 119 command[0] = 0x20;
jsa1969 2:544117df8c65 120 command[1] = 0x61;
jsa1969 2:544117df8c65 121 command[2] = ah_scaled >> 8;
jsa1969 2:544117df8c65 122 command[3] = ah_scaled & 0xFF;
jsa1969 2:544117df8c65 123 command[4] = generateCRC(command+2, 2);
jsa1969 2:544117df8c65 124
jsa1969 2:544117df8c65 125 if (readWordFromCommand(command, 5, 10)) {
jsa1969 2:544117df8c65 126 _absolute_humidity = absolute_humidity;
jsa1969 2:544117df8c65 127 return true;
jsa1969 2:544117df8c65 128 } else {
jsa1969 2:544117df8c65 129 return false;
jsa1969 2:544117df8c65 130 }
jsa1969 2:544117df8c65 131 }
jsa1969 2:544117df8c65 132
jsa1969 15:8bb5c580d453 133 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 134 {
jsa1969 46:2fed2865a0f3 135 if (_callbacks->write(_i2caddr, command, commandLength) != MICROBIT_OK) {
jsa1969 2:544117df8c65 136 return false;
jsa1969 2:544117df8c65 137 }
jsa1969 2:544117df8c65 138
jsa1969 42:ce269f988ac8 139 _callbacks->delay_ms_relaxed(delayms);
jsa1969 2:544117df8c65 140
jsa1969 44:67a19da5f269 141 if (readlen == 0) {
jsa1969 2:544117df8c65 142 return true;
jsa1969 44:67a19da5f269 143 }
jsa1969 2:544117df8c65 144
jsa1969 2:544117df8c65 145 uint8_t replylen = readlen * (SGP30_WORD_LEN +1);
jsa1969 2:544117df8c65 146 uint8_t replybuffer[replylen];
jsa1969 46:2fed2865a0f3 147 if (_callbacks->read(_i2caddr,replybuffer,replylen) != MICROBIT_OK) {
jsa1969 2:544117df8c65 148 return false;
jsa1969 2:544117df8c65 149 }
jsa1969 44:67a19da5f269 150
jsa1969 2:544117df8c65 151 for (uint8_t i=0; i<readlen; i++) {
jsa1969 2:544117df8c65 152 uint8_t crc = generateCRC(replybuffer+i*3, 2);
jsa1969 2:544117df8c65 153 if (crc != replybuffer[i * 3 + 2])
jsa1969 2:544117df8c65 154 return false;
jsa1969 2:544117df8c65 155 // success! store it
jsa1969 2:544117df8c65 156 readdata[i] = replybuffer[i*3];
jsa1969 2:544117df8c65 157 readdata[i] <<= 8;
jsa1969 2:544117df8c65 158 readdata[i] |= replybuffer[i*3 + 1];
jsa1969 2:544117df8c65 159 }
jsa1969 2:544117df8c65 160
jsa1969 2:544117df8c65 161 return true;
jsa1969 2:544117df8c65 162 }
jsa1969 2:544117df8c65 163
jsa1969 15:8bb5c580d453 164 uint8_t Sgp30::generateCRC(const uint8_t *data, const uint8_t datalen) {
jsa1969 2:544117df8c65 165 // calculates 8-Bit checksum with given polynomial
jsa1969 2:544117df8c65 166 uint8_t crc = SGP30_CRC8_INIT;
jsa1969 2:544117df8c65 167
jsa1969 2:544117df8c65 168 for (uint8_t i=0; i<datalen; i++) {
jsa1969 2:544117df8c65 169 crc ^= data[i];
jsa1969 2:544117df8c65 170 for (uint8_t b=0; b<8; b++) {
jsa1969 2:544117df8c65 171 if (crc & 0x80)
jsa1969 2:544117df8c65 172 crc = (crc << 1) ^ SGP30_CRC8_POLYNOMIAL;
jsa1969 2:544117df8c65 173 else
jsa1969 2:544117df8c65 174 crc <<= 1;
jsa1969 2:544117df8c65 175 }
jsa1969 2:544117df8c65 176 }
jsa1969 2:544117df8c65 177 return crc;
jsa1969 2:544117df8c65 178 }