Skip to content

Commit d1d9845

Browse files
committed
feat(bme280): Create module skeleton with const.py and device ID check.
1 parent 7d4bedf commit d1d9845

6 files changed

Lines changed: 246 additions & 0 deletions

File tree

lib/bme280/bme280/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from bme280.device import BME280
2+
3+
__all__ = [
4+
"BME280",
5+
]

lib/bme280/bme280/const.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from micropython import const
2+
3+
# I2C addresses
4+
BME280_I2C_ADDR_LOW = const(0x76) # SDO → GND
5+
BME280_I2C_ADDR_HIGH = const(0x77) # SDO → VDDIO
6+
BME280_I2C_DEFAULT_ADDR = const(0x76)
7+
8+
# Device identification
9+
REG_CHIP_ID = const(0xD0)
10+
BME280_CHIP_ID = const(0x60)
11+
12+
# Reset
13+
REG_SOFT_RESET = const(0xE0)
14+
SOFT_RESET_CMD = const(0xB6)
15+
16+
# Status
17+
REG_STATUS = const(0xF3)
18+
STATUS_MEASURING = const(0x08)
19+
STATUS_IM_UPDATE = const(0x01)
20+
21+
# Control registers
22+
REG_CTRL_HUM = const(0xF2)
23+
REG_CTRL_MEAS = const(0xF4)
24+
REG_CONFIG = const(0xF5)
25+
26+
# Data registers (burst read 0xF7-0xFE = 8 bytes)
27+
REG_DATA_START = const(0xF7)
28+
DATA_BLOCK_SIZE = const(8)
29+
30+
# Calibration data registers
31+
REG_CALIB_TEMP_PRESS = const(0x88) # 26 bytes: T1..T3, P1..P9, _, H1
32+
CALIB_TP_SIZE = const(26)
33+
REG_CALIB_HUM = const(0xE1) # 7 bytes: H2..H6
34+
CALIB_H_SIZE = const(7)
35+
36+
# Measurement modes
37+
MODE_SLEEP = const(0x00)
38+
MODE_FORCED = const(0x01)
39+
MODE_NORMAL = const(0x03)
40+
MODE_MASK = const(0x03)
41+
42+
# Oversampling
43+
OSRS_SKIP = const(0x00)
44+
OSRS_X1 = const(0x01)
45+
OSRS_X2 = const(0x02)
46+
OSRS_X4 = const(0x03)
47+
OSRS_X8 = const(0x04)
48+
OSRS_X16 = const(0x05)
49+
50+
# ctrl_meas bit positions
51+
OSRS_T_SHIFT = const(5)
52+
OSRS_P_SHIFT = const(2)
53+
54+
# IIR filter coefficients (config register bits 4:2)
55+
FILTER_OFF = const(0x00)
56+
FILTER_2 = const(0x01)
57+
FILTER_4 = const(0x02)
58+
FILTER_8 = const(0x03)
59+
FILTER_16 = const(0x04)
60+
FILTER_SHIFT = const(2)
61+
62+
# Standby time (config register bits 7:5)
63+
STANDBY_0_5_MS = const(0x00)
64+
STANDBY_62_5_MS = const(0x01)
65+
STANDBY_125_MS = const(0x02)
66+
STANDBY_250_MS = const(0x03)
67+
STANDBY_500_MS = const(0x04)
68+
STANDBY_1000_MS = const(0x05)
69+
STANDBY_SHIFT = const(5)
70+
71+
# Timing
72+
BOOT_DELAY_MS = const(10)
73+
RESET_DELAY_MS = const(10)

lib/bme280/bme280/device.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from time import sleep_ms
2+
3+
from bme280.const import (
4+
BME280_CHIP_ID,
5+
BME280_I2C_DEFAULT_ADDR,
6+
MODE_SLEEP,
7+
OSRS_P_SHIFT,
8+
OSRS_T_SHIFT,
9+
OSRS_X1,
10+
REG_CHIP_ID,
11+
REG_CTRL_HUM,
12+
REG_CTRL_MEAS,
13+
REG_SOFT_RESET,
14+
REG_STATUS,
15+
RESET_DELAY_MS,
16+
SOFT_RESET_CMD,
17+
STATUS_IM_UPDATE,
18+
)
19+
from bme280.exceptions import BME280InvalidDevice, BME280NotFound
20+
21+
22+
class BME280(object):
23+
"""MicroPython driver for the Bosch BME280 temperature, humidity, and pressure sensor."""
24+
25+
def __init__(self, i2c, address=BME280_I2C_DEFAULT_ADDR):
26+
self.i2c = i2c
27+
self.address = address
28+
self._check_device()
29+
self._configure_default()
30+
31+
# --------------------------------------------------
32+
# Low level I2C
33+
# --------------------------------------------------
34+
35+
def _read_reg(self, reg):
36+
"""Read a single byte from register."""
37+
return self.i2c.readfrom_mem(self.address, reg, 1)[0]
38+
39+
def _read_block(self, reg, length):
40+
"""Read a block of bytes from consecutive registers."""
41+
return self.i2c.readfrom_mem(self.address, reg, length)
42+
43+
def _write_reg(self, reg, value):
44+
"""Write a single byte to register."""
45+
self.i2c.writeto_mem(self.address, reg, bytes([value]))
46+
47+
# --------------------------------------------------
48+
# Device identification and initialization
49+
# --------------------------------------------------
50+
51+
def _check_device(self):
52+
"""Verify device presence and ID."""
53+
try:
54+
chip_id = self.device_id()
55+
except OSError as err:
56+
raise BME280NotFound(
57+
"BME280 not found at address 0x{:02X}".format(self.address)
58+
) from err
59+
if chip_id != BME280_CHIP_ID:
60+
raise BME280InvalidDevice(
61+
"Expected chip ID 0x{:02X}, got 0x{:02X}".format(BME280_CHIP_ID, chip_id)
62+
)
63+
64+
def _configure_default(self):
65+
"""Apply default configuration after reset."""
66+
# Wait for NVM copy to complete
67+
self._wait_boot()
68+
# Set humidity oversampling (must be written before ctrl_meas)
69+
self._write_reg(REG_CTRL_HUM, OSRS_X1)
70+
# Set temperature and pressure oversampling, sleep mode
71+
self._write_reg(
72+
REG_CTRL_MEAS,
73+
(OSRS_X1 << OSRS_T_SHIFT) | (OSRS_X1 << OSRS_P_SHIFT) | MODE_SLEEP,
74+
)
75+
76+
def _wait_boot(self, timeout_ms=50):
77+
"""Wait for NVM data copy to complete."""
78+
for _ in range(timeout_ms // 5):
79+
if not (self._read_reg(REG_STATUS) & STATUS_IM_UPDATE):
80+
return
81+
sleep_ms(5)
82+
83+
def device_id(self):
84+
"""Read chip ID register. Expected: 0x60."""
85+
return self._read_reg(REG_CHIP_ID)
86+
87+
def soft_reset(self):
88+
"""Perform a soft reset. Device returns to power-on defaults."""
89+
self._write_reg(REG_SOFT_RESET, SOFT_RESET_CMD)
90+
sleep_ms(RESET_DELAY_MS)
91+
self._wait_boot()
92+
93+
def reset(self):
94+
"""Reset and reconfigure."""
95+
self.soft_reset()
96+
self._configure_default()

lib/bme280/bme280/exceptions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class BME280Error(Exception):
2+
"""Base exception for BME280 driver."""
3+
4+
5+
class BME280NotFound(BME280Error):
6+
"""Device not found on I2C bus."""
7+
8+
9+
class BME280InvalidDevice(BME280Error):
10+
"""Device ID mismatch."""

lib/bme280/manifest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
metadata(
2+
description="Driver for BME280 pressure, humidity and temperature sensor.",
3+
version="0.0.1",
4+
)
5+
6+
package("bme280")

tests/scenarios/bme280.yaml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
driver: bme280
2+
driver_class: BME280
3+
i2c_address: 0x76
4+
5+
i2c:
6+
id: 1
7+
8+
mock_registers:
9+
# Chip ID (expected 0x60)
10+
0xD0: 0x60
11+
# Status (not measuring, NVM copy done)
12+
0xF3: 0x00
13+
# ctrl_hum (default)
14+
0xF2: 0x00
15+
# ctrl_meas (default sleep mode)
16+
0xF4: 0x00
17+
# config (default)
18+
0xF5: 0x00
19+
20+
tests:
21+
- name: "Verify chip ID register"
22+
action: read_register
23+
register: 0xD0
24+
expect: 0x60
25+
mode: [mock, hardware]
26+
27+
- name: "Read chip ID via method"
28+
action: call
29+
method: device_id
30+
expect: 0x60
31+
mode: [mock, hardware]
32+
33+
- name: "Soft reset sends reset command"
34+
action: script
35+
script: |
36+
i2c.clear_write_log()
37+
dev.soft_reset()
38+
log = i2c.get_write_log()
39+
sent_reset = any(reg == 0xE0 and data[0] == 0xB6 for reg, data in log)
40+
result = sent_reset
41+
expect_true: true
42+
mode: [mock]
43+
44+
- name: "Reset reconfigures device"
45+
action: script
46+
script: |
47+
i2c.clear_write_log()
48+
dev.reset()
49+
log = i2c.get_write_log()
50+
# Should write reset command + ctrl_hum + ctrl_meas
51+
wrote_reset = any(reg == 0xE0 for reg, data in log)
52+
wrote_ctrl_hum = any(reg == 0xF2 for reg, data in log)
53+
wrote_ctrl_meas = any(reg == 0xF4 for reg, data in log)
54+
result = wrote_reset and wrote_ctrl_hum and wrote_ctrl_meas
55+
expect_true: true
56+
mode: [mock]

0 commit comments

Comments
 (0)