Skip to content

Commit 988a972

Browse files
committed
bh1750: configurable accuracy and sensitivity (mtreg)
amend b8921b9 * customize our multipliers. accuracy is a generic multiplier, mostly depends on the environment temperature (see datasheet). sensitivity is a ratio between the currently set mtreg value and the default e.g. for mtreg 31, sensitivity is .45; for mtreg 69, sensitivity is 1. * non-blocking pre(); since we have tick(), just wait until the reading is available instead of stopping everything else in the sensor loop * more specific error state when we are not ready to return a value
1 parent 5226668 commit 988a972

File tree

3 files changed

+221
-54
lines changed

3 files changed

+221
-54
lines changed

code/espurna/config/sensors.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,22 @@
148148
#define BH1750_ADDRESS 0x00 // 0x00 means auto
149149
#endif
150150

151+
#ifndef BH1750_ACCURACY
152+
#define BH1750_ACCURACY 1.2 // RAW value conversion ratio
153+
// Allowed values are 0.96...1.44
154+
#endif
155+
156+
#ifndef BH1750_SENSITIVITY
157+
#define BH1750_SENSITIVITY 1.0 // Measurement sensitivity; value is derived from 'MTreg CURRENT'
158+
// `SENSITIVITY = MTreg CURRENT / MTreg DEFAULT` (up to 2 decimal places)
159+
// e.g. for MTreg allowed values of 31...254
160+
// * 31 -> 0.45 (min)
161+
// * 69 -> 1.0
162+
// * 138 -> 2.0
163+
// * 207 -> 3.0
164+
// * 254 -> 3.68 (max)
165+
#endif
166+
151167
#ifndef BH1750_MODE
152168
#define BH1750_MODE BH1750_CONTINUOUS_HIGH_RES_MODE
153169
#endif

code/espurna/sensor.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2043,6 +2043,8 @@ void load() {
20432043
{
20442044
auto* sensor = new BH1750Sensor();
20452045
sensor->setAddress(BH1750_ADDRESS);
2046+
sensor->setAccuracy(BH1750_ACCURACY);
2047+
sensor->setSensitivity(BH1750_SENSITIVITY);
20462048
sensor->setMode(BH1750_MODE);
20472049
add(sensor);
20482050
}

code/espurna/sensors/BH1750Sensor.h

Lines changed: 203 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,96 @@
99

1010
#include "I2CSensor.h"
1111

12-
#define BH1750_CONTINUOUS_HIGH_RES_MODE 0x10 // Start measurement at 1lx resolution. Measurement time is approx 120ms.
13-
#define BH1750_CONTINUOUS_HIGH_RES_MODE_2 0x11 // Start measurement at 0.5lx resolution. Measurement time is approx 120ms.
14-
#define BH1750_CONTINUOUS_LOW_RES_MODE 0x13 // Start measurement at 4lx resolution. Measurement time is approx 16ms.
15-
#define BH1750_ONE_TIME_HIGH_RES_MODE 0x20 // Start measurement at 1lx resolution. Measurement time is approx 120ms.
16-
// Device is automatically set to Power Down after measurement.
17-
#define BH1750_ONE_TIME_HIGH_RES_MODE_2 0x21 // Start measurement at 0.5lx resolution. Measurement time is approx 120ms.
18-
// Device is automatically set to Power Down after measurement.
19-
#define BH1750_ONE_TIME_LOW_RES_MODE 0x23 // Start measurement at 1lx resolution. Measurement time is approx 120ms.
20-
// Device is automatically set to Power Down after measurement.
21-
22-
static constexpr bool bh1750_is_mode2(unsigned char mode) {
23-
return (mode == BH1750_CONTINUOUS_HIGH_RES_MODE_2) || (mode == BH1750_ONE_TIME_HIGH_RES_MODE_2);
24-
}
12+
// Start measurement at 1lx resolution. Measurement time is approx 120ms.
13+
#define BH1750_CONTINUOUS_HIGH_RES_MODE BH1750Sensor::Mode::ContinuousHighRes
14+
15+
// Start measurement at 0.5lx resolution. Measurement time is approx 120ms.
16+
#define BH1750_CONTINUOUS_HIGH_RES_MODE_2 BH1750Sensor::Mode::ContinuousHighRes2
17+
18+
// Start measurement at 4lx resolution. Measurement time is approx 16ms.
19+
#define BH1750_CONTINUOUS_LOW_RES_MODE BH1750Sensor::Mode::ContinuousLowRes
20+
21+
// -//- as the above, but device is automatically set to Power Down after measurement.
22+
#define BH1750_ONE_TIME_HIGH_RES_MODE BH1750Sensor::Mode::OneTimeHighRes
23+
#define BH1750_ONE_TIME_HIGH_RES_MODE_2 BH1750Sensor::Mode::OneTimeHighRes2
24+
#define BH1750_ONE_TIME_LOW_RES_MODE BH1750Sensor::Mode::OneTimeLowRes
2525

2626
class BH1750Sensor : public I2CSensor<> {
2727
public:
28+
enum class Mode {
29+
ContinuousHighRes,
30+
ContinuousHighRes2,
31+
ContinuousLowRes,
32+
OneTimeHighRes,
33+
OneTimeHighRes2,
34+
OneTimeLowRes,
35+
};
36+
37+
private:
38+
static constexpr bool is_mode2(Mode mode) {
39+
return (mode == Mode::ContinuousHighRes2)
40+
|| (mode == Mode::OneTimeHighRes2);
41+
}
42+
43+
static constexpr uint8_t mode_to_reg(Mode mode) {
44+
return (mode == Mode::ContinuousHighRes)
45+
? 0b00010000
46+
: (mode == Mode::ContinuousHighRes2)
47+
? 0b00010001
48+
: (mode == Mode::ContinuousLowRes)
49+
? 0b00010011
50+
: (mode == Mode::OneTimeHighRes)
51+
? 0b00100000
52+
: (mode == Mode::OneTimeHighRes2)
53+
? 0b00100001
54+
: (mode == Mode::OneTimeLowRes)
55+
? 0b00100011
56+
: 0;
57+
}
58+
59+
static uint8_t sensitivity_to_mtime(double value) {
60+
static constexpr double Default { 69.0 };
61+
value = std::nearbyint(Default * value);
62+
63+
static constexpr double Min { 31.0 };
64+
static constexpr double Max { 254.0 };
65+
value = std::clamp(value, Min, Max);
66+
67+
return static_cast<uint8_t>(value);
68+
}
69+
70+
struct MeasurementTime {
71+
uint8_t low;
72+
uint8_t high;
73+
};
74+
75+
auto mtime_to_reg(uint8_t time) -> MeasurementTime {
76+
MeasurementTime out;
77+
78+
static constexpr uint8_t Low { 0b01000000 };
79+
out.low = (time >> 5) | Low; // aka MT[7,6,5]
2880

29-
void setMode(unsigned char mode) {
30-
if (_mode == mode) return;
81+
static constexpr uint8_t High { 0b01100000 };
82+
out.high = (time & 0b11111) | High; // aka MT[4,3,2,1,0]
83+
84+
return out;
85+
}
86+
87+
public:
88+
void setMode(Mode mode) {
3189
_mode = mode;
32-
_dirty = true;
3390
}
3491

35-
unsigned char getMode() const {
36-
return _mode;
92+
void setSensitivity(double sensitivity) {
93+
static constexpr double Min { 0.45 };
94+
static constexpr double Max { 3.68 };
95+
_sensitivity = std::clamp(sensitivity, Min, Max);
96+
}
97+
98+
void setAccuracy(double accuracy) {
99+
static constexpr double Min { 0.96 };
100+
static constexpr double Max { 1.44 };
101+
_accuracy = std::clamp(accuracy, Min, Max);
37102
}
38103

39104
// ---------------------------------------------------------------------
@@ -50,23 +115,24 @@ class BH1750Sensor : public I2CSensor<> {
50115

51116
// Initialization method, must be idempotent
52117
void begin() override {
53-
54118
if (!_dirty) {
55119
return;
56120
}
57121

58-
// I2C auto-discover
59122
static constexpr uint8_t addresses[] {0x23, 0x5C};
60123
auto address = findAndLock(addresses);
61124
if (address == 0) {
62125
return;
63126
}
64127

65-
// Run configuration on next update
66-
_run_configure = true;
128+
_mtreg = mtime_to_reg(sensitivity_to_mtime(_sensitivity));
129+
_modereg = mode_to_reg(_mode);
130+
131+
_init();
132+
_wait();
133+
67134
_ready = true;
68135
_dirty = false;
69-
70136
}
71137

72138
// Descriptive name of the sensor
@@ -79,71 +145,154 @@ class BH1750Sensor : public I2CSensor<> {
79145

80146
// Type for slot # index
81147
unsigned char type(unsigned char index) const override {
82-
if (index == 0) return MAGNITUDE_LUX;
148+
if (index == 0) {
149+
return MAGNITUDE_LUX;
150+
}
151+
83152
return MAGNITUDE_NONE;
84153
}
85154

155+
// Loop-like method, call it in your main loop
156+
virtual void tick() {
157+
if (_wait_reading && (TimeSource::now() - _wait_start) > _wait_duration) {
158+
_wait_reading = false;
159+
}
160+
}
161+
86162
// Pre-read hook (usually to populate registers with up-to-date data)
87163
void pre() override {
88164
_error = SENSOR_ERROR_OK;
89-
_lux = _read(lockedAddress());
165+
if (_wait_reading) {
166+
_error = SENSOR_ERROR_NOT_READY;
167+
return;
168+
}
169+
170+
const auto lux = _read_lux(lockedAddress());
171+
if (!lux.ok) {
172+
_error = SENSOR_ERROR_NOT_READY;
173+
_init();
174+
_wait();
175+
return;
176+
}
177+
178+
_lux = 0;
179+
if (lux.value > 0) {
180+
_lux = _value(lux.value);
181+
}
182+
183+
// repeatedly update mode b/c sensor
184+
// is powering down after each reading
185+
switch (_mode) {
186+
case Mode::OneTimeHighRes:
187+
case Mode::OneTimeHighRes2:
188+
case Mode::OneTimeLowRes:
189+
_init();
190+
_wait();
191+
break;
192+
default:
193+
break;
194+
}
90195
}
91196

92197
// Current value for slot # index
93198
double value(unsigned char index) override {
94-
if (index == 0) return _lux;
199+
if (index == 0) {
200+
return _lux;
201+
}
202+
95203
return 0;
96204
}
97205

98206
// Number of decimals for a unit (or -1 for default)
99207
signed char decimals(espurna::sensor::Unit unit) const {
100-
if (bh1750_is_mode2(_mode)) {
208+
if (is_mode2(_mode)) {
101209
return 2;
102210
}
103211

104212
return 1;
105213
}
106214

107215
protected:
216+
void _init(uint8_t address) {
217+
i2c_write_uint8(address, _mtreg.low);
218+
i2c_write_uint8(address, _mtreg.high);
219+
i2c_write_uint8(address, _modereg);
220+
}
221+
222+
void _init() {
223+
_init(lockedAddress());
224+
}
225+
226+
// Make sure to wait maximum amount of time specified at
227+
// Electrical Characteristics (VCC 3.0V, DVI 3.0V, Ta 25C) pg. 2/17
228+
// We take the maximum time values, not typival ones.
229+
void _wait() {
230+
double wait = 0;
231+
switch (_mode) {
232+
case Mode::ContinuousHighRes:
233+
case Mode::ContinuousHighRes2:
234+
case Mode::OneTimeHighRes:
235+
case Mode::OneTimeHighRes2:
236+
wait = 180.0;
237+
break;
238+
case Mode::ContinuousLowRes:
239+
case Mode::OneTimeLowRes:
240+
wait = 24.0;
241+
break;
242+
}
108243

109-
double _read(uint8_t address) {
110-
// For one-shot modes reconfigure sensor & wait for conversion
111-
if (_run_configure) {
244+
wait *= _sensitivity;
245+
wait = std::nearbyint(wait);
112246

113-
// Configure mode
114-
i2c_write_uint8(address, _mode);
247+
_wait_duration = TimeSource::duration(
248+
static_cast<TimeSource::duration::rep>(wait));
249+
_wait_start = TimeSource::now();
250+
_wait_reading = true;
251+
}
115252

116-
// According to datasheet
117-
// conversion time is ~16ms for low resolution
118-
// and ~120 for high resolution
119-
// but more time is needed
120-
espurna::time::blockingDelay(
121-
espurna::duration::Milliseconds { (_mode & 0x02) ? 24 : 180 });
253+
struct Lux {
254+
uint16_t value;
255+
bool ok;
256+
};
122257

123-
// Keep on running configure each time if one-shot mode
124-
_run_configure = (_mode & 0x20) > 0;
258+
Lux _read_lux(uint8_t address) {
259+
Lux out;
260+
out.value = i2c_read_uint16(address);
261+
out.ok = out.value != 0xffff;
125262

126-
}
263+
return out;
264+
}
127265

128-
uint16_t level = i2c_read_uint16(address);
129-
if (level == 0xFFFF) {
130-
_error = SENSOR_ERROR_CRC;
131-
_run_configure = true;
132-
return 0;
266+
// pg. 11/17
267+
// > The below formula is to calculate illuminance per 1 count.
268+
// > H-reslution mode : Illuminance per 1 count ( lx / count ) = 1 / 1.2 *( 69 / X )
269+
// > H-reslution mode2 : Illuminance per 1 count ( lx / count ) = 1 / 1.2 *( 69 / X ) / 2
270+
// - 1.2 is default accuracy; we allow a custom value
271+
// - `69 / X` is substituted by `1.0 / ACCURACY`
272+
// - optional division by two when in H-resolution MODE2
273+
double _value(uint16_t raw) {
274+
auto value = static_cast<double>(raw) / _accuracy;
275+
value *= (1.0 / _sensitivity);
276+
if (is_mode2(_mode)) {
277+
value /= 2.0;
133278
}
134279

135-
// When using HIGH Mode2, value is halved
136-
const auto multiplier = bh1750_is_mode2(_mode) ? 0.5 : 1.0;
137-
138-
// TODO also * MTreg?
139-
return ((double)level / 1.2) * multiplier;
280+
return value;
140281
}
141282

142-
unsigned char _mode;
143-
bool _run_configure = false;
283+
using TimeSource = espurna::time::CoreClock;
284+
TimeSource::time_point _wait_start;
285+
TimeSource::duration _wait_duration;
286+
bool _wait_reading = false;
287+
288+
MeasurementTime _mtreg;
289+
uint8_t _modereg;
144290

145-
double _lux = 0;
291+
Mode _mode;
292+
double _sensitivity;
293+
double _accuracy;
146294

295+
double _lux;
147296
};
148297

149298
#endif // SENSOR_SUPPORT && BH1750_SUPPORT

0 commit comments

Comments
 (0)