Key Takeaway: The BME280 sensor combined with an STM32 microcontroller over I2C gives you a production-ready environmental monitoring system measuring temperature (-40 to +85 C), pressure (300-1100 hPa), and humidity (0-100 percent RH). This guide provides complete STM32 HAL code, wiring diagrams, and compensation formulas for industrial applications.
STM32 + BME280 I2C Environmental Monitoring System
Temp Sensor
-40 to +85 C, 20-bit
Pressure
300-1100 hPa, 20-bit
Humidity
0-100% RH, 16-bit
Wiring: BME280 VCC-3.3V GND-GND SCL-PB6 SDA-PB7 SDO-GND (I2C Addr: 0x76)
Step 1: CubeMX
Enable I2C1, 100kHz Standard Mode
Step 2: Config
Read chip ID, set oversampling
Step 3: Read
Burst read 8 bytes from 0xF7
Compensation: Read calibration coeffs from NVM (0x88-0xA1, 0xE1-0xE7), apply Bosch formulas
Oversampling Options
Temp: x1, x2, x4, x8, x16
Pressure: x1, x2, x4, x8, x16
Recommended Config
Temp OSRS x2, Pressure OSRS x16
Humidity OSRS x1, IIR Filter x16
Code: HAL_I2C_Mem_Read – I2C_Master_Transmit/Receive – Compensation – Display on OLED / UART
Sources: Controllerstech, EmbeddedExpertIO, Ampheo, MicrocontrollersLab
Why STM32 + BME280 for Industrial Monitoring
The BME280, manufactured by Bosch Sensortec, is a combined digital humidity, pressure, and temperature sensor in a compact 2.5 mm x 2.5 mm x 0.93 mm LGA package. It communicates via I2C (up to 3.4 MHz) or SPI (up to 10 MHz), making it highly flexible for embedded projects. When paired with an STM32 microcontroller — such as the popular NUCLEO-F401RE with its ARM Cortex-M4 core and extensive peripheral set — you get a robust platform for industrial-grade environmental monitoring.
This combination is used extensively in weather stations, IoT sensor nodes, HVAC monitoring systems, smart agriculture, and factory floor environmental logging. The STM32’s HAL (Hardware Abstraction Layer) library makes the I2C communication clean and portable across the entire STM32 family.
Hardware Setup and Wiring
Connecting the BME280 to an STM32 Nucleo board is straightforward. The BME280 module typically comes as a breakout board with six pins. Here is the wiring for the NUCLEO-F401RE using I2C1:
| BME280 Pin | STM32 Pin | Connection |
|---|---|---|
| VCC | 3.3V | Power supply |
| GND | GND | Common ground |
| SCL | PB6 (I2C1_SCL) | I2C clock line |
| SDA | PB7 (I2C1_SDA) | I2C data line |
| SDO | GND | Sets I2C address to 0x76 |
Important: The SDO pin must be connected to GND when using the I2C interface. This sets the I2C address to 0x76 (7-bit). If SDO is pulled high, the address becomes 0x77. If you are also connecting an SSD1306 OLED display on the same I2C bus, the OLED typically uses address 0x3C, so there is no conflict.
STM32CubeMX Configuration
Start by creating a new STM32CubeMX project for your target microcontroller. For the NUCLEO-F401RE:
- Select the STM32F401RETx microcontroller
- Under Connectivity > I2C1, enable I2C and set the mode to “I2C”
- In Parameter Settings, set I2C Speed Mode to “Standard Mode” (100 kHz) for initial testing. You can switch to “Fast Mode” (400 kHz) after verifying stable communication
- Under System Core > GPIO, confirm PB6 and PB7 are set as I2C1_SCL and I2C1_SDA respectively
- Optionally enable USART2 for serial debugging output
- Generate the code for STM32CubeIDE
BME280 Initialization Code
The BME280 requires initialization before use. First, verify communication by reading the chip ID register (0xD0), which should return 0x60:
#define BME280_ADDR (0x76 << 1)
#define BME280_CHIP_ID_REG 0xD0
uint8_t bme280_read_chip_id(void)
{
uint8_t id = 0;
HAL_I2C_Mem_Read(&hi2c1, BME280_ADDR,
BME280_CHIP_ID_REG,
I2C_MEMADD_SIZE_8BIT,
&id, 1, HAL_MAX_DELAY);
return id;
}
After confirming communication, configure the sensor’s operating mode and oversampling settings:
void BME280_Config(void)
{
uint8_t data[2];
// Configure humidity oversampling (register 0xF2)
data[0] = 0xF2;
data[1] = 0x01; // Humidity oversampling x1
HAL_I2C_Master_Transmit(&hi2c1, BME280_ADDR, data, 2, HAL_MAX_DELAY);
// Configure measurement control (register 0xF4)
data[0] = 0xF4;
data[1] = 0xB7; // Temp osrs x2, Pressure osrs x16, Normal mode
HAL_I2C_Master_Transmit(&hi2c1, BME280_ADDR, data, 2, HAL_MAX_DELAY);
// Configure filter (register 0xF5)
data[0] = 0xF5;
data[1] = 0xA0; // Standby 0.5ms, IIR filter x16
HAL_I2C_Master_Transmit(&hi2c1, BME280_ADDR, data, 2, HAL_MAX_DELAY);
}
Reading Compensated Sensor Data
The BME280 outputs raw ADC values that must be compensated using calibration coefficients stored in the sensor’s NVM (Non-Volatile Memory). These coefficients are read from registers 0x88 to 0xA1 (temperature and pressure calibration) and 0xE1 to 0xE7 (humidity calibration).
For a complete implementation using the STM32 HAL I2C functions, you need these steps in your main loop:
void BME280_Measure(float *temperature, float *pressure, float *humidity)
{
uint8_t raw_data[8];
// Burst read 8 bytes starting from register 0xF7
HAL_I2C_Mem_Read(&hi2c1, BME280_ADDR, 0xF7,
I2C_MEMADD_SIZE_8BIT,
raw_data, 8, HAL_MAX_DELAY);
// Extract raw ADC values
int32_t raw_pressure = ((int32_t)raw_data[0] << 12) |
((int32_t)raw_data[1] << 4) |
((int32_t)raw_data[2] >> 4);
int32_t raw_temp = ((int32_t)raw_data[3] << 12) |
((int32_t)raw_data[4] << 4) |
((int32_t)raw_data[5] >> 4);
int32_t raw_hum = ((int32_t)raw_data[6] << 8) | raw_data[7];
// Apply compensation formulas (Bosch reference implementation)
// Temperature in Celsius, Pressure in Pa, Humidity in %RH
// See Bosch BME280 datasheet for the full compensation code
*temperature = compensate_temperature(raw_temp);
*pressure = compensate_pressure(raw_pressure);
*humidity = compensate_humidity(raw_hum);
}
In your main function, call this at your desired interval using HAL_Delay or a timer interrupt:
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
if (bme280_read_chip_id() != 0x60) {
Error_Handler(); // Sensor not detected
}
BME280_Config();
float temp, pressure, humidity;
while (1)
{
BME280_Measure(&temp, &pressure, &humidity);
// Send over UART or display on OLED
HAL_Delay(1000);
}
}
Performance Optimizations
Oversampling: The BME280 supports multiple oversampling settings that balance accuracy against power consumption and measurement time. For industrial monitoring, a configuration of temperature oversampling x2, pressure oversampling x16, and humidity oversampling x1 provides excellent accuracy with reasonable power draw. For battery-powered IoT sensor nodes, consider reducing pressure oversampling to x4 or x2.
IIR Filter: The built-in infinite impulse response (IIR) filter smooths out short-term fluctuations. A filter coefficient of 16 is suitable for stationary monitoring, while lower values work better for applications requiring fast response to environmental changes.
Operating Mode: Normal mode continuously takes measurements at the interval set by the standby time parameter. Forced mode takes a single measurement and then enters sleep mode, which is ideal for battery conservation in periodic monitoring applications.
Adding an OLED Display
Adding an SSD1306 OLED display on the same I2C bus is straightforward. The OLED uses address 0x3C, which does not conflict with the BME280 at 0x76. The wiring is identical: connect OLED SCL to PB6, SDA to PB7, VCC to 3.3V, and GND to GND. Libraries for the SSD1306 are widely available and integrate cleanly with the STM32 HAL I2C interface.
Popular STM32 development boards for this combination include the NUCLEO-F401RE (Cortex-M4, 84 MHz, available on Mouser and DigiKey) and the NUCLEO-F767ZI (Cortex-M7 for more demanding applications). The BME280 breakout modules are widely available at low cost, making this an accessible project for both prototyping and production deployment.
Frequently Asked Questions
Q: Can I use SPI instead of I2C with the BME280 on STM32?
A: Yes, the BME280 supports both I2C and SPI interfaces. SPI allows higher data rates (up to 10 MHz) but requires more pins (SCK, SDI, SDO, CS). The I2C interface only needs two wires (SCL, SDA).
Q: What is the BME280’s I2C address?
A: The default address is 0x76 (7-bit) when the SDO pin is connected to GND. If SDO is connected to VCC, the address changes to 0x77. In STM32 HAL, you pass this as (0x76 << 1).
Q: Do I need level shifters for the BME280?
A: The BME280 operates at 1.71V to 3.6V. Most STM32 Nucleo boards use 3.3V logic, so no level shifting is needed. If you are using a 5V microcontroller, you will need level shifters for the I2C lines.
Q: Why is my BME280 returning wrong temperature or pressure?
A: The BME280 requires calibration compensation. You must read the calibration coefficients from the sensor’s NVM and apply Bosch’s compensation formulas. Raw ADC values are not directly meaningful.
Q: Can I use multiple BME280 sensors on the same I2C bus?
A: You can use two BME280 modules by setting one to address 0x76 (SDO to GND) and the other to 0x77 (SDO to VCC). Additional sensors would require an I2C multiplexer.
Q: What is the maximum cable length for I2C with BME280?
A: For standard 100 kHz I2C, keep the cable under 1 meter for reliable communication. For longer distances, use lower pull-up resistor values (2.2k instead of 4.7k) or consider using differential I2C extenders.
Related Reading
Sources:
- Controllerstech — STM32 BME280 Interface Guide
- Ampheo — STM32 HAL Tutorial
- MicrocontrollersLab — BME280 with STM32 Nucleo
- EmbeddedExpertIO — Interfacing BMP180 with STM32
- Controllerstech — STM32 Weather Station with Mongoose


