diff --git a/examples/Example_01_BasicReadings/Example_01_BasicReadings.ino b/examples/Example_01_BasicReadings/Example_01_BasicReadings.ino index c9d7214..a020041 100644 --- a/examples/Example_01_BasicReadings/Example_01_BasicReadings.ino +++ b/examples/Example_01_BasicReadings/Example_01_BasicReadings.ino @@ -10,7 +10,7 @@ SparkFun Electronics Date: September, 2024 SparkFun code, firmware, and software is released under the MIT License. - Please see LICENSE.md for further details. + Please see LICENSE.md for further details. Hardware Connections: IoT RedBoard --> BMV080 @@ -21,22 +21,30 @@ Serial.print it out at 115200 baud to serial monitor. Feel like supporting our work? Buy a board from SparkFun! - https://www.sparkfun.com/products/????? + https://www.sparkfun.com/products/26554 */ -#include #include "SparkFun_BMV080_Arduino_Library.h" // CTRL+Click here to get the library: http://librarymanager/All#SparkFun_BMV080 +#include -Bmv080 bmv080; // Create an instance of the BMV080 class +SparkFunBMV080I2C bmv080; // Create an instance of the BMV080 class #define BMV080_ADDR 0x57 // SparkFun BMV080 Breakout defaults to 0x57 -i2c_device_t i2c_device = {}; // I2C device struct instance for Bosch API +// Some Dev boards have their QWIIC connector on Wire or Wire1 +// This #ifdef will help this sketch work across more products + +#ifdef ARDUINO_SPARKFUN_THINGPLUS_RP2040 +#define wirePort Wire1 +#else +#define wirePort Wire +#endif void setup() { Serial.begin(115200); - while(!Serial) delay(10); // Wait for Serial to become available. + while (!Serial) + delay(10); // Wait for Serial to become available. // Necessary for boards with native USB (like the SAMD51 Thing+). // For a final version of a project that does not need serial debug (or a USB cable plugged in), // Comment out this while loop, or it will prevent the remaining code from running. @@ -44,25 +52,24 @@ void setup() Serial.println(); Serial.println("BMV080 Example 1 - Basic Readings"); - Wire.begin(); + wirePort.begin(); - if (bmv080.begin(BMV080_ADDR, Wire) == false) { - Serial.println("BMV080 not detected at default I2C address. Check your jumpers and the hookup guide. Freezing..."); + if (bmv080.begin(BMV080_ADDR, wirePort) == false) + { + Serial.println( + "BMV080 not detected at default I2C address. Check your jumpers and the hookup guide. Freezing..."); while (1) - ; + ; } Serial.println("BMV080 found!"); - // Wire.setClock(400000); //Increase I2C data rate to 400kHz - - /* Communication interface initialization */ - i2c_init(&i2c_device); + // wirePort.setClock(400000); //Increase I2C data rate to 400kHz /* Initialize the Sensor (read driver, open, reset, id etc.)*/ - bmv080.init(&i2c_device); + bmv080.init(); /* Set the sensor mode to continuous mode */ - if(bmv080.setMode(SFE_BMV080_MODE_CONTINUOUS) == true) + if (bmv080.setMode(SFE_BMV080_MODE_CONTINUOUS) == true) { Serial.println("BMV080 set to continuous mode"); } @@ -74,13 +81,16 @@ void setup() void loop() { - if(bmv080.dataAvailable()) + if (bmv080.readSensor()) { - float pm25 = bmv080.getPM25(); + float pm25 = bmv080.PM25(); + float pm1 = bmv080.PM1(); Serial.print(pm25); + Serial.print("\t"); + Serial.print(pm1); - if(bmv080.getIsObstructed() == true) + if (bmv080.isObstructed() == true) { Serial.print("\tObstructed"); } diff --git a/examples/Example_02_DutyCycle/Example_02_DutyCycle.ino b/examples/Example_02_DutyCycle/Example_02_DutyCycle.ino index 2cc2623..0a3ffc6 100644 --- a/examples/Example_02_DutyCycle/Example_02_DutyCycle.ino +++ b/examples/Example_02_DutyCycle/Example_02_DutyCycle.ino @@ -21,16 +21,23 @@ Serial.print it out at 115200 baud to serial monitor. Feel like supporting our work? Buy a board from SparkFun! - https://www.sparkfun.com/products/????? + https://www.sparkfun.com/products/26554 */ #include #include "SparkFun_BMV080_Arduino_Library.h" // CTRL+Click here to get the library: http://librarymanager/All#SparkFun_BMV080 -Bmv080 bmv080; // Create an instance of the BMV080 class +SparkFunBMV080I2C bmv080; // Create an instance of the BMV080 class #define BMV080_ADDR 0x57 // SparkFun BMV080 Breakout defaults to 0x57 -i2c_device_t i2c_device = {}; // I2C device struct instance for Bosch API +// Some Dev boards have their QWIIC connector on Wire or Wire1 +// This #ifdef will help this sketch work across more products + +#ifdef ARDUINO_SPARKFUN_THINGPLUS_RP2040 +#define wirePort Wire1 +#else +#define wirePort Wire +#endif void setup() { @@ -44,22 +51,19 @@ void setup() Serial.println(); Serial.println("BMV080 Example 2 - Duty Cycle"); - Wire.begin(); + wirePort.begin(); - if (bmv080.begin(BMV080_ADDR, Wire) == false) { + if (bmv080.begin(BMV080_ADDR, wirePort) == false) { Serial.println("BMV080 not detected at default I2C address. Check your jumpers and the hookup guide. Freezing..."); while (1) ; } Serial.println("BMV080 found!"); - // Wire.setClock(400000); //Increase I2C data rate to 400kHz - - /* Communication interface initialization */ - i2c_init(&i2c_device); + // wirePort.setClock(400000); //Increase I2C data rate to 400kHz /* Initialize the Sensor (read driver, open, reset, id etc.)*/ - bmv080.init(&i2c_device); + bmv080.init(); /* Set the sensor Duty Cycling Period (seconds)*/ uint16_t duty_cycling_period = 20; @@ -85,13 +89,13 @@ void setup() void loop() { - if(bmv080.dataAvailable()) + if(bmv080.readSensor()) { - float pm25 = bmv080.getPM25(); + float pm25 = bmv080.PM25(); Serial.print(pm25); - if(bmv080.getIsObstructed() == true) + if(bmv080.isObstructed() == true) { Serial.print("\tObstructed"); } diff --git a/examples/Example_03_Interrupt/Example_03_Interrupt.ino b/examples/Example_03_Interrupt/Example_03_Interrupt.ino index 26281df..4d33066 100644 --- a/examples/Example_03_Interrupt/Example_03_Interrupt.ino +++ b/examples/Example_03_Interrupt/Example_03_Interrupt.ino @@ -25,20 +25,27 @@ Serial.print it out at 115200 baud to serial monitor. Feel like supporting our work? Buy a board from SparkFun! - https://www.sparkfun.com/products/????? + https://www.sparkfun.com/products/26554 */ #include #include "SparkFun_BMV080_Arduino_Library.h" // CTRL+Click here to get the library: http://librarymanager/All#SparkFun_BMV080 -Bmv080 bmv080; // Create an instance of the BMV080 class +SparkFunBMV080I2C bmv080; // Create an instance of the BMV080 class #define BMV080_ADDR 0x57 // SparkFun BMV080 Breakout defaults to 0x57 #define IRQ_Pin 14 -i2c_device_t i2c_device = {}; // I2C device struct instance for Bosch API - bool int_flag = false; +// Some Dev boards have their QWIIC connector on Wire or Wire1 +// This #ifdef will help this sketch work across more products + +#ifdef ARDUINO_SPARKFUN_THINGPLUS_RP2040 +#define wirePort Wire1 +#else +#define wirePort Wire +#endif + void setup() { Serial.begin(115200); @@ -51,22 +58,19 @@ void setup() Serial.println(); Serial.println("BMV080 Example 3 - Interrupt"); - Wire.begin(); + wirePort.begin(); - if (bmv080.begin(BMV080_ADDR, Wire) == false) { + if (bmv080.begin(BMV080_ADDR, wirePort) == false) { Serial.println("BMV080 not detected at default I2C address. Check your jumpers and the hookup guide. Freezing..."); while (1) ; } Serial.println("BMV080 found!"); - // Wire.setClock(400000); //Increase I2C data rate to 400kHz - - /* Communication interface initialization */ - i2c_init(&i2c_device); + // wirePort.setClock(400000); //Increase I2C data rate to 400kHz /* Initialize the Sensor (read driver, open, reset, id etc.)*/ - bmv080.init(&i2c_device); + bmv080.init(); /* Set the sensor mode to continuous mode */ // The hardware interrupt of the BMV080 sensor unit cannot be used as trigger @@ -91,11 +95,11 @@ void loop() int_flag = false; // Reset the flag do{ Serial.println("Reading BMV080"); - if(bmv080.dataAvailable()) + if(bmv080.readSensor()) { - float pm25 = bmv080.getPM25(); + float pm25 = bmv080.PM25(); Serial.print(pm25); - if(bmv080.getIsObstructed() == true) + if(bmv080.isObstructed() == true) { Serial.print("\tObstructed"); } diff --git a/examples/Example_04_SPI/Example_04_SPI.ino b/examples/Example_04_SPI/Example_04_SPI.ino index 10fc214..9af981a 100644 --- a/examples/Example_04_SPI/Example_04_SPI.ino +++ b/examples/Example_04_SPI/Example_04_SPI.ino @@ -29,15 +29,14 @@ Serial.print it out at 115200 baud to serial monitor. Feel like supporting our work? Buy a board from SparkFun! - https://www.sparkfun.com/products/????? + https://www.sparkfun.com/products/26554 */ #include #include "SparkFun_BMV080_Arduino_Library.h" // CTRL+Click here to get the library: http://librarymanager/All#SparkFun_BMV080 -Bmv080 bmv080; // Create an instance of the BMV080 class - -spi_device_t spi_device = {}; // SPI device struct instance for Bosch API +SparkFunBMV080SPI bmv080; // Create an instance of the BMV080 class +#define CS_PIN 15 // Define the chip select pin void setup() { @@ -48,19 +47,26 @@ void setup() // For a final version of a project that does not need serial debug (or a USB cable plugged in), // Comment out this while loop, or it will prevent the remaining code from running. + pinMode(CS_PIN, OUTPUT); + pinMode(MOSI, OUTPUT); + pinMode(SCK, OUTPUT); + pinMode(MISO, INPUT); + digitalWrite(CS_PIN, HIGH); + Serial.println(); Serial.println("BMV080 Example 4 - SPI"); - /* Communication interface initialization */ - spi_init(&spi_device); - - if (bmv080.initSPI(&spi_device) == false) { + if (bmv080.begin(CS_PIN, SPI) == false) { Serial.println("SPI init failure. Check your jumpers and the hookup guide. Freezing..."); while (1) ; } Serial.println("BMV080 SPI init successful"); + + /* Initialize the Sensor (read driver, open, reset, id etc.)*/ + bmv080.init(); + /* Set the sensor mode to continuous mode */ if(bmv080.setMode(SFE_BMV080_MODE_CONTINUOUS) == true) { @@ -74,13 +80,13 @@ void setup() void loop() { - if(bmv080.dataAvailable()) + if(bmv080.readSensor()) { - float pm25 = bmv080.getPM25(); + float pm25 = bmv080.PM25(); Serial.print(pm25); - if(bmv080.getIsObstructed() == true) + if(bmv080.isObstructed() == true) { Serial.print("\tObstructed"); } diff --git a/examples/Example_05_Parameters/Example_05_Parameters.ino b/examples/Example_05_Parameters/Example_05_Parameters.ino index 6078de3..ec61c4f 100644 --- a/examples/Example_05_Parameters/Example_05_Parameters.ino +++ b/examples/Example_05_Parameters/Example_05_Parameters.ino @@ -9,6 +9,7 @@ distribution_id do_obstruction_detection do_vibration_filtering + measurement_algorithm After these parameters are read and set, This example shows how to use the sensor in "continuous mode" to get particulate matter readings once every @@ -31,16 +32,23 @@ Serial.print it out at 115200 baud to serial monitor. Feel like supporting our work? Buy a board from SparkFun! - https://www.sparkfun.com/products/????? + https://www.sparkfun.com/products/26554 */ #include #include "SparkFun_BMV080_Arduino_Library.h" // CTRL+Click here to get the library: http://librarymanager/All#SparkFun_BMV080 -Bmv080 bmv080; // Create an instance of the BMV080 class +SparkFunBMV080I2C bmv080; // Create an instance of the BMV080 class #define BMV080_ADDR 0x57 // SparkFun BMV080 Breakout defaults to 0x57 -i2c_device_t i2c_device = {}; // I2C device struct instance for Bosch API +// Some Dev boards have their QWIIC connector on Wire or Wire1 +// This #ifdef will help this sketch work across more products + +#ifdef ARDUINO_SPARKFUN_THINGPLUS_RP2040 +#define wirePort Wire1 +#else +#define wirePort Wire +#endif void setup() { @@ -54,22 +62,19 @@ void setup() Serial.println(); Serial.println("BMV080 Example 5 - Get and Set Parameters"); - Wire.begin(); + wirePort.begin(); - if (bmv080.begin(BMV080_ADDR, Wire) == false) { + if (bmv080.begin(BMV080_ADDR, wirePort) == false) { Serial.println("BMV080 not detected at default I2C address. Check your jumpers and the hookup guide. Freezing..."); while (1) ; } Serial.println("BMV080 found!"); - // Wire.setClock(400000); //Increase I2C data rate to 400kHz - - /* Communication interface initialization */ - i2c_init(&i2c_device); + // wirePort.setClock(400000); //Increase I2C data rate to 400kHz /* Initialize the Sensor (read driver, open, reset, id etc.)*/ - bmv080.init(&i2c_device); + bmv080.init(); getSetParameters(); // Get and set parameters @@ -86,13 +91,13 @@ void setup() void loop() { - if(bmv080.dataAvailable()) + if(bmv080.readSensor()) { - float pm25 = bmv080.getPM25(); + float pm25 = bmv080.PM25(); Serial.print(pm25); - if(bmv080.getIsObstructed() == true) + if(bmv080.isObstructed() == true) { Serial.print("\tObstructed"); } @@ -113,6 +118,7 @@ void getSetParameters(void) uint32_t distribution_id = 0; bool do_obstruction_detection = false; bool do_vibration_filtering = false; + uint8_t measurementAlgorithm = E_BMV080_MEASUREMENT_ALGORITHM_BALANCED; /*************************************************************************** * @@ -122,22 +128,22 @@ void getSetParameters(void) /* Get default parameter "volumetric_mass_density" */ - volumetric_mass_density = bmv080.getVolumetricMassDensity(); + volumetric_mass_density = bmv080.volumetricMassDensity(); Serial.print("BMV080 parameter 'volumetric_mass_density' READ: "); Serial.println(volumetric_mass_density); /* Get default parameter "integration_time" */ - integration_time = bmv080.getIntegrationTime(); + integration_time = bmv080.integrationTime(); Serial.print("BMV080 parameter 'integration_time' READ: "); Serial.println(integration_time); /* Get default parameter "distribution_id" */ - distribution_id = bmv080.getDistributionId(); + distribution_id = bmv080.distributionId(); Serial.print("BMV080 parameter 'distribution_id' READ: "); Serial.println(distribution_id); /* Get default parameter "do_obstruction_detection" */ - do_obstruction_detection = bmv080.getDoObstructionDetection(); + do_obstruction_detection = bmv080.doObstructionDetection(); Serial.print("BMV080 parameter 'do_obstruction_detection' READ: "); if(do_obstruction_detection == true) { @@ -149,7 +155,7 @@ void getSetParameters(void) } /* Get default parameter "do_vibration_filtering" */ - do_vibration_filtering = bmv080.getDoVibrationFiltering(); + do_vibration_filtering = bmv080.doVibrationFiltering(); Serial.print("BMV080 parameter 'do_vibration_filtering' READ: "); if(do_vibration_filtering == true) { @@ -160,6 +166,25 @@ void getSetParameters(void) Serial.println("false"); } + /* Get default parameter "measurement_algorithm" */ + measurementAlgorithm = bmv080.measurementAlgorithm(); + Serial.print("BMV080 parameter 'measurement_algorithm' READ: "); + switch (measurementAlgorithm) + { + case E_BMV080_MEASUREMENT_ALGORITHM_FAST_RESPONSE: + Serial.println("Fast Response"); + break; + case E_BMV080_MEASUREMENT_ALGORITHM_BALANCED: + Serial.println("Balanced"); + break; + case E_BMV080_MEASUREMENT_ALGORITHM_HIGH_PRECISION: + Serial.println("High Precision"); + break; + default: + Serial.println("Unknown"); + break; + } + /*************************************************************************** * @@ -174,6 +199,7 @@ void getSetParameters(void) // distribution_id = 3; // do_obstruction_detection = true; // do_vibration_filtering = false; + // measurementAlgorithm = E_BMV080_MEASUREMENT_ALGORITHM_BALANCED; /* Set custom parameter "volumetric_mass_density" */ if(bmv080.setVolumetricMassDensity(volumetric_mass_density) == true) @@ -244,5 +270,30 @@ void getSetParameters(void) Serial.println("Error setting BMV080 parameter 'do_vibration_filtering'"); } + /* Set custom parameter "measurement_algorithm" */ + if(bmv080.setMeasurementAlgorithm(measurementAlgorithm) == true) + { + Serial.print("BMV080 parameter 'measurement_algorithm' SET TO: "); + switch (measurementAlgorithm) + { + case E_BMV080_MEASUREMENT_ALGORITHM_FAST_RESPONSE: + Serial.println("Fast Response"); + break; + case E_BMV080_MEASUREMENT_ALGORITHM_BALANCED: + Serial.println("Balanced"); + break; + case E_BMV080_MEASUREMENT_ALGORITHM_HIGH_PRECISION: + Serial.println("High Precision"); + break; + default: + Serial.println("Unknown"); + break; + } + } + else + { + Serial.println("Error setting BMV080 parameter 'measurement_algorithm'"); + } + Serial.println(); } \ No newline at end of file diff --git a/examples/Example_06_TwoSensors/Example_06_TwoSensors.ino b/examples/Example_06_TwoSensors/Example_06_TwoSensors.ino new file mode 100644 index 0000000..d067c6a --- /dev/null +++ b/examples/Example_06_TwoSensors/Example_06_TwoSensors.ino @@ -0,0 +1,173 @@ +/* + Using the BMV080 Particulate Matter PM2.5 Sensor + + This example shows how to use two BMV080 sensors on the same I2C bus. + + One sensor must have its AB0 Jumper changed to "0". + + The sensors will be in "continuous mode" to get + particulate matter readings once every second. + + It uses polling of the devices to check if new data is available. + + By: Pete Lewis + SparkFun Electronics + Date: September, 2024 + SparkFun code, firmware, and software is released under the MIT License. + Please see LICENSE.md for further details. + + Hardware Connections: + IoT RedBoard --> BMV080 + QWIIC --> QWIIC + + BMV080 "mode" jumper set to I2C (default) + + Serial.print it out at 115200 baud to serial monitor. + + Open a plotter to see the PM2.5 values from both sensors. + + Feel like supporting our work? Buy a board from SparkFun! + https://www.sparkfun.com/products/26554 +*/ + +#include "SparkFun_BMV080_Arduino_Library.h" // CTRL+Click here to get the library: http://librarymanager/All#SparkFun_BMV080 +#include + +SparkFunBMV080I2C bmv080; // Create an instance of the BMV080 class +#define BMV080_ADDR 0x57 // SparkFun BMV080 Breakout defaults to 0x57 + +SparkFunBMV080I2C bmv080_2; // Create an instance of the BMV080 class +#define BMV080_ADDR2 0x56 // AB0 Jumper set to 0 + +bool newDataAvailable = false; // Flag to indicate new data is available +bool newDataAvailable2 = false; // Flag to indicate new data is available + +float pm25 = 0.0; // Variable to store PM2.5 value +float pm25_2 = 0.0; // Variable to store PM2.5 value + +bool isObstructed = false; // Flag to indicate sensor is obstructed +bool isObstructed2 = false; // Flag to indicate sensor is obstructed + +// Some Dev boards have their QWIIC connector on Wire or Wire1 +// This #ifdef will help this sketch work across more products + +#ifdef ARDUINO_SPARKFUN_THINGPLUS_RP2040 +#define wirePort Wire1 +#else +#define wirePort Wire +#endif + +void setup() +{ + Serial.begin(115200); + + while (!Serial) + delay(10); // Wait for Serial to become available. + // Necessary for boards with native USB (like the SAMD51 Thing+). + // For a final version of a project that does not need serial debug (or a USB cable plugged in), + // Comment out this while loop, or it will prevent the remaining code from running. + + Serial.println(); + Serial.println("BMV080 Example 1 - Basic Readings"); + + wirePort.begin(); + + if (bmv080.begin(BMV080_ADDR, wirePort) == false) + { + Serial.println( + "BMV080 not detected at default I2C address. Check your jumpers and the hookup guide. Freezing..."); + while (1) + ; + } + Serial.println("BMV080 at 0x57 found!"); + + if (bmv080_2.begin(BMV080_ADDR2, wirePort) == false) + { + Serial.println( + "BMV080 not detected at 0x56 I2C address. Check your jumpers and the hookup guide. Freezing..."); + while (1) + ; + } + Serial.println("BMV080 at 0x56 found!"); + + // wirePort.setClock(400000); //Increase I2C data rate to 400kHz + + /* Initialize the Sensor (read driver, open, reset, id etc.)*/ + bmv080.init(); + + /* Set the sensor mode to continuous mode */ + if (bmv080.setMode(SFE_BMV080_MODE_CONTINUOUS) == true) + { + Serial.println("BMV080 set to continuous mode"); + } + else + { + Serial.println("Error setting BMV080 mode"); + } + + delay(500); + + bmv080_2.init(); + + if (bmv080_2.setMode(SFE_BMV080_MODE_CONTINUOUS) == true) + { + Serial.println("BMV080_2 set to continuous mode"); + } + else + { + Serial.println("Error setting BMV080_2 mode"); + } +} + +void loop() +{ + if (bmv080.readSensor()) + { + pm25 = bmv080.PM25(); + isObstructed = bmv080.isObstructed(); + newDataAvailable = true; + //Serial.println("Sensor 1 data available"); + } + delay(200); // needs a ~200ms delay in between talking to each sensor + + if (bmv080_2.readSensor()) + { + pm25_2 = bmv080_2.PM25(); + isObstructed2 = bmv080_2.isObstructed(); + newDataAvailable2 = true; + //Serial.println("Sensor 2 data available"); + } + + delay(200); // needs a ~200ms delay in between talking to each sensor + + if (newDataAvailable && newDataAvailable2) + { + //Serial.print("Sensor 1: "); + + Serial.print(pm25); + + if (isObstructed == true) + { + Serial.print(-1); + } + + //Serial.print("\tSensor 2: "); + + Serial.print(","); + + Serial.print(pm25_2); + + if (isObstructed2 == true) + { + Serial.print("-1"); + } + + Serial.println(); + + // reset variables + newDataAvailable = false; + newDataAvailable2 = false; + isObstructed = false; + isObstructed2 = false; + } +} diff --git a/examples/Example_07_Demo_Alphanumeric/Example_07_Demo_Alphanumeric.ino b/examples/Example_07_Demo_Alphanumeric/Example_07_Demo_Alphanumeric.ino new file mode 100644 index 0000000..6f1f7d7 --- /dev/null +++ b/examples/Example_07_Demo_Alphanumeric/Example_07_Demo_Alphanumeric.ino @@ -0,0 +1,119 @@ +/* + Using the BMV080 Particulate Matter PM2.5 Sensor + + This example shows how display the Pm2.5 readings on a SparkFun Qwiic + Alphanumeric display. + + It uses the sensor in "continuous mode" to get + particulate matter readings once every second. + + It uses polling of the device to check if new data is available. + + By: Pete Lewis + SparkFun Electronics + Date: September, 2024 + SparkFun code, firmware, and software is released under the MIT License. + Please see LICENSE.md for further details. + + Hardware Connections: + IoT RedBoard --> BMV080 + QWIIC --> QWIIC + + BMV080 "mode" jumper set to I2C (default) + + Serial.print it out at 115200 baud to serial monitor. + + Feel like supporting our work? Buy a board from SparkFun! + https://www.sparkfun.com/products/26554 +*/ + +#include "SparkFun_BMV080_Arduino_Library.h" // CTRL+Click here to get the library: http://librarymanager/All#SparkFun_BMV080 +#include + +SparkFunBMV080I2C bmv080; // Create an instance of the BMV080 class +#define BMV080_ADDR 0x57 // SparkFun BMV080 Breakout defaults to 0x57 + +#include //Click here to get the library: http://librarymanager/All#SparkFun_Qwiic_Alphanumeric_Display by SparkFun +HT16K33 display; +#define DISPLAY_ADDRESS 0x70 // Default I2C address when A0, A1 are floating + +// Some Dev boards have their QWIIC connector on Wire or Wire1 +// This #ifdef will help this sketch work across more products + +#ifdef ARDUINO_SPARKFUN_THINGPLUS_RP2040 +#define wirePort Wire1 +#else +#define wirePort Wire +#endif + +void setup() +{ + Serial.begin(115200); + + while (!Serial) + delay(10); // Wait for Serial to become available. + // Necessary for boards with native USB (like the SAMD51 Thing+). + // For a final version of a project that does not need serial debug (or a USB cable plugged in), + // Comment out this while loop, or it will prevent the remaining code from running. + + Serial.println(); + Serial.println("BMV080 Example 7 - Alphanumeric Display"); + + wirePort.begin(); + + if (bmv080.begin(BMV080_ADDR, wirePort) == false) + { + Serial.println( + "BMV080 not detected at default I2C address. Check your jumpers and the hookup guide. Freezing..."); + while (1) + ; + } + Serial.println("BMV080 found!"); + + // wirePort.setClock(400000); //Increase I2C data rate to 400kHz + + /* Initialize the Sensor (read driver, open, reset, id etc.)*/ + bmv080.init(); + + /* Set the sensor mode to continuous mode */ + if (bmv080.setMode(SFE_BMV080_MODE_CONTINUOUS) == true) + { + Serial.println("BMV080 set to continuous mode"); + } + else + { + Serial.println("Error setting BMV080 mode"); + } + + if (display.begin(DISPLAY_ADDRESS, DEFAULT_NOTHING_ATTACHED, DEFAULT_NOTHING_ATTACHED, DEFAULT_NOTHING_ATTACHED, wirePort) == false) + { + Serial.println("Qwiic Alphanumeric Device did not acknowledge! Freezing."); + while (1); + } + Serial.println("Qwiic Alphanumeric Display acknowledged."); + + display.setBrightness(5); // Set brightness to 5/16 full brightness + + display.print("PM2.5"); + +} + +void loop() +{ + if (bmv080.readSensor()) + { + float pm25 = bmv080.PM25(); + + Serial.print(pm25); + display.print(int(pm25)); + + if (bmv080.isObstructed() == true) + { + Serial.print("\tObstructed"); + display.print("Obst"); + } + + Serial.println(); + } + delay(100); +} diff --git a/examples/Example_08_Demo_Oled/Example_08_Demo_Oled.ino b/examples/Example_08_Demo_Oled/Example_08_Demo_Oled.ino new file mode 100644 index 0000000..afd58cc --- /dev/null +++ b/examples/Example_08_Demo_Oled/Example_08_Demo_Oled.ino @@ -0,0 +1,442 @@ +/* + Using the BMV080 Particulate Matter PM2.5 Sensor + + This example shows how display the PM1 and Pm2.5 readings on a SparkFun Qwiic + OLED Display, 1.3" wide. + + It uses the sensor in "continuous mode" to get + particulate matter readings once every second. + + It uses polling of the device to check if new data is available. + + By: Pete Lewis + SparkFun Electronics + Date: September, 2024 + SparkFun code, firmware, and software is released under the MIT License. + Please see LICENSE.md for further details. + + Hardware Connections: + IoT RedBoard --> QWIIC + QWIIC --> BMV080 + QWIIC --> QWIIC OLED Display + + BMV080 "mode" jumper set to I2C (default) + + Serial.print it out at 115200 baud to serial monitor. + + Feel like supporting our work? Buy a board from SparkFun! + https://www.sparkfun.com/products/26554 +*/ + +#define StatLedPin 13 + +// BMV080 Specifics +#include "SparkFun_BMV080_Arduino_Library.h" // CTRL+Click here to get the library: http://librarymanager/All#SparkFun_BMV080 +#include + +SparkFunBMV080I2C bmv080; // Create an instance of the BMV080 class +#define BMV080_ADDR 0x57 // SparkFun BMV080 Breakout defaults to 0x57 + +float pm1Value = 0.0; // PM1 value - global so we can update it in the loop and +float pm25Value = 0.0; // PM2.5 value - global so we can update it in the loop + +// OLED Specifics +#include //http://librarymanager/All#SparkFun_Qwiic_OLED + +// The Library supports four different types of SparkFun boards. The demo uses the following +// defines to determine which device is being used. Uncomment the device being used for this demo. + +//QwiicMicroOLED myOLED; +//QwiicTransparentOLED myOLED; +//QwiicNarrowOLED myOLED; +Qwiic1in3OLED myOLED; + +#include "res/qw_bmp_sparkfun.h" + +// Fonts +#include +#include +#include +#include +#include + +// An array of fonts to loop over +QwiicFont *demoFonts[] = { + &QW_FONT_5X7, + &QW_FONT_8X16, + &QW_FONT_31X48, + &QW_FONT_LARGENUM, + &QW_FONT_7SEGMENT}; +int nFONTS = sizeof(demoFonts) / sizeof(demoFonts[0]); +int iFont = 0; + +// Some vars for the title. +String strTitle = "<>"; +QwiicFont *pFntTitle = &QW_FONT_5X7; + +QwiicFont *pFntLabel = &QW_FONT_5X7; +QwiicFont *pFntValue = &QW_FONT_LARGENUM; + +int width; +int height; + +// x position of the PM2.5 label, this will be set in the +// writeStaticDisplayItems() function +int xPosPM25; + +// Fuel Guage Specifics +#include // Click here to get the library: http://librarymanager/All#SparkFun_MAX1704x_Fuel_Gauge_Arduino_Library +SFE_MAX1704X lipo(MAX1704X_MAX17048); // Create a MAX17048 +double soc = 0; // Variable to keep track of LiPo state-of-charge (SOC) + + +// Some Dev boards have their QWIIC connector on Wire or Wire1 +// This #ifdef will help this sketch work across more products + +#ifdef ARDUINO_SPARKFUN_THINGPLUS_RP2040 +#define wirePort Wire1 +#else +#define wirePort Wire +#endif + +void setup() +{ + pinMode(StatLedPin, OUTPUT); + digitalWrite(StatLedPin, LOW); + + lipo.disableDebugging(); // disable debugging for the MAX1704X fuel gauge + + Serial.begin(115200); + + while (!Serial) + delay(10); // Wait for Serial to become available. + // Necessary for boards with native USB (like the SAMD51 Thing+). + // For a final version of a project that does not need serial debug (or a USB cable plugged in), + // Comment out this while loop, or it will prevent the remaining code from running. + + Serial.println(); + Serial.println("BMV080 Example 8 - OLED Display"); + + wirePort.begin(); + + // Initalize the OLED device and related graphics system + if (myOLED.begin(wirePort) == false) + { + Serial.println("OLED Device begin failed. Freezing..."); + writeCenteredStringToDisplay("OLED Failure", true); + while (true) + ; + } + Serial.println("OLED Begin success"); + + // save device dims for the test routines + width = myOLED.getWidth(); + height = myOLED.getHeight(); + + showSplash(); + + myOLED.setFont(demoFonts[1]); + + if (bmv080.begin(BMV080_ADDR, wirePort) == false) + { + Serial.println( + "BMV080 not detected at default I2C address. Check your jumpers and the hookup guide. Freezing..."); + writeCenteredStringToDisplay("BMV080 Failure", true); + while (1) + ; + } + Serial.println("BMV080 found!"); + writeCenteredStringToDisplay("BMV080 Found", true); + + // wirePort.setClock(400000); //Increase I2C data rate to 400kHz + + /* Initialize the Sensor (read driver, open, reset, id etc.)*/ + bmv080.init(); + + /* Set the sensor mode to continuous mode */ + if (bmv080.setMode(SFE_BMV080_MODE_CONTINUOUS) == true) + { + Serial.println("BMV080 set to continuous mode"); + writeCenteredStringToDisplay("Continuous Mode Set", true); + } + else + { + Serial.println("Error setting BMV080 mode"); + writeCenteredStringToDisplay("BMV080 Mode Error", true); + } + + // Set up the MAX17048 LiPo fuel gauge: + if (lipo.begin() == false) // Connect to the MAX17043 using the default wire port + { + Serial.println(F("MAX17048 not detected. Please check wiring. Freezing.")); + writeCenteredStringToDisplay("MAX17048 Failure", true); + while (1) + ; + } + Serial.println("MAX17048 found!"); + + // Quick start restarts the MAX17043 in hopes of getting a more accurate + // guess for the SOC. + lipo.quickStart(); +} + +void loop() +{ + if (bmv080.readSensor()) + { + pm25Value = bmv080.PM25(); + pm1Value = bmv080.PM1(); + + Serial.print(pm1Value); + Serial.print("\t"); + Serial.print(pm25Value); + writeStaticDisplayItems(); + writeValuesToDisplay(); + // lipo.getSOC() returns the estimated state of charge (e.g. 79%) + soc = lipo.getSOC(); + Serial.print("\tBatt: "); + Serial.print(soc); // Print the battery state of charge + Serial.print(" %"); + writeBatteryLevelToDisplay(); + myOLED.display(); // actually command the display to show the scene + + if (bmv080.isObstructed() == true) + { + Serial.print("\tObstructed"); + writeObstructedBoarder(); + writeCenteredStringToDisplay("Obstructed", false); // don't clear the screen + // so the obstructed message is displayed on top of the PM values + } + + Serial.println(); + toggleHeartbeat(); + } + delay(100); +} + +void showSplash() +{ + int x0 = (width - QW_BMP_SPARKFUN.width) / 2; + if (x0 < 0) + x0 = 0; + + int y0 = (height - QW_BMP_SPARKFUN.height) / 2; + if (y0 < 0) + y0 = 0; + + myOLED.erase(); + myOLED.bitmap(x0, y0, QW_BMP_SPARKFUN); + myOLED.display(); + delay(2000); + + // Clear the screen + myOLED.erase(); + myOLED.display(); +} + +// Write the static display items to the screen +void writeStaticDisplayItems() +{ + // clear the screen + myOLED.erase(); + + myOLED.setFont(&QW_FONT_8X16); + + // draw the PM1 static text label + // calculate the x position of the PM1 label + // this is 1/4 the screen width minus 1/2 the width of the label + int xPosPM1Text = myOLED.getWidth() / 4 - myOLED.getStringWidth("PM1") / 2; + myOLED.text(xPosPM1Text, 0, "PM1", 1); + + // draw the PM2.5 static text label + // calculate the x position of the PM2.5 label + // this is 3/4 the screen width minus 1/2 the width of the label + int xPosPM25Text = (myOLED.getWidth() / 4) * 3 - myOLED.getStringWidth("PM2.5") / 2; + myOLED.text(xPosPM25Text, 0, "PM2.5", 1); + + // // draw the vertical separator line + // myOLED.line(myOLED.getWidth() / 2, 0, myOLED.getWidth() / 2, myOLED.getHeight(), 1); + // // draw a second line to make it more visible + // myOLED.line(myOLED.getWidth() / 2 + 1, 0, myOLED.getWidth() / 2 + 1, myOLED.getHeight(), 1); + +} + +// Write the PM1 and PM2.5 values to the display +void writeValuesToDisplay() +{ + // set the font to the large number font + myOLED.setFont(&QW_FONT_LARGENUM); + + // draw the PM1 value + String toPrint = "blank"; + toPrint = String(int(pm1Value)); + + // calculate the x position of the PM1 value + // we want it to be centered in the left half of the screen + int xPosPM1Value = (myOLED.getWidth() / 4) - (myOLED.getStringWidth(toPrint) / 2); + + // calculate the y start position of the PM1 value + // we want the bottom of the text to align with the bottom of the screen + int yPosPM1Value = myOLED.getHeight() - myOLED.getStringHeight(toPrint); + myOLED.text(xPosPM1Value, yPosPM1Value, toPrint, 1); + + // draw the PM2.5 value + // calculate the x position of the PM2.5 value + // we want it to be centered in the right half of the screen + int xPosPM25Value = (myOLED.getWidth() / 4) * 3 - (myOLED.getStringWidth(toPrint) / 2); + toPrint = String(int(pm25Value)); + myOLED.text(xPosPM25Value, yPosPM1Value, toPrint, 1); // same y position as the PM1 value +} + +// Write a string to the display that is centered horizontally and vertically +void writeCenteredStringToDisplay(String toPrint, bool clearScreen) +{ + // clear the screen + if(clearScreen) + { + myOLED.erase(); + } + + // set the font to the 8x16 font + myOLED.setFont(&QW_FONT_5X7); + + // calculate the x position of the toPrint text + // we want it to be centered in the screen horizontally + // and vertically + int xPosToPrint = (myOLED.getWidth() / 2) - (myOLED.getStringWidth(toPrint) / 2); + int yPosToPrint = (myOLED.getHeight() / 2) - (myOLED.getStringHeight(toPrint) / 2); + + // draw the string as text + myOLED.text(xPosToPrint, yPosToPrint, toPrint, 1); + myOLED.display(); +} + +// blink the status LED +void blinkStatLed() +{ + digitalWrite(StatLedPin, HIGH); + delay(10); + digitalWrite(StatLedPin, LOW); +} + +// toggle "heartbeat" rectangle in the upper right corner of the screen +void toggleHeartbeat() +{ + static bool bHeartbeat = false; + + // heartbeat rectangle is 3x3 pixels + uint8_t rectWidth = 3; + uint8_t rectHeight = 3; + + // heartbeat rectangle is in the upper right corner of the screen + uint8_t rectStartX = myOLED.getWidth() - rectWidth; + uint8_t rectStartY = 0; + + // draw the rectangle + myOLED.rectangleFill(rectStartX, rectStartY, rectWidth, rectHeight, 1); + + // toggle the heartbeat + bHeartbeat = !bHeartbeat; + + // if the heartbeat is off, erase the rectangle + if (!bHeartbeat) + { + myOLED.rectangleFill(rectStartX, rectStartY, rectWidth, rectHeight, 0); + } + + myOLED.display(); +} + +// Write the battery level to the display +// Create a rectangular battery level indicator on the bottom right side of the screen +// fill in the rectangle with a percentage of the battery level +// each section of the rectangle represents 25% of the battery level +void writeBatteryLevelToDisplay() +{ + // calculate the x position of the battery level indicator + // this is the right side of the screen minus 14 pixels + int xPosBatteryLevelIndicator = (myOLED.getWidth()- 14); + + // draw the battery level indicator outline + // this rectangle is an outline of the battery level indicator + // it looks like a small battery + // it is 6 pixels tall and 12 pixels wide + // the inside of the rectangle is not filled in + // the rectangle is drawn in white + // the inside of the rectangle will be filled in with the battery level + myOLED.rectangle(xPosBatteryLevelIndicator, myOLED.getHeight() - 6, 12, 6, 1); + + // draw the end shape of the battery level indicator + // this makes it look like a traditional battery level indicator + // like the end of a AA battery + // draw a filled in rectangle that will be the end shape of the battery level indicator + // it is 2 pixels tall and 2 pixels wide + // it is drawn in white + myOLED.rectangle(xPosBatteryLevelIndicator + 12, myOLED.getHeight() - 4, 2, 2, 1); + + // draw the inner sections of the battery level indicator + // There are 3 blocks inside the battery level indicator + // each block represents 33% of the battery level + // the left most block represents 0-33% of the battery level + // the middle block represents 34-66% of the battery level + // the right most block represents 67-100% of the battery level + // the blocks are filled in with white + // the blocks are 2 pixels tall and 2 pixels wide + // there is a 1 pixel gap between each block + + // calculate the y position of the battery level indicator blocks + // this is the bottom of the screen minus 4 pixels + int yPosBatteryLevelBlocks = myOLED.getHeight() - 4; + + // calculate the x position of the left most block + // this is the right side of the screen minus 12 pixels + int xPosLeftBlock = (myOLED.getWidth() - 12); + + // calculate the x position of the middle block + // this is the right side of the screen minus 9 pixels + int xPosMiddleBlock = (myOLED.getWidth() - 9); + + // calculate the x position of the right most block + // this is the right side of the screen minus 6 pixels + int xPosRightBlock = (myOLED.getWidth() - 6); + + // write all 3 battery block indicators as black rectangles + myOLED.rectangle(xPosLeftBlock, yPosBatteryLevelBlocks, 2, 2, 0); + myOLED.rectangle(xPosMiddleBlock, yPosBatteryLevelBlocks, 2, 2, 0); + myOLED.rectangle(xPosRightBlock, yPosBatteryLevelBlocks, 2, 2, 0); + + // if the battery level is at least 33% + // fill in the left most block + if (soc > 32) + { + myOLED.rectangle(xPosLeftBlock, yPosBatteryLevelBlocks, 2, 2, 1); + } + + // if the battery level is between 34% and 66% + // fill in the middle block + if (soc > 33) + { + myOLED.rectangle(xPosMiddleBlock, yPosBatteryLevelBlocks, 2, 2, 1); + } + + // if the battery level is greater than 66% + // fill in the right most block + if (soc > 66) + { + myOLED.rectangle(xPosRightBlock, yPosBatteryLevelBlocks, 2, 2, 1); + } +} + +void writeObstructedBoarder() +{ + // set fort to 5x7 + myOLED.setFont(&QW_FONT_5X7); + + int xPosObstructed = (myOLED.getWidth() / 2) - (myOLED.getStringWidth("Obstructed") / 2) - 2; + int yPosObstructed = (myOLED.getHeight() / 2) - (myOLED.getStringHeight("Obstructed") / 2) - 2; + int widthObstructed = myOLED.getStringWidth("Obstructed") + 4; + int heightObstructed = myOLED.getStringHeight("Obstructed") + 4; + + // draw the black filled rectangle + myOLED.rectangleFill(xPosObstructed, yPosObstructed, widthObstructed, heightObstructed, 0); +} \ No newline at end of file diff --git a/keywords.txt b/keywords.txt index a0b329e..40862ae 100644 --- a/keywords.txt +++ b/keywords.txt @@ -4,7 +4,8 @@ # Class ######################################################### -Bmv080 KEYWORD1 +SparkFunBMV080I2C KEYWORD1 +SparkFunBMV080SPI KEYWORD1 ######################################################### # Methods and Functions @@ -12,11 +13,9 @@ Bmv080 KEYWORD1 begin KEYWORD2 init KEYWORD2 -initSPI KEYWORD2 isConnected KEYWORD2 getDriverVersion KEYWORD2 open KEYWORD2 -openSPI KEYWORD2 reset KEYWORD2 getID KEYWORD2 setMode KEYWORD2 diff --git a/library.properties b/library.properties index d6a9f0c..bbfe96a 100644 --- a/library.properties +++ b/library.properties @@ -6,6 +6,7 @@ sentence=Library for SparkFun BMV080 PM 2.5 Sensor paragraph=A very fast and efficient Arduino library for the BMV080 category=Sensor url=https://github.com/sparkfun/SparkFun_Qwiic_OLED_Arduino_Library -architectures=esp32,esp32s2,esp32s3,cortex-m33 +architectures=esp32,esp32s2,esp32s3,cortex-m33,cortex-m0plus,cortex-m4f precompiled=true +depends=SparkFun Toolkit ldflags=-lbmv080 diff --git a/src/SparkFun_BMV080_Arduino_Library.h b/src/SparkFun_BMV080_Arduino_Library.h index 23bd364..f0ee587 100644 --- a/src/SparkFun_BMV080_Arduino_Library.h +++ b/src/SparkFun_BMV080_Arduino_Library.h @@ -3,11 +3,7 @@ SparkFun BMV080 Library header file by Pete Lewis @SparkFun Electronics - September 2024 - - Based on original source code written by - Fischer Moseley @ SparkFun Electronics - Original Creation Date: July 24, 2019 + September 2025 This file implements the BMV080 class, prototyped in SparkFun_BMV080_Arduino_Library.h @@ -25,20 +21,19 @@ #pragma once -// TODO: Add includes as needed (e.g. #include , #include ) -#include "sfeBmv080.h" -#include -// #include "Arduino.h" -#include -#include -#include "combridge.h" +#include +#include "sfeTk/sfeDevBMV080.h" // The BMV080 Bosch API requires a larger than usual stack size // In particular, bmv080_serve_interrupt is the culprit. -SET_LOOP_TASK_STACK_SIZE(60 * 1024); // 60KB +// If we are an ESP32 architecture, then we need to increase the loop stack size +// to 60KB. This is because the ESP32 has a 32KB stack size by default. +#if defined(ESP32) +SET_LOOP_TASK_STACK_SIZE(60 * 1024); // 60KB +#endif -class Bmv080 : public sfeBmv080 +class SparkFunBMV080I2C : public sfeDevBMV080 { public: /// @brief Begins the Device @@ -49,18 +44,45 @@ class Bmv080 : public sfeBmv080 { // Setup Arudino I2C bus _theI2CBus.init(wirePort, address); + _theI2CBus.setByteOrder(SFTK_MSBFIRST); // Begin the sensor - return sfeBmv080::begin(&_theI2CBus) == kSTkErrOk; + sfeTkError_t rc = sfeDevBMV080::begin(&_theI2CBus); + + return rc == kSTkErrOk ? isConnected() : false; } /// @brief Checks if the Device is connected /// @return True if the sensor is connected, false otherwise bool isConnected() { - return sfeBmv080::isConnected() == kSTkErrOk; + return _theI2CBus.ping() == kSTkErrOk; } private: sfeTkArdI2C _theI2CBus; }; + +class SparkFunBMV080SPI : public sfeDevBMV080 +{ + public: + /// @brief Begins the Device with SPI as the communication bus + /// @param csPin The chip select pin for the sensor + /// @param spiPort The SPI port to use for communication + /// @param spiSettings The SPI settings to use for communication + /// @return True if successful, false otherwise + bool begin(uint8_t csPin, SPIClass &spiPort = SPI, SPISettings spiSettings = SPISettings(100000, MSBFIRST, SPI_MODE0)) + { + + // Setup Arduino SPI bus + _theSPIBus.init(spiPort, spiSettings, csPin, true); + + // Begin the sensor + sfeTkError_t rc = sfeDevBMV080::begin(&_theSPIBus); + + return rc == kSTkErrOk ? true : false; + } + + private: + sfeTkArdSPI _theSPIBus; +}; \ No newline at end of file diff --git a/src/combridge.cpp b/src/combridge.cpp deleted file mode 100644 index 704aef2..0000000 --- a/src/combridge.cpp +++ /dev/null @@ -1,240 +0,0 @@ -/** - * Copyright (C) Bosch Sensortec GmbH. All Rights Reserved. Confidential. - * - * Disclaimer - * - * Common: - * Bosch Sensortec products are developed for the consumer goods industry. They may only be used - * within the parameters of the respective valid product data sheet. Bosch Sensortec products are - * provided with the express understanding that there is no warranty of fitness for a particular purpose. - * They are not fit for use in life-sustaining, safety or security sensitive systems or any system or device - * that may lead to bodily harm or property damage if the system or device malfunctions. In addition, - * Bosch Sensortec products are not fit for use in products which interact with motor vehicle systems. - * The resale and/or use of products are at the purchaser's own risk and his own responsibility. The - * examination of fitness for the intended use is the sole responsibility of the Purchaser. - * - * The purchaser shall indemnify Bosch Sensortec from all third party claims, including any claims for - * incidental, or consequential damages, arising from any product use not covered by the parameters of - * the respective valid product data sheet or not approved by Bosch Sensortec and reimburse Bosch - * Sensortec for all costs in connection with such claims. - * - * The purchaser must monitor the market for the purchased products, particularly with regard to - * product safety and inform Bosch Sensortec without delay of all security relevant incidents. - * - * Engineering Samples are marked with an asterisk (*) or (e). Samples may vary from the valid - * technical specifications of the product series. They are therefore not intended or fit for resale to third - * parties or for use in end products. Their sole purpose is internal client testing. The testing of an - * engineering sample may in no way replace the testing of a product series. Bosch Sensortec - * assumes no liability for the use of engineering samples. By accepting the engineering samples, the - * Purchaser agrees to indemnify Bosch Sensortec from all claims arising from the use of engineering - * samples. - * - * Special: - * This software module (hereinafter called "Software") and any information on application-sheets - * (hereinafter called "Information") is provided free of charge for the sole purpose to support your - * application work. The Software and Information is subject to the following terms and conditions: - * - * The Software is specifically designed for the exclusive use for Bosch Sensortec products by - * personnel who have special experience and training. Do not use this Software if you do not have the - * proper experience or training. - * - * This Software package is provided `` as is `` and without any expressed or implied warranties, - * including without limitation, the implied warranties of merchantability and fitness for a particular - * purpose. - * - * Bosch Sensortec and their representatives and agents deny any liability for the functional impairment - * of this Software in terms of fitness, performance and safety. Bosch Sensortec and their - * representatives and agents shall not be liable for any direct or indirect damages or injury, except as - * otherwise stipulated in mandatory applicable law. - * - * The Information provided is believed to be accurate and reliable. Bosch Sensortec assumes no - * responsibility for the consequences of use of such Information nor for any infringement of patents or - * other rights of third parties which may result from its use. No license is granted by implication or - * otherwise under any patent or patent rights of Bosch. Specifications mentioned in the Information are - * subject to change without notice. - * - * It is not allowed to deliver the source code of the Software to any third party without permission of - * Bosch Sensortec. - * - * @file combridge.cpp - * - * @brief This file contains the serial communication interface (e.g. SPI and I2C) functions. - * Messages can be read and written. In addition, there is a waiting mechanism that waits for a defined time. - */ - - -/* Includes ------------------------------------------------------------------*/ -#include -#include "combridge.h" - -/* Private define ------------------------------------------------------------*/ -#define SPI_CLK_FREQ ((uint32_t)(1e6)) - -#define I2C_CLK_FREQ ((uint32_t)(100e3)) - -/* BMV080 I2C address - * note that the BMV080 pins are connected such that I2C Address Bit 0 = 0 and I2C Address Bit 1 = 0 - */ -#define BMV080_I2C_ADDRESS 0x57 - - -/* Exported functions --------------------------------------------------------*/ -void spi_init(spi_device_t *spi_device) -{ - SPISettings spi_settings(SPI_CLK_FREQ, MSBFIRST, SPI_MODE0); - spi_device->instance = &SPI; - spi_device->settings = spi_settings; - - pinMode(SS, OUTPUT); - pinMode(MOSI, OUTPUT); - pinMode(SCK, OUTPUT); - pinMode(MISO, INPUT); - digitalWrite(SS, HIGH); - spi_device->instance->begin(); -} - - -void i2c_init(i2c_device_t *i2c_device) -{ - i2c_device->instance = &Wire; - i2c_device->instance->begin(); - i2c_device->instance->setClock(I2C_CLK_FREQ); - i2c_device->instance->setBufferSize(512); -} - - -int8_t combridge_spi_read_16bit(bmv080_sercom_handle_t handle, uint16_t header, uint16_t *payload, uint16_t payload_length) -{ - int8_t return_value = E_COMBRIDGE_OK; - spi_device_t *spi_device = (spi_device_t *)handle; - - digitalWrite(SS, LOW); - spi_device->instance->beginTransaction(spi_device->settings); - spi_device->instance->transfer16(header); - - uint16_t payload_index = 0; - for (; payload_index < payload_length; payload_index++) - { - payload[payload_index] = spi_device->instance->transfer16(0); - } - spi_device->instance->endTransaction(); - digitalWrite(SS, HIGH); - - return return_value; -} - - -int8_t combridge_spi_write_16bit(bmv080_sercom_handle_t handle, uint16_t header, const uint16_t *payload, uint16_t payload_length) -{ - int8_t return_value = E_COMBRIDGE_OK; - spi_device_t *spi_device = (spi_device_t *)handle; - - digitalWrite(SS, LOW); - spi_device->instance->beginTransaction(spi_device->settings); - spi_device->instance->transfer16(header); - - uint16_t payload_index = 0; - for (; payload_index < payload_length; payload_index++) - { - spi_device->instance->transfer16(payload[payload_index]); - } - spi_device->instance->endTransaction(); - digitalWrite(SS, HIGH); - - return E_COMBRIDGE_OK; -} - - -int8_t combridge_i2c_read_16bit(bmv080_sercom_handle_t handle, uint16_t header, uint16_t *payload, uint16_t payload_length) -{ - int8_t return_value = E_COMBRIDGE_OK; - i2c_device_t *i2c_device = (i2c_device_t *)handle; - uint8_t * payload_byte = (uint8_t*)payload; - - /* 16-bit header left shifted by 1, since the R/W bit (most significant bit) is passed along with the 7-bit device address */ - uint16_t header_adjusted = header << 1; - - i2c_device->instance->beginTransmission(BMV080_I2C_ADDRESS); - i2c_device->instance->write((header_adjusted >> 8) & 0xFF); - i2c_device->instance->write(header_adjusted & 0xFF); - - if(i2c_device->instance->endTransmission(true) != 0) - { - return_value = E_COMBRIDGE_ERROR_WRITE_HEADER; - return return_value; - } - - i2c_device->instance->requestFrom(BMV080_I2C_ADDRESS, payload_length * 2); - - uint16_t payload_index = 0; - while(i2c_device->instance->available() && (payload_index < (payload_length * 2) )) - { - payload_byte[payload_index++] = i2c_device->instance->read(); - //Serial.print("."); - } - //Serial.print("payload_length"); - //Serial.println(payload_length); - - if(payload_index != (payload_length * 2)) - { - return_value = E_COMBRIDGE_ERROR_READ; - } - - /* Conversion of payload from big endian to little endian */ - for (payload_index = 0; payload_index < payload_length; payload_index++) - { - uint16_t swapped_word = ((payload[payload_index] << 8) | (payload[payload_index] >> 8)) & 0xffff; - payload[payload_index] = swapped_word; - } - - return return_value; -} - - -int8_t combridge_i2c_write_16bit(bmv080_sercom_handle_t handle, uint16_t header, const uint16_t *payload, uint16_t payload_length) -{ - int8_t return_value = E_COMBRIDGE_OK; - - i2c_device_t *i2c_device = (i2c_device_t *)handle; - uint8_t * payload_byte = (uint8_t*)payload; - - /* 16-bit header left shifted by 1, since the R/W bit (most significant bit) is passed along with the 7-bit device address */ - uint16_t header_adjusted = header << 1; - - /* Conversion of payload from little endian to big endian (dynamic allocation is used) */ - uint16_t *payload_swapped = (uint16_t *)calloc(payload_length, sizeof(uint16_t)); - if(payload_swapped) - { - for (uint16_t payload_index = 0; payload_index < payload_length; payload_index++) - { - payload_swapped[payload_index] = ((payload[payload_index] << 8) | (payload[payload_index] >> 8)) & 0xffff; - } - } - else - { - return_value = E_COMBRIDGE_ERROR_WRITE_HEADER; - return return_value; - } - - i2c_device->instance->beginTransmission(BMV080_I2C_ADDRESS); - i2c_device->instance->write((header_adjusted >> 8) & 0xFF); - i2c_device->instance->write(header_adjusted & 0xFF); - i2c_device->instance->write((uint8_t *)payload_swapped, payload_length * 2); - - if(i2c_device->instance->endTransmission(true) != 0) - { - return_value = E_COMBRIDGE_ERROR_WRITE; - } - - free(payload_swapped); - - return return_value; -} - - -int8_t combridge_delay(uint32_t period) -{ - delay(period); - - return E_COMBRIDGE_OK; -} diff --git a/src/combridge.h b/src/combridge.h deleted file mode 100644 index 318b28a..0000000 --- a/src/combridge.h +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright (C) Bosch Sensortec GmbH. All Rights Reserved. Confidential. - * - * Disclaimer - * - * Common: - * Bosch Sensortec products are developed for the consumer goods industry. They may only be used - * within the parameters of the respective valid product data sheet. Bosch Sensortec products are - * provided with the express understanding that there is no warranty of fitness for a particular purpose. - * They are not fit for use in life-sustaining, safety or security sensitive systems or any system or device - * that may lead to bodily harm or property damage if the system or device malfunctions. In addition, - * Bosch Sensortec products are not fit for use in products which interact with motor vehicle systems. - * The resale and/or use of products are at the purchaser's own risk and his own responsibility. The - * examination of fitness for the intended use is the sole responsibility of the Purchaser. - * - * The purchaser shall indemnify Bosch Sensortec from all third party claims, including any claims for - * incidental, or consequential damages, arising from any product use not covered by the parameters of - * the respective valid product data sheet or not approved by Bosch Sensortec and reimburse Bosch - * Sensortec for all costs in connection with such claims. - * - * The purchaser must monitor the market for the purchased products, particularly with regard to - * product safety and inform Bosch Sensortec without delay of all security relevant incidents. - * - * Engineering Samples are marked with an asterisk (*) or (e). Samples may vary from the valid - * technical specifications of the product series. They are therefore not intended or fit for resale to third - * parties or for use in end products. Their sole purpose is internal client testing. The testing of an - * engineering sample may in no way replace the testing of a product series. Bosch Sensortec - * assumes no liability for the use of engineering samples. By accepting the engineering samples, the - * Purchaser agrees to indemnify Bosch Sensortec from all claims arising from the use of engineering - * samples. - * - * Special: - * This software module (hereinafter called "Software") and any information on application-sheets - * (hereinafter called "Information") is provided free of charge for the sole purpose to support your - * application work. The Software and Information is subject to the following terms and conditions: - * - * The Software is specifically designed for the exclusive use for Bosch Sensortec products by - * personnel who have special experience and training. Do not use this Software if you do not have the - * proper experience or training. - * - * This Software package is provided `` as is `` and without any expressed or implied warranties, - * including without limitation, the implied warranties of merchantability and fitness for a particular - * purpose. - * - * Bosch Sensortec and their representatives and agents deny any liability for the functional impairment - * of this Software in terms of fitness, performance and safety. Bosch Sensortec and their - * representatives and agents shall not be liable for any direct or indirect damages or injury, except as - * otherwise stipulated in mandatory applicable law. - * - * The Information provided is believed to be accurate and reliable. Bosch Sensortec assumes no - * responsibility for the consequences of use of such Information nor for any infringement of patents or - * other rights of third parties which may result from its use. No license is granted by implication or - * otherwise under any patent or patent rights of Bosch. Specifications mentioned in the Information are - * subject to change without notice. - * - * It is not allowed to deliver the source code of the Software to any third party without permission of - * Bosch Sensortec. - * - * @file combridge.h - * - * @brief Header for the serial communication interface functions in combridge.c. - */ - -#ifndef COMBRIDGE_H_ -#define COMBRIDGE_H_ - -/* Includes ------------------------------------------------------------------*/ -#include "bmv080.h" -#include -#include -#include - - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -/* Exported types ------------------------------------------------------------*/ - -/*! -* @brief SPI device structure -*/ -typedef struct -{ - /*! Instance of arduino SPI protocol instance */ - SPIClass *instance; - /*! Instance of arduino SPI settings to be applied before every transmission */ - SPISettings settings; -} spi_device_t; - -/*! -* @brief I2C device structure -*/ -typedef struct -{ - /*! Instance of arduino I2C protocol instance */ - TwoWire *instance; -} i2c_device_t; - -/* Exported constants --------------------------------------------------------*/ -/*! 0: Status codes returned when there is no warning or error */ -#define E_COMBRIDGE_OK ((int8_t)0) -/*! -1: Status codes returned when memory allocation fails */ -#define E_COMBRIDGE_ERROR_MEMORY_ALLOCATION ((int8_t)-1) -/*! -2: Status codes returned when the read operation fails */ -#define E_COMBRIDGE_ERROR_READ ((int8_t)-2) -/*! -3: Status codes returned when the write operation fails */ -#define E_COMBRIDGE_ERROR_WRITE ((int8_t)-3) -/*! -4: Status codes returned when writing the header fails */ -#define E_COMBRIDGE_ERROR_WRITE_HEADER ((int8_t)-4) -/*! -5: Status codes returned when a reference is null */ -#define E_COMBRIDGE_ERROR_NULLPTR ((int8_t)-5) - -/* Exported functions prototypes ---------------------------------------------*/ - -/* Initialization functions */ -void spi_init(spi_device_t *spi_device); -void i2c_init(i2c_device_t *i2c_device); - -/* SPI read and write functions */ -int8_t combridge_spi_read_16bit(bmv080_sercom_handle_t handle, uint16_t header, uint16_t *payload, uint16_t payload_length); -int8_t combridge_spi_write_16bit(bmv080_sercom_handle_t handle, uint16_t header, const uint16_t *payload, uint16_t payload_length); - -/* I2C read and write functions */ -int8_t combridge_i2c_read_16bit(bmv080_sercom_handle_t handle, uint16_t header, uint16_t *payload, uint16_t payload_length); -int8_t combridge_i2c_write_16bit(bmv080_sercom_handle_t handle, uint16_t header, const uint16_t *payload, uint16_t payload_length); - -/* Delay function */ -int8_t combridge_delay(uint32_t period); - - -#ifdef __cplusplus -} -#endif - -#endif /* COMBRIDGE_H_ */ diff --git a/src/cortex-m0plus/libbmv080.a b/src/cortex-m0plus/libbmv080.a new file mode 100644 index 0000000..6121831 Binary files /dev/null and b/src/cortex-m0plus/libbmv080.a differ diff --git a/src/cortex-m0plus/libpostProcessor.a b/src/cortex-m0plus/libpostProcessor.a new file mode 100644 index 0000000..7fa73c4 Binary files /dev/null and b/src/cortex-m0plus/libpostProcessor.a differ diff --git a/src/cortex-m33/libbmv080.a b/src/cortex-m33/libbmv080.a new file mode 100644 index 0000000..f96a86d Binary files /dev/null and b/src/cortex-m33/libbmv080.a differ diff --git a/src/cortex-m33/libpostProcessor.a b/src/cortex-m33/libpostProcessor.a new file mode 100644 index 0000000..339cfd5 Binary files /dev/null and b/src/cortex-m33/libpostProcessor.a differ diff --git a/src/cortex-m4/libbmv080.a b/src/cortex-m4/libbmv080.a new file mode 100644 index 0000000..d1e291e Binary files /dev/null and b/src/cortex-m4/libbmv080.a differ diff --git a/src/cortex-m4/libpostProcessor.a b/src/cortex-m4/libpostProcessor.a new file mode 100644 index 0000000..41c7f93 Binary files /dev/null and b/src/cortex-m4/libpostProcessor.a differ diff --git a/src/cortex-m4f/libbmv080.a b/src/cortex-m4f/libbmv080.a new file mode 100644 index 0000000..9668871 Binary files /dev/null and b/src/cortex-m4f/libbmv080.a differ diff --git a/src/cortex-m4f/libpostProcessor.a b/src/cortex-m4f/libpostProcessor.a new file mode 100644 index 0000000..287108f Binary files /dev/null and b/src/cortex-m4f/libpostProcessor.a differ diff --git a/src/esp32/libbmv080.a b/src/esp32/libbmv080.a index 8941dec..a05eed5 100644 Binary files a/src/esp32/libbmv080.a and b/src/esp32/libbmv080.a differ diff --git a/src/esp32/libpostProcessor.a b/src/esp32/libpostProcessor.a index 8d07ae4..c4c887c 100644 Binary files a/src/esp32/libpostProcessor.a and b/src/esp32/libpostProcessor.a differ diff --git a/src/esp32s2/libbmv080.a b/src/esp32s2/libbmv080.a new file mode 100644 index 0000000..540e132 Binary files /dev/null and b/src/esp32s2/libbmv080.a differ diff --git a/src/esp32s2/libpostProcessor.a b/src/esp32s2/libpostProcessor.a new file mode 100644 index 0000000..418784f Binary files /dev/null and b/src/esp32s2/libpostProcessor.a differ diff --git a/src/esp32s3/libbmv080.a b/src/esp32s3/libbmv080.a new file mode 100644 index 0000000..a05eed5 Binary files /dev/null and b/src/esp32s3/libbmv080.a differ diff --git a/src/esp32s3/libpostProcessor.a b/src/esp32s3/libpostProcessor.a new file mode 100644 index 0000000..c4c887c Binary files /dev/null and b/src/esp32s3/libpostProcessor.a differ diff --git a/src/sfeBmv080.cpp b/src/sfeBmv080.cpp deleted file mode 100644 index 9f6cd3e..0000000 --- a/src/sfeBmv080.cpp +++ /dev/null @@ -1,475 +0,0 @@ -/****************************************************************************** - sfeQwiicBuzzer.h - SparkFun Qwiic Buzzer Library header file - - by Pete Lewis @SparkFun Electronics - January 2024 - - Based on original source code written by - Fischer Moseley @ SparkFun Electronics - Original Creation Date: July 24, 2019 - - Development environment specifics: - IDE: Arduino 2.2.1 - Hardware Platform: Arduino Uno/SparkFun Redboard - Qwiic Buzzer Version: v10 - - SPDX-License-Identifier: MIT - - Copyright (c) 2023 SparkFun Electronics - - Distributed as-is; no warranty is given. -******************************************************************************/ - -#include "sfeBmv080.h" -#include "bmv080_defs.h" -#include "bmv080.h" - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -void bmv080_service_routine(const bmv080_handle_t handle, void* callback_parameters); -void use_sensor_output(bmv080_output_t bmv080_output, void* callback_parameters); - - -/* Custom function for consuming sensor readings */ -void use_sensor_output(bmv080_output_t bmv080_output, void* callback_parameters) -{ - //data_ready_callback_count += 1; - //print_function_t print = (print_function_t)callback_parameters; - - - ((sfeBmv080*)callback_parameters)->setSensorValue(bmv080_output); - - //Serial.println(bmv080_output.pm2_5); - - //Serial.println("u"); - - //sfeBmv080::_sensorValue.pm2_5 = bmv080_output.pm2_5; // update the class variable with the new PM2.5 value - //setSensorValue(bmv080_output.pm2_5); - //sensorValue.pm2_5 = bmv080_output.pm2_5; - // print("Runtime: %.2f s, PM2.5: %.0f ug/m^3, obstructed: %s, outside detection limits: %s\r\n", - // bmv080_output.runtime_in_sec, bmv080_output.pm2_5, (bmv080_output.is_obstructed ? "yes" : "no"), (bmv080_output.is_outside_detection_limits ? "yes" : "no")); -} - - -void bmv080_service_routine(const bmv080_handle_t handle, void* callback_parameters) -{ - /* The interrupt is served by the BMV080 sensor driver */ - bmv080_status_code_t bmv080_current_status = bmv080_serve_interrupt(handle, (bmv080_callback_data_ready_t)use_sensor_output, callback_parameters); - if (bmv080_current_status != E_BMV080_OK) - { - printf("Fetching measurement data failed with BMV080 status %d\r\n", (int32_t)bmv080_current_status); - } -} - - -#ifdef __cplusplus -} -#endif - - -sfeTkError_t sfeBmv080::begin(sfeTkII2C *theBus) -{ - // Nullptr check - if (theBus == nullptr) - return kSTkErrFail; - - // Set bus pointer - _theBus = theBus; - - sfeTkError_t err; - err = isConnected(); - // Check whether the ping was successful - if (err != kSTkErrOk) - return err; - - // Done! - return kSTkErrOk; -} - -sfeTkError_t sfeBmv080::isConnected() -{ - // Just ping the device address - return _theBus->ping(); -} - -float sfeBmv080::getPM25() -{ - return _sensorValue.pm2_5; -} - -bool sfeBmv080::getIsObstructed() -{ - return _sensorValue.is_obstructed; -} - -void sfeBmv080::setSensorValue(bmv080_output_t bmv080_output) -{ - _dataAvailable = true; - _sensorValue.pm2_5 = bmv080_output.pm2_5; - _sensorValue.runtime_in_sec = bmv080_output.runtime_in_sec; - _sensorValue.is_obstructed = bmv080_output.is_obstructed; - _sensorValue.is_outside_detection_limits = bmv080_output.is_outside_detection_limits; -} - -bool sfeBmv080::setMode(uint8_t mode) -{ - bmv080_status_code_t bmv080_current_status; // return status from the Bosch API function - - if(mode == SFE_BMV080_MODE_CONTINUOUS) - { - bmv080_current_status = bmv080_start_continuous_measurement(bmv080_handle_class); - } - else if(mode == SFE_BMV080_MODE_DUTY_CYCLE) - { - bmv080_duty_cycling_mode_t duty_cycling_mode = E_BMV080_DUTY_CYCLING_MODE_0; - bmv080_current_status = bmv080_start_duty_cycling_measurement(bmv080_handle_class, (bmv080_callback_tick_t)millis, duty_cycling_mode); - } - - // check if the mode was set correctly - if (bmv080_current_status == E_BMV080_OK) - { - return true; - } - else - { - return false; - } -} - -bool sfeBmv080::dataAvailable() -{ - bmv080_service_routine(bmv080_handle_class, this); - if(_dataAvailable == true) - { - _dataAvailable = false; - return true; - } - else - return false; -} - -bool sfeBmv080::init(i2c_device_t *i2c_device) -{ - if(getDriverVersion() == false) - { - return false; - } - - if(open(i2c_device) == false) - { - return false; - } - - if(reset() == false) - { - return false; - } - - if(getID() == false) - { - return false; - } - - return true; -} - -bool sfeBmv080::open(i2c_device_t *i2c_device) -{ - bmv080_sercom_handle_t sercom_handle = (bmv080_sercom_handle_t)i2c_device; - bmv080_callback_read_t read = (const bmv080_callback_read_t)combridge_i2c_read_16bit; - bmv080_callback_write_t write = (const bmv080_callback_write_t)combridge_i2c_write_16bit; - bmv080_callback_delay_t delay_ms = (const bmv080_callback_delay_t)combridge_delay; - - bmv080_status_code_t bmv080_current_status = bmv080_open(&bmv080_handle_class, sercom_handle, read, write, delay_ms); - - if (bmv080_current_status != E_BMV080_OK) - { - Serial.println("BMV080 open failed"); - return false; - } - else - { - Serial.println("BMV080 open successfully"); - return true; - } -} - -bool sfeBmv080::initSPI(spi_device_t *spi_device) -{ - if(getDriverVersion() == false) - { - return false; - } - - if(openSPI(spi_device) == false) - { - return false; - } - - if(reset() == false) - { - return false; - } - - if(getID() == false) - { - return false; - } - - return true; -} - -bool sfeBmv080::openSPI(spi_device_t *spi_device) -{ - bmv080_sercom_handle_t sercom_handle = (bmv080_sercom_handle_t)spi_device; - bmv080_callback_read_t read = (const bmv080_callback_read_t)combridge_spi_read_16bit; - bmv080_callback_write_t write = (const bmv080_callback_write_t)combridge_spi_write_16bit; - bmv080_callback_delay_t delay_ms = (const bmv080_callback_delay_t)combridge_delay; - - bmv080_status_code_t bmv080_current_status = bmv080_open(&bmv080_handle_class, sercom_handle, read, write, delay_ms); - - if (bmv080_current_status != E_BMV080_OK) - { - Serial.println("BMV080 open failed"); - return false; - } - else - { - Serial.println("BMV080 open successfully"); - return true; - } -} - -bool sfeBmv080::reset() -{ - bmv080_status_code_t bmv080_current_status = bmv080_reset(bmv080_handle_class); - - if (bmv080_current_status != E_BMV080_OK) - { - Serial.println("BMV080 reset failed"); - return false; - } - else - { - Serial.println("BMV080 reset successfully"); - return true; - } -} - -bool sfeBmv080::getDriverVersion() -{ - uint16_t major = 0; - uint16_t minor = 0; - uint16_t patch = 0; - char git_hash[12]; - int32_t commits_ahead = 0; - - bmv080_status_code_t bmv080_current_status = bmv080_get_driver_version(&major, &minor, &patch, git_hash, &commits_ahead); - - if (bmv080_current_status != E_BMV080_OK) - { - printf("Error getting BMV080 driver version: %d\n", bmv080_current_status); - return false; - } - - printf("BMV080 driver version: %d.%d.%d\n", major, minor, patch); - return true; -} - -bool sfeBmv080::getID() -{ - char id[13]; - memset(id, 0x00, 13); - bmv080_status_code_t bmv080_current_status = bmv080_get_sensor_id(bmv080_handle_class, id); - - if (bmv080_current_status != E_BMV080_OK) - { - printf("Error getting BMV080 sensor ID: %d\n", bmv080_current_status); - return false; - } - else - { - printf("BMV080 sensor ID: %s\n", id); - return true; - } -} - -uint16_t sfeBmv080::getDutyCyclingPeriod() -{ - uint16_t duty_cycling_period = 0; - bmv080_status_code_t bmv080_current_status = bmv080_get_parameter(bmv080_handle_class, "duty_cycling_period", (void*)&duty_cycling_period); - if (bmv080_current_status != E_BMV080_OK) - { - printf("Error getting BMV080 Duty Cycling Period: %d\n", bmv080_current_status); - return 0; - } - else - { - printf("BMV080 Duty Cycling Period Read: %d\n", duty_cycling_period); - return duty_cycling_period; - } -} - -bool sfeBmv080::setDutyCyclingPeriod(uint16_t duty_cycling_period) -{ - bmv080_status_code_t bmv080_current_status = bmv080_set_parameter(bmv080_handle_class, "duty_cycling_period", (void*)&duty_cycling_period); - if (bmv080_current_status != E_BMV080_OK) - { - printf("Error setting BMV080 Duty Cycling Period: %d\n", bmv080_current_status); - return false; - } - else - { - printf("BMV080 Duty Cycling Period Set: %d\n", duty_cycling_period); - return true; - } -} - -float sfeBmv080::getVolumetricMassDensity() -{ - float volumetric_mass_density = 0.0; - bmv080_status_code_t bmv080_current_status = bmv080_get_parameter(bmv080_handle_class, "volumetric_mass_density", (void*)&volumetric_mass_density); - if (bmv080_current_status != E_BMV080_OK) - { - printf("Error getting BMV080 Volumetric Mass Density: %d\n", bmv080_current_status); - return 0.0; - } - else - { - return volumetric_mass_density; - } -} - -bool sfeBmv080::setVolumetricMassDensity(float volumetric_mass_density) -{ - bmv080_status_code_t bmv080_current_status = bmv080_set_parameter(bmv080_handle_class, "volumetric_mass_density", (void*)&volumetric_mass_density); - if (bmv080_current_status != E_BMV080_OK) - { - printf("Error setting BMV080 Volumetric Mass Density: %d\n", bmv080_current_status); - return false; - } - else - { - return true; - } -} - -float sfeBmv080::getIntegrationTime() -{ - float integration_time = 0.0; - bmv080_status_code_t bmv080_current_status = bmv080_get_parameter(bmv080_handle_class, "integration_time", (void*)&integration_time); - if (bmv080_current_status != E_BMV080_OK) - { - printf("Error getting BMV080 Integration Time: %d\n", bmv080_current_status); - return 0.0; - } - else - { - return integration_time; - } -} - -bool sfeBmv080::setIntegrationTime(float integration_time) -{ - bmv080_status_code_t bmv080_current_status = bmv080_set_parameter(bmv080_handle_class, "integration_time", (void*)&integration_time); - if (bmv080_current_status != E_BMV080_OK) - { - printf("Error setting BMV080 Integration Time: %d\n", bmv080_current_status); - return false; - } - else - { - return true; - } -} - -uint32_t sfeBmv080::getDistributionId() -{ - uint32_t distribution_id = 0; - bmv080_status_code_t bmv080_current_status = bmv080_get_parameter(bmv080_handle_class, "distribution_id", (void*)&distribution_id); - if (bmv080_current_status != E_BMV080_OK) - { - printf("Error getting BMV080 Distribution ID: %d\n", bmv080_current_status); - return 0; - } - else - { - return distribution_id; - } -} - -bool sfeBmv080::setDistributionId(uint32_t distribution_id) -{ - bmv080_status_code_t bmv080_current_status = bmv080_set_parameter(bmv080_handle_class, "distribution_id", (void*)&distribution_id); - if (bmv080_current_status != E_BMV080_OK) - { - printf("Error setting BMV080 Distribution ID: %d\n", bmv080_current_status); - return false; - } - else - { - return true; - } -} - -bool sfeBmv080::getDoObstructionDetection() -{ - bool do_obstruction_detection = false; - bmv080_status_code_t bmv080_current_status = bmv080_get_parameter(bmv080_handle_class, "do_obstruction_detection", (void*)&do_obstruction_detection); - if (bmv080_current_status != E_BMV080_OK) - { - printf("Error getting BMV080 Obstruction Detection: %d\n", bmv080_current_status); - return false; - } - else - { - return do_obstruction_detection; - } -} - -bool sfeBmv080::setDoObstructionDetection(bool do_obstruction_detection) -{ - bmv080_status_code_t bmv080_current_status = bmv080_set_parameter(bmv080_handle_class, "do_obstruction_detection", (void*)&do_obstruction_detection); - if (bmv080_current_status != E_BMV080_OK) - { - printf("Error setting BMV080 Obstruction Detection: %d\n", bmv080_current_status); - return false; - } - else - { - return true; - } -} - -bool sfeBmv080::getDoVibrationFiltering() -{ - bool do_vibration_filtering = false; - bmv080_status_code_t bmv080_current_status = bmv080_get_parameter(bmv080_handle_class, "do_vibration_filtering", (void*)&do_vibration_filtering); - if (bmv080_current_status != E_BMV080_OK) - { - printf("Error getting BMV080 Vibration Filtering: %d\n", bmv080_current_status); - return false; - } - else - { - return do_vibration_filtering; - } -} - -bool sfeBmv080::setDoVibrationFiltering(bool do_vibration_filtering) -{ - bmv080_status_code_t bmv080_current_status = bmv080_set_parameter(bmv080_handle_class, "do_vibration_filtering", (void*)&do_vibration_filtering); - if (bmv080_current_status != E_BMV080_OK) - { - printf("Error setting BMV080 Vibration Filtering: %d\n", bmv080_current_status); - return false; - } - else - { - return true; - } -} - diff --git a/src/sfeBmv080.h b/src/sfeBmv080.h deleted file mode 100644 index f5a968e..0000000 --- a/src/sfeBmv080.h +++ /dev/null @@ -1,177 +0,0 @@ -/****************************************************************************** - sfeBmv080.h - SparkFun BMV080 Library header file - - by Pete Lewis @SparkFun Electronics - September 2024 - - This file implements the BMV080 class, prototyped in SparkFun_BMV080_Arduino_Library.h - - Development environment specifics: - IDE: Arduino 2.3.3 - Hardware Platform: SparkFun IoT Redboard ESP32 - BMV080 Breakout HW Version: v01 - - SPDX-License-Identifier: MIT - - Copyright (c) 2024 SparkFun Electronics - - Distributed as-is; no warranty is given. -******************************************************************************/ - -#pragma once - -#include "bmv080.h" -#include "bmv080_defs.h" -#include "combridge.h" - -#include -#include - -#define SFE_BMV080_DEFAULT_ADDRESS 0x57 -#define SFE_BMV080_DEFAULT_IRQ_PIN 14 - -#define SFE_BMV080_MODE_CONTINUOUS 0 -#define SFE_BMV080_MODE_DUTY_CYCLE 1 - - - -class sfeBmv080 -{ - public: - /// @brief Default constructor - sfeBmv080() : _theBus{nullptr} - { - } - - /// @brief Begins the Device - /// @param theBus I2C bus to use for communication - /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t begin(sfeTkII2C *theBus = nullptr); - - /// @brief Checks if the Device is connected - /// @return 0 for succuss, negative for errors, positive for warnings - sfeTkError_t isConnected(); - - /// @brief Initialize the sensor i2c - /// @details This function initializes the sensor and should be called - /// @details before any other functions. It calls Open, Reset, getDriverVersion, and getID. - /// @param i2c_device The I2C device to use - /// @return True if successful, false otherwise - bool init(i2c_device_t *i2c_device); - - /// @brief Initialize the sensor SPI - /// @details This function initializes the sensor and should be called - /// @details before any other functions. It calls Open, Reset, getDriverVersion, and getID. - /// @param spi_device The SPI device to use - /// @return True if successful, false otherwise - bool initSPI(spi_device_t *spi_device); - - /// @brief Get the version information of this sensor driver. - /// @return True if successful, false otherwise - bool getDriverVersion(); - - /// @brief Open a sensor unit by initializing a new handle. - /// @param i2c_device The I2C device to use - /// @return True if successful, false otherwise - bool open(i2c_device_t *i2c_device); - - /// @brief Open a sensor unit by initializing a new handle. - /// @param spi_device The SPI device to use - /// @return True if successful, false otherwise - bool openSPI(spi_device_t *spi_device); - - /// @brief Reset the sensor - /// @return True if successful, false otherwise - bool reset(); - - /// @brief Get the ID of the sensor - /// @return True if successful, false otherwise - bool getID(); - - /// @brief Set the mode of the sensor - /// @param mode SFE_BMV080_MODE_CONTINUOUS, SFE_BMV080_MODE_DUTY_CYCLE - /// @return True if successful, false otherwise - bool setMode(uint8_t mode); - - /// @brief Get the PM2.5 value - /// @return The PM2.5 value as a float in ug/m3 - float getPM25(); - - /// @brief Get the obstruction status - /// @return True if obstructed, false otherwise - bool getIsObstructed(); - - - void setSensorValue(bmv080_output_t bmv080_output); - - /// @brief Check if new data is available - /// @details This function should be called in the main loop to check if new data is available - /// @details If new data is available, the data can be read using getPM25 and getIsObstructed - /// @return True if new data is available, false otherwise - bool dataAvailable(); - - /// @brief Get the duty cycling period - /// @return The duty cycling period in seconds - uint16_t getDutyCyclingPeriod(); - - /// @brief Set the duty cycling period - /// @param period The duty cycling period in seconds - /// @return True if successful, false otherwise - bool setDutyCyclingPeriod(uint16_t duty_cycling_period); - - /// @brief Get a parameter: "volumetric_mass_density" - /// @return float volumetric_mass_density - float getVolumetricMassDensity(); - - /// @brief Set a parameter: "volumetric_mass_density" - /// @param volumetric_mass_density - /// @return True if successful, false otherwise - bool setVolumetricMassDensity(float volumetric_mass_density); - - /// @brief Get a parameter: "integration_time" - /// @return float integration_time - float getIntegrationTime(); - - /// @brief Set a parameter: "integration_time" - /// @param integration_time - /// @return True if successful, false otherwise - bool setIntegrationTime(float integration_time); - - /// @brief Get a parameter: "distribution_id" - /// @return uint32_t distribution_id - uint32_t getDistributionId(); - - /// @brief Set a parameter: "distribution_id" - /// @param distribution_id - /// @return True if successful, false otherwise - bool setDistributionId(uint32_t distribution_id); - - /// @brief Get a parameter: "do_obstruction_detection" - /// @return bool do_obstruction_detection - bool getDoObstructionDetection(); - - /// @brief Set a parameter: "do_obstruction_detection" - /// @param do_obstruction_detection - /// @return True if successful, false otherwise - bool setDoObstructionDetection(bool do_obstruction_detection); - - /// @brief Get a parameter: "do_vibration_filtering" - /// @return bool do_vibration_filtering - bool getDoVibrationFiltering(); - - /// @brief Set a parameter: "do_vibration_filtering" - /// @param do_vibration_filtering - /// @return True if successful, false otherwise - bool setDoVibrationFiltering(bool do_vibration_filtering); - - - - private: - bmv080_handle_t bmv080_handle_class = NULL; - bool _dataAvailable = false; - bmv080_output_t _sensorValue; - - protected: - sfeTkII2C *_theBus; -}; \ No newline at end of file diff --git a/src/bmv080.h b/src/sfeTk/bmv080.h similarity index 93% rename from src/bmv080.h rename to src/sfeTk/bmv080.h index 81e4d0f..a0e3822 100644 --- a/src/bmv080.h +++ b/src/sfeTk/bmv080.h @@ -151,6 +151,10 @@ bmv080_status_code_t bmv080_reset(const bmv080_handle_t handle); * "distribution_id" uint32_t 3 For internal use only. * "do_obstruction_detection" bool true Set if obstruction detection feature is enabled. * "do_vibration_filtering" bool false Set if vibration filter is enabled. +* "measurement_algorithm" bmv080_measurement_algorithm_t E_BMV080_MEASUREMENT_ALGORITHM_HIGH_PRECISION (3) Selection of measurement algorithm based on the +*
use case, as defined by the type bmv080_measurement_algorithm_t, +*
in bmv080_defs.h. For a duty cycling measurement, this parameter is +*
fixed to E_BMV080_MEASUREMENT_ALGORITHM_FAST_RESPONSE. * * * @pre A valid _handle_ generated by _bmv080_open_ is required. @@ -192,7 +196,11 @@ bmv080_status_code_t bmv080_get_parameter(const bmv080_handle_t handle, const ch * "distribution_id" uint32_t 3 For internal use only. * "do_obstruction_detection" bool true Set if obstruction detection feature is enabled. * "do_vibration_filtering" bool false Set if vibration filter is enabled. -* s +* "measurement_algorithm" bmv080_measurement_algorithm_t E_BMV080_MEASUREMENT_ALGORITHM_HIGH_PRECISION (3) Selection of measurement algorithm based on the +*
use case, as defined by the type bmv080_measurement_algorithm_t, +*
in bmv080_defs.h. For a duty cycling measurement, this parameter is +*
fixed to E_BMV080_MEASUREMENT_ALGORITHM_FAST_RESPONSE. +* * * @pre A valid _handle_ generated by _bmv080_open_ is required. * @pre This function must be called before _bmv080_start_continuous_measurement_ or _bmv080_start_duty_cycling_measurement_ diff --git a/src/bmv080_defs.h b/src/sfeTk/bmv080_defs.h similarity index 96% rename from src/bmv080_defs.h rename to src/sfeTk/bmv080_defs.h index be08636..5a84bb7 100644 --- a/src/bmv080_defs.h +++ b/src/sfeTk/bmv080_defs.h @@ -364,6 +364,16 @@ typedef enum E_BMV080_DUTY_CYCLING_MODE_0 = 0 } bmv080_duty_cycling_mode_t; +/*! +* @brief Measurement algorithm choices. +*/ +typedef enum +{ + E_BMV080_MEASUREMENT_ALGORITHM_FAST_RESPONSE = 1, + E_BMV080_MEASUREMENT_ALGORITHM_BALANCED = 2, + E_BMV080_MEASUREMENT_ALGORITHM_HIGH_PRECISION = 3 +} bmv080_measurement_algorithm_t; + /*! * @brief Placeholder structure for extended sensor output information. */ @@ -374,27 +384,36 @@ struct bmv080_extended_info_s; */ typedef struct { + /*! runtime_in_sec: estimate of the time passed since the start of the measurement, in seconds */ + float runtime_in_sec; + /*! pm2_5_mass_concentration: PM2.5 value in ug/m3 */ + float pm2_5_mass_concentration; + /*! pm1_mass_concentration: PM1 value in ug/m3 */ + float pm1_mass_concentration; + /*! is_obstructed: flag to indicate whether the sensor is obstructed and cannot perform a valid measurement */ + bool is_obstructed; + /*! is_outside_measurement_range: flag to indicate whether the PM2.5 concentration is + * outside the specified measurement range (0..1000 ug/m3) + */ + bool is_outside_measurement_range; /*! reserved_0: for internal use only */ float reserved_0; /*! reserved_1: for internal use only */ float reserved_1; /*! reserved_2: for internal use only */ float reserved_2; - /*! pm2_5: PM2.5 value in ug/m3 */ - float pm2_5; - /*! runtime_in_sec: estimate of the time passed since the start of the measurement, in seconds */ - float runtime_in_sec; - /*! is_obstructed: flag to indicate whether the sensor is obstructed and cannot perform a valid measurement */ - bool is_obstructed; - /*! is_outside_detection_limits: flag to indicate whether the PM2.5 concentration is outside the detection limits - * and a valid measurement cannot be performed - */ - bool is_outside_detection_limits; + /*! reserved_3: for internal use only */ + float reserved_3; + /*! reserved_4: for internal use only */ + float reserved_4; + /*! reserved_5: for internal use only */ + float reserved_5; + /*! reserved_6: for internal use only */ + float reserved_6; /*! extended_info: for internal use only */ struct bmv080_extended_info_s *extended_info; }bmv080_output_t; - /********************************************************************************************************************* * Callback definitions *********************************************************************************************************************/ @@ -464,8 +483,4 @@ typedef uint32_t(*bmv080_callback_tick_t)(void); */ typedef void(*bmv080_callback_data_ready_t)(bmv080_output_t bmv080_output, void* callback_parameters); - -/* Function pointer to facilitate printing output / status information to the console */ -typedef int (*print_function_t)(const char *const _format, ...); - #endif /* BMV080_DEFS_H_ */ diff --git a/src/sfeTk/sfeDevBMV080.cpp b/src/sfeTk/sfeDevBMV080.cpp new file mode 100644 index 0000000..0a57986 --- /dev/null +++ b/src/sfeTk/sfeDevBMV080.cpp @@ -0,0 +1,409 @@ +/****************************************************************************** + sfeDevBMV080.cpp + SparkFun BMV080 Library CPP file + + by Pete Lewis @SparkFun Electronics + September 2024 + + BMV080 Breakout HW Version: v01 + + SPDX-License-Identifier: MIT + + Copyright (c) 2025 SparkFun Electronics + + Distributed as-is; no warranty is given. +******************************************************************************/ + +#include "sfeDevBMV080.h" +#include "bmv080.h" +#include "bmv080_defs.h" + +// need our bus I2C type for some I2C specific features +#include "sfeTk/sfeTkII2C.h" +#include "sfeTk/sfeToolkit.h" + +#include + +// Some communication functions used with the system. These are from the original code from +// Bosch - so keeping them the same. It is unclear if the library they provide depends on these +// specific values - it probably does - so leaving as is. + +#define E_COMBRIDGE_OK ((int8_t)0) +/*! -1: Status codes returned when memory allocation fails */ +#define E_COMBRIDGE_ERROR_MEMORY_ALLOCATION ((int8_t)-1) +/*! -2: Status codes returned when the read operation fails */ +#define E_COMBRIDGE_ERROR_READ ((int8_t)-2) +/*! -3: Status codes returned when the write operation fails */ +#define E_COMBRIDGE_ERROR_WRITE ((int8_t)-3) +/*! -4: Status codes returned when writing the header fails */ +#define E_COMBRIDGE_ERROR_WRITE_HEADER ((int8_t)-4) +/*! -5: Status codes returned when a reference is null */ +#define E_COMBRIDGE_ERROR_NULLPTR ((int8_t)-5) + +// C function used in this library only - so static + +/* Our bus read and write functions */ + +// -------------------------------------------------------------------------------------------- +// BOSCH API Callbacks +// -------------------------------------------------------------------------------------------- +// Callback for reading data-- called from the Bosch supplied library +// +// static int8_t device_read_16bit_CB(bmv080_sercom_handle_t handle, uint16_t header, uint16_t *payload, +int8_t sfeDevBMV080::device_read_16bit_CB(bmv080_sercom_handle_t handle, uint16_t header, uint16_t *payload, + uint16_t payload_length) +{ + if (handle == nullptr) + return E_COMBRIDGE_ERROR_NULLPTR; + + // Our output var. + size_t nRead = 0; + + // Get our sparkfun toolkit bus object/interface + sfeTkIBus *theBus = (sfeTkIBus *)handle; + + if (theBus->type() == kBusTypeI2C) // I2C specific shift + header = header << 1; + + sfeTkError_t rc = theBus->readRegister(header, payload, payload_length, nRead); + + if (rc != kSTkErrOk || nRead != payload_length) + return E_COMBRIDGE_ERROR_READ; + + return E_COMBRIDGE_OK; +} + +// -------------------------------------------------------------------------------------------- +// Callback for reading data-- called from the Bosch supplied library +// +int8_t sfeDevBMV080::device_write_16bit_CB(bmv080_sercom_handle_t handle, uint16_t header, const uint16_t *payload, + uint16_t payload_length) +{ + if (handle == nullptr) + return E_COMBRIDGE_ERROR_NULLPTR; + + sfeTkIBus *theBus = (sfeTkIBus *)handle; + + if (theBus->type() == kBusTypeI2C) // I2C specific shift + header = header << 1; + + sfeTkError_t rc = theBus->writeRegister(header, payload, payload_length); + + // okay, not okay? + return rc == kSTkErrOk ? E_COMBRIDGE_OK : E_COMBRIDGE_ERROR_WRITE; +} + +// -------------------------------------------------------------------------------------------- +// Delay callback function for the Bosch library +// +int8_t sfeDevBMV080::device_delay_CB(uint32_t period) +{ + sftk_delay_ms(period); + // delay(period); + + return E_COMBRIDGE_OK; +} + +//--------------------------------------------------------------------- +// helpful class method/callback + +/* Custom function for consuming sensor readings */ +void sfeDevBMV080::set_sensor_value(bmv080_output_t bmv080_output, void *callback_parameters) +{ + ((sfeDevBMV080 *)callback_parameters)->setSensorValue(bmv080_output); +} + +//--------------------------------------------------------------------- +// End CB functions +//--------------------------------------------------------------------- +//--------------------------------------------------------------------- +// Core object implementation +//--------------------------------------------------------------------- +sfeTkError_t sfeDevBMV080::begin(sfeTkIBus *theBus) +{ + // Nullptr check + if (theBus == nullptr) + return kSTkErrFail; + + // Set bus pointer + _theBus = theBus; + + return kSTkErrOk; +} + +//--------------------------------------------------------------------- +float sfeDevBMV080::PM25() +{ + return _sensorValue.pm2_5_mass_concentration; +} + +//--------------------------------------------------------------------- +float sfeDevBMV080::PM1() +{ + return _sensorValue.pm1_mass_concentration; +} + +//--------------------------------------------------------------------- +bool sfeDevBMV080::isObstructed() +{ + return _sensorValue.is_obstructed; +} + +//--------------------------------------------------------------------- +void sfeDevBMV080::setSensorValue(bmv080_output_t bmv080_output) +{ + // TODO: should here be a mode where the library user can set register a callback function to handle the data? + // This way the end user can get all the sensor data at once - possible issue is stack/re-entrancy + _dataAvailable = true; + + // cache the latest sensor values - copy output to our class variable + _sensorValue = bmv080_output; +} + +//--------------------------------------------------------------------- +// Read the latest values from the sensor. +// +// Return the value if a struct is passed in. +bool sfeDevBMV080::readSensor(bmv080_output_t *bmv080_output /* default is nullptr*/) +{ + _dataAvailable = false; + if (!sensorServiceRoutine()) + return false; + + if (_dataAvailable && bmv080_output != nullptr) + *bmv080_output = _sensorValue; + + return _dataAvailable; +} + +//--------------------------------------------------------------------- +bool sfeDevBMV080::setMode(uint8_t mode) +{ + bmv080_status_code_t bmv080_current_status = E_BMV080_ERROR_PARAM_INVALID_VALUE; // return status from the Bosch API function + + if (mode == SFE_BMV080_MODE_CONTINUOUS) + { + bmv080_current_status = bmv080_start_continuous_measurement(_bmv080_handle_class); + } + else if (mode == SFE_BMV080_MODE_DUTY_CYCLE) + { + bmv080_duty_cycling_mode_t duty_cycling_mode = E_BMV080_DUTY_CYCLING_MODE_0; + bmv080_current_status = bmv080_start_duty_cycling_measurement( + _bmv080_handle_class, (bmv080_callback_tick_t)sftk_ticks_ms(), duty_cycling_mode); + } + + // check if the mode was set correctly + return (bmv080_current_status == E_BMV080_OK); +} + +//--------------------------------------------------------------------- +// Called to pump the service routine of the BMV080 sensor driver +// + +bool sfeDevBMV080::sensorServiceRoutine(void) +{ + if (_bmv080_handle_class == NULL) + return false; + /* The interrupt is served by the BMV080 sensor driver */ + bmv080_status_code_t bmv080_current_status = + bmv080_serve_interrupt(_bmv080_handle_class, (bmv080_callback_data_ready_t)set_sensor_value, (void *)this); + + return (bmv080_current_status == E_BMV080_OK); +} + +//--------------------------------------------------------------------- +// Our init method +bool sfeDevBMV080::init() +{ + // Do we have a bus? + if (_theBus == nullptr) + return false; + + uint16_t major, minor, patch; + char id[kBMV800IDLength]; + if (!driverVersion(major, minor, patch) || !open() || !reset() || !ID(id)) + return false; + + return true; +} + +//--------------------------------------------------------------------- +bool sfeDevBMV080::open() +{ + if (_theBus == nullptr) + return false; + + // Open the device - pass in the data read, data write and delay functions callbacks. Note - the "sercom_handle_t" + // is just a pointer to our Toolkit communication bus objects + + bmv080_status_code_t status = bmv080_open( + &_bmv080_handle_class, (bmv080_sercom_handle_t)_theBus, (bmv080_callback_read_t)device_read_16bit_CB, + (bmv080_callback_write_t)device_write_16bit_CB, (bmv080_callback_delay_t)device_delay_CB); + + return (status == E_BMV080_OK); +} + +//--------------------------------------------------------------------- +bool sfeDevBMV080::reset() +{ + bmv080_status_code_t bmv080_current_status = bmv080_reset(_bmv080_handle_class); + + return (bmv080_current_status == E_BMV080_OK); +} +//--------------------------------------------------------------------- +bool sfeDevBMV080::driverVersion(uint16_t &major, uint16_t &minor, uint16_t &patch) +{ + char git_hash[12]; + int32_t commits_ahead = 0; + + bmv080_status_code_t bmv080_current_status = + bmv080_get_driver_version(&major, &minor, &patch, git_hash, &commits_ahead); + + return (bmv080_current_status == E_BMV080_OK); +} + +//--------------------------------------------------------------------- + +// Method to get the ID +bool sfeDevBMV080::ID(char idOut[kBMV800IDLength]) +{ + memset(idOut, 0x00, kBMV800IDLength); + bmv080_status_code_t bmv080_current_status = bmv080_get_sensor_id(_bmv080_handle_class, idOut); + + return (bmv080_current_status == E_BMV080_OK); +} + +//--------------------------------------------------------------------- +uint16_t sfeDevBMV080::dutyCyclingPeriod() +{ + uint16_t duty_cycling_period = 0; + bmv080_status_code_t bmv080_current_status = + bmv080_get_parameter(_bmv080_handle_class, "duty_cycling_period", (void *)&duty_cycling_period); + + return (bmv080_current_status == E_BMV080_OK ? duty_cycling_period : 0); +} + +//--------------------------------------------------------------------- +bool sfeDevBMV080::setDutyCyclingPeriod(uint16_t duty_cycling_period) +{ + bmv080_status_code_t bmv080_current_status = + bmv080_set_parameter(_bmv080_handle_class, "duty_cycling_period", (void *)&duty_cycling_period); + + return (bmv080_current_status == E_BMV080_OK); +} + +//--------------------------------------------------------------------- +float sfeDevBMV080::volumetricMassDensity() +{ + float volumetric_mass_density = 0.0; + bmv080_status_code_t bmv080_current_status = + bmv080_get_parameter(_bmv080_handle_class, "volumetric_mass_density", (void *)&volumetric_mass_density); + + return (bmv080_current_status == E_BMV080_OK ? volumetric_mass_density : 0.0); +} + +//--------------------------------------------------------------------- +bool sfeDevBMV080::setVolumetricMassDensity(float volumetric_mass_density) +{ + bmv080_status_code_t bmv080_current_status = + bmv080_set_parameter(_bmv080_handle_class, "volumetric_mass_density", (void *)&volumetric_mass_density); + + return (bmv080_current_status == E_BMV080_OK); +} + +//--------------------------------------------------------------------- +float sfeDevBMV080::integrationTime() +{ + float integration_time = 0.0; + bmv080_status_code_t bmv080_current_status = + bmv080_get_parameter(_bmv080_handle_class, "integration_time", (void *)&integration_time); + + return (bmv080_current_status == E_BMV080_OK ? integration_time : 0.0); +} + +//--------------------------------------------------------------------- +bool sfeDevBMV080::setIntegrationTime(float integration_time) +{ + bmv080_status_code_t bmv080_current_status = + bmv080_set_parameter(_bmv080_handle_class, "integration_time", (void *)&integration_time); + + return (bmv080_current_status == E_BMV080_OK); +} + +//--------------------------------------------------------------------- +uint32_t sfeDevBMV080::distributionId() +{ + uint32_t distribution_id = 0; + bmv080_status_code_t bmv080_current_status = + bmv080_get_parameter(_bmv080_handle_class, "distribution_id", (void *)&distribution_id); + + return (bmv080_current_status == E_BMV080_OK ? distribution_id : 0); +} + +//--------------------------------------------------------------------- + +bool sfeDevBMV080::setDistributionId(uint32_t distribution_id) +{ + bmv080_status_code_t bmv080_current_status = + bmv080_set_parameter(_bmv080_handle_class, "distribution_id", (void *)&distribution_id); + + return (bmv080_current_status == E_BMV080_OK); +} + +//--------------------------------------------------------------------- +bool sfeDevBMV080::doObstructionDetection() +{ + bool do_obstruction_detection = false; + bmv080_status_code_t bmv080_current_status = + bmv080_get_parameter(_bmv080_handle_class, "do_obstruction_detection", (void *)&do_obstruction_detection); + + return (bmv080_current_status == E_BMV080_OK ? do_obstruction_detection : false); +} + +//--------------------------------------------------------------------- +bool sfeDevBMV080::setDoObstructionDetection(bool do_obstruction_detection) +{ + bmv080_status_code_t bmv080_current_status = + bmv080_set_parameter(_bmv080_handle_class, "do_obstruction_detection", (void *)&do_obstruction_detection); + + return (bmv080_current_status == E_BMV080_OK); +} + +//--------------------------------------------------------------------- +bool sfeDevBMV080::doVibrationFiltering() +{ + bool do_vibration_filtering = false; + bmv080_status_code_t bmv080_current_status = + bmv080_get_parameter(_bmv080_handle_class, "do_vibration_filtering", (void *)&do_vibration_filtering); + + return (bmv080_current_status == E_BMV080_OK ? do_vibration_filtering : false); +} + +//--------------------------------------------------------------------- +bool sfeDevBMV080::setDoVibrationFiltering(bool do_vibration_filtering) +{ + bmv080_status_code_t bmv080_current_status = + bmv080_set_parameter(_bmv080_handle_class, "do_vibration_filtering", (void *)&do_vibration_filtering); + + return (bmv080_current_status == E_BMV080_OK); +} + +//--------------------------------------------------------------------- +uint8_t sfeDevBMV080::measurementAlgorithm() +{ + bmv080_measurement_algorithm_t measurement_algorithm; + bmv080_status_code_t bmv080_current_status = + bmv080_get_parameter(_bmv080_handle_class, "measurement_algorithm", (void *)&measurement_algorithm); + + return (bmv080_current_status == E_BMV080_OK ? (uint8_t)measurement_algorithm : 0); +} + +//--------------------------------------------------------------------- +bool sfeDevBMV080::setMeasurementAlgorithm(uint8_t measurement_algorithm) +{ + bmv080_measurement_algorithm_t bmv080_measurement_algorithm = (bmv080_measurement_algorithm_t)measurement_algorithm; + bmv080_status_code_t bmv080_current_status = + bmv080_set_parameter(_bmv080_handle_class, "measurement_algorithm", (void *)&bmv080_measurement_algorithm); + + return (bmv080_current_status == E_BMV080_OK); +} \ No newline at end of file diff --git a/src/sfeTk/sfeDevBMV080.h b/src/sfeTk/sfeDevBMV080.h new file mode 100644 index 0000000..3ee5b2e --- /dev/null +++ b/src/sfeTk/sfeDevBMV080.h @@ -0,0 +1,609 @@ +/****************************************************************************** + sfeDevBMV080.h + SparkFun BMV080 Library header file + + by Pete Lewis @SparkFun Electronics + September 2025 + + This file implements the BMV080 class, prototyped in SparkFun_BMV080_Arduino_Library.h + + BMV080 Breakout HW Version: v01 + + SPDX-License-Identifier: MIT + + Copyright (c) 2025 SparkFun Electronics + + Distributed as-is; no warranty is given. +******************************************************************************/ + +#pragma once + +#include "bmv080.h" +#include "bmv080_defs.h" + +// Include the platform independent layer of the SparkFun Toolkit +#include +#include +#include + +#define SFE_BMV080_DEFAULT_ADDRESS 0x57 +#define SFE_BMV080_DEFAULT_IRQ_PIN 14 + +#define SFE_BMV080_MODE_CONTINUOUS 0 +#define SFE_BMV080_MODE_DUTY_CYCLE 1 + +class sfeDevBMV080 +{ + public: + /// @brief Default constructor + sfeDevBMV080() : _theBus{nullptr} + { + } + + /** + * @brief Begins communication with the BMV080 sensor + * + * This method initializes the communication interface with the sensor. + * It must be called before init() and any other operations with the sensor. + * + * @param theBus SparkFun Toolkit bus interface to use for communication. + * + * @return sfeTkError_t Status code: + * - 0: Success + * - Negative: Error occurred + * - Positive: Warning condition + * + * @see init() + * @see open() + */ + sfeTkError_t begin(sfeTkIBus *theBus = nullptr); + + /** + * @brief Initializes the BMV080 sensor + * + * This method performs complete initialization of the sensor by: + * - Opening communication with the sensor + * - Performing a soft reset + * - Getting driver version information + * - Reading the sensor ID + * + * It must be called after begin() and before attempting any other operations. + * + * @return true if initialization was successful + * @return false if any initialization step failed + * + * @see begin() + * @see open() + * @see reset() + * @see driverVersion() + * @see ID() + */ + bool init(void); + + /** + * @brief Gets the version information of the BMV080 sensor driver + * + * This method retrieves the vendor-supplied version information for the + * sensor driver software. The version follows semantic versioning format + * with major, minor, and patch numbers. + * + * @param[out] major Major version number indicating incompatible API changes + * @param[out] minor Minor version number indicating backwards-compatible functionality + * @param[out] patch Patch version number indicating backwards-compatible bug fixes + * + * @return true if the version information was successfully retrieved + * @return false if there was an error getting the version information + * + * @see init() + */ + bool driverVersion(uint16_t &major, uint16_t &minor, uint16_t &patch); + + /** + * @brief Opens and initializes communication with the BMV080 sensor + * + * This method initializes a new handle for communicating with the sensor. + * It must be called before attempting to configure or read from the sensor. + * + * @return true if the sensor was successfully opened and handle created + * @return false if the sensor could not be opened or handle creation failed + * + * @note This method is automatically called by init() + * + * @see init() + * @see begin() + */ + bool open(void); + + /** + * @brief Resets the BMV080 sensor to its default state + * + * This method performs a soft reset of the sensor, returning all settings + * to their default values. The sensor will need to be reconfigured after + * a reset. + * + * @return true if the reset was successful + * @return false if the reset failed + * + * @note After reset, you may need to reinitialize settings like operational mode + * and duty cycling period + * + * @see init() + * @see setMode() + */ + bool reset(void); + + /** + * @brief Length of the BMV080 sensor ID string buffer + * + * This constant defines the required buffer size for storing the sensor's + * unique identification string. The buffer must be at least this size + * when calling the ID() method. + * + * @see ID() + */ + static const size_t kBMV800IDLength = 13; + /** + * @brief Gets the unique identifier of the BMV080 sensor + * + * This method retrieves the sensor's unique identification string. + * The ID can be used to distinguish between different BMV080 sensors + * or verify the sensor's authenticity. + * + * @param[out] idOut Buffer to store the sensor's ID string. + * Must be at least kBMV800IDLength (13) bytes long. + * + * @return true if the ID was successfully retrieved + * @return false if there was an error reading the ID + * + * @note The buffer must be pre-allocated with at least kBMV800IDLength bytes + * + * @see kBMV800IDLength + */ + bool ID(char idOut[kBMV800IDLength]); + + /** + * @brief Sets the operational mode of the BMV080 sensor + * + * This method configures how the sensor takes measurements. It supports two modes: + * continuous measurement or duty-cycled measurement. + * + * @param mode The desired operational mode: + * - SFE_BMV080_MODE_CONTINUOUS: Sensor takes measurements continuously + * - SFE_BMV080_MODE_DUTY_CYCLE: Sensor takes measurements at specified intervals + * + * @return true if the mode was set successfully + * @return false if setting the mode failed + * + * @note When using duty cycle mode, the measurement interval can be configured + * using setDutyCyclingPeriod() + * + * @see setDutyCyclingPeriod() + * @see readSensor() + * @see bmv080_output_t + */ + bool setMode(uint8_t mode); + + /** + * @brief Gets the PM2.5 (particulate matter ≤2.5 µm) concentration + * + * This method returns the latest PM2.5 reading from the sensor's internal cache. + * The value represents the mass concentration of particles with a diameter + * of 2.5 micrometers or less. + * + * @return The PM2.5 concentration in micrograms per cubic meter (µg/m³) + * + * @note The PM2.5 value is updated when readSensor() is called + * + * @see readSensor() + * @see PM1() + * @see bmv080_output_t + */ + float PM25(void); + + /** + * @brief Gets the PM1 (particulate matter ≤1.0 µm) concentration + * + * This method returns the latest PM1 reading from the sensor's internal cache. + * The value represents the mass concentration of particles with a diameter + * of 1.0 micrometers or less. + * + * @return The PM1 concentration in micrograms per cubic meter (µg/m³) + * + * @note The PM1 value is updated when readSensor() is called + * + * @see readSensor() + * @see PM25() + * @see bmv080_output_t + */ + float PM1(void); + + /** + * @brief Checks if the BMV080 sensor is obstructed + * + * This method returns the obstruction status from the latest sensor reading. + * An obstruction could be caused by dust, debris, or other particles + * blocking the sensor's optical path. + * + * @return true if the sensor is obstructed + * @return false if the sensor is not obstructed + * + * @note The obstruction status is updated when readSensor() is called + * + * @see readSensor() + * @see bmv080_output_t + */ + bool isObstructed(); + + /** + * @brief Internal method to set sensor values from callback + * + * This method is called by the BMV080 driver callback to update internal sensor data. + * It stores the latest sensor readings and sets the data available flag. + * + * @param bmv080_output The sensor output structure containing the latest readings + * including PM2.5, PM1, and obstruction status + * + * @note This is primarily an internal method used as part of the callback mechanism + * from the Bosch BMV080 driver. It should not typically be called directly + * by library users. + * + * @see set_sensor_value() + * @see bmv080_output_t + */ + void setSensorValue(bmv080_output_t bmv080_output); + + /** + * @brief Reads the latest sensor values from the BMV080 + * + * This method triggers a sensor reading and updates the internal data cache. + * If a pointer to a bmv080_output_t struct is provided, it will be populated + * with the latest sensor values. + * + * @param[out] bmv080_output Optional pointer to a bmv080_output_t struct to store the sensor readings. + * If nullptr (default), the values are only stored internally. + * + * @return true if new data was successfully read from the sensor + * @return false if the sensor read failed or no new data is available + * + * @note This method clears the _dataAvailable flag before attempting to read new data + * + * @see sensorServiceRoutine() + * @see bmv080_output_t + */ + bool readSensor(bmv080_output_t *bmv080_output = nullptr); + + /** + * @brief Gets the current duty cycling period setting + * + * Returns the time interval between measurements when the sensor is in + * duty cycle mode. This setting has no effect when the sensor is in + * continuous measurement mode. + * + * @return The duty cycling period in seconds + * + * @note This setting only affects the sensor when in SFE_BMV080_MODE_DUTY_CYCLE mode + * + * @see setDutyCyclingPeriod() + * @see setMode() + */ + uint16_t dutyCyclingPeriod(); + + /** + * @brief Sets the time interval between measurements in duty cycle mode + * + * This method configures how frequently the sensor takes measurements when + * operating in duty cycle mode. A longer period reduces power consumption + * but provides less frequent updates. + * + * @param duty_cycling_period The time between measurements in seconds + * + * @return true if the period was successfully set + * @return false if setting the period failed + * + * @note This setting only takes effect when the sensor is in SFE_BMV080_MODE_DUTY_CYCLE mode + * + * @see dutyCyclingPeriod() + * @see setMode() + */ + bool setDutyCyclingPeriod(uint16_t duty_cycling_period); + + /** + * @brief Gets the volumetric mass density setting + * + * This method returns the current volumetric mass density setting used for + * particle concentration calculations. This value affects how the sensor + * converts between particle counts and mass concentrations. + * + * @return The volumetric mass density in grams per cubic centimeter (g/cm³) + * + * @see setVolumetricMassDensity() + * @see PM25() + * @see PM1() + */ + float volumetricMassDensity(); + + /** + * @brief Sets the volumetric mass density for particle concentration calculations + * + * This method configures the density value used to convert between particle + * counts and mass concentrations. This setting affects the accuracy of + * PM2.5 and PM1 measurements based on the expected particle density. + * + * @param volumetric_mass_density The particle density in grams per cubic centimeter (g/cm³) + * + * @return true if the density was successfully set + * @return false if setting the density failed + * + * @see volumetricMassDensity() + * @see PM25() + * @see PM1() + */ + bool setVolumetricMassDensity(float volumetric_mass_density); + + /** + * @brief Gets the sensor's integration time setting + * + * This method returns the current integration time setting used for + * particle measurements. The integration time affects the sensor's + * measurement accuracy and response time. + * + * @return The integration time in milliseconds (ms) + * + * @see setIntegrationTime() + * @see readSensor() + */ + float integrationTime(); + + /** + * @brief Sets the sensor's integration time for measurements + * + * This method configures the integration time used for particle measurements. + * Longer integration times can improve measurement accuracy but increase + * response time and power consumption. + * + * @param integration_time The measurement integration time in milliseconds (ms) + * + * @return true if the integration time was successfully set + * @return false if setting the integration time failed + * + * @see integrationTime() + * @see readSensor() + */ + bool setIntegrationTime(float integration_time); + + /** + * @brief Gets the current distribution ID setting + * + * This method returns the distribution ID used by the sensor for particle + * size classification. The distribution ID affects how particles are + * categorized into different size bins. + * + * @return The current distribution ID value + * + * @see setDistributionId() + * @see PM25() + * @see PM1() + */ + uint32_t distributionId(); + + /** + * @brief Sets the distribution ID for particle size classification + * + * This method configures which particle size distribution model the sensor + * uses for classifying particles. Different distribution IDs are optimized + * for different types of particles and environments. + * + * @param distribution_id The distribution ID to use for particle classification + * + * @return true if the distribution ID was successfully set + * @return false if setting the distribution ID failed + * + * @see distributionId() + * @see PM25() + * @see PM1() + */ + bool setDistributionId(uint32_t distribution_id); + + /** + * @brief Gets the obstruction detection setting + * + * This method returns whether the sensor's obstruction detection feature + * is enabled. When enabled, the sensor will monitor for any blockages + * in its optical path. + * + * @return true if obstruction detection is enabled + * @return false if obstruction detection is disabled + * + * @see setDoObstructionDetection() + * @see isObstructed() + */ + bool doObstructionDetection(); + + /** + * @brief Enables or disables the sensor's obstruction detection feature + * + * This method controls whether the sensor actively monitors for obstructions + * in its optical path. When enabled, the sensor will report blockages + * through the isObstructed() method. + * + * @param do_obstruction_detection true to enable obstruction detection, + * false to disable it + * + * @return true if the setting was successfully changed + * @return false if changing the setting failed + * + * @see doObstructionDetection() + * @see isObstructed() + */ + bool setDoObstructionDetection(bool do_obstruction_detection); + + /** + * @brief Gets the vibration filtering setting + * + * This method returns whether the sensor's vibration filtering feature + * is enabled. When enabled, the sensor applies algorithms to reduce + * measurement noise caused by mechanical vibrations. + * + * @return true if vibration filtering is enabled + * @return false if vibration filtering is disabled + * + * @see setDoVibrationFiltering() + * @see readSensor() + */ + bool doVibrationFiltering(); + + /** + * @brief Enables or disables vibration filtering + * + * This method controls whether the sensor applies vibration filtering + * algorithms to reduce measurement noise caused by mechanical vibrations. + * Enabling this feature can improve measurement accuracy in environments + * with significant vibration. + * + * @param do_vibration_filtering true to enable vibration filtering, + * false to disable it + * + * @return true if the setting was successfully changed + * @return false if changing the setting failed + * + * @see doVibrationFiltering() + * @see readSensor() + */ + bool setDoVibrationFiltering(bool do_vibration_filtering); + + /** + * @brief Gets the current measurement algorithm setting + * + * This method returns the measurement algorithm used by the sensor for + * particle analysis. Different algorithms can be optimized for specific + * measurement conditions or particle types. + * + * @return The current measurement algorithm identifier + * + * @see setMeasurementAlgorithm() + * @see readSensor() + */ + uint8_t measurementAlgorithm(); + + /** + * @brief Sets the measurement algorithm for particle analysis + * + * This method configures which algorithm the sensor uses for analyzing + * particle measurements. Different algorithms can be optimized for + * specific types of particles or measurement environments. + * + * @param measurement_algorithm The algorithm identifier to use for measurements + * + * @return true if the algorithm was successfully set + * @return false if setting the algorithm failed + * + * @see measurementAlgorithm() + * @see readSensor() + */ + bool setMeasurementAlgorithm(uint8_t measurement_algorithm); + + private: + // bosch bmv080 library callback functions (static methods to be used as callbacks) + /** + * @brief Callback function for reading 16-bit values from the BMV080 sensor + * + * This static method serves as a callback for the Bosch BMV080 driver to read + * 16-bit registers from the sensor over the communication bus. + * + * @param handle The SERCOM handle for communication with the sensor + * @param reg_addr The register address to read from + * @param[out] reg_data Pointer to store the read data + * @param len Number of 16-bit words to read + * + * @return int8_t Status code: + * - 0: Success + * - Negative: Error occurred + * + * @note This is an internal callback used by the Bosch BMV080 driver + * + * @see device_write_16bit_CB() + */ + static int8_t device_read_16bit_CB(bmv080_sercom_handle_t, uint16_t, uint16_t *, uint16_t); + + /** + * @brief Callback function for writing 16-bit values to the BMV080 sensor + * + * This static method serves as a callback for the Bosch BMV080 driver to write + * 16-bit registers to the sensor over the communication bus. + * + * @param handle The SERCOM handle for communication with the sensor + * @param reg_addr The register address to write to + * @param[in] reg_data Pointer to the data to write + * @param len Number of 16-bit words to write + * + * @return int8_t Status code: + * - 0: Success + * - Negative: Error occurred + * + * @note This is an internal callback used by the Bosch BMV080 driver + * + * @see device_read_16bit_CB() + */ + static int8_t device_write_16bit_CB(bmv080_sercom_handle_t, uint16_t, const uint16_t *, uint16_t); + + /** + * @brief Callback function for implementing delays in the BMV080 sensor driver + * + * This static method serves as a callback for the Bosch BMV080 driver to + * implement timing delays required by the sensor operations. + * + * @param delay_ms The delay duration in milliseconds + * + * @return int8_t Status code: + * - 0: Success + * - Negative: Error occurred + * + * @note This is an internal callback used by the Bosch BMV080 driver + * + * @see device_read_16bit_CB() + * @see device_write_16bit_CB() + */ + static int8_t device_delay_CB(uint32_t); + + /** + * @brief Static callback for updating sensor values from the BMV080 driver + * + * This static method serves as a callback function for the Bosch BMV080 driver + * to update sensor readings. It receives new measurement data and passes it + * to the appropriate instance through the void pointer to user data. + * + * @param bmv080_output The sensor output structure containing the latest readings + * @param[in] user_data Pointer to the sfeDevBMV080 instance (cast from void*) + * + * @note This is an internal callback used by the Bosch BMV080 driver + * + * @see setSensorValue() + * @see bmv080_output_t + */ + static void set_sensor_value(bmv080_output_t, void *); + + /** + * @brief Services the BMV080 sensor driver + * + * This method pumps the service routine of the BMV080 sensor driver, + * allowing it to process measurements and update sensor values. + * It is called internally by readSensor() to maintain proper + * sensor operation. + * + * @return true if the service routine completed successfully + * @return false if an error occurred during servicing + * + * @note This is an internal method used by the driver + * + * @see readSensor() + * @see set_sensor_value() + */ + bool sensorServiceRoutine(void); + + bmv080_handle_t _bmv080_handle_class = NULL; + bool _dataAvailable = false; + bmv080_output_t _sensorValue; + + protected: + sfeTkIBus *_theBus; +}; \ No newline at end of file