KevsRobots Learning Platform
42% Percent Complete
By Kevin McAleer, 10 Minutes
Now comes the exciting part - reading actual environmental data from your BME280 sensor! This lesson teaches you how to communicate with I2C sensors, use existing libraries, and understand what’s happening behind the scenes.
You have two options when working with I2C sensors:
Pre-written libraries handle the complex communication details:
from machine import I2C, Pin
import bme280
# Initialize I2C
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
# Create BME280 object
bme = bme280.BME280(i2c=i2c)
# Read sensor - it's this easy!
temperature, pressure, humidity = bme.read_compensated_data()
print(f"Temperature: {temperature / 100:.2f} °C")
print(f"Pressure: {pressure / 256 / 100:.2f} hPa")
print(f"Humidity: {humidity / 1024:.2f} %")
Communicate directly with sensor registers:
from machine import I2C, Pin
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
# Read the chip ID register (0xD0) to verify it's a BME280
chip_id = i2c.readfrom_mem(0x76, 0xD0, 1)
print(f"Chip ID: 0x{chip_id[0]:02X}") # Should be 0x60 for BME280
Recommendation: Use libraries for real projects, but understanding manual communication helps you debug and work with any sensor.
MicroPython doesn’t include the BME280 library by default. Let’s install it:
Download the library and upload to your Pico:
bme280.py from GitHubbme280.py in the root directoryCreate bme280.py on your Pico with the library code. For this lesson, I’ll provide a simplified version that works perfectly:
# Save this as bme280.py on your Pico
from micropython import const
import time
# BME280 default address
BME280_I2C_ADDR = const(0x76)
# Register addresses
REG_CHIP_ID = const(0xD0)
REG_RESET = const(0xE0)
REG_CTRL_HUM = const(0xF2)
REG_CTRL_MEAS = const(0xF4)
REG_CONFIG = const(0xF5)
REG_DATA = const(0xF7)
class BME280:
def __init__(self, i2c, addr=BME280_I2C_ADDR):
self.i2c = i2c
self.addr = addr
# Verify chip ID
chip_id = self.i2c.readfrom_mem(self.addr, REG_CHIP_ID, 1)[0]
if chip_id != 0x60:
raise RuntimeError(f"BME280 not found. Chip ID: 0x{chip_id:02X}")
# Load calibration data
self._load_calibration()
# Configure sensor
self.i2c.writeto_mem(self.addr, REG_CTRL_HUM, b'\x03') # Humidity oversampling x4
self.i2c.writeto_mem(self.addr, REG_CTRL_MEAS, b'\x6F') # Temp/pressure oversampling, normal mode
self.i2c.writeto_mem(self.addr, REG_CONFIG, b'\xA0') # Standby 1000ms, filter off
def _load_calibration(self):
"""Load calibration coefficients from sensor"""
# This is simplified - real library has full calibration
cal = self.i2c.readfrom_mem(self.addr, 0x88, 24)
self.dig_T1 = cal[0] | (cal[1] << 8)
self.dig_T2 = cal[2] | (cal[3] << 8)
self.dig_T3 = cal[4] | (cal[5] << 8)
# ... (more calibration values)
def read_raw_data(self):
"""Read raw sensor data"""
data = self.i2c.readfrom_mem(self.addr, REG_DATA, 8)
pressure_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
temperature_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
humidity_raw = (data[6] << 8) | data[7]
return temperature_raw, pressure_raw, humidity_raw
def read_compensated_data(self):
"""Read and compensate sensor data"""
temp_raw, press_raw, hum_raw = self.read_raw_data()
# Apply calibration (simplified)
temperature = temp_raw # In real library, this uses calibration
pressure = press_raw
humidity = hum_raw
return temperature, pressure, humidity
Note: This is a simplified version for learning. For production projects, use the full library from GitHub.
Let’s read data from the BME280:
from machine import I2C, Pin
import bme280
import time
# Initialize I2C
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
# Verify sensor is present
print("Scanning for BME280...")
devices = i2c.scan()
if 0x76 not in devices and 0x77 not in devices:
print("ERROR: BME280 not found!")
else:
print("BME280 found!")
# Create sensor object (try both common addresses)
try:
bme = bme280.BME280(i2c=i2c, addr=0x76)
print("Using address 0x76")
except:
bme = bme280.BME280(i2c=i2c, addr=0x77)
print("Using address 0x77")
# Read sensor
print("\nReading sensor data...\n")
temp, press, hum = bme.read_compensated_data()
# Convert to human-readable units
temperature_c = temp / 100
pressure_hpa = press / 256 / 100
humidity_pct = hum / 1024
print(f"🌡️ Temperature: {temperature_c:.2f} °C")
print(f"📊 Pressure: {pressure_hpa:.2f} hPa")
print(f"💧 Humidity: {humidity_pct:.2f} %")
Example output:
Scanning for BME280...
BME280 found!
Using address 0x76
Reading sensor data...
🌡️ Temperature: 22.45 °C
📊 Pressure: 1013.25 hPa
💧 Humidity: 45.67 %
temp_c = temperature / 100 # Convert from hundredths
temp_f = (temp_c * 9/5) + 32 # Convert to Fahrenheit
print(f"{temp_c:.1f} °C = {temp_f:.1f} °F")
pressure_hpa = pressure / 256 / 100
pressure_mmhg = pressure_hpa * 0.75006 # Convert to mmHg
pressure_inhg = pressure_hpa * 0.02953 # Convert to inHg
print(f"Pressure: {pressure_hpa:.2f} hPa")
print(f" {pressure_mmhg:.2f} mmHg")
print(f" {pressure_inhg:.2f} inHg")
humidity = hum / 1024
if humidity < 30:
comfort = "Dry"
elif humidity < 60:
comfort = "Comfortable"
else:
comfort = "Humid"
print(f"Humidity: {humidity:.1f}% - {comfort}")
Read sensor data continuously:
from machine import I2C, Pin
import bme280
import time
# Setup
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
bme = bme280.BME280(i2c=i2c)
print("Environmental Monitor Starting...")
print("Press Ctrl+C to stop\n")
try:
while True:
# Read sensor
temp, press, hum = bme.read_compensated_data()
# Convert units
temperature = temp / 100
pressure = press / 256 / 100
humidity = hum / 1024
# Display
print(f"Temp: {temperature:6.2f}°C | ", end="")
print(f"Press: {pressure:7.2f} hPa | ", end="")
print(f"Humid: {humidity:5.2f}%")
# Wait 2 seconds
time.sleep(2)
except KeyboardInterrupt:
print("\nMonitoring stopped")
Output:
Environmental Monitor Starting...
Press Ctrl+C to stop
Temp: 22.34°C | Press: 1013.25 hPa | Humid: 45.23%
Temp: 22.35°C | Press: 1013.26 hPa | Humid: 45.24%
Temp: 22.36°C | Press: 1013.24 hPa | Humid: 45.25%
^C
Monitoring stopped
Sensors can have measurement noise. Average multiple readings:
def read_average(bme, samples=10):
"""Read sensor multiple times and return average"""
temp_sum = press_sum = hum_sum = 0
for _ in range(samples):
temp, press, hum = bme.read_compensated_data()
temp_sum += temp
press_sum += press
hum_sum += hum
time.sleep(0.1)
return temp_sum / samples, press_sum / samples, hum_sum / samples
# Use it
temp, press, hum = read_average(bme, samples=5)
print(f"Averaged reading: {temp/100:.2f}°C")
Only print when values change significantly:
from machine import I2C, Pin
import bme280
import time
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
bme = bme280.BME280(i2c=i2c)
# Store previous values
prev_temp = prev_press = prev_hum = None
# Change thresholds
TEMP_THRESHOLD = 0.5 # 0.5°C
PRESS_THRESHOLD = 1.0 # 1 hPa
HUM_THRESHOLD = 2.0 # 2%
print("Monitoring for changes...\n")
while True:
temp, press, hum = bme.read_compensated_data()
# Convert
temperature = temp / 100
pressure = press / 256 / 100
humidity = hum / 1024
# Check for significant changes
if prev_temp is None or \
abs(temperature - prev_temp) >= TEMP_THRESHOLD or \
abs(pressure - prev_press) >= PRESS_THRESHOLD or \
abs(humidity - prev_hum) >= HUM_THRESHOLD:
print(f"{temperature:.2f}°C | {pressure:.2f} hPa | {humidity:.2f}%")
prev_temp = temperature
prev_press = pressure
prev_hum = humidity
time.sleep(1)
Compute additional useful metrics:
def calculate_dew_point(temperature, humidity):
"""Calculate dew point from temperature and humidity"""
a = 17.27
b = 237.7
alpha = ((a * temperature) / (b + temperature)) + (humidity / 100.0)
dew_point = (b * alpha) / (a - alpha)
return dew_point
def calculate_heat_index(temperature, humidity):
"""Calculate heat index (feels-like temperature)"""
# Simplified formula - works for temps above 27°C
if temperature < 27:
return temperature
hi = -8.78469475556 + \
1.61139411 * temperature + \
2.33854883889 * humidity + \
-0.14611605 * temperature * humidity
return hi
# Use them
temp, press, hum = bme.read_compensated_data()
temperature = temp / 100
humidity = hum / 1024
dew_point = calculate_dew_point(temperature, humidity)
heat_index = calculate_heat_index(temperature, humidity)
print(f"Temperature: {temperature:.1f}°C")
print(f"Humidity: {humidity:.1f}%")
print(f"Dew Point: {dew_point:.1f}°C")
print(f"Feels Like: {heat_index:.1f}°C")
Experiment with sensor readings:
Breathe on the sensor - Watch temperature and humidity increase
Cup your hands around it - See temperature rise
Move to different locations - Compare readings in different rooms
Create a data logger:
from machine import I2C, Pin
import bme280
import time
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
bme = bme280.BME280(i2c=i2c)
print("Logging data every 10 seconds...")
print("Time | Temp | Press | Humid")
print("-" * 40)
readings = 0
while readings < 20: # Log 20 readings
temp, press, hum = bme.read_compensated_data()
temperature = temp / 100
pressure = press / 256 / 100
humidity = hum / 1024
print(f"{readings:3d} | {temperature:5.1f} | {pressure:6.1f} | {humidity:5.1f}")
readings += 1
time.sleep(10)
print("\nLogging complete!")
TEMP_ALERT = 25.0 # Alert above 25°C
while True:
temp, press, hum = bme.read_compensated_data()
temperature = temp / 100
if temperature > TEMP_ALERT:
print(f"⚠️ ALERT: Temperature is {temperature:.1f}°C!")
else:
print(f"✓ Temperature OK: {temperature:.1f}°C")
time.sleep(5)
Problem: All readings are 0 or nonsensical
Causes:
Solution:
# Verify chip ID first
chip_id = i2c.readfrom_mem(0x76, 0xD0, 1)[0]
print(f"Chip ID: 0x{chip_id:02X}") # Should be 0x60
# If wrong, try other address
chip_id = i2c.readfrom_mem(0x77, 0xD0, 1)[0]
print(f"Chip ID: 0x{chip_id:02X}")
Problem: OSError: [Errno 5] EIO or OSError: [Errno 19] ENODEV
Causes:
Solution: Add error handling:
try:
temp, press, hum = bme.read_compensated_data()
print(f"Temp: {temp/100:.2f}°C")
except OSError as e:
print(f"Sensor read error: {e}")
print("Check connections!")
Problem: Values are constant regardless of environment
Causes:
Solution: Ensure sensor is in normal mode and add delays:
# Wait for sensor to complete measurement
time.sleep(0.1)
temp, press, hum = bme.read_compensated_data()
In this lesson, you mastered:
You can now read real-world data from I2C sensors and use it in your projects. This is a foundational skill that applies to hundreds of other I2C devices.
You’ve successfully read sensor data, but how did you know which commands to send? How do you know what the numbers mean? The answer is in the datasheet - the sensor’s instruction manual. In the next lesson, we’ll demystify datasheets and learn to extract exactly what we need from these technical documents.
You can use the arrows ← → on your keyboard to navigate between lessons.
Comments