@ -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 1l x resolution . Measurement time is approx 120 ms .
# define BH1750_CONTINUOUS_HIGH_RES_MODE BH1750Sensor::Mode::ContinuousHighRes
/ / Start measurement at 0.5 lx resolution . Measurement time is approx 120 ms .
# define BH1750_CONTINUOUS_HIGH_RES_MODE_2 BH1750Sensor::Mode::ContinuousHighRes2
/ / Start measurement at 4l x resolution . Measurement time is approx 16 ms .
# 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 )
? 0 b00010000
: ( mode = = Mode : : ContinuousHighRes2 )
? 0 b00010001
: ( mode = = Mode : : ContinuousLowRes )
? 0 b00010011
: ( mode = = Mode : : OneTimeHighRes )
? 0 b00100000
: ( mode = = Mode : : OneTimeHighRes2 )
? 0 b00100001
: ( mode = = Mode : : OneTimeLowRes )
? 0 b00100011
: 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 { 0 b01000000 } ;
out . low = ( time > > 5 ) | Low ; / / aka MT [ 7 , 6 , 5 ]
void setMode ( unsigned char mode ) {
if ( _mode = = mode ) return ;
static constexpr uint8_t High { 0 b01100000 } ;
out . high = ( time & 0 b11111 ) | 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.0 V , DVI 3.0 V , Ta 25 C ) 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 ~ 16 ms 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