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:
Tue Dec 04 06:50:48 2018 +0000
Revision:
2:544117df8c65
Child:
5:9a04d6d0c2ff
sgp30

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 2:544117df8c65 45 bool Sgp30::IAQmeasure(void) {
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 2:544117df8c65 58 bool Sgp30::setHumidity(uint32_t rel_humidity, uint32_t temperature) {
jsa1969 2:544117df8c65 59 return setHumidity(Physics::absHumidity(rel_humidity, temperature));
jsa1969 2:544117df8c65 60 }
jsa1969 2:544117df8c65 61
jsa1969 2:544117df8c65 62 bool Sgp30::IAQinit(void) {
jsa1969 2:544117df8c65 63 uint8_t command[2];
jsa1969 2:544117df8c65 64 command[0] = 0x20;
jsa1969 2:544117df8c65 65 command[1] = 0x03;
jsa1969 2:544117df8c65 66 return readWordFromCommand(command, 2, 10);
jsa1969 2:544117df8c65 67 }
jsa1969 2:544117df8c65 68
jsa1969 2:544117df8c65 69 bool Sgp30::setHumidity(uint32_t absolute_humidity) {
jsa1969 2:544117df8c65 70 if (absolute_humidity > 256000) {
jsa1969 2:544117df8c65 71 return false;
jsa1969 2:544117df8c65 72 }
jsa1969 2:544117df8c65 73
jsa1969 2:544117df8c65 74 if (absolute_humidity == _absolute_humidity) {
jsa1969 2:544117df8c65 75 return true;
jsa1969 2:544117df8c65 76 }
jsa1969 2:544117df8c65 77
jsa1969 2:544117df8c65 78 uint16_t ah_scaled = (uint16_t)(((uint64_t)absolute_humidity * 256 * 16777) >> 24);
jsa1969 2:544117df8c65 79 uint8_t command[5];
jsa1969 2:544117df8c65 80 command[0] = 0x20;
jsa1969 2:544117df8c65 81 command[1] = 0x61;
jsa1969 2:544117df8c65 82 command[2] = ah_scaled >> 8;
jsa1969 2:544117df8c65 83 command[3] = ah_scaled & 0xFF;
jsa1969 2:544117df8c65 84 command[4] = generateCRC(command+2, 2);
jsa1969 2:544117df8c65 85
jsa1969 2:544117df8c65 86 if (readWordFromCommand(command, 5, 10)) {
jsa1969 2:544117df8c65 87 _absolute_humidity = absolute_humidity;
jsa1969 2:544117df8c65 88 return true;
jsa1969 2:544117df8c65 89 } else {
jsa1969 2:544117df8c65 90 return false;
jsa1969 2:544117df8c65 91 }
jsa1969 2:544117df8c65 92 }
jsa1969 2:544117df8c65 93
jsa1969 2:544117df8c65 94 bool Sgp30::readWordFromCommand(uint8_t command[], uint8_t commandLength, uint16_t delayms, uint16_t *readdata, uint8_t readlen)
jsa1969 2:544117df8c65 95 {
jsa1969 2:544117df8c65 96 if (_callbacks->write(_i2caddr, command, commandLength) != MICROBIT_OK) {
jsa1969 2:544117df8c65 97 return false;
jsa1969 2:544117df8c65 98 }
jsa1969 2:544117df8c65 99
jsa1969 2:544117df8c65 100 _callbacks->delay_ms(delayms);
jsa1969 2:544117df8c65 101
jsa1969 2:544117df8c65 102 if (readlen == 0)
jsa1969 2:544117df8c65 103 return true;
jsa1969 2:544117df8c65 104
jsa1969 2:544117df8c65 105 uint8_t replylen = readlen * (SGP30_WORD_LEN +1);
jsa1969 2:544117df8c65 106 uint8_t replybuffer[replylen];
jsa1969 2:544117df8c65 107 if (_callbacks->read(_i2caddr,replybuffer,replylen) != MICROBIT_OK) {
jsa1969 2:544117df8c65 108 return false;
jsa1969 2:544117df8c65 109 }
jsa1969 2:544117df8c65 110
jsa1969 2:544117df8c65 111 for (uint8_t i=0; i<readlen; i++) {
jsa1969 2:544117df8c65 112 uint8_t crc = generateCRC(replybuffer+i*3, 2);
jsa1969 2:544117df8c65 113 if (crc != replybuffer[i * 3 + 2])
jsa1969 2:544117df8c65 114 return false;
jsa1969 2:544117df8c65 115 // success! store it
jsa1969 2:544117df8c65 116 readdata[i] = replybuffer[i*3];
jsa1969 2:544117df8c65 117 readdata[i] <<= 8;
jsa1969 2:544117df8c65 118 readdata[i] |= replybuffer[i*3 + 1];
jsa1969 2:544117df8c65 119 }
jsa1969 2:544117df8c65 120
jsa1969 2:544117df8c65 121 return true;
jsa1969 2:544117df8c65 122 }
jsa1969 2:544117df8c65 123
jsa1969 2:544117df8c65 124 uint8_t Sgp30::generateCRC(uint8_t *data, uint8_t datalen) {
jsa1969 2:544117df8c65 125 // calculates 8-Bit checksum with given polynomial
jsa1969 2:544117df8c65 126 uint8_t crc = SGP30_CRC8_INIT;
jsa1969 2:544117df8c65 127
jsa1969 2:544117df8c65 128 for (uint8_t i=0; i<datalen; i++) {
jsa1969 2:544117df8c65 129 crc ^= data[i];
jsa1969 2:544117df8c65 130 for (uint8_t b=0; b<8; b++) {
jsa1969 2:544117df8c65 131 if (crc & 0x80)
jsa1969 2:544117df8c65 132 crc = (crc << 1) ^ SGP30_CRC8_POLYNOMIAL;
jsa1969 2:544117df8c65 133 else
jsa1969 2:544117df8c65 134 crc <<= 1;
jsa1969 2:544117df8c65 135 }
jsa1969 2:544117df8c65 136 }
jsa1969 2:544117df8c65 137 return crc;
jsa1969 2:544117df8c65 138 }