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 14:17:21 2018 +0000
Revision:
5:9a04d6d0c2ff
Parent:
2:544117df8c65
Child:
6:fc6f2f08724b
cleanup UI (less text, more dots)

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 5:9a04d6d0c2ff 58 bool Sgp30::test(void) {
jsa1969 5:9a04d6d0c2ff 59 uint8_t command[2];
jsa1969 5:9a04d6d0c2ff 60 command[0] = 0x20;
jsa1969 5:9a04d6d0c2ff 61 command[1] = 0x32;
jsa1969 5:9a04d6d0c2ff 62 uint16_t reply[2];
jsa1969 5:9a04d6d0c2ff 63 if (! readWordFromCommand(command, 2, 220, reply, 2)) {
jsa1969 5:9a04d6d0c2ff 64 return false;
jsa1969 5:9a04d6d0c2ff 65 }
jsa1969 5:9a04d6d0c2ff 66
jsa1969 5:9a04d6d0c2ff 67 return reply[0]==0xD4 && reply[1]==0x00;
jsa1969 5:9a04d6d0c2ff 68 }
jsa1969 2:544117df8c65 69 bool Sgp30::setHumidity(uint32_t rel_humidity, uint32_t temperature) {
jsa1969 2:544117df8c65 70 return setHumidity(Physics::absHumidity(rel_humidity, temperature));
jsa1969 2:544117df8c65 71 }
jsa1969 2:544117df8c65 72
jsa1969 2:544117df8c65 73 bool Sgp30::IAQinit(void) {
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 2:544117df8c65 80 bool Sgp30::setHumidity(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 2:544117df8c65 105 bool Sgp30::readWordFromCommand(uint8_t command[], uint8_t commandLength, uint16_t delayms, uint16_t *readdata, uint8_t readlen)
jsa1969 2:544117df8c65 106 {
jsa1969 2:544117df8c65 107 if (_callbacks->write(_i2caddr, command, commandLength) != MICROBIT_OK) {
jsa1969 2:544117df8c65 108 return false;
jsa1969 2:544117df8c65 109 }
jsa1969 2:544117df8c65 110
jsa1969 2:544117df8c65 111 _callbacks->delay_ms(delayms);
jsa1969 2:544117df8c65 112
jsa1969 2:544117df8c65 113 if (readlen == 0)
jsa1969 2:544117df8c65 114 return true;
jsa1969 2:544117df8c65 115
jsa1969 2:544117df8c65 116 uint8_t replylen = readlen * (SGP30_WORD_LEN +1);
jsa1969 2:544117df8c65 117 uint8_t replybuffer[replylen];
jsa1969 2:544117df8c65 118 if (_callbacks->read(_i2caddr,replybuffer,replylen) != MICROBIT_OK) {
jsa1969 2:544117df8c65 119 return false;
jsa1969 2:544117df8c65 120 }
jsa1969 2:544117df8c65 121
jsa1969 2:544117df8c65 122 for (uint8_t i=0; i<readlen; i++) {
jsa1969 2:544117df8c65 123 uint8_t crc = generateCRC(replybuffer+i*3, 2);
jsa1969 2:544117df8c65 124 if (crc != replybuffer[i * 3 + 2])
jsa1969 2:544117df8c65 125 return false;
jsa1969 2:544117df8c65 126 // success! store it
jsa1969 2:544117df8c65 127 readdata[i] = replybuffer[i*3];
jsa1969 2:544117df8c65 128 readdata[i] <<= 8;
jsa1969 2:544117df8c65 129 readdata[i] |= replybuffer[i*3 + 1];
jsa1969 2:544117df8c65 130 }
jsa1969 2:544117df8c65 131
jsa1969 2:544117df8c65 132 return true;
jsa1969 2:544117df8c65 133 }
jsa1969 2:544117df8c65 134
jsa1969 2:544117df8c65 135 uint8_t Sgp30::generateCRC(uint8_t *data, uint8_t datalen) {
jsa1969 2:544117df8c65 136 // calculates 8-Bit checksum with given polynomial
jsa1969 2:544117df8c65 137 uint8_t crc = SGP30_CRC8_INIT;
jsa1969 2:544117df8c65 138
jsa1969 2:544117df8c65 139 for (uint8_t i=0; i<datalen; i++) {
jsa1969 2:544117df8c65 140 crc ^= data[i];
jsa1969 2:544117df8c65 141 for (uint8_t b=0; b<8; b++) {
jsa1969 2:544117df8c65 142 if (crc & 0x80)
jsa1969 2:544117df8c65 143 crc = (crc << 1) ^ SGP30_CRC8_POLYNOMIAL;
jsa1969 2:544117df8c65 144 else
jsa1969 2:544117df8c65 145 crc <<= 1;
jsa1969 2:544117df8c65 146 }
jsa1969 2:544117df8c65 147 }
jsa1969 2:544117df8c65 148 return crc;
jsa1969 2:544117df8c65 149 }