I have a few questions concerning using the bsec-IAQ in MCU sleep mode.. In the library, you have a delay_ms that uses delay() function from Arduino… how is this used? Do I need to be concern about this when the MCU sleeps? if so, it would be nice if you would make this function __weak__ so that it can be overwritten in the main program. What is the relationship of sensor.nextCall to MCU sleep timing? Should BEC state ONLY be saved if IAQ>3? If I run your “BME680-basic_config_state.ino”, modified for FRAM memory, and using Serial Port 1, it works just fine and converges on an IAQ of 3 in a 1 hour or less. I also acquired an ESP32 board and attach a BME680 to run your example code. Works fine.. I have a SAMD21 M0 MCU on a customer’s board with a BME680 and a FRAM (EEPROM like) NV memory.. I have been unable to get my test code to acquire an IAQ of anything other than zero when I attempted to sleep or delay for a fixed time… I based the code on the ESP32 example, but have been unable to make it work properly. I’m sure it’s something I’m missing or do not understand about using the BEQ library. Any ideas?? Note: On thing that I did notice, was in bsec.h, nextCall is an int64_t, as time is always positive in this case, I suggest that it should be an uint64_t, and for consistency, so should outputTimestamp. Also callTimeMs in bsec.cpp Thanks ~~~~~~~~~~~~~~~~~~~~~~~ Sensor: RT: 25.69, P: 1002.22, RH: 61.61, Gas: 81.36, IAQ: 154.46, Q: 0, T: 25.68, H: 61.66 *Writing state to FRAM ***Going to sleep until: 946697746 ***Wakeing up at: 946697747 **CurentTime: 946697747 **nextRunTime: 946697757 *Reading state from FRAM **iaqTime: 12947000 **nextRunTime: 946697757 **nextCall: 12950000 Sensor: RT: 25.70, P: 1002.20, RH: 61.61, Gas: 81.60, IAQ: 154.46, Q: 0, T: 25.69, H: 61.63 *Writing state to FRAM ***Going to sleep until: 946697757 ***Wakeing up at: 946697758 **CurentTime: 946697758 **nextRunTime: 946697768 *Reading state from FRAM **iaqTime: 12958000 **nextRunTime: 946697768 **nextCall: 12961000 Sensor: RT: 25.70, P: 1002.20, RH: 61.62, Gas: 81.20, IAQ: 154.46, Q: 0, T: 25.69, H: 61.64 *Writing state to FRAM ***Going to sleep until: 946697768 ***Wakeing up at: 946697769 **CurentTime: 946697769 **nextRunTime: 946697779 *Reading state from FRAM **iaqTime: 12969000 **nextRunTime: 946697779 **nextCall: 12972000 Sensor: RT: 25.71, P: 1002.20, RH: 61.55, Gas: 81.52, IAQ: 154.46, Q: 0, T: 25.70, H: 61.55 *Writing state to FRAM ***Going to sleep until: 946697779 /* *****************************************************************************
*
* *****************************************************************************
*
* CHANGE LOG:
*
* DATE REV DESCRIPTION
* ----------- --- ----------------------------------------------------------
* 01-Aug-2020 1.0 TRL - First Build of test version
*
*
* Notes: 1) Tested with Arduino 1.8.13
* 2) Tested using a Feather M0 SAMD21 CPU
* 3) Using a 64k Fram via I2C
* 4) Requires a SAMD21 Feather M0 varient.h file
*
* Todo: 1) Add support for Flash as EEPROM to use when FRAM is not avilable
* 2)
* 3)
*
*
*
* We need to define a FRAM map for this project:
*
* Function Size Base address Note:
* bsec_blob 1 + 139 0x00 1st byte is a flag, need to reserve ~150 bytes
*
*
* tom@lafleur.us
*
****************************************************************************** */
/* ************************************************************* */
#define SKETCHNAME "BME680-sleep test"
#define SKETCHVERSION "Ver: 1.0b"
#include <Wire.h>
#include <RTCZero.h> // https://github.com/arduino-libraries/RTCZero
#include <FRAM_MB85RC_I2C.h> // https://github.com/sosandroid/FRAM_MB85RC_I2C
#include <bsec.h> // https://github.com/BoschSensortec/BSEC-Arduino-library
// Include EEPROM-like API for FlashStorage
//#include <FlashAsEEPROM.h> // https://www.arduinolibraries.info/libraries/flash-storage
#define MyDelay // if this is defined, we will delay and not sleep...
//#define DumpBLOB // will print out bsec state BLOB on save and get
/* Configure the BSEC library with information about the sensor
18v/33v = Voltage at Vdd. 1.8V or 3.3V
3s/300s = BSEC operating mode, BSEC_SAMPLE_RATE_LP or BSEC_SAMPLE_RATE_ULP
4d/28d = Operating age of the sensor in days
generic_18v_3s_4d
generic_18v_3s_28d
generic_18v_300s_4d
generic_18v_300s_28d
generic_33v_3s_4d
generic_33v_3s_28d
generic_33v_300s_4d
generic_33v_300s_28d
*/
const uint8_t bsec_config_iaq[] = {
#include "config/generic_33v_3s_4d/bsec_iaq.txt"
};
// Create an object of the class Bsec
Bsec iaqSensor;
uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0};
// Creating object for FRAM chip
FRAM_MB85RC_I2C FRAM;
// Creating object for RTC
RTCZero rtc;
// Require by bsec
String output;
// functions forward declarations
void doSleep(uint32_t sTime);
bool CheckSensor(void);
void getState(void);
void saveState(void);
// Global variables....
const char CompileDate[32] = __DATE__ ", " __TIME__;
uint64_t nextRunTime = 0;
// Projects defines
#define BME680_ADDRESS (0x77) // I2C device address of the BME680 sensor
#define BSEC_ADDR 0 // base location in FRAM of bsec-blob... blob size is 139 bytes
#define TX_Interval 1 // in minutes
#ifndef ADAFRUIT_FEATHER_M0
#error Requires a Feather M0 varient.h file
#endif
/* ************************************************************** */
/* ************************************************************** */
void setup(void)
{
Serial.begin (115200); // Initialize USB/serial connection ( will disconnect when we sleep...)
Serial1.begin(115200); // debug port
delay(2000);
#ifdef MyDelay
Serial1.printf("\n\nStarted with delay: %s\n%s\n%s\n", SKETCHNAME, SKETCHVERSION, CompileDate);
#else
Serial1.printf("\n\nStarted with sleep: %s\n%s\n%s\n", SKETCHNAME, SKETCHVERSION, CompileDate);
#endif
Wire.begin();
FRAM.begin();
rtc.begin(true); // initialize the realtime clock (RTC), reset time...
Serial1.printf("**curTime: %lu sec\n", rtc.getEpoch() );
// Start BME680
iaqSensor.begin(BME680_ADDRESS, Wire);
if (!CheckSensor()) { Serial1.printf("Failed to init BME680, check wiring!\n"); }
Serial1.printf("\nBSEC library version %d.%d.%d.%d\n",
iaqSensor.version.major,
iaqSensor.version.minor,
iaqSensor.version.major_bugfix,
iaqSensor.version.minor_bugfix
);
iaqSensor.setConfig(bsec_config_iaq); // set operation mode
if (!CheckSensor()) { Serial1.printf("Failed to set BME680 config!\n"); }
// loadCalibrationData(); // load pass calabration data if available
getState(); // laod pass operational state from FRAM if available
if (!CheckSensor()) { Serial1.printf("Failed to set BME680 state!\n"); }
bsec_virtual_sensor_t sensorList[10] = {
BSEC_OUTPUT_RAW_TEMPERATURE,
BSEC_OUTPUT_RAW_PRESSURE,
BSEC_OUTPUT_RAW_HUMIDITY,
BSEC_OUTPUT_RAW_GAS,
BSEC_OUTPUT_IAQ,
BSEC_OUTPUT_STATIC_IAQ,
BSEC_OUTPUT_CO2_EQUIVALENT,
BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
};
iaqSensor.updateSubscription(sensorList, sizeof(sensorList) / sizeof(sensorList[0]), BSEC_SAMPLE_RATE_LP); // BSEC_SAMPLE_RATE_ULP
if (!CheckSensor()) { Serial1.printf("Failed to update iaq subscription!\n"); }
}
/* ************************************************************** */
/* ************************************************************** */
void loop(void)
{
nextRunTime = ( (10) + rtc.getEpoch() ); // 10 sec...
//nextRunTime = ( (TX_Interval * 60) + rtc.getEpoch() ); // get current time, add delay = next run time in sec
Serial1.printf("**CurentTime: %lu\n", rtc.getEpoch());
Serial1.printf("**nextRunTime: %lu\n", nextRunTime);
getState(); // reload beq data to device as we have just woken up......
if (!CheckSensor()) { Serial1.printf("Failed to set BME680 state!\n"); }
uint64_t iaqTime = (uint64_t) ((rtc.getEpoch() - 946684800l) * 1000);
Serial1.printf("**iaqTime: %lu\n",iaqTime);
if (iaqSensor.run(iaqTime )) // start the sensor with current time from RTC in ms, wait for data
{ // If new data is available
Serial1.printf("**nextRunTime: %lu\n", nextRunTime );
Serial1.printf("**nextCall: %lu\n", iaqSensor.nextCall );
if (!CheckSensor()) { Serial1.printf("*Data not valid!\n"); }
output = String("Sensor");
output += ": RT: " + String(iaqSensor.rawTemperature);
output += ", P: " + String(iaqSensor.pressure/100);
output += ", RH: " + String(iaqSensor.rawHumidity);
output += ", Gas: " + String(iaqSensor.gasResistance/1000);
output += "k, IAQ: " + String(iaqSensor.iaq);
output += ", Q: " + String(iaqSensor.iaqAccuracy);
output += ", T: " + String(iaqSensor.temperature);
output += ", H: " + String(iaqSensor.humidity);
Serial1.println(output);
saveState(); // save beq data here prior to sleep
Serial1.printf("***Going to sleep until: %lu\n", nextRunTime);
doSleep(nextRunTime); // deep sleep for x seconds
Serial1.printf("***Wakeing up at: %lu\n", (uint64_t) rtc.getEpoch());
}
}
/* ************************************************************** */
void doSleep(uint32_t sTime)
{
#ifdef MyDelay
while ( rtc.getEpoch() <= sTime ) { delay(1); } // this will fake sleeping for testing...
#else
rtc.setAlarmEpoch(sTime); // set new alarm time in seconds
rtc.enableAlarm(rtc.MATCH_HHMMSS); // currently only checking for HMS, not days or years
rtc.standbyMode(); // bring CPU into deep sleep mode (until woken up by the RTC alarm)
#endif
}
/* ************************************************************** */
bool CheckSensor()
{
if (iaqSensor.status < BSEC_OK)
{
Serial1.printf("BSEC error, status %d!", iaqSensor.status);
return false;
} else
if (iaqSensor.status > BSEC_OK)
{
Serial1.printf("BSEC warning, status %d!", iaqSensor.status);
}
if (iaqSensor.bme680Status < BME680_OK)
{
Serial1.printf("Sensor error, bme680_status %d!", iaqSensor.bme680Status);
return false;
} else if (iaqSensor.bme680Status > BME680_OK)
{
Serial1.printf("Sensor warning, status %d!", iaqSensor.bme680Status);
}
return true;
}
/* ************************************************************** */
void getState(void)
{
uint8_t Blob_Size; // its less that 256 bytes in size
FRAM.readByte(BSEC_ADDR, &Blob_Size);
if (Blob_Size == BSEC_MAX_STATE_BLOB_SIZE) // for now, see if we have blob size in FRAM... (should be CRC16 in future...)
{
// we have an Existing state in FRAM
Serial1.printf("*Reading state from FRAM\n");
for (uint32_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE; i++)
{
uint8_t rdata;
FRAM.readByte(i + 1, &rdata);
bsecState[i] = rdata;
#ifdef DumpBLOB
Serial1.printf("%02x ", rdata);
if (i % 16 == 15) { Serial1.print("\n"); }
if ( i == (BSEC_MAX_STATE_BLOB_SIZE -1 )) { Serial1.print("\n"); }
#endif
}
iaqSensor.setState(bsecState);
CheckSensor();
} else
{
// Erase the FRAM with zeroes
Serial1.printf("*Erasing FRAM\n");
for (uint32_t i = BSEC_ADDR; i < BSEC_MAX_STATE_BLOB_SIZE + 1; i++)
FRAM.writeByte(i, 0);
}
}
/* ************************************************************** */
void saveState(void)
{
iaqSensor.getState(bsecState);
CheckSensor();
Serial1.printf("*Writing state to FRAM\n");
for (uint32_t i = BSEC_ADDR; i < BSEC_MAX_STATE_BLOB_SIZE ; i++)
{
FRAM.writeByte(i + 1, bsecState[i]);
#ifdef DumpBLOB
Serial1.printf("%02x ", bsecState[i]);
if (i % 16 == 15) { Serial1.print("\n"); }
if ( i == (BSEC_MAX_STATE_BLOB_SIZE -1 )) { Serial1.print("\n"); }
#endif
}
FRAM.writeByte(BSEC_ADDR, BSEC_MAX_STATE_BLOB_SIZE); // save blob size as flag... (should be CRC16 in future...)
}
/* ********************* The End ******************************** */
/* ************************************************************** */
~~~~~~~~~~~~~~~~~~~~~~~~~~~ /* *****************************************************************************
*
* *****************************************************************************
*
* CHANGE LOG:
*
* DATE REV DESCRIPTION
* ----------- --- ----------------------------------------------------------
* 01-Aug-2020 1.0 TRL - First Build of test version
*
*
* Notes: 1) Tested with Arduino 1.8.13
* 2) Tested using a Feather M0 SAMD21 CPU
* 3) Using a 64k Fram via I2C
* 4) Requires a SAMD21 Feather M0 varient.h file
*
* Todo: 1) Add support for Flash as EEPROM to use when FRAM is not avilable
* 2)
* 3)
*
*
*
* We need to define a FRAM map for this project:
*
* Function Size Base address Note:
* bsec_blob 1 + 139 0x00 1st byte is a flag, need to reserve ~150 bytes
*
*
* tom@lafleur.us
*
****************************************************************************** */
/* ************************************************************* */
#define SKETCHNAME "BME680-sleep test"
#define SKETCHVERSION "Ver: 1.0b"
#include <Wire.h>
#include <RTCZero.h> // https://github.com/arduino-libraries/RTCZero
#include <FRAM_MB85RC_I2C.h> // https://github.com/sosandroid/FRAM_MB85RC_I2C
#include <bsec.h> // https://github.com/BoschSensortec/BSEC-Arduino-library
// Include EEPROM-like API for FlashStorage
//#include <FlashAsEEPROM.h> // https://www.arduinolibraries.info/libraries/flash-storage
#define MyDelay // if this is defined, we will delay and not sleep...
//#define DumpBLOB // will print out bsec state BLOB on save and get
/* Configure the BSEC library with information about the sensor
18v/33v = Voltage at Vdd. 1.8V or 3.3V
3s/300s = BSEC operating mode, BSEC_SAMPLE_RATE_LP or BSEC_SAMPLE_RATE_ULP
4d/28d = Operating age of the sensor in days
generic_18v_3s_4d
generic_18v_3s_28d
generic_18v_300s_4d
generic_18v_300s_28d
generic_33v_3s_4d
generic_33v_3s_28d
generic_33v_300s_4d
generic_33v_300s_28d
*/
const uint8_t bsec_config_iaq[] = {
#include "config/generic_33v_3s_4d/bsec_iaq.txt"
};
// Create an object of the class Bsec
Bsec iaqSensor;
uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0};
// Creating object for FRAM chip
FRAM_MB85RC_I2C FRAM;
// Creating object for RTC
RTCZero rtc;
// Require by bsec
String output;
// functions forward declarations
void doSleep(uint32_t sTime);
bool CheckSensor(void);
void getState(void);
void saveState(void);
// Global variables....
const char CompileDate[32] = __DATE__ ", " __TIME__;
uint64_t nextRunTime = 0;
// Projects defines
#define BME680_ADDRESS (0x77) // I2C device address of the BME680 sensor
#define BSEC_ADDR 0 // base location in FRAM of bsec-blob... blob size is 139 bytes
#define TX_Interval 1 // in minutes
#ifndef ADAFRUIT_FEATHER_M0
#error Requires a Feather M0 varient.h file
#endif
/* ************************************************************** */
/* ************************************************************** */
void setup(void)
{
Serial.begin (115200); // Initialize USB/serial connection ( will disconnect when we sleep...)
Serial1.begin(115200); // debug port
delay(2000);
#ifdef MyDelay
Serial1.printf("\n\nStarted with delay: %s\n%s\n%s\n", SKETCHNAME, SKETCHVERSION, CompileDate);
#else
Serial1.printf("\n\nStarted with sleep: %s\n%s\n%s\n", SKETCHNAME, SKETCHVERSION, CompileDate);
#endif
Wire.begin();
FRAM.begin();
rtc.begin(true); // initialize the realtime clock (RTC), reset time...
Serial1.printf("**curTime: %lu sec\n", rtc.getEpoch() );
// Start BME680
iaqSensor.begin(BME680_ADDRESS, Wire);
if (!CheckSensor()) { Serial1.printf("Failed to init BME680, check wiring!\n"); }
Serial1.printf("\nBSEC library version %d.%d.%d.%d\n",
iaqSensor.version.major,
iaqSensor.version.minor,
iaqSensor.version.major_bugfix,
iaqSensor.version.minor_bugfix
);
iaqSensor.setConfig(bsec_config_iaq); // set operation mode
if (!CheckSensor()) { Serial1.printf("Failed to set BME680 config!\n"); }
// loadCalibrationData(); // load pass calabration data if available
getState(); // laod pass operational state from FRAM if available
if (!CheckSensor()) { Serial1.printf("Failed to set BME680 state!\n"); }
bsec_virtual_sensor_t sensorList[10] = {
BSEC_OUTPUT_RAW_TEMPERATURE,
BSEC_OUTPUT_RAW_PRESSURE,
BSEC_OUTPUT_RAW_HUMIDITY,
BSEC_OUTPUT_RAW_GAS,
BSEC_OUTPUT_IAQ,
BSEC_OUTPUT_STATIC_IAQ,
BSEC_OUTPUT_CO2_EQUIVALENT,
BSEC_OUTPUT_BREATH_VOC_EQUIVALENT,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
};
iaqSensor.updateSubscription(sensorList, sizeof(sensorList) / sizeof(sensorList[0]), BSEC_SAMPLE_RATE_LP); // BSEC_SAMPLE_RATE_ULP
if (!CheckSensor()) { Serial1.printf("Failed to update iaq subscription!\n"); }
}
/* ************************************************************** */
/* ************************************************************** */
void loop(void)
{
nextRunTime = ( (10) + rtc.getEpoch() ); // 10 sec...
//nextRunTime = ( (TX_Interval * 60) + rtc.getEpoch() ); // get current time, add delay = next run time in sec
Serial1.printf("**CurentTime: %lu\n", rtc.getEpoch());
Serial1.printf("**nextRunTime: %lu\n", nextRunTime);
getState(); // reload beq data to device as we have just woken up......
if (!CheckSensor()) { Serial1.printf("Failed to set BME680 state!\n"); }
if (iaqSensor.run(rtc.getEpoch() * 1000 )) // start the sensor with current time from RTC in ms, wait for data
{ // If new data is available
Serial1.printf("**nextRunTime: %lu\n", nextRunTime );
Serial1.printf("**nextCall: %lu\n", iaqSensor.nextCall );
if (!CheckSensor()) { Serial1.printf("*Data not valid!\n"); }
output = String("Sensor");
output += ": RT: " + String(iaqSensor.rawTemperature);
output += ", P: " + String(iaqSensor.pressure/100);
output += ", RH: " + String(iaqSensor.rawHumidity);
output += ", Gas: " + String(iaqSensor.gasResistance/1000);
output += ", IAQ: " + String(iaqSensor.iaq);
output += ", Q: " + String(iaqSensor.iaqAccuracy);
output += ", T: " + String(iaqSensor.temperature);
output += ", H: " + String(iaqSensor.humidity);
Serial1.println(output);
saveState(); // save beq data here prior to sleep
Serial1.printf("***Going to sleep until: %lu\n", nextRunTime);
doSleep(nextRunTime); // deep sleep for x seconds
Serial1.printf("***Wakeing up at: %lu\n", (uint64_t) rtc.getEpoch());
}
}
/* ************************************************************** */
void doSleep(uint32_t sTime)
{
#ifdef MyDelay
while ( rtc.getEpoch() <= sTime ) { delay(1); } // this will fake sleeping for testing...
#else
rtc.setAlarmEpoch(sTime); // set new alarm time in seconds
rtc.enableAlarm(rtc.MATCH_HHMMSS); // currently only checking for HMS, not days or years
rtc.standbyMode(); // bring CPU into deep sleep mode (until woken up by the RTC alarm)
#endif
}
/* ************************************************************** */
bool CheckSensor()
{
if (iaqSensor.status < BSEC_OK)
{
Serial1.printf("BSEC error, status %d!", iaqSensor.status);
return false;
} else
if (iaqSensor.status > BSEC_OK)
{
Serial1.printf("BSEC warning, status %d!", iaqSensor.status);
}
if (iaqSensor.bme680Status < BME680_OK)
{
Serial1.printf("Sensor error, bme680_status %d!", iaqSensor.bme680Status);
return false;
} else if (iaqSensor.bme680Status > BME680_OK)
{
Serial1.printf("Sensor warning, status %d!", iaqSensor.bme680Status);
}
return true;
}
/* ************************************************************** */
void getState(void)
{
uint8_t Blob_Size; // its less that 256 bytes in size
FRAM.readByte(BSEC_ADDR, &Blob_Size);
if (Blob_Size == BSEC_MAX_STATE_BLOB_SIZE) // for now, see if we have blob size in FRAM... (should be CRC16 in future...)
{
// we have an Existing state in FRAM
Serial1.printf("*Reading state from FRAM\n");
for (uint32_t i = 0; i < BSEC_MAX_STATE_BLOB_SIZE; i++)
{
uint8_t rdata;
FRAM.readByte(i + 1, &rdata);
bsecState[i] = rdata;
#ifdef DumpBLOB
Serial1.printf("%02x ", rdata);
if (i % 16 == 15) { Serial1.print("\n"); }
if ( i == (BSEC_MAX_STATE_BLOB_SIZE -1 )) { Serial1.print("\n"); }
#endif
}
iaqSensor.setState(bsecState);
CheckSensor();
} else
{
// Erase the FRAM with zeroes
Serial1.printf("*Erasing FRAM\n");
for (uint32_t i = BSEC_ADDR; i < BSEC_MAX_STATE_BLOB_SIZE + 1; i++)
FRAM.writeByte(i, 0);
}
}
/* ************************************************************** */
void saveState(void)
{
iaqSensor.getState(bsecState);
CheckSensor();
Serial1.printf("*Writing state to FRAM\n");
for (uint32_t i = BSEC_ADDR; i < BSEC_MAX_STATE_BLOB_SIZE ; i++)
{
FRAM.writeByte(i + 1, bsecState[i]);
#ifdef DumpBLOB
Serial1.printf("%02x ", bsecState[i]);
if (i % 16 == 15) { Serial1.print("\n"); }
if ( i == (BSEC_MAX_STATE_BLOB_SIZE -1 )) { Serial1.print("\n"); }
#endif
}
FRAM.writeByte(BSEC_ADDR, BSEC_MAX_STATE_BLOB_SIZE); // save blob size as flag... (should be CRC16 in future...)
}
/* ********************* The End ******************************** */
/* ************************************************************** */ ~~~
... View more