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:
Fri Jun 03 17:05:56 2022 +0000
Revision:
60:6b21ca38ee7c
Parent:
52:112eaa5282c6
cleanup

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