9
9
10
10
#include " I2CSensor.h"
11
11
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
25
25
26
26
class BH1750Sensor : public I2CSensor <> {
27
27
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]
28
80
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) {
31
89
_mode = mode;
32
- _dirty = true ;
33
90
}
34
91
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);
37
102
}
38
103
39
104
// ---------------------------------------------------------------------
@@ -50,23 +115,24 @@ class BH1750Sensor : public I2CSensor<> {
50
115
51
116
// Initialization method, must be idempotent
52
117
void begin () override {
53
-
54
118
if (!_dirty) {
55
119
return ;
56
120
}
57
121
58
- // I2C auto-discover
59
122
static constexpr uint8_t addresses[] {0x23 , 0x5C };
60
123
auto address = findAndLock (addresses);
61
124
if (address == 0 ) {
62
125
return ;
63
126
}
64
127
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
+
67
134
_ready = true ;
68
135
_dirty = false ;
69
-
70
136
}
71
137
72
138
// Descriptive name of the sensor
@@ -79,71 +145,154 @@ class BH1750Sensor : public I2CSensor<> {
79
145
80
146
// Type for slot # index
81
147
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
+
83
152
return MAGNITUDE_NONE;
84
153
}
85
154
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
+
86
162
// Pre-read hook (usually to populate registers with up-to-date data)
87
163
void pre () override {
88
164
_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
+ }
90
195
}
91
196
92
197
// Current value for slot # index
93
198
double value (unsigned char index) override {
94
- if (index == 0 ) return _lux;
199
+ if (index == 0 ) {
200
+ return _lux;
201
+ }
202
+
95
203
return 0 ;
96
204
}
97
205
98
206
// Number of decimals for a unit (or -1 for default)
99
207
signed char decimals (espurna::sensor::Unit unit) const {
100
- if (bh1750_is_mode2 (_mode)) {
208
+ if (is_mode2 (_mode)) {
101
209
return 2 ;
102
210
}
103
211
104
212
return 1 ;
105
213
}
106
214
107
215
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
+ }
108
243
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);
112
246
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
+ }
115
252
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
+ };
122
257
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 ;
125
262
126
- }
263
+ return out;
264
+ }
127
265
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 ;
133
278
}
134
279
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;
140
281
}
141
282
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;
144
290
145
- double _lux = 0 ;
291
+ Mode _mode;
292
+ double _sensitivity;
293
+ double _accuracy;
146
294
295
+ double _lux;
147
296
};
148
297
149
298
#endif // SENSOR_SUPPORT && BH1750_SUPPORT
0 commit comments