Skip to main content

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:

ControllerSoldered 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.

Select Board
InterruptTrigger.ino
#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:

Serial MonitorAutoscrollShow timestampNewline115200 baud
Message (Enter to send message to the board)
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 None

Creates 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 bool

Sets 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

NameTypeDescription
addressuint8_tThe sensor's I2C address. Defaults to BMV080_DEFAULT_ADDRESS, which is 0x57, the board's default.
wirePortTwoWire &The I2C port to use. Defaults to Wire.
init()returns bool

Prepares 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 bool

Starts measuring in the mode you choose. Use continuous mode with the interrupt.

Returnstrue when the Bosch driver started the measurement, false otherwise.

Parameters

NameTypeDescription
modeuint8_tBMV080_MODE_CONTINUOUS for continuous measurement, as here, or BMV080_MODE_DUTY_CYCLE for duty cycling.
readSensor(bmv080_output_t *bmv080_output = nullptr)returns bool

Lets 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

NameTypeDescription
bmv080_outputbmv080_output_t *Optional. A Bosch output structure to copy the new reading into. The example leaves it out.
PM25()returns float

Returns the PM2.5 value of the latest reading: particles 2.5 µm across or smaller.

ReturnsPM2.5 mass concentration in µg/m³.

isObstructed()returns bool

Returns 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.