Read on interrupt
What this example shows
Letting the sensor tell your controller when to read. The BMV080 signals on its
active-low IRQ output, and this example catches the falling edge with an
interrupt, then reads PM2.5 while IRQ stays low. The sensor runs
in continuous mode: its interrupt cannot be used as a trigger in duty cycling
mode.
Besides the Qwiic cable or the I2C wiring from the Quick Start, connect one more wire:
| Controller | Soldered BMV080 Particulate Matter Sensor |
|---|---|
GPIO 13 (IRQ_PIN in the sketch) | IRQ |
The example uses pin 13. If your board has no interrupt on that pin, the sketch
prints Not a valid interrupt pin!: wire IRQ to a pin that has one and change
IRQ_PIN to match.
The example code
This is InterruptTrigger.ino from the pinned library, without its header
comment. The sketch itself is exactly as shipped. It uses the board's default
address, 0x57.
This example also uses attachInterrupt(), detachInterrupt(),
digitalPinToInterrupt(), pinMode() and digitalRead()
(Arduino reference),
which come with the Arduino core and are not part of the Soldered BMV080
library.
#include "BMV080-SOLDERED.h"
BMV080 bmv080; // Create an instance of the BMV080 class
#define BMV080_ADDR 0x57 // BMV080 default I2C address
#define IRQ_PIN 13 // Declare interrupt pin
bool triggered = false; // Interrupt flag
void setup()
{
Serial.begin(115200);
while (!Serial) delay(10); // Wait for Serial to become available
Wire.begin();
// Attempt to initialize the sensor via I2C
if (bmv080.begin(BMV080_ADDR, Wire) == false)
{
Serial.println("BMV080 not detected");
while (1)
;
}
Serial.println("BMV080 found!");
// Initialize the sensor
if (bmv080.init())
{
Serial.println("BMV sensor initialized");
}
else
{
Serial.println("BMV initialization failed");
while (1)
;
}
// Set the sensor mode to continuous mode
// The hardware interrupt of the BMV080 sensor unit cannot be used as trigger
// in duty cycling mode.
if(bmv080.setMode(BMV080_MODE_CONTINUOUS) == true)
{
Serial.println("BMV080 set to continuous mode");
}
else
{
Serial.println("Error setting BMV080 mode");
}
enable_external_interrupt(true); // Enable external interrupt handling for the IRQ pin
}
void loop()
{
// Check if interrupt flag was set
if (triggered == true)
{
Serial.println("Interrupt triggered");
triggered = false; // Reset the interrupt flag
// Keep reading sensor data
do
{
Serial.print("Reading BMV080: ");
if (bmv080.readSensor())
{
float pm25 = bmv080.PM25();
Serial.print(pm25);
if (bmv080.isObstructed() == true)
{
Serial.print("\tObstructed");
}
Serial.println();
}
} while (digitalRead(IRQ_PIN) == LOW);
}
delay(150);
Serial.print(".");
}
void enable_external_interrupt(bool enable)
{
int checkPin = digitalPinToInterrupt(IRQ_PIN);
if (checkPin == -1)
{
Serial.println("Not a valid interrupt pin!");
}
else
{
Serial.println("Valid interrupt pin.");
}
pinMode(IRQ_PIN, INPUT_PULLUP); // Configure IRQ pin as input with internal pull-up resistor
if (enable)
{ // Enabel external interrupt
attachInterrupt(digitalPinToInterrupt(IRQ_PIN), ISR_handler, FALLING );
}
else
{ // Disable external interrupt
detachInterrupt(digitalPinToInterrupt(IRQ_PIN) );
}
}
void ISR_handler(void)
{
// Set interrupt flag
triggered = true;
}
Expected result, here from an ESP32 development board (ESP32-WROOM-32) with
IRQ wired to GPIO 13 and IRQ_PIN left unchanged:
BMV080 found!
BMV sensor initialized
BMV080 set to continuous mode
Valid interrupt pin.
......Interrupt triggered
Reading BMV080: 7.18
......Interrupt triggered
Reading BMV080: 7.26
......Interrupt triggered
Reading BMV080: 7.09
A dot appears every 150 ms while the sketch waits. Each time the sensor
signals, the sketch prints Interrupt triggered, then Reading BMV080: with
the PM2.5 value in µg/m³, followed by Obstructed when the sensor's view is
blocked. Your numbers depend on the air around the sensor.
Functions used
BMV080()returns NoneCreates the sensor object. It takes no arguments: the address and the Wire port are given to begin().
ReturnsConstructor; no return value.
begin(const uint8_t address = BMV080_DEFAULT_ADDRESS, TwoWire &wirePort = Wire)returns boolSets the I2C address and Wire port the library uses and checks that the sensor answers at that address. It does not start the I2C bus, so call Wire.begin() first, as the example does.
Returnstrue when the sensor answered at the address, false otherwise.
Parameters
| Name | Type | Description |
|---|---|---|
address | uint8_t | The sensor's I2C address. Defaults to BMV080_DEFAULT_ADDRESS, which is 0x57, the board's default. |
wirePort | TwoWire & | The I2C port to use. Defaults to Wire. |
init()returns boolPrepares the sensor through the Bosch driver: reads the driver version, opens the sensor, performs a soft reset and reads the sensor's ID. Call it after begin() and before anything else.
Returnstrue when every step succeeded, false when one failed.
setMode(uint8_t mode)returns boolStarts measuring in the mode you choose. Use continuous mode with the interrupt.
Returnstrue when the Bosch driver started the measurement, false otherwise.
Parameters
| Name | Type | Description |
|---|---|---|
mode | uint8_t | BMV080_MODE_CONTINUOUS for continuous measurement, as here, or BMV080_MODE_DUTY_CYCLE for duty cycling. |
readSensor(bmv080_output_t *bmv080_output = nullptr)returns boolLets the Bosch driver fetch and process data from the sensor once. When a new reading arrives, the library keeps it for PM25() and isObstructed().
Returnstrue when a new reading arrived during this call, false otherwise.
Parameters
| Name | Type | Description |
|---|---|---|
bmv080_output | bmv080_output_t * | Optional. A Bosch output structure to copy the new reading into. The example leaves it out. |
PM25()returns floatReturns the PM2.5 value of the latest reading: particles 2.5 µm across or smaller.
ReturnsPM2.5 mass concentration in µg/m³.
isObstructed()returns boolReturns the obstruction flag of the latest reading. The sensor sets it when a fixed object sits in its optical path.
Returnstrue when the latest reading was obstructed, false otherwise.
Putting it together
The interrupt routine only sets a flag, and loop() does the reading. IRQ is
active low, so the example triggers on the falling edge, and it sets the pin as
an input with pull-up.