├── .gitignore ├── LICENSE ├── README.md ├── examples ├── BareMinimum │ └── BareMinimum.ino ├── Non_blocking │ └── Non_blocking.ino ├── SampleRate │ └── SampleRate.ino ├── Saturated │ └── Saturated.ino ├── Sweep_MTreg │ └── Sweep_MTreg.ino ├── TestSuite │ └── TestSuite.ino └── TwoSensors │ └── TwoSensors.ino ├── keywords.txt ├── library.properties └── src ├── hp_BH1750.cpp └── hp_BH1750.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Arduino 2 | .development 3 | 4 | # Visual Studio code 5 | .vscode/ 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Stefan Armborst 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Light-Sensor BH1750 [![](https://img.shields.io/badge/iki-available-succsess?style=plastic&logo=wikipedia)](https://github.com/Starmbi/hp_BH1750/wiki) 2 | ## a high-performance non-blocking library 3 | ### the most comprehensive library avialable 4 | #### tested with Arduino and ESP8266 5 | ------------- 6 | 7 | 8 | 9 | ### Do you need... 10 | * the total control over this sensor? 11 | * exact timings? 12 | * fastest responses? 13 | * over to 100 or even 1000 samples per second? 14 | * an easy inteface with non-blocking readings? 15 | * inelligent autoranging functions? 16 | * extended range? 17 | 18 | ### Try this library! 19 | ----- 20 | 21 | ### Take a look at the comprehensive *[Wiki](../../wiki)!* 22 | 23 | ------------- 24 | ## Installation 25 | 26 | Click "Clone or download" -> "Download ZIP" button. 27 | 28 | - **(For Arduino >= 1.5.x)** Use the way above, or Library Manager. Open Arduino 29 | IDE, click `Sketch -> Include library -> Add .ZIP library ` and select the 30 | downloaded archive. 31 | 32 | - **(For Arduino < 1.5.x)** Extract the archive to 33 | ``/My Documents/Arduino/libraries/`` folder and rename it 34 | to `hp_BH1750`. Restart IDE. 35 | --------------- 36 | 37 | ## My intention to write this library: 38 | 39 | For a project I need the exact timings for the start and the end time of a conversion. 40 | This chip is well suited if you need a value every few seconds or minutes. 41 | While the sensor provides good results, the design is terrible and makes it nearly impossible to obtain the time of beginning either of ending of a measurement. 42 | To understand this problem you should know the working principle of this chip: 43 | 44 | ## Working principle of BH1750 45 | 46 | The sensor can measure in 3 different qualities: one ```low quality``` mode and two ```high quality``` modes. 47 | To enhance the range, the sampling time is adjustable from 31 to 254. 48 | However, this is not a measuring time in seconds or milliseconds, but it is called **M**easurement **T**ime **Reg**ister = ***MTreg***. 49 | At [auto ranging](#autoranging-function) you can read more about *MTreg*. 50 | 51 | So let's take a look at the [datasheet](https://www.mouser.com/datasheet/2/348/bh1750fvi-e-186247.pdf "BH1750") 52 | 53 | | Parameter | Symbol | Min. | Typ. | Max. | Units | Conditions | 54 | |:----------|:-------|-----:|-----:|-----:|-------|------------| 55 | | H-Resolution Mode Measurement Time | **t**HR- | | 120 | 180 | ms | | 56 | | L-Resolution Mode Measurement Time | **t**lR- | | 16 | 24 | ms | | 57 | 58 | These timings refer to the default *MTreg* value of 69. 59 | As you can see, for ```high quality``` we have a deviation between 120 and 180 milli seconds! Thats is a difference of 50%! 60 | 61 | ### This difference of time is chip dependend and will never change. 62 | 63 | If you bought a good chip, the time is close to 120 ms, if you bought a bad chip it is 180 ms. 64 | 65 | To be sure to always read the newest value we have to wait at least 180 ms at *MTreg* 69. 66 | Why should we wait 180 ms if we have a good chip that is finished after 120 ms? 67 | But how do we find out how good our chip really is? Unfortunately it is not that easy. 68 | 69 | * The first bad news: The chip will never notify us when there is a new value. 70 | * The second bad news: the last measured value **always remains in the data register**. 71 | 72 | So if you start a measurement and read out the value directly afterwards, you will *not* get a new value, but the value of the *last measurement* before that. 73 | The only way to get the result as fast as possible, is to ask the sensor until you get a different result. 74 | If you have a busy application this is no good solution, as communication over the I2C bus consumes a lot of time. 75 | And if the brightnesses are the same for successive measurements, you will never notice when you get a new result. 76 | 77 | ### And here comes one trick of the library: ### 78 | It is possible to reset the data register to zero (0) before starting a measurement. 79 | If we ask the sensor for a result, directly after starting a new measurement, we will get a result of zero. 80 | So we ask the sensor for a new value, until we get a value greater than zero. 81 | (Yes we need a little amount of light for this.) 82 | After getting a new result, we can stop the time since the start of the measurement. 83 | 84 | If we get a measurement time of 120 ms at *MTreg*=69, congratulations, we got a good chip. 85 | 86 | The data sheet describes that the ***MTreg* value is proportional to the measuring time**. 87 | 88 | So, with this true conversion time we can calculate the conversion time for every *MTreg*! But is this true? 89 | Here I made a few measurements with the trick above: 90 | 91 | ![](/../../wiki/media/time_vs_MTreg.png) 92 | 93 | At first glance, the formula seems to be right. 94 | We see that for both qualities (High/Low) the conversion time is proportional to the *MTreg*. 95 | But if we take a closer look, we notice that the two straight lines do not meet at the zero point, but 1.5 ms above it. 96 | Independent of the sampling time, my chip needs additional 1.5 ms to provide the value. Of course, the execution code also contributes to this by some microseconds. 97 | 98 | To take care of this offset we need 2 readings at different *MTreg's* for each quality (low & high). 99 | With this timings we can calculate the expected time for each combination of *MTreg* and quality! 100 | Don't worry, the function ```calibrateTimings()``` will do the job for us. 101 | 102 | After this function has been executed, we can measure at the fastest speed for each *MTReg* and quality! 103 | 104 | And here is a working example of non-blocking readings at the fastest possible time: 105 | ```C++ 106 | #include //include the library 107 | hp_BH1750 sensor; //define the sensor 108 | void setup() 109 | { 110 | sensor.begin(BH1750_TO_GROUND); //A: set address and init sensor 111 | sensor.calibrateTiming(); //B: calibrate the timings, about 855ms with a bad chip 112 | sensor.start(); //C: start the first measurement 113 | } 114 | void loop() 115 | { 116 | if (sensor.hasValue()) { //D: most important function of this library! 117 | float lux = sensor.getLux(); //E: read the result 118 | sensor.start(); //F: start next measurement 119 | } 120 | // do a lot of other stuff here G:// 121 | } 122 | ``` 123 | ### Some explanations to the code: 124 | ```At line A:``` We set the address (address pin BH1750_TO_GROUND or BH1750_TO_VCC) 125 | Also we init the timing parameters according to the datasheet to the most pessimistic values. 126 | This ensures that the sensor will works correctly even with no calibration. 127 | 128 | ```At line B:``` We calibrate the Sensor. For this we use by default the highest and lowest MTreg's (31 and 254) at both qualitys. 129 | You can change the two MTreg-values, if you want, for example ```sensor.calibrateTiming(50,150);``` 130 | Since this only needs to be done once for each chip, the values can be stored in the eeprom or set directly in the code after test measurements. This library offers the appropriate functions for this. 131 | The easiest way is to always calibrate the sensor in the *setup* section, as shown above. 132 | 133 | ```At line C:``` We start the measurement with default quality and *MTreg*. 134 | You can change this values, for example ```sensor.start(BH1750_QUALITY_HIGH, 100);``` 135 | 136 | ```At line D:``` Here comes the magic. At starting time the sensor sets a internal timer and calculates the conversion time for the given quality and *MTreg*. 137 | If you ask ```sensor.hasValue()``` the function will return immediately without reading a value if the calculated conversion time is not finished yet. This is **30 times faster** than asking the sensor! 138 | 139 | So we jump directly to ```line G:``` and can read other sensors, do some calculations, wait for keypresses and so on... 140 | Now we cycle into the loop until the calculated conversion time is over. Then the sensor will do a true read and delivers the result in line E:. 141 | 142 | If you are satisfied with a blocking read, the code becomes even easier: 143 | 144 | ```C++ 145 | void loop() 146 | { 147 | sensor.start(); //start measurement 148 | float lux = sensor.getLux(); //read the result 149 | 150 | } 151 | ``` 152 | 153 | As I described above, the sensor will only return with a result if it is greater than zero. 154 | But what if it is very dark and the result is really zero? 155 | In this case the sensor waits for an adjustable timeout and then outputs zero as the result. A timeout of 5-10 ms is sufficient. 156 | 157 | Another notable feature of this library is the 158 | ## Autoranging function 159 | Why autoranging? 160 | 161 | Here a table for comparison of the range and precision of different MTreg's and qualities: 162 | 163 | | Quality | MTreg | resolution lux | highest lux | time | 164 | |---------|------:|-------------:|-----------:|-------:| 165 | | LOW | 31 | 7.4 | 121557 | 7 | 166 | | LOW | 254 | 0.9 | 14836 | 59 | 167 | | HIGH | 31 | 1.85 | 121557 | 54 | 168 | | HIGH | 254 | 0.23 | 14836 | 442 | 169 | | HIGH2 | 31 | 0.93 | 60778 | 54 | 170 | | HIGH2 | 254 | 0.11 | 7418 | 442| 171 | 172 | Internally each lux value is calculated from a 16-bit value (two bytes). This covers a range from 0-65535 digits. 173 | You can read it with ```sensor.getRaw()``` instead or additional to ```sensor.getLux()```. 174 | 175 | At each *MTreg* the lowest value is always 0 Lux . 176 | Only the maximal lux value differs at different *MTreg's*. 177 | 178 | With the highest *MTReg* of 254 you can reach only **7418** Lux, but you have a high resolution of 7418/65535 = **0.11** Lux. 179 | With the lowest *MTreg* of 31 you can reach **60778** Lux, but only have a resolution of 60778/65535 = **0.93** Lux. 180 | 181 | If you need more then **60778** Lux you have to switch to from quality ```HIGH2```" to quality ```HIGH```. 182 | This doubles your range to **121557** Lux, but also halves the resolution. 183 | 184 | Now let's explain the difference between quality ```LOW``` and ```HIGH``` 185 | As you can see: quality ```LOW``` and quality ```HIGH``` have the same range (highest lux). 186 | But the conversion time of ```LOW``` is 7.5 times faster than ```HIGH``` (442 ms against 59ms at MTreg 254). 187 | The drawback is the resolution: ```LOW``` x is 4 times less sensitive: 188 | 189 | With quality ```HIGH``` you will read raw data like: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ... 190 | With quality ```LOW ``` you will read raw data like: 0, 0, 0, 0, 4, 4, 4, 4, 8, 8, ... 191 | 192 | But 4 times less sensitivity against 7.5 times faster conversion time is a good deal! 193 | 194 | ***To obtain the highest resolution you should change the MTreg and quality according to your brightness.*** 195 | 196 | After each measurement the *MTReg* should be adjusted, so that the measured brightness is at the upper limit of the *MTreg* range. 197 | But this can be dangerous: 198 | If the brightness increases, you are out of the range of your maximal value an the sensor is saturated. 199 | So it is better to go a few percent lower than the maximal value of the *MTreg*. 200 | To minimize the change, being overexposed, you can set the *MTreg* so that your value is in the middle of the range e.g to 50 %. 201 | 202 | Does it all sound pretty complicated? 203 | With one line of code all this can be done for you! 204 | 205 | ```C++ 206 | { 207 | if (sensor.hasValue()) { 208 | float lux = sensor.getLux(); 209 | sensor.adjustSettings(90); //new line! 210 | sensor.start(); 211 | } 212 | ``` 213 | 214 | Please note the new line ```sensor.adjustSettings(90);``` just before starting a new measurement. 215 | This is a very powerfull function. 216 | It takes the last measurement and calculates the new *MTReg* and quality so that we are in the desired range. 217 | This range is passed to the function as parameter, in this example, ```90``` %. 218 | 219 | If the function detects, that the last result was overexposed, it takes a short measurement (~10 ms) with the lowest quality and resolution and calculate from this value the new *Mtreg* and quality. This short measurement is blocking! 220 | 221 | If there are longer periods between the measurements, the last value is not so meaningful for a new calculation, because the brightness may have changed. In this case it would be better to alway's take a short measurement before the high resolution measurement. You will loose about 10 ms but at low intensities you do not have to repeat a 500 ms measurement. 222 | You can easily force this with a second parameter for the function: 223 | ```sensor.adjustSettings(90,TRUE);``` Here the declaration of the function: 224 | ```void adjustSettings(byte Percent = 50, bool forcePreShot=false);``` 225 | 226 | If you start the measurement in quality ```HIGH``` OR ```HIGH2``` this function will switch between this qualities if necessary. 227 | If you start the measurement in quality ```LOW``` it will stay at this quality because it asumes that you want the highest speed. 228 | -------------------------------------------------------------------------------- /examples/BareMinimum/BareMinimum.ino: -------------------------------------------------------------------------------- 1 | //for help look at: https://github.com/Starmbi/hp_BH1750/wiki 2 | #include 3 | #include // include the library 4 | hp_BH1750 BH1750; // create the sensor 5 | 6 | void setup() 7 | { 8 | // put your setup code here, to run once: 9 | Serial.begin(9600); 10 | bool avail = BH1750.begin(BH1750_TO_GROUND);// init the sensor with address pin connetcted to ground 11 | // result (bool) wil be be "false" if no sensor found 12 | if (!avail) { 13 | Serial.println("No BH1750 sensor found!"); 14 | while (true) {}; 15 | } 16 | } 17 | 18 | void loop() 19 | { 20 | // put your main code here, to run repeatedly: 21 | BH1750.start(); //starts a measurement 22 | float lux=BH1750.getLux(); // waits until a conversion finished 23 | Serial.println(lux); 24 | } 25 | -------------------------------------------------------------------------------- /examples/Non_blocking/Non_blocking.ino: -------------------------------------------------------------------------------- 1 | //for help look at: https://github.com/Starmbi/hp_BH1750/wiki 2 | #include 3 | #include //inlude the library 4 | hp_BH1750 BH1750; //create the sensor object 5 | 6 | void setup() 7 | { 8 | // put your setup code here, to run once: 9 | Serial.begin(9600); 10 | 11 | bool avail = BH1750.begin(BH1750_TO_GROUND); // will be false no sensor found 12 | // use BH1750_TO_GROUND or BH1750_TO_VCC depending how you wired the address pin of the sensor. 13 | if (!avail) { 14 | Serial.println("No BH1750 sensor found!"); 15 | while (true) {}; 16 | } 17 | 18 | // BH1750.calibrateTiming(); //uncomment this line if you want to speed up your sensor 19 | Serial.printf("conversion time: %dms\n", BH1750.getMtregTime()); 20 | 21 | BH1750.start(); //start the first measurement in setup 22 | } 23 | 24 | void loop() 25 | { 26 | // put your main code here, to run repeatedly: 27 | 28 | if (BH1750.hasValue() == true) { // non blocking reading 29 | float lux = BH1750.getLux(); 30 | Serial.println(lux); 31 | BH1750.start(); 32 | } 33 | // do a lot of other stuff here, while sensor is waiting for the result... 34 | } 35 | -------------------------------------------------------------------------------- /examples/SampleRate/SampleRate.ino: -------------------------------------------------------------------------------- 1 | // for help look at: https://github.com/Starmbi/hp_BH1750/wiki 2 | // to speed up the measurements you can change the BH1750_MTREG_LOW = 31 3 | // in the hp_BH1750.h file to a lower value 4 | 5 | #include 6 | #include // include the library 7 | 8 | //stores the first MAX_VAL samples in a array. 9 | const unsigned int MAX_VAL = 150; // for Arduino (low memory) 10 | // const unsigned int MAX_VAL = 1500; //for ESP8266 11 | 12 | unsigned int val[MAX_VAL]; // adjust the value to your free memory on the board. 13 | hp_BH1750 sens; 14 | void setup() 15 | { 16 | // put your setup code here, to run once: 17 | Wire.setClock(400000); // uncomment this line if you have problems with communication 18 | Serial.begin(9600); 19 | //Serial.begin(115200); // try this line for faster printing, uncomment the line above 20 | sens.begin(BH1750_TO_GROUND); // change to (BH1750_TO_VCC) if address pin connected to VCC 21 | sens.calibrateTiming(); // you need a little brightness for this 22 | } 23 | 24 | void loop() 25 | { 26 | // put your main code here, to run repeatedly: 27 | Serial.println("***********"); 28 | unsigned int t = millis() + 1000; 29 | unsigned int c = 0; 30 | while (millis() <= t) 31 | { 32 | sens.start(BH1750_QUALITY_LOW, 1); //will be adjusted to lowest allowed MTreg (default = 31) 33 | if (c < MAX_VAL) 34 | { 35 | val[c] = sens.getRaw(); 36 | } 37 | else 38 | { 39 | sens.getRaw(); 40 | } 41 | yield(); // feed the watchdog of ESP6682 42 | c++; 43 | } 44 | unsigned int readVal = c; 45 | if (readVal > MAX_VAL) 46 | c = MAX_VAL; 47 | // Serial.print(c); 48 | // Serial.print(char(9)); 49 | // Serial.println(sens.getRaw()); 50 | for (unsigned int i = 0; i < c; i++) 51 | { 52 | //Serial.print(i); 53 | Serial.print(val[i]); 54 | Serial.print(char(9)); 55 | if ((i + 1) % 10 == 0) 56 | Serial.println(""); 57 | yield(); // feed the watchdog of ESP6682 58 | } 59 | Serial.println(""); 60 | Serial.print(readVal); 61 | Serial.println(" Samples per second"); 62 | Serial.println(""); 63 | delay(1000); 64 | } 65 | -------------------------------------------------------------------------------- /examples/Saturated/Saturated.ino: -------------------------------------------------------------------------------- 1 | //for help look at: https://github.com/Starmbi/hp_BH1750/wiki 2 | // 3 | // Please apply a lot of light to the sensor (more than 7417 lux). 4 | // that is the maximum brightness, the sensor is capable at high sensitivity. 5 | // Please note, that the first measurement with low quality shows higher values. 6 | // The second measurement will not get higher , because it's already saturated. 7 | // With a hack you can calulate the brightness from the saturated result. 8 | // For this, you use the reduced conversion time of the sensor, that happens if the sensor is saturated. 9 | // Divide the estimated time thru the measured time: "BH1750.getMtregTime() / BH1750.getTime()" 10 | // Multipy this factor to the saturated Value. 11 | // Compare this result to the first measurement with low quality. 12 | // With this hack you will get even valid results on high quality, when the measurement with low quality is already saturated! 13 | // For best results, close the serial monitor and open the "serial plotter" with . 14 | 15 | #include 16 | #include 17 | hp_BH1750 BH1750; 18 | void setup() { 19 | // put your setup code here, to run once: 20 | Serial.begin(9600); 21 | BH1750.begin(BH1750_TO_GROUND);// change to BH1750_TO_VCC if address pin is connected to VCC. 22 | BH1750.calibrateTiming(); 23 | } 24 | 25 | void loop() { 26 | // put your main code here, to run repeatedly: 27 | BH1750.start(BH1750_QUALITY_LOW, 31); // starts a measurement at low quality 28 | Serial.print(BH1750.getLux()); // do a blocking read and waits until a result receives 29 | Serial.print(char(9)); 30 | BH1750.start(BH1750_QUALITY_HIGH2, 254); // starts a measurement with high quality but restricted range in brightness 31 | float val = BH1750.getLux(); 32 | Serial.print(val); 33 | Serial.print(char(9)); 34 | if (BH1750.saturated() == true){ 35 | val = val * BH1750.getMtregTime() / BH1750.getTime(); // here we calculate from a saturated sensor the brightness! 36 | } 37 | Serial.print(val); 38 | //Serial.print(char(9)); 39 | //Serial.print(sens.getTime()); 40 | Serial.println(""); 41 | } 42 | -------------------------------------------------------------------------------- /examples/Sweep_MTreg/Sweep_MTreg.ino: -------------------------------------------------------------------------------- 1 | // for help look at: https://github.com/Starmbi/hp_BH1750/wiki 2 | // 3 | // This demo tests if your sensor is suitable for lower MTreg-values than the manufacturer recommends. 4 | // In a loop, the default Mtreg of 69 is lowered to the lowest allowed MTreg of 31. 5 | // You will see a decreasing conversion time. 6 | // 7 | // If you want lower MTregs than 31, you have to hack this library! 8 | // 9 | // Please go to this library in your local library folder. 10 | // Often the path is: "C:\Users\\Documents\Arduino\libraries\hp_BH1750\src\". 11 | // Create a backup copy of the File "hp_BH1750.h" 12 | // Open the file "hp_BH1750.h" with a text editor and change the line: 13 | // "BH1750_MTREG_LOW = 31," to 14 | // "BH1750_MTREG_LOW = 1,". 15 | // Do not type the quotation marks! 16 | // Save the file and compile this sketch again. 17 | // Now you can check up to which MTReg your sensor works correctly. 18 | // It is quite unlikely that the sensor will be damaged, if operates outside its specification. 19 | // My sensors are working without problems with MTreg's down to 1! :-) 20 | // 21 | // To restore the original settings, change back to "BH1750_MTREG_LOW = 31,", 22 | // or restore the "hp_BH1750.h" with your backup copy. 23 | // You have to compile this sketch again. 24 | 25 | #include 26 | #include // include library 27 | 28 | hp_BH1750 BH1750; // create sensor object 29 | void setup() { 30 | // put your setup code here, to run once: 31 | Serial.begin(9600); 32 | BH1750.begin(BH1750_TO_GROUND); 33 | BH1750.calibrateTiming(); 34 | } 35 | 36 | void loop() { 37 | // put your main code here, to run repeatedly: 38 | Serial.println("***********"); 39 | for (int i = 69; i >=(int)BH1750_MTREG_LOW; i--) { 40 | BH1750.start(BH1750_QUALITY_LOW, i); 41 | float lux = BH1750.getLux(); 42 | Serial.print(BH1750.getMtreg()); 43 | Serial.print(char(9)); 44 | Serial.print(lux); 45 | Serial.print(" Lux "); 46 | Serial.print(char(9)); 47 | Serial.print(BH1750.getTime()); 48 | Serial.println(" ms."); 49 | delay(100); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/TestSuite/TestSuite.ino: -------------------------------------------------------------------------------- 1 | // for help look at: https://github.com/Starmbi/hp_BH1750/wiki 2 | // to speed up the measurements, please use a higher baud rate than the default 9600 baud. 3 | #include 4 | #include 5 | hp_BH1750 BH1750; 6 | 7 | unsigned int value; 8 | unsigned long loops = 0; 9 | byte percent = 50; 10 | byte mtreg = BH1750_MTREG_DEFAULT; 11 | bool show = true; 12 | bool fsens = false; 13 | bool preShot = false; 14 | bool autoQ = false; 15 | char buf[150]; 16 | BH1750Timing ti; 17 | BH1750Address addr = BH1750_TO_GROUND; 18 | void printHelp() 19 | { 20 | Serial.println(F("*********************************************************************************************")); 21 | Serial.println(F("Enter all commands in the upper field of the serial monitor and press to submit.")); 22 | Serial.println(F("")); 23 | Serial.println(F("Enter \"h\" to see this help again! ")); 24 | Serial.println(F("")); 25 | Serial.println(F("Enter \"m\" followed by a number between 31 to 254, to change the quality")); 26 | Serial.println(F("Enter \"c\" to calibrate the sensor timing")); 27 | Serial.println(F("Enter \"i\" to init the sensor to factory timing")); 28 | Serial.println(F("Enter \"q\" followed by a number between 1 to 3, to change the quality")); 29 | Serial.println(F("Enter \"a\" to toggle between fixed MTreg and AUTORANGE")); 30 | Serial.println(F("Enter \"s\" to toggle between all informations or only for Lux. Try serial plotter!")); 31 | Serial.println(F("Enter \"o\" with a positive or negative number to change the time offset")); 32 | Serial.println(F("Enter \"t\" followed by a number to change the timeout in ms.")); 33 | Serial.println(F("Enter \"f\" to toggle between forced readings or only waiting for the estimated conversion time")); 34 | Serial.println(F("Enter \"r\" to toggle between forced preShots in autorange mode")); 35 | Serial.print(F("*********************************************************************************************")); 36 | waitKeyPress(); 37 | } 38 | 39 | void busyDelay(unsigned int ms) 40 | { 41 | unsigned long t = millis() + ms; 42 | while (millis() < t) 43 | BH1750.hasValue(); 44 | } 45 | 46 | void flushQueue(unsigned int timeout) 47 | { 48 | while (Serial.available()) 49 | Serial.read(); 50 | busyDelay(timeout); 51 | while (Serial.available()) 52 | { 53 | Serial.read(); 54 | busyDelay(timeout); 55 | } 56 | } 57 | 58 | void waitKeyPress() 59 | { 60 | Serial.println(F("")); 61 | Serial.println(F("Press any key and to continue.")); 62 | flushQueue(0); 63 | while (Serial.available() == 0) 64 | { //wait until keypressed 65 | } 66 | flushQueue(3); 67 | } 68 | 69 | String getQual() 70 | { 71 | BH1750Quality q = BH1750.getQuality(); 72 | switch (q) 73 | { 74 | case BH1750_QUALITY_LOW: 75 | return "Low "; 76 | break; 77 | case BH1750_QUALITY_HIGH: 78 | return "High "; 79 | break; 80 | case BH1750_QUALITY_HIGH2: 81 | default: 82 | return "High2"; 83 | break; 84 | } 85 | } 86 | 87 | void printCalib() 88 | { 89 | ti = BH1750.getTiming(); 90 | sprintf(buf, "MTregs:\t\t %3i %3i", ti.mtregLow, ti.mtregHigh); 91 | Serial.println(buf); 92 | sprintf(buf, "Low quality:\t %3i %3i ms.", ti.mtregLow_qualityLow, ti.mtregLow_qualityHigh); 93 | Serial.println(buf); 94 | sprintf(buf, "High quality:\t %3i %3i ms.", ti.mtregHigh_qualityLow, ti.mtregHigh_qualityHigh); 95 | Serial.println(buf); 96 | Serial.println(""); 97 | } 98 | 99 | void setup() 100 | { 101 | Serial.begin(9600); 102 | Serial.println(""); 103 | if (BH1750.begin(addr) == false) 104 | { 105 | Serial.println("Sensor not found"); 106 | } 107 | 108 | while (Serial.available() == 0) 109 | { 110 | Serial.println("enter any key and press to continue"); 111 | delay(1000); 112 | } 113 | printHelp(); 114 | 115 | //BH1750.calibrateTiming(); //you can calibrate the sensor-timings, every time with pressing 116 | BH1750.start(BH1750_QUALITY_HIGH2, mtreg); 117 | } 118 | 119 | void loop() 120 | { 121 | lookSerial(); 122 | loops++; 123 | if (BH1750.hasValue(fsens)) 124 | { 125 | if (show == true) 126 | { 127 | char q[6]; 128 | getQual().toCharArray(q, 6); 129 | unsigned long t = (float)loops / (float)BH1750.getTime() * 1000; 130 | sprintf(buf, "Loops/sec %8lu | Reads %4u | T calc/is %4u %4u | Timeout %3i | Offset %4i | Quality %s | MTreg %3u | Raw %5u | Lux ", 131 | t, BH1750.getReads(), BH1750.getMtregTime(), BH1750.getTime(), BH1750.getTimeout(), BH1750.getTimeOffset(), q, BH1750.getMtreg(), BH1750.getRaw()); 132 | Serial.print(buf); 133 | dtostrf(BH1750.getLux(), 9, 2, buf); 134 | Serial.print(buf); 135 | if (BH1750.saturated() == true) { 136 | Serial.print(" *"); 137 | } 138 | else 139 | { 140 | Serial.print(" "); 141 | } 142 | Serial.print("| "); 143 | if (fsens == true) 144 | { 145 | Serial.print("F"); 146 | } 147 | else 148 | Serial.print("-"); 149 | if (autoQ == true) 150 | { 151 | Serial.print(" A "); 152 | Serial.print(BH1750.getPercent()); 153 | Serial.print("%"); 154 | if (preShot == true) Serial.print(" pS"); 155 | } 156 | } 157 | else 158 | { 159 | Serial.print(BH1750.getLux()); 160 | } 161 | Serial.println(""); 162 | if (autoQ) 163 | BH1750.adjustSettings(percent, preShot); 164 | if (!BH1750.start()) 165 | Serial.println("No sensor found"); 166 | loops = 0; 167 | } 168 | } 169 | 170 | void lookSerial() 171 | { 172 | BH1750Quality mode; 173 | int v; 174 | BH1750Timing t; 175 | Serial.setTimeout(3); 176 | if (Serial.available() > 0) 177 | { 178 | busyDelay(3); 179 | //Serial.println("----------"); 180 | v = Serial.read(); 181 | if (v > 96) 182 | { //lowercase a 183 | byte lc; 184 | switch (v) 185 | { 186 | case 104: //h = help 187 | printHelp(); 188 | break; 189 | case 109: //m setMtreg 190 | v = Serial.parseInt(); 191 | if (v < 255 && v > 0) 192 | BH1750.writeMtreg(v); 193 | break; 194 | case 105: //i init 195 | BH1750.begin(addr); 196 | break; 197 | case 112: // p set percentage for autorange 198 | percent = Serial.parseInt(); 199 | break; 200 | case 113: //q = sets BH1750Quality 201 | v = Serial.parseInt(); 202 | if (v == 1) 203 | mode = BH1750_QUALITY_LOW; 204 | if (v == 2) 205 | mode = BH1750_QUALITY_HIGH; 206 | if (v == 3) 207 | mode = BH1750_QUALITY_HIGH2; 208 | BH1750.setQuality(mode); 209 | break; 210 | case 97: //a toggle autoranging on and off 211 | autoQ = !autoQ; 212 | break; 213 | case 99: //c calibrates the timing of the sensor 214 | ti = BH1750.getTiming(); 215 | Serial.println(F("before Calibration")); 216 | printCalib(); 217 | lc = BH1750.calibrateTiming(); // lc<2 = Calibration accepted 218 | switch (lc) 219 | { 220 | case BH1750_CAL_OK: 221 | Serial.println("Calibration succsessfull"); //0 222 | break; 223 | case BH1750_CAL_MTREG_CHANGED: 224 | Serial.print("Calibration partial succsessfull, MTREG adjustet to "); //1 225 | t = BH1750.getTiming(); 226 | Serial.println(t.mtregHigh); 227 | break; 228 | case BH1750_CAL_TOO_BRIGHT: 229 | Serial.println("Calibration failed, too bright"); //2 230 | break; 231 | case BH1750_CAL_TOO_DARK: 232 | Serial.println("Calibration failed, too dark"); //3 233 | break; 234 | case BH1750_CAL_COMMUNICATION_ERROR: 235 | Serial.println("Communication Error"); //4 236 | break; 237 | } 238 | Serial.println(F("after Calibration")); 239 | printCalib(); 240 | waitKeyPress(); 241 | BH1750.start(); 242 | break; 243 | case 111: // o change the time offset in ms. 244 | v = Serial.parseInt(); 245 | BH1750.setTimeOffset(v); 246 | break; 247 | case 116: //t = timeout 248 | v = Serial.parseInt(); 249 | BH1750.setTimeout(v); 250 | break; 251 | case 102: //f force true readings and endabled preShot in auto ranging mode 252 | v = Serial.parseInt(); 253 | fsens = !fsens; 254 | break; 255 | case 114: //s 256 | preShot = !preShot; 257 | break; 258 | case 115: //s 259 | show = !show; 260 | break; 261 | } 262 | } 263 | flushQueue(3); 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /examples/TwoSensors/TwoSensors.ino: -------------------------------------------------------------------------------- 1 | //for help look at: https://github.com/Starmbi/hp_BH1750/wiki 2 | // 3 | // This is an example of non blocking reads with two sensors. 4 | // 5 | // If you work with 2 (or more) sensors, you have to consider, 6 | // that each sensor can have a different sampling time, even at the same settings. 7 | // To keep the sensors synchronized, you have to wait for the slowest sensor, 8 | // before you start the next measurements. 9 | // If you look into the loop, you will find a somewhat complicated query, 10 | // if the sensors have finished their measurement. 11 | // You can achieve the same behaviour, if you uncomment the line with "//**" and 12 | // comment the lines with "//*", if you only need the brightness of the two sensors. 13 | // But if you also need the conversion time of the two sensors, this shorter way will fail! 14 | // 15 | // look at the line: 16 | // if ((sens1.hasValue(forceReading) == true) && (sens2.hasValue(forceReading) == true)) { 17 | // 18 | // If sens1 has no value, sen2 will never be asked because the query is interrupted at this point! 19 | // At C++, if the first condition of an AND operation is already false, the second condition is never executed. 20 | // To get the timings of the sensorst, this will only work, if sens2 is the slower sensor. 21 | // To avoid that we have to force both querys with: 22 | // ready1 = sens1.hasValue(forceReading); //* 23 | // ready2 = sens2.hasValue(forceReading); //* 24 | 25 | 26 | #include 27 | #include 28 | 29 | hp_BH1750 sens1; // create first sensor 30 | hp_BH1750 sens2; // create second sensor 31 | 32 | unsigned int lux1, lux2; // hold the results 33 | bool ready1, ready2; // measurement finished? 34 | bool forceReading; // force readings and do not only wait for estimated measurement time. 35 | 36 | 37 | void setup() { 38 | forceReading = true; // also try false 39 | Serial.begin(9600); 40 | Serial.println(""); 41 | sens1.begin(BH1750_TO_VCC); // adress pin is connected to VCC (5V or 3.3V) 42 | sens2.begin(BH1750_TO_GROUND); // adress pin is connected to ground 43 | sens1.calibrateTiming(); // Calibrate for fastest conversion time 44 | sens2.calibrateTiming(); // Calibrate for fastest conversion time 45 | 46 | sens1.start(BH1750_QUALITY_HIGH2, BH1750_MTREG_DEFAULT); // same settings for both sensors 47 | sens2.start(BH1750_QUALITY_HIGH2, BH1750_MTREG_DEFAULT); 48 | } 49 | 50 | void loop() { 51 | ready1 = sens1.hasValue(forceReading); //* 52 | ready2 = sens2.hasValue(forceReading); //* 53 | if ((ready1 == true) && (ready2 == true)) { //* when both seniors have completed their measurement, 54 | //if ((sens1.hasValue(forceReading) == true) && (sens2.hasValue(forceReading) == true)) { //** 55 | 56 | lux1 = sens1.getLux(); 57 | lux2 = sens2.getLux(); 58 | Serial.print(lux1); 59 | Serial.print("\t"); 60 | Serial.println(lux2); 61 | sens1.start(); // start again after measuremnt is finished 62 | sens2.start(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | hp_BH1750 KEYWORD1 2 | BH1750Quality LITERAL1 3 | BH1750_QUALITY_HIGH LITERAL1 4 | BH1750_QUALITY_HIGH2 LITERAL1 5 | BH1750_QUALITY_LOW LITERAL1 6 | 7 | BH1750CalResult LITERAL1 8 | BH1750_CAL_OK LITERAL1 9 | BH1750_CAL_MTREG_CHANGED LITERAL1 10 | BH1750_CAL_TOO_BRIGHT LITERAL1 11 | BH1750_CAL_TOO_DARK LITERAL1 12 | BH1750_CAL_COMMUNICATION_ERROR LITERAL1 13 | 14 | BH1750Timing LITERAL1 15 | mtregLow LITERAL1 16 | mtregHigh LITERAL1 17 | mtregLow_qualityHigh LITERAL1 18 | mtregHigh_qualityHigh LITERAL1 19 | mtregLow_qualityLow LITERAL1 20 | mtregHigh_qualityLow LITERAL1 21 | 22 | BH1750MtregLimit LITERAL1 23 | BH1750_MTREG_LOW LITERAL1 24 | BH1750_MTREG_HIGH LITERAL1 25 | BH1750_MTREG_DEFAULT LITERAL1 26 | 27 | BH1750Address LITERAL1 28 | BH1750_TO_GROUND LITERAL1 29 | BH1750_TO_VCC LITERAL1 30 | 31 | begin KEYWORD2 32 | powerOff KEYWORD2 33 | powerOn KEYWORD2 34 | reset KEYWORD2 35 | 36 | writeMtreg KEYWORD2 37 | setQuality KEYWORD2 38 | calibrateTiming KEYWORD2 39 | start KEYWORD2 40 | hasValue KEYWORD2 41 | processed KEYWORD2 42 | saturated KEYWORD2 43 | getLux KEYWORD2 44 | calcLux KEYWORD2 45 | luxFactor KEYWORD2 46 | setTiming KEYWORD2 47 | getTiming KEYWORD2 48 | getQuality KEYWORD2 49 | setTimeout KEYWORD2 50 | setTimeOffset KEYWORD2 51 | getTimeOffset KEYWORD2 52 | getTimeout KEYWORD2 53 | getMtreg KEYWORD2 54 | convertTimeToMtreg KEYWORD2 55 | getPercent KEYWORD2 56 | getRaw KEYWORD2 57 | getReads KEYWORD2 58 | getTime KEYWORD2 59 | getMtregTime KEYWORD2 60 | adjustSettings KEYWORD2 61 | calcSettings KEYWORD2 -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=hp_BH1750 2 | version=1.0.2 3 | author=Stefan Armborst 4 | maintainer=Stefan Armborst 5 | sentence=Digital light sensor breakout boards containing the BH1750FVI IC 6 | paragraph=high performance non-blocking BH1750 library 7 | category=Sensors 8 | url=https://github.com/Starmbi/hp_BH1750 9 | architectures=* 10 | -------------------------------------------------------------------------------- /src/hp_BH1750.cpp: -------------------------------------------------------------------------------- 1 | // This is a library for the light sensor BH1750 2 | // Datasheet https://www.mouser.com/datasheet/2/348/bh1750fvi-e-186247.pdf 3 | // Donwnload library at https://github.com/Starmbi/hp_BH1750 4 | // Help and infos are provided at https://github.com/Starmbi/hp_BH1750/wiki 5 | // Copyright (c) Stefan Armborst, 2020 6 | 7 | #include 8 | #include 9 | 10 | //******************************************************************************************** 11 | // standard constructor 12 | 13 | hp_BH1750::hp_BH1750() 14 | { 15 | } 16 | 17 | //******************************************************************************************** 18 | // Set address BH1750_TO_GROUND = 0x23 or BH1750_TO_VCC = 0x5C and connect sensor a compatible wire object 19 | // that can be one of the two wire objects of ESP 32 or a software wire object. 20 | // Set timing parameters to safe values as stated in datasheet 21 | 22 | bool hp_BH1750::begin(byte address, TwoWire *myWire) 23 | { 24 | _wire = myWire; 25 | _wire->begin(); // Initialisation of wire object with standard SDA/SCL lines 26 | _address = address; // Store one of the two available addresses 27 | _mtreg = BH1750_MTREG_DEFAULT; // Default sensitivity 28 | _quality = BH1750_QUALITY_HIGH2; // Sets quality to most sensitive mode (recommend, exept it is really bright) 29 | _qualFak = 0.5; // Is used for lux calculation (is 1 for BH1750_QUALITY_HIGH and BH1750_QUALITY_LOW) 30 | _offset = 0; // See "setOffset" 31 | _timeout = 10; // See "setTimeOut" 32 | 33 | _timing.mtregLow = BH1750_MTREG_LOW; // Use lowest sensitivity for calibrateTiming 34 | _timing.mtregHigh = BH1750_MTREG_HIGH; // .. and then use highest sensitivity for calibrateTiming 35 | _timing.mtregLow_qualityHigh = 81; // Most pessimistic timing data from datasheet 36 | _timing.mtregHigh_qualityHigh = 663; 37 | _timing.mtregLow_qualityLow = 11; 38 | _timing.mtregHigh_qualityLow = 89; 39 | return writeMtreg(BH1750_MTREG_DEFAULT); // Set standard sensitivity 40 | } 41 | //******************************************************************************************** 42 | // Returns the current quality BH1750_QUALITY_HIGH = 0x20, BH1750_QUALITY_HIGH2 = 0x21, BH1750_QUALITY_LOW = 0x23 43 | 44 | BH1750Quality hp_BH1750::getQuality() const 45 | { 46 | return _quality; 47 | } 48 | 49 | //******************************************************************************************** 50 | // Awakes the sensor from sleeping mode (only used before reset) 51 | // Is not neccessary before reading a value or starting measurement 52 | 53 | bool hp_BH1750::powerOn() 54 | { 55 | return writeByte(0x1); 56 | } 57 | 58 | //******************************************************************************************** 59 | // For low power consuming. This mode is entered automatically if a measurement is finished 60 | 61 | bool hp_BH1750::powerOff() 62 | { 63 | return writeByte(0x0); 64 | } 65 | 66 | //******************************************************************************************** 67 | // This sets the previous measurement result to zero (0) 68 | // Is only working after powerOn command (already included in this function) 69 | 70 | bool hp_BH1750::reset() 71 | { 72 | if (powerOn() == false) 73 | return false; 74 | return writeByte(0x7); 75 | } 76 | 77 | //******************************************************************************************** 78 | // Private function. Sends command to sensor 79 | 80 | bool hp_BH1750::writeByte(byte b) 81 | { 82 | _wire->beginTransmission(_address); 83 | _wire->write(b); 84 | return (_wire->endTransmission() == 0); 85 | } 86 | 87 | //******************************************************************************************** 88 | // Private function. Adjust mtreg to a valid range 89 | 90 | byte hp_BH1750::checkMtreg(byte mtreg) 91 | { 92 | if (mtreg < BH1750_MTREG_LOW) 93 | { 94 | mtreg = BH1750_MTREG_LOW; 95 | } 96 | if (mtreg > BH1750_MTREG_HIGH) 97 | { 98 | mtreg = BH1750_MTREG_HIGH; 99 | } 100 | return mtreg; 101 | } 102 | 103 | //******************************************************************************************** 104 | // Start a single shot measurement with given mtreg and quality 105 | 106 | bool hp_BH1750::start() 107 | { 108 | reset(); // Reset the last result in data register to zero (0) 109 | bool result = writeByte(_quality); 110 | luxCache = (69.0 / _mtreg) * _qualFak; 111 | _startMillis = millis(); // Stores the start time 112 | _resultMillis = _startMillis + _mtregTime + _offset; // Add the pre-calculated conversion time to start time, 113 | _timeoutMillis = _startMillis + _mtregTime + _timeout; // to predict the time when conversion should be finished 114 | _nReads = 0; // Reset count for true readings to the sensor 115 | _value = 0; // Reset last result 116 | _time = 0; // Reset last measured conversion time 117 | _processed = false; // Value not readed by user 118 | return result; 119 | } 120 | 121 | //******************************************************************************************** 122 | // Start measurement and set quality and sensitivity 123 | bool hp_BH1750::start(BH1750Quality quality, byte mtreg) 124 | { 125 | setQuality(quality); 126 | mtreg = checkMtreg(mtreg); 127 | if (mtreg != _mtreg) 128 | { 129 | _mtregTime = getMtregTime(mtreg); 130 | writeMtreg(mtreg); // Mtreg is only send to sensor if it is different from last measurement, 131 | } // because mtreg is stored in the chip 132 | return start(); 133 | } 134 | 135 | //******************************************************************************************** 136 | // Start 4 Measurements with different sensitivities and qualities and measure each conversion time 137 | // 2 measurements with low quality and 2 measurements with high quality 138 | // For each quality we use the lowest and highest sensitivity 139 | // This calibration needs about one second to execute 140 | 141 | byte hp_BH1750::calibrateTiming(byte mtregHigh, byte mtregLow) 142 | { 143 | // Store current values for restore if calibration failed 144 | 145 | BH1750Quality orgQ = _quality; 146 | byte orgM = _mtreg; 147 | BH1750Timing orgT = _timing; 148 | BH1750Timing nt; 149 | 150 | if (!begin(_address)) 151 | { 152 | _timing = orgT; 153 | setQuality(orgQ); 154 | _mtreg = orgM; 155 | _mtregTime = getMtregTime(); 156 | return BH1750_CAL_COMMUNICATION_ERROR; // Set timing to most pessimistic timing 157 | } 158 | 159 | _mtreg = 0; // A non existing value, to force a new calculation 160 | nt.mtregHigh = mtregHigh; // Set highest and lowest senitivity to the empty mtreg object 161 | nt.mtregLow = mtregLow; 162 | unsigned int time = readChange(nt.mtregHigh, BH1750_QUALITY_LOW, false); // Start measurement and return after conversation 163 | int newMtreg = mtregHigh; // Set highest sensitivity for first measurement 164 | if (_value > 0) // We found a valid value and can continue 165 | { 166 | if (_value == BH1750_SATURATED) // ..but unfortunately the light was to bright 167 | { 168 | nt.mtregLow_qualityLow = readChange(nt.mtregLow, BH1750_QUALITY_LOW, false); // ..so we measure at lowest sensitvity 169 | if (_value == BH1750_SATURATED) 170 | { // even lowest senitivity saturated = much to bright! 171 | _timing = orgT; 172 | writeMtreg(orgM); 173 | setQuality(orgQ); 174 | return BH1750_CAL_TOO_BRIGHT; 175 | } 176 | newMtreg = (float)BH1750_SATURATED / (_value * 1.1) * BH1750_MTREG_LOW + 0.5; // ..and calculate the highest possible sensitivity without saturation 177 | if (newMtreg > BH1750_MTREG_HIGH) 178 | newMtreg = BH1750_MTREG_HIGH; 179 | nt.mtregHigh = (byte)newMtreg; 180 | nt.mtregHigh_qualityLow = readChange(nt.mtregHigh, BH1750_QUALITY_LOW, false); // Now we repeat the measurement at high sensitivity with the adjusted mtreg 181 | } // saturated 182 | else 183 | { // not saturated 184 | nt.mtregHigh_qualityLow = time; 185 | // OK, first measurement was not saturated, so we continue 186 | // Here, for low sensitivity we DO NOT reset the sensor before measuring 187 | // With this trick we can even detect a value of zero what can easiely happen at low sensitvity 188 | 189 | nt.mtregLow_qualityLow = readChange(nt.mtregLow, BH1750_QUALITY_LOW, true); 190 | } 191 | // MtregHigh>0; 192 | // After measuring in Low-mode, we switch to high quality and repeat the two measurements with low and high sensitvity 193 | 194 | nt.mtregHigh_qualityHigh = readChange(nt.mtregHigh, BH1750_QUALITY_HIGH, false); // - 1; 195 | nt.mtregLow_qualityHigh = readChange(nt.mtregLow, BH1750_QUALITY_HIGH, true); // - 1; 196 | // Calibraton was succsessfull, so we update the new timing parameters to the sensor 197 | _timing = nt; 198 | writeMtreg(orgM); 199 | setQuality(orgQ); 200 | if (nt.mtregHigh == mtregHigh) 201 | { 202 | return BH1750_CAL_OK; 203 | } 204 | else 205 | { 206 | return BH1750_CAL_MTREG_CHANGED; 207 | } 208 | } // MtregHigh=0; 209 | // Calibration was too dark even for highest sensitivity 210 | // So we restore the last valid timing parameters and return false 211 | _timing = orgT; 212 | writeMtreg(orgM); 213 | setQuality(orgQ); 214 | return BH1750_CAL_TOO_DARK; 215 | } 216 | 217 | //******************************************************************************************** 218 | // Private function 219 | // With the last parameter we can decide if we reset the sensor and look for values >0, 220 | // or if we let remain the last valid measurement in the storage of the sensor and look for a value<> last value 221 | // This function is only used for calibration, to detect even a value of zero (0) 222 | // This is a modified version from "startMeasure" 223 | 224 | unsigned int hp_BH1750::readChange(byte mtreg, BH1750Quality quality, bool change) 225 | { 226 | unsigned int curVal = 0; 227 | unsigned int val; 228 | if (change == true) 229 | { 230 | curVal = _value; 231 | } 232 | else 233 | { 234 | reset(); 235 | } 236 | writeMtreg(mtreg); 237 | setQuality(quality); 238 | writeByte(quality); 239 | _startMillis = millis(); 240 | _resultMillis = _startMillis + _mtregTime + _offset; 241 | _timeoutMillis = _startMillis + _mtregTime + _timeout; 242 | _nReads = 0; 243 | _value = 0; 244 | _time = 0; 245 | do 246 | { 247 | val = readValue(); 248 | } while (val == curVal && millis() <= _timeoutMillis); 249 | _time = millis() - _startMillis; 250 | return _time; 251 | } 252 | 253 | //******************************************************************************************** 254 | // Overloaded 255 | // Return the estimated time for a given combination of quality and sensitivity 256 | unsigned int hp_BH1750::getMtregTime(byte mtreg, BH1750Quality quality) const 257 | { 258 | if (quality == BH1750_QUALITY_LOW) 259 | { 260 | return (int)0.5 + _timing.mtregLow_qualityLow + ((_timing.mtregHigh_qualityLow - _timing.mtregLow_qualityLow) / (float)(_timing.mtregHigh - _timing.mtregLow)) * (mtreg - _timing.mtregLow); 261 | } 262 | else 263 | { 264 | return (int)0.5 + _timing.mtregLow_qualityHigh + ((_timing.mtregHigh_qualityHigh - _timing.mtregLow_qualityHigh) / (float)(_timing.mtregHigh - _timing.mtregLow)) * (mtreg - _timing.mtregLow); 265 | } 266 | } 267 | 268 | //******************************************************************************************** 269 | // Overloaded 270 | 271 | unsigned int hp_BH1750::getMtregTime() const 272 | { 273 | return getMtregTime(_mtreg, _quality); 274 | } 275 | 276 | //******************************************************************************************** 277 | // Overloaded 278 | 279 | unsigned int hp_BH1750::getMtregTime(byte mtreg) const 280 | { 281 | return getMtregTime(mtreg, _quality); 282 | } 283 | 284 | //******************************************************************************************** 285 | // Return current sensitivity 286 | 287 | byte hp_BH1750::getMtreg() const 288 | { 289 | return _mtreg; 290 | } 291 | 292 | //******************************************************************************************** 293 | // Return last conversation time 294 | unsigned int hp_BH1750::getTime() const 295 | { 296 | return _time; 297 | } 298 | 299 | //******************************************************************************************** 300 | // Set quality for next measurements and adjust internal values 301 | void hp_BH1750::setQuality(BH1750Quality quality) 302 | { 303 | _quality = quality; 304 | if (_quality == BH1750_QUALITY_HIGH2) 305 | { 306 | _qualFak = 0.5; // For lux calculation 307 | } 308 | else 309 | { 310 | _qualFak = 1; // For lux calculation 311 | } 312 | _mtregTime = getMtregTime(); // Calculate estimated conversion time 313 | } 314 | 315 | //******************************************************************************************** 316 | // Most used function in this library 317 | // In the main loop you should ask frequently with this function for a new value 318 | // If the estimated time for a new value not reached this function, 319 | // this function jumps back very quick, without ask the sensor 320 | // If forceSensor is true, then every time this function is called, the sensor is asked 321 | bool hp_BH1750::hasValue(bool forceSensor) 322 | { 323 | unsigned long mil = millis(); 324 | if (!forceSensor) 325 | { 326 | if (_resultMillis > mil) // We are below the estimated time, so we return quickly 327 | { 328 | return false; 329 | } 330 | else 331 | { // Time is over 332 | _value = readValue(); 333 | if (_value > 0) 334 | return true; 335 | if (_time > 0) 336 | return true; // Timeout 337 | return false; // Not timed out yet 338 | } 339 | } 340 | else 341 | { // forceSensor, so we always read from sensor 342 | _value = readValue(); 343 | if (_value > 0) 344 | return true; 345 | if (_time > 0) 346 | return true; // Timeout 347 | return false; // Forced but not out-timed yet 348 | } 349 | } 350 | 351 | //******************************************************************************************** 352 | // Here we can fine tune the estimated time 353 | // The offset can be negativ or positve 354 | // For example an offset of -2, reads 2 milli seconds earlier than estimated 355 | 356 | void hp_BH1750::setTimeOffset(int offset) 357 | { 358 | (_offset = offset); 359 | } 360 | 361 | //******************************************************************************************** 362 | // Return the current offset 363 | int hp_BH1750::getTimeOffset() const 364 | { 365 | return _offset; 366 | } 367 | 368 | //******************************************************************************************** 369 | // Set sensitivity and send it to the sensor 370 | 371 | bool hp_BH1750::writeMtreg(byte mtreg) 372 | { 373 | mtreg = checkMtreg(mtreg); 374 | _mtreg = mtreg; 375 | _mtregTime = getMtregTime(); 376 | // Change sensitivity measurement time 377 | // We send two bytes: 3 Bits und 5 Bits, with a prefix. 378 | // High bit: 01000_MT[7,6,5] 379 | // Low bit: 011_MT[4,3,2,1,0] 380 | uint8_t hiByte = _mtreg >> 5; 381 | hiByte |= 0b01000000; 382 | writeByte(hiByte); 383 | uint8_t loByte = mtreg & 0b00011111; 384 | loByte |= 0b01100000; 385 | return writeByte(loByte); 386 | } 387 | 388 | //******************************************************************************************** 389 | // Return the physically reads form the sensor 390 | 391 | unsigned int hp_BH1750::getReads() const 392 | { 393 | return _nReads; 394 | } 395 | 396 | //******************************************************************************************** 397 | // Return a value between 0 65535 (two bytes) 398 | 399 | unsigned int hp_BH1750::getRaw() 400 | { 401 | if (_time > 0) 402 | return _value; 403 | yield(); 404 | do 405 | { 406 | _value = readValue(); 407 | } while (_time == 0); 408 | _processed = true; 409 | return _value; 410 | } 411 | 412 | bool hp_BH1750::processed() const 413 | { 414 | return _processed; 415 | } 416 | 417 | //******************************************************************************************** 418 | // Private function that reads the value physically 419 | 420 | unsigned int hp_BH1750::readValue() 421 | { 422 | byte buff[2]; 423 | unsigned int req = _wire->requestFrom((int)_address, (int)2); // request two bytes 424 | if (req < 2 || _wire->available() < 2) 425 | { 426 | _time = 999; 427 | _value = 0; 428 | return _value; // Sensor not found or other problem 429 | } 430 | 431 | buff[0] = _wire->read(); // Receive one byte 432 | buff[1] = _wire->read(); // Receive one byte 433 | _nReads++; // Inc the physically count of reads 434 | _value = ((buff[0] << 8) | buff[1]); 435 | 436 | unsigned long mil = millis(); 437 | if (_value > 0 && _time == 0) 438 | _time = mil - _startMillis; // Conversion time 439 | if (mil >= (_timeoutMillis) && (_time == 0)) 440 | _time = mil - _startMillis; 441 | return _value; 442 | } 443 | 444 | //******************************************************************************************** 445 | // Get the current timeout in milliseconds 446 | 447 | unsigned int hp_BH1750::getTimeout() const 448 | { 449 | return _timeout; 450 | } 451 | 452 | void hp_BH1750::setTimeout(int timeout) 453 | { 454 | _timeout = timeout; 455 | } 456 | 457 | //******************************************************************************************** 458 | // Return all timing parameters, collected in a struct 459 | 460 | BH1750Timing hp_BH1750::getTiming() const 461 | { 462 | return _timing; 463 | } 464 | 465 | //******************************************************************************************** 466 | // Set all timing parameters (for example stored in eprom before) 467 | 468 | void hp_BH1750::setTiming(BH1750Timing timing) 469 | { 470 | _timing = timing; 471 | } 472 | 473 | //******************************************************************************************** 474 | // If you want to measure a certain time in millisconds, this funcion calculates the right mtreg 475 | // Only vaild result, if sensor is calibrated! 476 | 477 | byte hp_BH1750::convertTimeToMtreg(unsigned int time, BH1750Quality quality) 478 | { 479 | float v; 480 | switch (quality) 481 | { 482 | case BH1750_QUALITY_HIGH: 483 | case BH1750_QUALITY_HIGH2: 484 | v = (((float)time - (float)_timing.mtregLow_qualityHigh) * (_timing.mtregHigh - _timing.mtregLow)) / (_timing.mtregHigh_qualityHigh - _timing.mtregLow_qualityHigh) + _timing.mtregLow; 485 | break; 486 | case BH1750_QUALITY_LOW: 487 | default: 488 | v = ((time - _timing.mtregLow_qualityLow) * (_timing.mtregHigh - _timing.mtregLow)) / (_timing.mtregHigh_qualityLow - _timing.mtregLow_qualityLow) + _timing.mtregLow; 489 | break; 490 | } 491 | v += 0.5; 492 | byte tempMtreg; 493 | Serial.println(v); 494 | if (v < 0) 495 | tempMtreg = 0; 496 | else if (v > BH1750_MTREG_HIGH) 497 | tempMtreg = 255; 498 | else 499 | tempMtreg = v; 500 | // if (v < BH1750_MTREG_LOW) tempMtreg = 0; 501 | // if (v > BH1750_MTREG_HIGH) tempMtreg = 255; 502 | // if (v >=BH1750_MTREG_LOW && v <= BH1750_MTREG_HIGH) tempMtreg = v; 503 | 504 | return tempMtreg; 505 | } 506 | 507 | //******************************************************************************************** 508 | // Return the light value, converted to Lux 509 | 510 | float hp_BH1750::getLux() 511 | { 512 | return (float)getRaw() / luxFactor * luxCache; 513 | } 514 | 515 | //******************************************************************************************** 516 | // Calculate a given digital value (from getRaw()) to Lux 517 | 518 | float hp_BH1750::calcLux(int raw) const 519 | { 520 | return (float)raw / luxFactor * _qualFak * 69 / _mtreg; 521 | } 522 | 523 | // Calculate a given digital value (from getRaw()) to Lux 524 | 525 | float hp_BH1750::calcLux(int raw, BH1750Quality quality, int mtreg) const 526 | { 527 | float qualFak = 0.5; 528 | if (quality != BH1750_QUALITY_HIGH2) 529 | qualFak = 1.0; 530 | return (float)raw / luxFactor * qualFak * 69 / mtreg; 531 | } 532 | 533 | // Check if last value is over 65535 raw data 534 | 535 | bool hp_BH1750::saturated() const 536 | { 537 | return (_value == BH1750_SATURATED); 538 | } 539 | 540 | //******************************************************************************************** 541 | // This is a function that you can call after you received a value 542 | // Depending of the last value, this function adjusts the mtreg and the quality so that 543 | // the next measurement with similar light conditions is in the desired range 544 | // The desired range is set with the parameter "percent" 545 | // If you set it to 50% the next raw data should be near to 50% of the maximum digits of 65535=saturated, this is 32768‬ 546 | // Rf the light condition changes you have enough buffer that you are not out of range (saturated) 547 | // If you want the highest precision you should choose a percentage of 90-95% 548 | // So it is more likley that the next value could be saturated, but you have a better resolution 549 | // If the next value is saturated, this function does a quick measurement at low resolution (~10 ms) 550 | // and calculates suitable parameters for the next measurement 551 | // With this function you cover the whole sensitivity of the sensor without manually adjust any paramaeters! 552 | // If you start the measurement with quality BH1750_QUALITY_HIGH = 0x20 or BH1750_QUALITY_HIGH2 = 0x21 the function will 553 | // switch between this values when requiered 554 | // If you use the fast, but low qualtiy BH1750_QUALITY_LOW = 0x23, the function will not change this quality. 555 | 556 | bool hp_BH1750::adjustSettings(float percent, bool forcePreShot) 557 | { 558 | if (_value == BH1750_SATURATED || forcePreShot == true) // If last result is saturated, perfom a measurement at low sensitivity 559 | { 560 | BH1750Quality temp = _quality; 561 | start(BH1750_QUALITY_LOW, BH1750_MTREG_LOW); 562 | getRaw(); 563 | if (temp != BH1750_QUALITY_LOW) _quality = BH1750_QUALITY_HIGH; 564 | } 565 | calcSettings(_value, _quality, _mtreg, percent); 566 | setQuality(_quality); 567 | return writeMtreg(_mtreg); 568 | } 569 | 570 | //******************************************************************************************** 571 | // This function is like adjustSettings(), but only calculates the new settings without send them to the sensor 572 | // You have to declare the variables BH1750Quality and mtreg bevore calling this function 573 | // After calling this function this variables contain the appropiate values 574 | 575 | void hp_BH1750::calcSettings(unsigned int value, BH1750Quality &qual, byte &mtreg, float percent) 576 | { 577 | if (percent > 100.0) 578 | percent = 100.0; 579 | _percent = percent; 580 | if (value==0) 581 | value=1; 582 | float highBound = value / percent * 100.0; 583 | unsigned long newMtreg = (float)BH1750_SATURATED / highBound * mtreg + .5; 584 | switch (qual) 585 | { 586 | case BH1750_QUALITY_HIGH: 587 | if (newMtreg >= BH1750_MTREG_LOW * 2) 588 | { 589 | newMtreg /= 2; 590 | qual = BH1750_QUALITY_HIGH2; 591 | } 592 | break; 593 | case BH1750_QUALITY_HIGH2: 594 | if (newMtreg < BH1750_MTREG_LOW) 595 | { 596 | qual = BH1750_QUALITY_HIGH; 597 | newMtreg *= 2; 598 | } 599 | case BH1750_QUALITY_LOW: 600 | default: ; 601 | } 602 | if (newMtreg > BH1750_MTREG_HIGH) 603 | newMtreg = BH1750_MTREG_HIGH; 604 | mtreg = newMtreg; 605 | } 606 | 607 | float hp_BH1750::getPercent() const { 608 | return _percent; 609 | } 610 | -------------------------------------------------------------------------------- /src/hp_BH1750.h: -------------------------------------------------------------------------------- 1 | // This is a library for the light sensor BH1750 2 | // Datasheet https://www.mouser.com/datasheet/2/348/bh1750fvi-e-186247.pdf 3 | // Donwnload library at https://github.com/Starmbi/hp_BH1750 4 | // Help and infos are provided at https://github.com/Starmbi/hp_BH1750/wiki 5 | // Copyright (c) Stefan Amrborst, 2020 6 | 7 | #ifndef hp_BH1750_h 8 | #define hp_BH1750_h 9 | #if defined(ARDUINO) && ARDUINO >= 100 10 | #include 11 | #else 12 | #include 13 | #endif 14 | #include 15 | static const unsigned int BH1750_SATURATED = 65535; 16 | enum BH1750Quality 17 | { 18 | BH1750_QUALITY_HIGH = 0x20, 19 | BH1750_QUALITY_HIGH2 = 0x21, 20 | BH1750_QUALITY_LOW = 0x23, 21 | }; 22 | enum BH1750CalResult 23 | { 24 | BH1750_CAL_OK = 0, 25 | BH1750_CAL_MTREG_CHANGED = 1, 26 | BH1750_CAL_TOO_BRIGHT = 2, 27 | BH1750_CAL_TOO_DARK = 3, 28 | BH1750_CAL_COMMUNICATION_ERROR = 4, 29 | }; 30 | struct BH1750Timing 31 | { 32 | byte mtregLow; 33 | byte mtregHigh; 34 | unsigned int mtregLow_qualityHigh; 35 | unsigned int mtregHigh_qualityHigh; 36 | unsigned int mtregLow_qualityLow; 37 | unsigned int mtregHigh_qualityLow; 38 | }; 39 | 40 | enum BH1750MtregLimit 41 | { 42 | BH1750_MTREG_LOW = 31, //the datashet specifies 31 as minimum value 43 | //but you can go even lower (depending on your specific chip) 44 | //BH1750_MTREG_LOW=5 works with my chip and enhances the range 45 | //from 121.556,8 Lux to 753.652,5 Lux. 46 | BH1750_MTREG_HIGH = 254, 47 | BH1750_MTREG_DEFAULT = 69 48 | }; 49 | 50 | enum BH1750Address 51 | { 52 | BH1750_TO_GROUND = 0x23, 53 | BH1750_TO_VCC = 0x5C 54 | }; 55 | extern TwoWire Wire; /**< Forward declaration of Wire object */ 56 | class hp_BH1750 57 | { 58 | public: 59 | hp_BH1750(); 60 | 61 | //hp_BH1750(); 62 | 63 | bool begin(byte address, TwoWire *myWire = &Wire); 64 | bool reset(); 65 | bool powerOn(); 66 | bool powerOff(); 67 | bool writeMtreg(byte mtreg); 68 | void setQuality(BH1750Quality quality); 69 | byte calibrateTiming(byte mtregHigh = BH1750_MTREG_HIGH, byte mtregLow = BH1750_MTREG_LOW); 70 | bool start(); 71 | bool start(BH1750Quality quality, byte mtreg); 72 | bool hasValue(bool forceSensor = false); 73 | bool processed() const; 74 | bool saturated() const; 75 | float getLux(); 76 | float calcLux(int raw) const; 77 | float calcLux(int raw, BH1750Quality quality, int mtreg) const; 78 | float luxFactor = 1.2; 79 | void setTiming(BH1750Timing timing); 80 | BH1750Timing getTiming() const; 81 | 82 | BH1750Quality getQuality() const; 83 | 84 | void setTimeout(int timeout); 85 | 86 | void setTimeOffset(int offset); 87 | int getTimeOffset() const; 88 | unsigned int getTimeout() const; 89 | byte getMtreg() const; 90 | byte convertTimeToMtreg(unsigned int time, BH1750Quality quality); 91 | float getPercent() const; 92 | unsigned int getRaw(); 93 | unsigned int getReads() const; 94 | unsigned int getTime() const; 95 | unsigned int getMtregTime() const; 96 | unsigned int getMtregTime(byte mtreg) const; 97 | unsigned int getMtregTime(byte mtreg, BH1750Quality quality) const; 98 | 99 | bool adjustSettings(float percent = 50.0, bool forcePreShot = false); 100 | void calcSettings(unsigned int value, BH1750Quality &qual, byte &mtreg, float percent); 101 | 102 | private: 103 | TwoWire *_wire; 104 | byte _address; 105 | byte _mtreg; 106 | byte _percent=50; 107 | bool _processed = false; 108 | unsigned int _mtregTime; 109 | unsigned long _startMillis; 110 | unsigned long _resultMillis; 111 | unsigned long _timeoutMillis; 112 | unsigned long _timeout = 10; 113 | int _offset = 0; 114 | unsigned int _nReads; 115 | unsigned int _time; 116 | unsigned int _value; 117 | float _qualFak = 0.5; 118 | float luxCache; 119 | BH1750Quality _quality; 120 | BH1750Timing _timing; 121 | 122 | byte checkMtreg(byte mtreg); 123 | bool writeByte(byte b); 124 | 125 | unsigned int readValue(); 126 | unsigned int readChange(byte mtreg, BH1750Quality quality, bool change); 127 | }; 128 | #endif 129 | --------------------------------------------------------------------------------