Browse Source

bh1750: configurable accuracy and sensitivity (mtreg)

amend b8921b9a89

* 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
pull/2569/head
Maxim Prokhorov 1 year ago
parent
commit
988a972484
3 changed files with 221 additions and 54 deletions
  1. +16
    -0
      code/espurna/config/sensors.h
  2. +2
    -0
      code/espurna/sensor.cpp
  3. +203
    -54
      code/espurna/sensors/BH1750Sensor.h

+ 16
- 0
code/espurna/config/sensors.h View File

@ -148,6 +148,22 @@
#define BH1750_ADDRESS 0x00 // 0x00 means auto
#endif
#ifndef BH1750_ACCURACY
#define BH1750_ACCURACY 1.2 // RAW value conversion ratio
// Allowed values are 0.96...1.44
#endif
#ifndef BH1750_SENSITIVITY
#define BH1750_SENSITIVITY 1.0 // Measurement sensitivity; value is derived from 'MTreg CURRENT'
// `SENSITIVITY = MTreg CURRENT / MTreg DEFAULT` (up to 2 decimal places)
// e.g. for MTreg allowed values of 31...254
// * 31 -> 0.45 (min)
// * 69 -> 1.0
// * 138 -> 2.0
// * 207 -> 3.0
// * 254 -> 3.68 (max)
#endif
#ifndef BH1750_MODE
#define BH1750_MODE BH1750_CONTINUOUS_HIGH_RES_MODE
#endif


+ 2
- 0
code/espurna/sensor.cpp View File

@ -2043,6 +2043,8 @@ void load() {
{
auto* sensor = new BH1750Sensor();
sensor->setAddress(BH1750_ADDRESS);
sensor->setAccuracy(BH1750_ACCURACY);
sensor->setSensitivity(BH1750_SENSITIVITY);
sensor->setMode(BH1750_MODE);
add(sensor);
}


+ 203
- 54
code/espurna/sensors/BH1750Sensor.h View File

@ -9,31 +9,96 @@
#include "I2CSensor.h"
#define BH1750_CONTINUOUS_HIGH_RES_MODE 0x10 // Start measurement at 1lx resolution. Measurement time is approx 120ms.
#define BH1750_CONTINUOUS_HIGH_RES_MODE_2 0x11 // Start measurement at 0.5lx resolution. Measurement time is approx 120ms.
#define BH1750_CONTINUOUS_LOW_RES_MODE 0x13 // Start measurement at 4lx resolution. Measurement time is approx 16ms.
#define BH1750_ONE_TIME_HIGH_RES_MODE 0x20 // Start measurement at 1lx resolution. Measurement time is approx 120ms.
// Device is automatically set to Power Down after measurement.
#define BH1750_ONE_TIME_HIGH_RES_MODE_2 0x21 // Start measurement at 0.5lx resolution. Measurement time is approx 120ms.
// Device is automatically set to Power Down after measurement.
#define BH1750_ONE_TIME_LOW_RES_MODE 0x23 // Start measurement at 1lx resolution. Measurement time is approx 120ms.
// Device is automatically set to Power Down after measurement.
static constexpr bool bh1750_is_mode2(unsigned char mode) {
return (mode == BH1750_CONTINUOUS_HIGH_RES_MODE_2) || (mode == BH1750_ONE_TIME_HIGH_RES_MODE_2);
}
// Start measurement at 1lx resolution. Measurement time is approx 120ms.
#define BH1750_CONTINUOUS_HIGH_RES_MODE BH1750Sensor::Mode::ContinuousHighRes
// Start measurement at 0.5lx resolution. Measurement time is approx 120ms.
#define BH1750_CONTINUOUS_HIGH_RES_MODE_2 BH1750Sensor::Mode::ContinuousHighRes2
// Start measurement at 4lx resolution. Measurement time is approx 16ms.
#define BH1750_CONTINUOUS_LOW_RES_MODE BH1750Sensor::Mode::ContinuousLowRes
// -//- as the above, but device is automatically set to Power Down after measurement.
#define BH1750_ONE_TIME_HIGH_RES_MODE BH1750Sensor::Mode::OneTimeHighRes
#define BH1750_ONE_TIME_HIGH_RES_MODE_2 BH1750Sensor::Mode::OneTimeHighRes2
#define BH1750_ONE_TIME_LOW_RES_MODE BH1750Sensor::Mode::OneTimeLowRes
class BH1750Sensor : public I2CSensor<> {
public:
enum class Mode {
ContinuousHighRes,
ContinuousHighRes2,
ContinuousLowRes,
OneTimeHighRes,
OneTimeHighRes2,
OneTimeLowRes,
};
private:
static constexpr bool is_mode2(Mode mode) {
return (mode == Mode::ContinuousHighRes2)
|| (mode == Mode::OneTimeHighRes2);
}
static constexpr uint8_t mode_to_reg(Mode mode) {
return (mode == Mode::ContinuousHighRes)
? 0b00010000
: (mode == Mode::ContinuousHighRes2)
? 0b00010001
: (mode == Mode::ContinuousLowRes)
? 0b00010011
: (mode == Mode::OneTimeHighRes)
? 0b00100000
: (mode == Mode::OneTimeHighRes2)
? 0b00100001
: (mode == Mode::OneTimeLowRes)
? 0b00100011
: 0;
}
static uint8_t sensitivity_to_mtime(double value) {
static constexpr double Default { 69.0 };
value = std::nearbyint(Default * value);
static constexpr double Min { 31.0 };
static constexpr double Max { 254.0 };
value = std::clamp(value, Min, Max);
return static_cast<uint8_t>(value);
}
struct MeasurementTime {
uint8_t low;
uint8_t high;
};
auto mtime_to_reg(uint8_t time) -> MeasurementTime {
MeasurementTime out;
static constexpr uint8_t Low { 0b01000000 };
out.low = (time >> 5) | Low; // aka MT[7,6,5]
void setMode(unsigned char mode) {
if (_mode == mode) return;
static constexpr uint8_t High { 0b01100000 };
out.high = (time & 0b11111) | High; // aka MT[4,3,2,1,0]
return out;
}
public:
void setMode(Mode mode) {
_mode = mode;
_dirty = true;
}
unsigned char getMode() const {
return _mode;
void setSensitivity(double sensitivity) {
static constexpr double Min { 0.45 };
static constexpr double Max { 3.68 };
_sensitivity = std::clamp(sensitivity, Min, Max);
}
void setAccuracy(double accuracy) {
static constexpr double Min { 0.96 };
static constexpr double Max { 1.44 };
_accuracy = std::clamp(accuracy, Min, Max);
}
// ---------------------------------------------------------------------
@ -50,23 +115,24 @@ class BH1750Sensor : public I2CSensor<> {
// Initialization method, must be idempotent
void begin() override {
if (!_dirty) {
return;
}
// I2C auto-discover
static constexpr uint8_t addresses[] {0x23, 0x5C};
auto address = findAndLock(addresses);
if (address == 0) {
return;
}
// Run configuration on next update
_run_configure = true;
_mtreg = mtime_to_reg(sensitivity_to_mtime(_sensitivity));
_modereg = mode_to_reg(_mode);
_init();
_wait();
_ready = true;
_dirty = false;
}
// Descriptive name of the sensor
@ -79,25 +145,67 @@ class BH1750Sensor : public I2CSensor<> {
// Type for slot # index
unsigned char type(unsigned char index) const override {
if (index == 0) return MAGNITUDE_LUX;
if (index == 0) {
return MAGNITUDE_LUX;
}
return MAGNITUDE_NONE;
}
// Loop-like method, call it in your main loop
virtual void tick() {
if (_wait_reading && (TimeSource::now() - _wait_start) > _wait_duration) {
_wait_reading = false;
}
}
// Pre-read hook (usually to populate registers with up-to-date data)
void pre() override {
_error = SENSOR_ERROR_OK;
_lux = _read(lockedAddress());
if (_wait_reading) {
_error = SENSOR_ERROR_NOT_READY;
return;
}
const auto lux = _read_lux(lockedAddress());
if (!lux.ok) {
_error = SENSOR_ERROR_NOT_READY;
_init();
_wait();
return;
}
_lux = 0;
if (lux.value > 0) {
_lux = _value(lux.value);
}
// repeatedly update mode b/c sensor
// is powering down after each reading
switch (_mode) {
case Mode::OneTimeHighRes:
case Mode::OneTimeHighRes2:
case Mode::OneTimeLowRes:
_init();
_wait();
break;
default:
break;
}
}
// Current value for slot # index
double value(unsigned char index) override {
if (index == 0) return _lux;
if (index == 0) {
return _lux;
}
return 0;
}
// Number of decimals for a unit (or -1 for default)
signed char decimals(espurna::sensor::Unit unit) const {
if (bh1750_is_mode2(_mode)) {
if (is_mode2(_mode)) {
return 2;
}
@ -105,45 +213,86 @@ class BH1750Sensor : public I2CSensor<> {
}
protected:
void _init(uint8_t address) {
i2c_write_uint8(address, _mtreg.low);
i2c_write_uint8(address, _mtreg.high);
i2c_write_uint8(address, _modereg);
}
void _init() {
_init(lockedAddress());
}
// Make sure to wait maximum amount of time specified at
// Electrical Characteristics (VCC 3.0V, DVI 3.0V, Ta 25C) pg. 2/17
// We take the maximum time values, not typival ones.
void _wait() {
double wait = 0;
switch (_mode) {
case Mode::ContinuousHighRes:
case Mode::ContinuousHighRes2:
case Mode::OneTimeHighRes:
case Mode::OneTimeHighRes2:
wait = 180.0;
break;
case Mode::ContinuousLowRes:
case Mode::OneTimeLowRes:
wait = 24.0;
break;
}
double _read(uint8_t address) {
// For one-shot modes reconfigure sensor & wait for conversion
if (_run_configure) {
wait *= _sensitivity;
wait = std::nearbyint(wait);
// Configure mode
i2c_write_uint8(address, _mode);
_wait_duration = TimeSource::duration(
static_cast<TimeSource::duration::rep>(wait));
_wait_start = TimeSource::now();
_wait_reading = true;
}
// According to datasheet
// conversion time is ~16ms for low resolution
// and ~120 for high resolution
// but more time is needed
espurna::time::blockingDelay(
espurna::duration::Milliseconds { (_mode & 0x02) ? 24 : 180 });
struct Lux {
uint16_t value;
bool ok;
};
// Keep on running configure each time if one-shot mode
_run_configure = (_mode & 0x20) > 0;
Lux _read_lux(uint8_t address) {
Lux out;
out.value = i2c_read_uint16(address);
out.ok = out.value != 0xffff;
}
return out;
}
uint16_t level = i2c_read_uint16(address);
if (level == 0xFFFF) {
_error = SENSOR_ERROR_CRC;
_run_configure = true;
return 0;
// pg. 11/17
// > The below formula is to calculate illuminance per 1 count.
// > H-reslution mode : Illuminance per 1 count ( lx / count ) = 1 / 1.2 *( 69 / X )
// > H-reslution mode2 : Illuminance per 1 count ( lx / count ) = 1 / 1.2 *( 69 / X ) / 2
// - 1.2 is default accuracy; we allow a custom value
// - `69 / X` is substituted by `1.0 / ACCURACY`
// - optional division by two when in H-resolution MODE2
double _value(uint16_t raw) {
auto value = static_cast<double>(raw) / _accuracy;
value *= (1.0 / _sensitivity);
if (is_mode2(_mode)) {
value /= 2.0;
}
// When using HIGH Mode2, value is halved
const auto multiplier = bh1750_is_mode2(_mode) ? 0.5 : 1.0;
// TODO also * MTreg?
return ((double)level / 1.2) * multiplier;
return value;
}
unsigned char _mode;
bool _run_configure = false;
using TimeSource = espurna::time::CoreClock;
TimeSource::time_point _wait_start;
TimeSource::duration _wait_duration;
bool _wait_reading = false;
MeasurementTime _mtreg;
uint8_t _modereg;
double _lux = 0;
Mode _mode;
double _sensitivity;
double _accuracy;
double _lux;
};
#endif // SENSOR_SUPPORT && BH1750_SUPPORT

Loading…
Cancel
Save