Introduction
This PM2.5 air quality sensor is built around the Bosch BMV080, the world's smallest particulate matter sensor. It utilizes a laser-based measurement principle and a fanless design, featuring core characteristics such as ±10μg/m³ high accuracy, a broad measurement range of 0-1000μg/m³, an ultra-compact size of 24×20mm, low power consumption of 6μA, and a long lifespan of 10 years with low failure rates.
Traditional PM2.5 sensors typically rely on fans to draw in air, leading to issues like large size, high noise, susceptibility to dust accumulation, and a short lifespan (approximately 1-2 years), which severely impact device integration and user experience. This product employs an innovative optical counting principle to directly measure freely diffusing particles, eliminating the need for fans or air ducts. This fundamentally avoids the aforementioned problems and significantly enhances reliability.
Thanks to the BMV080's minuscule size—over 450 times smaller than comparable devices on the market—this product is built upon it with a compact Breakout board design. All pins are accessible on the board, supporting I2C and SPI communication while fully retaining the sensor's native functionality. Furthermore, we provide an Arduino sample library to facilitate direct performance validation and rapid integration for customers.
Integrating high accuracy, miniaturization, ultra-low power consumption, and complete silent operation, this product is the ideal choice for building next-generation compact and efficient air quality monitoring solutions. It is perfectly suited for integration into portable monitors, elegantly designed desktop air companions, silent air purifiers, modern fresh air systems, and other space-constrained environmental monitoring applications.
Features
- Multi-Parameter Measurement: Simultaneously measures PM1, PM2.5, and PM10 mass concentrations.
- Multi-Protocol Communication: Supports I2C and SPI communication for flexible integration.
- Professional-Grade Accuracy: ±10μg/m³ high precision ensures accurate and reliable data.
- Wide Measurement Range: 0-1000μg/m³, covering conditions from daily levels to severe pollution.
- Laser-Based Principle: Utilizes laser scattering technology for stable and dependable measurements.
- Ultra-Low Power Consumption: Operating current as low as 6μA, extremely power-efficient.
- Silent & Maintenance-Free: Fanless design ensures lifelong silent operation and low failure rates.
- Compact Size: 24×20×4.24mm, easy to integrate into various devices.
Application
- Desktop Air Companion
- Air Quality Monitor
- Air Ventilation System
- Smart Air Purifier
- PM2.5 Detector
Dimension Diagram
Unit: mm
Specifications
Basic Parameters
- Operating Voltage: DC 3.3V
- Operating Current: 70mA @3.3V (Continuous Measurement Mode)
- Sleep Current: 6μA @3.3V
- Communication Method: I2C / SPI
- Communication Interface: 2.54-7P Pin Header Socket
- I2C Address: 0x57 (Default) / 0x54 / 0x55 / 0x56
BMV080 Parameters
- PM2.5 Measurement Range: 0 ~ 1000 μg/m³
- PM2.5 Output Resolution: 1 μg/m³
- Measurement Accuracy: ±10 μg/m³ (0 ~ 100 μg/m³), ±10% (101 ~ 1000 μg/m³)
- Minimum Detectable Particle Size: 0.5 μm
- Measurement Mode: Continuous Measurement and Periodic Measurement
- Start-up Time: 1.2 s
- Stabilization Time: 10 s
- Condensation: Not allowed on the laser diode
- Laser Class: Class 1, complies with IEC 60825-1 standard
Physical Dimensions
- Dimensions: 24×20×4.24 mm
- Mounting Hole Inner Diameter: 2.0 mm
- Mounting Hole Spacing: 19 mm
Function Diagram

Interface Pin Description
| Pin Silkscreen | Function Description |
|---|---|
| 3V3 | Power Positive (Supply Input: 3.3V) |
| GND | Power Negative (Ground) |
| SCL/SCK | Multiplexed Pin: Clock Line (SCL) in I2C mode, Clock Line (SCK) in SPI mode |
| SDA/MOSI | Multiplexed Pin: Data Line (SDA) in I2C mode, Data Input Line (MOSI) in SPI mode |
| MISO | Active exclusively in SPI mode, serving as Data Output Line |
| CSB | Active exclusively in SPI mode, functioning as Chip Select Pin |
| INT | Interrupt Output Pin (General-Purpose) |
Operating Mode Switching (I2C/SPI)
- Default State: Factory-configured for I2C mode, eliminating the need for additional operations.
- SPI Mode Switching Steps:
- Remove the 0Ω resistor at the "I2C" position on the board (as indicated in the figure below) and re-solder it to the "SPI" side pad;
- Solder one 0Ω resistor each to the pads beneath the "H" silkscreen corresponding to "CSB" and "MISO" on the right side (to ensure stable SPI signal transmission).

I2C Address Configuration (Effective Only in I2C Mode)
The I2C address of the BMV080 sensor is determined by the voltage levels of the CSB and MISO pins (pad configuration). The correspondence is as follows:
| I2C Address | CSB Level | MISO Level |
|---|---|---|
| 0x57 | High (H) | High (H) |
| 0x56 | High (H) | Low (L) |
| 0x55 | Low (L) | High (H) |
| 0x54 | Low (L) | Low (L) |
Usage Notes
Startup Timing Requirements
After powering up, the BMV080 must complete several initialization processes: hardware initialization, laser preheating, optical system self-test, and fan stabilization (typically taking several seconds). To prevent data reading anomalies, the host controller program must include a "initialization delay + ready state verification" logic to ensure the sensor is fully operational before data acquisition.
Installation and Measurement Requirements
- Measurement Distance: For measurements on white reflective surfaces perpendicular to the laser emission direction, the distance between the sensor and the measured surface must be ≥ 350mm.
- Occlusion Handling: Temporary occlusion will cause data interruption, but normal output will resume after the obstruction is removed.
- Pin Header Soldering: If using pin headers, they must be soldered on the sensor's backside (with pins facing downward) to avoid blocking the laser lens.
Maintenance Points
The protective transparent cover of the BMV080, if contaminated with dust, will obstruct light transmission, degrade measurement accuracy, and potentially trigger the "occlusion alarm." Regular inspection and cleaning of the lens (using a lint-free cloth) is recommended.
Tutorial for Using Arduino IDE
- Hardware Preparation
- SEN0663 Fermion: BMV080 PM2.5 Sensor × 1
- DFR0654 FireBeetle 2 ESP32-E Development Board × 1
- Several Dupont Wires
- Software Preparation
- Download and install Arduino IDE: [Download Link]
- Download dependent libraries:
- DFRobot_BMV080 Library: [Download Link]
- Library Installation Guide: [[View Installation Tutorial]](https://wiki.dfrobot.com/Arduino libraries installed)
Example 1: Continuous Data Reading in I2C Mode
Objective
Establish communication between the ESP32-E and BMV080 via the I2C interface to continuously read PM2.5 measurement values.
Step 1: Wiring Configuration

Connect the BMV080 sensor to the ESP32-E as illustrated, with the key wiring correspondences outlined below:
- Sensor Pin "3V3" → ESP32-E 3.3V
- Sensor Pin "GND" → ESP32-E GND
- Sensor I2C Pin "SCL" → ESP32-E SCL (default GPIO22)
- Sensor I2C Pin "SDA" → ESP32-E SDA (default GPIO21)
- Sensor Pad Configuration:
- On the sensor’s "I2C/SPI Switching Pads", solder one 0Ω resistor to the I2C side (default mode; no modification to the factory configuration is required).
- Solder one 0Ω resistor each to the pads beneath the "H" silkscreen corresponding to the CSB and MISO pins (the I2C address will be 0x57 in this configuration).
Step 2: Code Upload
Launch the Arduino IDE, copy the following code, and upload it to the ESP32-E:
#include "DFRobot_BMV080.h"
// Set the stack size of the loop task to 60KB
SET_LOOP_TASK_STACK_SIZE(60 * 1024);
// I2C address selection table:
/*
* --------------------------------------
* | CSB Pin | MISO Pin | Address |
* --------------------------------------
* | 0 | 0 | 0x54 |
* --------------------------------------
* | 0 | 1 | 0x55 |
* --------------------------------------
* | 1 | 0 | 0x56 |
* --------------------------------------
* | 1 | 1 | 0x57 |
* --------------------------------------
*/
// Create an I2C sensor instance with address 0x57 (can be modified according to actual wiring)
DFRobot_BMV080_I2C sensor(&Wire, 0x57);
float pm1, pm2_5, pm10;
void setup() {
char id[13]; // Used to store the chip ID of the BMV080 sensor
Serial.begin(115200);
while (!Serial)
delay(100); // Wait for the serial port to be ready
Serial.println("Continuous reading mode started");
while (sensor.begin() != 0) {
Serial.println("Sensor initialization failed! Please check if the sensor connection is correct");
delay(1000);
}
Serial.println("Sensor initialization successful");
while (sensor.openBmv080()) {
Serial.println("Sensor opening failed");
delay(1000);
}
Serial.println("Sensor opened successfully");
// Get and print the chip ID
sensor.getBmv080ID(id);
Serial.println("Chip ID: " + String(id));
// Enable obstruction detection (a prompt will be shown if obstructed, indicating data may be invalid)
sensor.setObstructionDetection(true);
// Set measurement mode to continuous mode
if (0 == sensor.setBmv080Mode(CONTINUOUS_MODE)) {
Serial.println("Measurement mode set successfully (continuous mode)");
} else {
Serial.println("Failed to set measurement mode");
}
}
void loop() {
if (sensor.getBmv080Data(&pm1, &pm2_5, &pm10)) {
// Print PM1, PM2.5, PM10 values
Serial.print("PM1: " + String(pm1) + " ug/m³ ");
Serial.print("PM2.5: " + String(pm2_5) + " ug/m³ ");
Serial.print("PM10: " + String(pm10) + " ug/m³");
// Check if obstructed
if (sensor.ifObstructed()) {
Serial.print(" Note: Sensor is obstructed, data may be invalid");
}
Serial.println();
}
delay(100);
}
Result

Example 2: Continuous Data Reading in SPI Mode
Objective
Establish communication between the ESP32-E and BMV080 via the SPI interface to continuously read PM2.5 measurement values.
Step 1: Wiring Configuration

Connect the BMV080 sensor to the ESP32-E as illustrated, with the key wiring correspondences detailed below:
- Sensor Pin "3V3" → ESP32-E 3.3V
- Sensor Pin "GND" → ESP32-E GND
- Sensor SPI Pin "SCK" → ESP32-E SCK (default GPIO18)
- Sensor SPI Pin "MOSI" → ESP32-E MOSI (default GPIO23)
- Sensor SPI Pin "MISO" → ESP32-E MISO (default GPIO19)
- Sensor SPI Pin "CSB" → ESP32-E GPIO13
- Pad Configuration:
- On the sensor’s "I2C/SPI Switching Pads", solder one 0Ω resistor to the SPI side.
- Solder one 0Ω resistor each to the pads beneath the "H" silkscreen corresponding to the CSB and MISO pins.
Step 2: Code Upload
Launch the Arduino IDE, copy the following code, and upload it to the ESP32-E:
#include "DFRobot_BMV080.h"
// Set loop task stack size (ESP32 requires sufficient memory to run the sensor, preventing crashes)
SET_LOOP_TASK_STACK_SIZE(60 * 1024);
// Key SPI configuration: Define chip select (CS) pin, modify according to actual ESP32-E wiring (example uses pin 13)
#define SPI_CS_PIN 13
DFRobot_BMV080_SPI sensor(&SPI, SPI_CS_PIN);
float pm1, pm2_5, pm10;
void setup() {
char id[13];
Serial.begin(115200);
while (!Serial) delay(100);
Serial.println("Starting SPI mode to read PM values");
while (sensor.begin() != 0) {
Serial.println("Sensor initialization failed! Please check wiring!");
delay(1000);
}
Serial.println("Sensor initialization successful");
while (sensor.openBmv080()) {
Serial.println("Sensor opening failed");
delay(1000);
}
Serial.println("Sensor opened successfully");
// Read and print chip ID (used to confirm normal hardware recognition)
sensor.getBmv080ID(id);
Serial.print("Chip ID: ");
Serial.println(id);
// Set to continuous measurement mode (basic mode for reading PM values)
if (0 == sensor.setBmv080Mode(CONTINUOUS_MODE)) {
Serial.println("Continuous measurement mode enabled");
} else {
Serial.println("Mode setting failed");
}
}
void loop() {
if (sensor.getBmv080Data(&pm1, &pm2_5, &pm10)) {
Serial.print("PM1: " + String(pm1) + " ug/m³ ");
Serial.print("PM2.5: " + String(pm2_5) + " ug/m³ ");
Serial.print("PM10: " + String(pm10) + " ug/m³");
// Check for obstruction
if (sensor.ifObstructed()) {
Serial.print(" Note: Sensor is obstructed, data may be invalid");
}
Serial.println();
}
delay(100);
}
Result

API Functions
/**
* @fn begin
* @brief Check if the sensor is connected.
* @return 0 if the sensor is connected.
* @return 1 if the sensor is not connected.
*/
int begin(void);
/**
* @fn openBmv080
* @brief Initialize the BMV080 sensor
* @pre Must be called first in order to create the _handle_ required by other functions.
* @post The _handle_ must be destroyed via _bmv080_close_.
* @note This function usually only needs to be called once.
* @note It must be called before any other functions that interact with the sensor.
* @return 0 successful.
* @return other values. See the bmv080_status_code_t enumeration in bmv080_defs.h for details.
*/
uint16_t openBmv080(void);
/**
* @fn closeBmv080
* @brief Turn off the sensor. The sensor will stop functioning. If you need to use it again, you need to call the openBmv080 function.
* @pre Must be called last in order to destroy the _handle_ created by _bmv080_open_.
* @return 1 successful.
* @return 0 error, when the _handle_ is NULL or not called stopBmv080 function before.
*/
bool closeBmv080(void);
/**
* @fn resetBmv080
* @brief Reset a sensor unit including both hardware and software.
* @pre A valid _handle_ generated by _bmv080_open_ is required.
* @post Any parameter changed through _bmv080_set_parameter_ is reverted back to its default.
* @return 1 successful.
* @return 0 error, when the _handle_ is NULL(may be not call openBmv080 or stopBmv080 function before).
*/
bool resetBmv080(void);
/**
* @fn getBmv080DV
* @brief Get the BMV080 sensor's driver version
* @param major: Major version number
* @param minor: Minor version number
* @param patch: Patch version number
* @return 1 successful.
* @return 0 error.
*/
bool getBmv080DV(uint16_t &major, uint16_t &minor, uint16_t &patch);
/**
* @fn getBmv080ID
* @brief Get the BMV080 sensor's ID
* @param id: Pointer to a char array to store the ID (must be at least 13 bytes long to accommodate the null terminator)
* @return 1 successful.
* @return 0 error.
*/
bool getBmv080ID(char *id);
/**
* @fn getBmv080Data
* @brief Get the BMV080 sensor's data
* @param PM1: PM1.0 concentration (ug/m3)
* @param PM2_5: PM2.5 concentration (ug/m3)
* @param PM10: PM10 concentration (ug/m3)
* @param allData: All data from the BMV080 sensor (optional),This is a structure. It has the following members.
* runtime_in_sec: estimate of the time passed since the start of the measurement, in seconds
* pm2_5_mass_concentration: PM2.5 value in ug/m3
* pm1_mass_concentration: PM1 value in ug/m3
* pm10_mass_concentration: PM10 value in ug/m3
* pm2_5_number_concentration: PM2.5 value in particles/cm3
* pm1_number_concentration: PM1 value in particles/cm3
* pm10_number_concentration: PM10 value in particles/cm3
* is_obstructed: flag to indicate whether the sensor is obstructed and cannot perform a valid measurement
* is_outside_measurement_range: flag to indicate whether the PM2.5 concentration is outside the specified measurement range (0..1000 ug/m3)
* @note This function should be called at least once every 1 second.When the BMV080 sensor data is ready, the function will return 1.
* @return 1 successful, when the BMV080 sensor data is ready.
* @return 0 unsuccessful, when the BMV080 sensor data is not ready.
*/
bool getBmv080Data(float *PM1, float *PM2_5, float *PM10, bmv080_output_t *allData=NULL);
/**
* @fn setBmv080Mode
* @brief Set the BMV080 sensor's mode.After calling this function, the sensor will start to collect data.
* @param mode: The mode to set, either CONTINUOUS_MODE or DUTY_CYCLE_MODE
* CONTINUOUS_MODE: Sensor takes measurements continuously
* DUTY_CYCLE_MODE: Sensor takes measurements at specified intervals
* @return 0 successful
* @return -1 mode is invalid
* @return -2 precondition is unsatisfied (for example, if the sensor is currently running in continuous mode, you should stop the measurement first).
* @return other error, see the bmv080_status_code_t enumeration in bmv080_defs.h for details.
*/
int setBmv080Mode(uint8_t mode);
/**
* @fn stopBmv080
* @brief Stop the measurement. If you need to continue the measurement, you need to call the setBmv080Mode function.
* @pre Must be called at the end of a data acquisition cycle to ensure that the sensor unit is ready for the next measurement cycle.
* @return 1 successful
* @return 0 error
*/
bool stopBmv080(void);
/**
* @fn setIntegrationTime
* @brief Set the measurement window.
* @note In duty cycling mode, this measurement window is also the sensor ON time.
* @param integration_time The measurement integration time in seconds (s).
* @return 0 successful
* @return -1 integration_time is invalid, must be greater than or equal to 1.0s
* @return -2 duty_cycling_period must larger than integration_time by at least 2 seconds.
* @return -3 precondition is unsatisfied (for example, if the sensor is currently running in continuous mode, you should stop the measurement first).
* @return other error, see the bmv080_status_code_t enumeration in bmv080_defs.h for details.
*/
int setIntegrationTime(float integration_time);
/**
* @fn getIntegrationTime
* @brief Get the current integration time.
* @return The current integration time in seconds (s).
* @return NAN error, or not call openBmv080 or stopBmv080 function before.
*/
float getIntegrationTime(void);
/**
* @fn setDutyCyclingPeriod
* @brief Set the duty cycling period.
* @n Duty cycling period (sum of integration time and sensor OFF / sleep time).
* @note This must be greater than integration time by at least 2 seconds.
* @param duty_cycling_period The duty cycling period in seconds (s).
* @return 0 successful
* @return -1 duty_cycling_period is invalid, must be greater than or equal to 12s
* @return -2 integration_time must less than duty_cycling_period by at least 2 seconds.
* @return -3 precondition is unsatisfied (for example, if the sensor is currently running in continuous mode, you should stop the measurement first).
* @return other error, see the bmv080_status_code_t enumeration in bmv080_defs.h for details.
*/
int setDutyCyclingPeriod(uint16_t duty_cycling_period);
/**
* @fn getDutyCyclingPeriod
* @brief Get the current duty cycling period.
* @return The current duty cycling period in seconds (s).
* @return 0 error
*/
uint16_t getDutyCyclingPeriod(void);
/**
* @fn setObstructionDetection
* @brief Set if obstruction detection feature is enabled.
* @param obstructed true to enable obstruction detection, false to disable.
* @return 1 successful
* @return 0 error, or not call openBmv080 or stopBmv080 function before.
*/
bool setObstructionDetection(bool obstructed);
/**
* @fn getObstructionDetection
* @brief Get if obstruction detection feature is enabled.
* @return 1 if obstruction detection is enabled.
* @return 0 if obstruction detection is disabled.
* @return -1 error, or not call openBmv080 or stopBmv080 function before.
*/
int getObstructionDetection(void);
/**
* @fn ifObstructed
* @brief Check whether the sensor receiver is blocked.
* @return 1 Obstructed
* @return 0 not obstructed
*/
bool ifObstructed(void);
/**
* @fn setDoVibrationFiltering
* @brief Enable or disable the Do Vibration Filtering feature.
* @param do_vibration_filtering 1 to enable, 0 to disable.
* @return 1 successful
* @return 0 error, or not call openBmv080 or stopBmv080 function before.
*/
bool setDoVibrationFiltering(bool do_vibration_filtering);
/**
* @fn getDoVibrationFiltering
* @brief Get the status of the Do Vibration Filtering feature.
* @return 1 if vibration filtering is enabled.
* @return 0 if vibration filtering is disabled.
* @return -1 error, or not call openBmv080 or stopBmv080 function before.
*/
int getDoVibrationFiltering(void);
/**
* @fn setMeasurementAlgorithm
* @brief Set the measurement algorithm.
* @param measurement_algorithm The measurement algorithm to use.
* FAST_RESPONSE //Fast response,suitable for scenarios requiring quick response
* BALANCED //Balanced, suitable for scenarios where a balance needs to be struck between precision and rapid response
* HIGH_PRECISION //High precision, suitable for scenarios requiring high accuracy
* @return 0 successful
* @return -1 measurement_algorithm is invalid
* @return -3 precondition is unsatisfied (for example, if the sensor is currently running in continuous mode, you should stop the measurement first).
* @return other error, see the bmv080_status_code_t enumeration in bmv080_defs.h for details.
*/
int setMeasurementAlgorithm(uint8_t measurement_algorithm);
/**
* @fn getMeasurementAlgorithm
* @brief Get the current measurement algorithm.
* @return The current measurement algorithm.
* FAST_RESPONSE //Fast response,suitable for scenarios requiring quick response
* BALANCED //Balanced, suitable for scenarios where a balance needs to be struck between precision and rapid response
* HIGH_PRECISION //High precision, suitable for scenarios requiring high accuracy
* 0 error, or not call openBmv080 or stopBmv080 function before.
*/
uint8_t getMeasurementAlgorithm(void);
