Skip to content

Commit 91d9016

Browse files
authored
feat(bme280): Read factory calibration trimming parameters. (#309)
* feat(bme280): Read factory calibration trimming parameters. * fix(bme280): Fix calibration comment values to match mock register bytes.
1 parent 3e691ef commit 91d9016

3 files changed

Lines changed: 159 additions & 5 deletions

File tree

lib/bme280/bme280/device.py

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1+
import struct
12
from time import sleep_ms
23

34
from bme280.const import (
45
BME280_CHIP_ID,
56
BME280_I2C_DEFAULT_ADDR,
7+
CALIB_H_SIZE,
8+
CALIB_TP_SIZE,
69
MODE_SLEEP,
710
OSRS_P_SHIFT,
811
OSRS_T_SHIFT,
912
OSRS_X1,
13+
REG_CALIB_HUM,
14+
REG_CALIB_TEMP_PRESS,
1015
REG_CHIP_ID,
1116
REG_CTRL_HUM,
1217
REG_CTRL_MEAS,
@@ -26,6 +31,8 @@ def __init__(self, i2c, address=BME280_I2C_DEFAULT_ADDR):
2631
self.i2c = i2c
2732
self.address = address
2833
self._check_device()
34+
self._wait_boot()
35+
self._read_calibration()
2936
self._configure_default()
3037

3138
# --------------------------------------------------
@@ -48,6 +55,42 @@ def _write_reg(self, reg, value):
4855
# Device identification and initialization
4956
# --------------------------------------------------
5057

58+
# --------------------------------------------------
59+
# Calibration data
60+
# --------------------------------------------------
61+
62+
def _read_calibration(self):
63+
"""Read factory calibration (trimming) parameters from NVM."""
64+
# Block 1: 0x88, 26 bytes — T1..T3, P1..P9, _, H1
65+
tp = self._read_block(REG_CALIB_TEMP_PRESS, CALIB_TP_SIZE)
66+
(
67+
self.dig_T1, self.dig_T2, self.dig_T3,
68+
self.dig_P1, self.dig_P2, self.dig_P3,
69+
self.dig_P4, self.dig_P5, self.dig_P6,
70+
self.dig_P7, self.dig_P8, self.dig_P9,
71+
_, self.dig_H1,
72+
) = struct.unpack("<HhhHhhhhhhhhBB", tp)
73+
74+
# Block 2: 0xE1, 7 bytes — H2..H6
75+
hum = self._read_block(REG_CALIB_HUM, CALIB_H_SIZE)
76+
self.dig_H2 = struct.unpack("<h", hum[0:2])[0]
77+
self.dig_H3 = hum[2]
78+
# H4 and H5 share register 0xE5 (12-bit values packed across 3 bytes)
79+
self.dig_H4 = (hum[3] << 4) | (hum[4] & 0x0F)
80+
if self.dig_H4 > 2047:
81+
self.dig_H4 -= 4096
82+
self.dig_H5 = (hum[4] >> 4) | (hum[5] << 4)
83+
if self.dig_H5 > 2047:
84+
self.dig_H5 -= 4096
85+
self.dig_H6 = struct.unpack("<b", bytes([hum[6]]))[0]
86+
87+
# t_fine is computed during temperature compensation
88+
self.t_fine = 0
89+
90+
# --------------------------------------------------
91+
# Device identification and initialization
92+
# --------------------------------------------------
93+
5194
def _check_device(self):
5295
"""Verify device presence and ID."""
5396
try:
@@ -62,9 +105,7 @@ def _check_device(self):
62105
)
63106

64107
def _configure_default(self):
65-
"""Apply default configuration after reset."""
66-
# Wait for NVM copy to complete
67-
self._wait_boot()
108+
"""Apply default configuration."""
68109
# Set humidity oversampling (must be written before ctrl_meas)
69110
self._write_reg(REG_CTRL_HUM, OSRS_X1)
70111
# Set temperature and pressure oversampling, sleep mode
@@ -92,6 +133,7 @@ def soft_reset(self):
92133
self._wait_boot()
93134

94135
def reset(self):
95-
"""Reset and reconfigure."""
136+
"""Reset, re-read calibration, and reconfigure."""
96137
self.soft_reset()
138+
self._read_calibration()
97139
self._configure_default()

tests/fake_machine/i2c.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def __init__(self, bus_id=None, *, registers=None, address=None, **kwargs):
1616
self._registers = {}
1717
self._address = address
1818
self._write_log = []
19+
self._read_log = []
1920

2021
if registers:
2122
for reg, value in registers.items():
@@ -26,6 +27,7 @@ def __init__(self, bus_id=None, *, registers=None, address=None, **kwargs):
2627

2728
def readfrom_mem(self, addr, reg, nbytes, *, addrsize=8):
2829
self._check_address(addr)
30+
self._read_log.append(reg)
2931
data = self._registers.get(reg, b"\x00" * nbytes)
3032
return data[:nbytes]
3133

@@ -65,6 +67,13 @@ def get_write_log(self):
6567
def clear_write_log(self):
6668
self._write_log.clear()
6769

70+
def get_read_log(self):
71+
"""Return list of register addresses read."""
72+
return list(self._read_log)
73+
74+
def clear_read_log(self):
75+
self._read_log.clear()
76+
6877
def _check_address(self, addr):
6978
if self._address is not None and addr != self._address:
7079
raise OSError("I2C device not found at 0x{:02X}".format(addr))

tests/scenarios/bme280.yaml

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,23 @@ mock_registers:
1717
# config (default)
1818
0xF5: 0x00
1919

20+
# Calibration block 1: 0x88, 26 bytes
21+
# dig_T1=27504(u16) dig_T2=26435(s16) dig_T3=-1000(s16)
22+
# dig_P1=36381(u16) dig_P2=-10685(s16) dig_P3=3024(s16)
23+
# dig_P4=2855(s16) dig_P5=140(s16) dig_P6=-7(s16)
24+
# dig_P7=15500(s16) dig_P8=-14600(s16) dig_P9=6000(s16)
25+
# _=0 dig_H1=75(u8)
26+
0x88: [0x70, 0x6B, 0x43, 0x67, 0x18, 0xFC,
27+
0x1D, 0x8E, 0x43, 0xD6, 0xD0, 0x0B,
28+
0x27, 0x0B, 0x8C, 0x00, 0xF9, 0xFF,
29+
0x8C, 0x3C, 0xF8, 0xC6, 0x70, 0x17,
30+
0x00, 0x4B]
31+
32+
# Calibration block 2: 0xE1, 7 bytes
33+
# dig_H2=366(s16) dig_H3=0(u8)
34+
# dig_H4=306(s12) dig_H5=57(s12) dig_H6=30(s8)
35+
0xE1: [0x6E, 0x01, 0x00, 0x13, 0x92, 0x03, 0x1E]
36+
2037
tests:
2138
- name: "Verify chip ID register"
2239
action: read_register
@@ -47,10 +64,96 @@ tests:
4764
i2c.clear_write_log()
4865
dev.reset()
4966
log = i2c.get_write_log()
50-
# Should write reset command + ctrl_hum + ctrl_meas
5167
wrote_reset = any(reg == 0xE0 for reg, data in log)
5268
wrote_ctrl_hum = any(reg == 0xF2 for reg, data in log)
5369
wrote_ctrl_meas = any(reg == 0xF4 for reg, data in log)
5470
result = wrote_reset and wrote_ctrl_hum and wrote_ctrl_meas
5571
expect_true: true
5672
mode: [mock]
73+
74+
# ----- Calibration -----
75+
76+
- name: "Calibration data parsed correctly for temperature"
77+
action: script
78+
script: |
79+
result = (
80+
dev.dig_T1 == 27504
81+
and dev.dig_T2 == 26435
82+
and dev.dig_T3 == -1000
83+
)
84+
expect_true: true
85+
mode: [mock]
86+
87+
- name: "Calibration data parsed correctly for pressure"
88+
action: script
89+
script: |
90+
result = (
91+
dev.dig_P1 == 36381
92+
and dev.dig_P2 == -10685
93+
and dev.dig_P9 == 6000
94+
)
95+
expect_true: true
96+
mode: [mock]
97+
98+
- name: "Calibration data parsed correctly for humidity"
99+
action: script
100+
script: |
101+
result = (
102+
dev.dig_H1 == 75
103+
and dev.dig_H2 == 366
104+
and dev.dig_H3 == 0
105+
and dev.dig_H6 == 30
106+
)
107+
expect_true: true
108+
mode: [mock]
109+
110+
- name: "H4 and H5 cross-register parsing"
111+
action: script
112+
script: |
113+
# From mock: hum bytes [0x6E, 0x01, 0x00, 0x13, 0x92, 0x03, 0x1E]
114+
# H4 = (hum[3] << 4) | (hum[4] & 0x0F) = (0x13 << 4) | (0x92 & 0x0F) = 0x132 = 306, then sign
115+
# H5 = (hum[4] >> 4) | (hum[5] << 4) = (0x92 >> 4) | (0x03 << 4) = 0x09 | 0x30 = 0x39 = 57
116+
result = (
117+
dev.dig_H4 == 306
118+
and dev.dig_H5 == 57
119+
)
120+
expect_true: true
121+
mode: [mock]
122+
123+
- name: "t_fine initialized to zero"
124+
action: script
125+
script: |
126+
result = dev.t_fine == 0
127+
expect_true: true
128+
mode: [mock]
129+
130+
- name: "Init reads STATUS before calibration registers"
131+
action: script
132+
script: |
133+
# The full init I2C log should show:
134+
# 1. Read chip ID (0xD0)
135+
# 2. Read STATUS (0xF3) — _wait_boot before calibration
136+
# 3. Read calibration block 1 (0x88)
137+
# 4. Read calibration block 2 (0xE1)
138+
# 5. Write ctrl_hum (0xF2) + ctrl_meas (0xF4)
139+
# We re-instantiate to capture the full init sequence
140+
i2c.clear_read_log()
141+
from bme280 import BME280
142+
dev2 = BME280(i2c, address=0x76)
143+
log = i2c.get_read_log()
144+
regs_read = [reg for reg in log]
145+
# STATUS (0xF3) must appear before calibration (0x88)
146+
status_idx = None
147+
calib_idx = None
148+
for idx, reg in enumerate(regs_read):
149+
if reg == 0xF3 and status_idx is None:
150+
status_idx = idx
151+
if reg == 0x88 and calib_idx is None:
152+
calib_idx = idx
153+
result = (
154+
status_idx is not None
155+
and calib_idx is not None
156+
and status_idx < calib_idx
157+
)
158+
expect_true: true
159+
mode: [mock]

0 commit comments

Comments
 (0)