KevsRobots Learning Platform
56% Percent Complete
By Kevin McAleer, 9 Minutes
Now that you can read basic sensor data and understand datasheets, letβs explore the BME280βs advanced features. This lesson covers power modes, oversampling, filtering, and optimization techniques that make your projects more efficient and accurate.
The BME280 has three operating modes:
Control modes through register 0xF4 (ctrl_meas):
from machine import I2C, Pin
import time
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
BME280_ADDR = 0x76
# Power modes (bits 1:0 of ctrl_meas)
MODE_SLEEP = 0b00
MODE_FORCED = 0b01
MODE_NORMAL = 0b11
def set_mode(mode):
"""Set BME280 power mode"""
# Read current control register
ctrl = i2c.readfrom_mem(BME280_ADDR, 0xF4, 1)[0]
# Clear mode bits and set new mode
ctrl = (ctrl & 0xFC) | mode
# Write back
i2c.writeto_mem(BME280_ADDR, 0xF4, bytes([ctrl]))
mode_names = {0: "Sleep", 1: "Forced", 3: "Normal"}
print(f"Mode set to: {mode_names.get(mode, 'Unknown')}")
# Try each mode
set_mode(MODE_SLEEP)
time.sleep(1)
set_mode(MODE_FORCED)
time.sleep(1)
set_mode(MODE_NORMAL)
Perfect for battery-powered sensors:
from machine import I2C, Pin
import time
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
BME280_ADDR = 0x76
def read_forced_mode():
"""Read sensor in forced mode (most power efficient)"""
# Set forced mode
# Configuration: temp_os=1x, press_os=1x, mode=forced
ctrl_config = 0b00100101 # 0x25
i2c.writeto_mem(BME280_ADDR, 0xF4, bytes([ctrl_config]))
# Wait for measurement to complete (max 10ms for 1x oversampling)
time.sleep_ms(10)
# Read data registers
data = i2c.readfrom_mem(BME280_ADDR, 0xF7, 8)
# Extract raw values
press_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
hum_raw = (data[6] << 8) | data[7]
print(f"Temp: {temp_raw}, Press: {press_raw}, Hum: {hum_raw}")
# Sensor automatically returns to sleep mode
# Take readings every 10 seconds (very low power)
for i in range(5):
print(f"\nReading {i+1}:")
read_forced_mode()
time.sleep(10)
Power savings:
Oversampling reduces noise by taking multiple measurements and averaging:
| Setting | Samples | Resolution | Measurement Time |
|---|---|---|---|
| Skip | 0 | Output 0x80000 | 0ms |
| x1 | 1 | Standard | 2.3ms |
| x2 | 2 | Better | 4.6ms |
| x4 | 4 | Good | 9.2ms |
| x8 | 8 | Very good | 18.4ms |
| x16 | 16 | Excellent | 36.8ms |
from machine import I2C, Pin
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
BME280_ADDR = 0x76
# Oversampling values (bits in ctrl_meas register 0xF4)
OVERSAMPLE_SKIP = 0b000
OVERSAMPLE_1 = 0b001
OVERSAMPLE_2 = 0b010
OVERSAMPLE_4 = 0b011
OVERSAMPLE_8 = 0b100
OVERSAMPLE_16 = 0b101
def configure_oversampling(temp_os, press_os, hum_os):
"""Configure oversampling for temp, pressure, and humidity"""
# Humidity oversampling (register 0xF2)
i2c.writeto_mem(BME280_ADDR, 0xF2, bytes([hum_os]))
# Temperature and pressure oversampling (register 0xF4)
# Format: [temp_os(3 bits)][press_os(3 bits)][mode(2 bits)]
ctrl_meas = (temp_os << 5) | (press_os << 2) | 0b11 # Normal mode
i2c.writeto_mem(BME280_ADDR, 0xF4, bytes([ctrl_meas]))
print(f"Configured: Temp x{1<<temp_os}, Press x{1<<press_os}, Hum x{1<<hum_os}")
# High accuracy configuration
configure_oversampling(
temp_os=OVERSAMPLE_2, # x2 temperature oversampling
press_os=OVERSAMPLE_16, # x16 pressure oversampling
hum_os=OVERSAMPLE_1 # x1 humidity oversampling
)
Low noise environment:
# Fast readings, lower accuracy
configure_oversampling(OVERSAMPLE_1, OVERSAMPLE_1, OVERSAMPLE_1)
# Measurement time: ~7ms
High noise environment:
# Slower readings, higher accuracy
configure_oversampling(OVERSAMPLE_2, OVERSAMPLE_16, OVERSAMPLE_2)
# Measurement time: ~50ms
Weather station (recommended):
# Balanced accuracy and speed
configure_oversampling(OVERSAMPLE_2, OVERSAMPLE_16, OVERSAMPLE_1)
# Good pressure accuracy for altitude/weather, fast temp/humidity
The BME280 has a built-in Infinite Impulse Response (IIR) filter to reduce short-term noise:
| Coefficient | Filter Samples | Response Time |
|---|---|---|
| 0 | Off | Fastest |
| 1 | 2 | Fast |
| 2 | 5 | Medium |
| 3 | 11 | Slow |
| 4 | 22 | Slowest |
from machine import I2C, Pin
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
BME280_ADDR = 0x76
# Filter coefficients
FILTER_OFF = 0b000
FILTER_2 = 0b001
FILTER_4 = 0b010
FILTER_8 = 0b011
FILTER_16 = 0b100
def set_filter(filter_coef):
"""Configure IIR filter"""
# Config register (0xF5): [standby(3)][filter(3)][reserved(1)][spi3w_en(1)]
config = (filter_coef << 2)
i2c.writeto_mem(BME280_ADDR, 0xF5, bytes([config]))
filter_names = {0: "OFF", 1: "x2", 2: "x4", 3: "x8", 4: "x16"}
print(f"Filter set to: {filter_names[filter_coef]}")
# Enable moderate filtering
set_filter(FILTER_4)
Use filtering when:
Donβt use filtering when:
In normal mode, the sensor alternates between measurement and standby. Configure standby duration:
| Setting | Standby Time | Sample Rate |
|---|---|---|
| 0 | 0.5 ms | 2000 Hz |
| 1 | 62.5 ms | 16 Hz |
| 2 | 125 ms | 8 Hz |
| 3 | 250 ms | 4 Hz |
| 4 | 500 ms | 2 Hz |
| 5 | 1000 ms | 1 Hz |
| 6 | 10 ms | 100 Hz |
| 7 | 20 ms | 50 Hz |
from machine import I2C, Pin
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
BME280_ADDR = 0x76
# Standby times
STANDBY_0_5 = 0b000 # 0.5ms (2000 Hz)
STANDBY_62_5 = 0b001 # 62.5ms (16 Hz)
STANDBY_125 = 0b010 # 125ms (8 Hz)
STANDBY_250 = 0b011 # 250ms (4 Hz)
STANDBY_500 = 0b100 # 500ms (2 Hz)
STANDBY_1000 = 0b101 # 1000ms (1 Hz)
STANDBY_10 = 0b110 # 10ms (100 Hz)
STANDBY_20 = 0b111 # 20ms (50 Hz)
def set_standby_time(standby):
"""Set standby time between measurements"""
# Config register (0xF5)
config = (standby << 5) # Standby in bits 7:5
i2c.writeto_mem(BME280_ADDR, 0xF5, bytes([config]))
standby_ms = [0.5, 62.5, 125, 250, 500, 1000, 10, 20][standby]
print(f"Standby time: {standby_ms}ms")
# Sample once per second
set_standby_time(STANDBY_1000)
Hereβs a complete setup for a weather station:
from machine import I2C, Pin
import time
class BME280_Optimized:
def __init__(self, i2c, addr=0x76):
self.i2c = i2c
self.addr = addr
# Verify sensor
chip_id = self.i2c.readfrom_mem(self.addr, 0xD0, 1)[0]
if chip_id != 0x60:
raise RuntimeError(f"BME280 not found. Chip ID: 0x{chip_id:02X}")
print("β BME280 detected")
# Configure for weather monitoring
self.configure_weather_station()
def configure_weather_station(self):
"""Optimal settings for weather station"""
# Humidity oversampling x1
self.i2c.writeto_mem(self.addr, 0xF2, bytes([0b001]))
# Temperature x2, Pressure x16, Normal mode
# 010 (temp x2), 101 (press x16), 11 (normal)
ctrl_meas = 0b01010111 # 0x57
self.i2c.writeto_mem(self.addr, 0xF4, bytes([ctrl_meas]))
# Standby 1000ms, filter x16
# 101 (1000ms), 100 (filter x16), 0, 0
config = 0b10110000 # 0xB0
self.i2c.writeto_mem(self.addr, 0xF5, bytes([config]))
print("Weather station configuration applied:")
print(" - Temperature: x2 oversampling")
print(" - Pressure: x16 oversampling")
print(" - Humidity: x1 oversampling")
print(" - Filter: x16 (smooth readings)")
print(" - Sample rate: 1 Hz")
def read_raw(self):
"""Read raw sensor data"""
data = self.i2c.readfrom_mem(self.addr, 0xF7, 8)
press_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
hum_raw = (data[6] << 8) | data[7]
return temp_raw, press_raw, hum_raw
# Usage
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
sensor = BME280_Optimized(i2c)
print("\nMonitoring weather...")
for i in range(10):
temp_raw, press_raw, hum_raw = sensor.read_raw()
print(f"Reading {i+1}: Temp={temp_raw}, Press={press_raw}, Hum={hum_raw}")
time.sleep(2)
Understanding timing helps optimize your code:
Time = t_base + (temp_os + press_os + hum_os) Γ t_oversample
Where:
Examples:
# x1 all: 1 + (1 + 1 + 1) Γ 2 = 7 ms
# x2 temp, x16 press, x1 hum: 1 + (2 + 16 + 1) Γ 2 = 39 ms
from machine import I2C, Pin
import time
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
BME280_ADDR = 0x76
def wait_for_measurement():
"""Wait until measurement is complete"""
# Trigger forced mode measurement
ctrl = 0b00100101 # x1 oversampling, forced mode
i2c.writeto_mem(BME280_ADDR, 0xF4, bytes([ctrl]))
# Check status register (0xF3) bit 3 (measuring)
while True:
status = i2c.readfrom_mem(BME280_ADDR, 0xF3, 1)[0]
measuring = (status >> 3) & 0x01
if not measuring:
break # Measurement complete
time.sleep_ms(1)
print("Measurement ready")
wait_for_measurement()
Experiment with BME280 features:
# Test different oversampling
for os_level in [1, 2, 4, 8, 16]:
configure_oversampling(os_level, os_level, os_level)
# Take readings and note noise levels
# Read with filter OFF
set_filter(FILTER_OFF)
readings_unfiltered = [read_sensor() for _ in range(10)]
# Read with filter ON
set_filter(FILTER_16)
readings_filtered = [read_sensor() for _ in range(10)]
# Compare stability
# Calculate average current
# Normal mode @ 1Hz with moderate oversampling
# Expected: ~3-5 ΞΌA
def adaptive_config(rapid_changes_detected):
"""Adjust configuration based on conditions"""
if rapid_changes_detected:
# Fast response
set_filter(FILTER_OFF)
configure_oversampling(OVERSAMPLE_1, OVERSAMPLE_1, OVERSAMPLE_1)
set_standby_time(STANDBY_62_5) # 16 Hz
else:
# Stable, filtered readings
set_filter(FILTER_16)
configure_oversampling(OVERSAMPLE_2, OVERSAMPLE_16, OVERSAMPLE_1)
set_standby_time(STANDBY_1000) # 1 Hz
In this lesson, you mastered:
You can now configure the BME280 optimally for any application - from ultra-low-power battery sensors to high-accuracy weather stations.
Youβve mastered I2C with a real sensor. Now letβs explore SPI - a faster, more flexible protocol used in displays, SD cards, and high-speed sensors. In the next lesson, weβll understand what makes SPI different and when to choose it over I2C.
You can use the arrows β β on your keyboard to navigate between lessons.
Comments