降雨センサーに続き、気温、湿度、照度、風向、風速が測れる気象センサーを作ってみた。
風速計については以下のものがある。
・回転式(発電/回転数検出、風車/風杯など)
・風圧式(ピトー管、振り子)
・超音波式(消費電力大)
・熱式(消費電力大、サーミスタなど)
微風まで計測でき低消費電力かつ機械的可動部の少ないものとなると振り子式のみであるが、その振り子の角度の計測は?とか...風向も測りたいし...とか考えながらアマゾンを散策してたらたまたまジョイスティックが目にとまった。ジョイスティックなら方向も角度も測れるじゃん?とアマゾンや秋月電子から適当にいくつか購入し試してみた結果、ニュートラルの遊びが少なく全方位同じ力で動作するアルプス電気のものが良さそうだ。ただ、気になるのはジョイスティックの耐久性が10万サイクル/方向であること。製品仕様としては十分すぎるとは思うけど風で動作させるとなると常に動きまわるためあっというまかもしれないが、とりあえず消耗品と割り切るしかないか...
省電力化のためジョイスティック回路には計測時のみ電源を供給するようにしてみた。気温/湿度(SHT31)と照度(BH1750)センサーについても計測時のみ電源供給するようにしてみたのだがそれらについては未計測時(パワーダウンモード)の消費電流が0.2uA/0.01uAということがあとでわかったので意味なかったかも。(-_-;)
さらに可能な限り省電力化するために10秒毎に間欠起動させジョイスティック値のみを取得、1分毎にジョイスティック値(最小、最大、平均)、温度、湿度、照度、電池電圧を送信するようにしてみた。実測での平均消費電流は20uA未満だったので計算上ではボタン電池でも1年以上は持つはず...たぶん。
ちなみに照度データからはいろいろなものが見えてきて面白い。日の出/日の入り時刻などがわかるし天気予報データだけでは現地の雲の状況まではわからないから太陽光発電の発電量と照度から設備の故障や異常を正確に早く発見することに役立つはずだ。太陽光発電してる人に照度センサーは必須かも。
【主要部品】
防水プラケース [KH-F20]
ユニバーサル基板 [UP75X50A]
TWELITE RED PAL アンテナ内蔵タイプ★特価★ [MW-R-PAL-P]
SHT31使用 高精度温湿度センサーモジュールキット
ジョイスティックRKJXK122000D DIP化キット
KKHMF 3個 BH1750 GY302
光 アルミパイプ 4丸×395mm AP395-4
発泡スチロール球 直径75mm ※手芸用品店で購入したほうが安い
【A】取り付け部品付きシャフトカプラー 2mm – 4mm
試験してみるとなぜか不定期に送信データ抜けが発生してしまう。特価のTWELITEをaitendoで見つけてついポチッてしまったのだが4個購入したうちの2個は送信データ抜けが激しかったりする。詳しく調べてみないとわからないがやはり安いのには訳があるということなのかな?
【ホスト側で保存したログ・データの例】
日付 時刻、電圧、気温、湿度、照度、X{最小、最大、平均}、Y{最小、最大、平均}、東を0度とした角度、16方位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
2024/06/13 14:10:30,2783,28.31,77.50,4060,-15,-3,-13,-15,-15,-15,230,SW 2024/06/13 14:11:30,2783,28.25,77.62,3520,-8,14,0,-14,13,-2,270,S 2024/06/13 14:12:31,2783,28.25,77.69,3090,-12,-11,-11,11,13,12,132,NNW 2024/06/13 14:13:31,2783,28.12,77.69,3216,-14,-11,-12,7,14,10,140,NW 2024/06/13 14:14:31,2783,28.19,77.75,3636,-14,-13,-13,7,8,7,151,NW 2024/06/13 14:15:31,2783,28.19,77.75,4120,-10,-9,-9,-25,7,-8,222,WSW 2024/06/13 14:16:31,2783,28.31,77.69,4076,-11,-10,-10,0,11,2,168,WNW 2024/06/13 14:17:32,2783,28.25,77.69,3846,-11,-6,-10,1,6,1,174,WNW 2024/06/13 14:18:32,2783,28.31,77.81,3673,-15,-9,-10,6,12,7,145,NW 2024/06/13 14:19:32,2783,28.38,77.81,3620,-15,-15,-15,7,8,7,154,NW 2024/06/13 14:20:32,2783,28.44,77.81,4276,-8,17,0,7,8,7,90,N 2024/06/13 14:21:33,2783,28.44,77.62,4713,-15,-8,-11,-15,8,-2,191,W 2024/06/13 14:22:33,2787,28.69,77.38,5690,-15,-14,-14,-12,6,-7,207,WSW 2024/06/13 14:23:33,2787,28.94,77.38,4753,-14,-9,-12,-2,10,3,165,WNW 2024/06/13 14:24:33,2783,29.12,77.38,5283,-14,-3,-9,-15,10,-4,204,WSW 2024/06/13 14:25:33,2787,29.00,77.56,3910,-6,-2,-3,-14,-6,-10,254,SSW 2024/06/13 14:26:34,2787,29.31,77.38,4820,-11,-5,-7,-7,17,4,150,NW 2024/06/13 14:27:34,2787,29.38,77.50,4460,-11,-10,-10,9,13,11,132,NNW 2024/06/13 14:28:34,2787,29.50,77.50,4836,-8,-4,-7,-32,13,4,150,NW 2024/06/13 14:29:34,2787,29.62,77.50,4366,-11,-3,-6,-26,35,-5,220,WSW 2024/06/13 14:30:35,2787,29.69,77.50,4266,-17,-10,-14,1,14,8,150,NW |
【外観】
本体が太陽に照らされるとケース内の温度が急上昇してしまうので百葉箱などを使うべきだろうが風も検出するので防水対策もかねてカバーを取り付けてみた。
風向はジョイスティック値をatan2(y,x)*180/3.14で角度変換できるが風速計算についてはこれからデータ取りしてみないとわからない。ちなみに球の大きさや取付位置を調整することで感度調整ができる。
【履歴】
2024-06-16
10分に1回ほど送信データ抜けが起こっていたが正規品のTWELITEと交換したみたところ数時間に1回程度に改善。受信機の近くに設置すれば抜けは起こらないので設置場所による電波状況の問題もありそう...
2024-06-14
送信データ抜けの件について調べてたら不定期に送信出力が3dBm弱程度落ちるのを発見。これが原因かと思ったが送信出力の指定をしていない場合の規定の動作らしく送信出力指定しても抜けは発生するし何度送信リトライをしても送信失敗となるから送信周波数が狂ったりしてるのかもしれない...測定器持ってないのでわからないけど。
送信出力について色々試してみたが、規定値は0dBm(3dBmまでの変動を許容)で、+3dBmに変更するとかなり通信距離が延びるようだ。JN5164は0dBm、JN5169は+10dBmが最大出力であるが+3dBmを超える送信出力は消費電流が多くなるためボタン電池では駆動しきれないし電池電圧の低下にも耐えられない。それとJN5164よりJN5169のほうが消費電流が少なめなのでボタン電池駆動するときはJN5169で+3dBmを指定するのが良さそうだ。
【回路】
ジョイスティックの計測時はGPIO制御によりXとYを直列接続に構成し直して測定しない軸をプルアップ抵抗代わりとして利用するとともに電池の電圧変動の対策としてその中間点(+V)をADCリファレンス電圧としている。そのために基板をリワークしY-GNDをNC端子に接続し直す必要がある。
【ファームウェア】
SHT31/BH1750の測定は最低16msと比較的長い時間がかかることから省電力化のため最初に測定開始だけしておき次の間欠起動時(1秒後)に測定値を取得するようにしている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 |
/* main.cpp - weather sensor for NXP JN516x Series Copyright (c) 2024 Sasapea's Lab. All right reserved. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ #include <limits.h> #include "device.h" #include "bh1750.h" #include "sht3x.h" #define WPAN_ADDRESS 10 #define SEND_INTERVAL 60 // Sec #define DATA_INTERVAL 10 // Sec #define TX_TIMEOUT 100 // ms #define WIRE_SPEED Wire::SPEED_400KHZ #define SHT3X_VDD Gpio::DIO5 #define BH1750_VDD Gpio::DIO8 typedef enum { JOYSTICK_X = 0, JOYSTICK_Y = 1, } JOYSTICK; static struct { AdcConfig::SRC src; Gpio::PIN pin; } sensors[] = { { AdcConfig::SRC_ADC_3, Gpio::DIO17 }, // X { AdcConfig::SRC_ADC_1, Gpio::DIO1 }, // Y }; static SHT3X sht3x; static BH1750 bh1750; static bool measurement; static struct __attribute__((packed)) { uint8_t command[3]; uint8_t seqnum; int16_t temperature; uint16_t humidity; uint32_t lux; uint16_t battery; struct __attribute__((packed)) { int16_t min; int16_t max; int16_t avg; } data[2]; } packet = { .command = { 'W', 'S', 'D' } }; static uint8_t count; static int32_t sum[2]; static int txstatus; void initADC(void) { for (size_t xy = 0; xy < sizeof(sensors) / sizeof(sensors[0]); ++xy) { Gpio::PIN pin = sensors[xy].pin; Gpio::digitalWrite(pin, LOW); Gpio::pinMode(pin, Gpio::OUTPUT); } } uint16_t readADC(JOYSTICK xy) { AdcConfig::SRC src = sensors[xy].src; Gpio::PIN pin = sensors[xy].pin; AdcConfig adc; adc.source(src); adc.samples(AdcConfig::SAMPLES_8); adc.reference(1500); // external VREF(ADC2) Gpio::digitalWrite(pin, HIGH); Adc::begin(Adc::MODE_SINGLE_SHOT, adc); Adc::start(); while (!Adc::conversion()) yield(); uint16_t rv = Adc::read(); Adc::end(); Gpio::digitalWrite(pin, LOW); return rv; } uint16_t readVolt(void) { AdcConfig adc; adc.source(AdcConfig::SRC_ADC_VOLT); Adc::begin(Adc::MODE_SINGLE_SHOT, adc); Adc::start(); while (!Adc::conversion()) yield(); uint16_t rv = Adc::voltage(); Adc::end(); return rv; } void WPanTransmitCallback(uint8 u8Handle, uint8 u8Status) { switch (u8Handle) { case 0x01: txstatus = u8Status; break; } } void clear(void) { count = 0; for (size_t i = 0; i < sizeof(packet.data) / sizeof(packet.data[0]); ++i) { sum[i] = 0; packet.data[i].min = SHRT_MAX; packet.data[i].max = SHRT_MIN; packet.data[i].avg = 0; } } void readSensor(void) { // // Clear Data // if (count == 0) clear(); // // Send Data ? // bool send = ++count >= SEND_INTERVAL / DATA_INTERVAL; // // Read JoyStick Data // int16_t data[] = { (int16_t)(readADC(JOYSTICK_X) - 512), (int16_t)(readADC(JOYSTICK_Y) - 512) }; for (size_t i = 0; i < sizeof(data) / sizeof(data[0]); ++i) { sum[i] += data[i]; if (send) packet.data[i].avg = sum[i] / count; if (packet.data[i].min > data[i]) packet.data[i].min = data[i]; if (packet.data[i].max < data[i]) packet.data[i].max = data[i]; } // // Send Data // if (send) { // // Read Battery Voltage // packet.battery = readVolt(); // // Setup Packet // packet.seqnum++; // // Setup WPan // WPan::begin(WPAN_ADDRESS, WPAN_PANID, WPAN_CHANNEL); // // Send Data // txstatus = -1; WPan::transmit(WPAN_BRIDGE, (uint8_t *)&packet, sizeof(packet), 0x01 | WPAN_OPT_DUPLICATE); for (uint32_t t = System::millis(); (txstatus == -1) && (System::millis() - t < TX_TIMEOUT); ) yield(); // // End WPan // WPan::end(); // // Clear Data Count // count = 0; } } void wakeup(void) { // // Read Data of BH1750 and SHT31 // if (measurement) { // // Init Wire // Wire::begin(WIRE_SPEED); // // Read BH1750 // bh1750.read(); packet.lux = bh1750.lux(); // // Read SHT3x // sht3x.read(); packet.temperature = sht3x.temperatureC(); packet.humidity = sht3x.humidity(); // // End Wire // Wire::end(); } } void setup(void) { // // Init ADC // initADC(); // // Power Control of BH1750 and SHT31 // measurement = (count == 0); Gpio::digitalWrite(SHT3X_VDD, HIGH); Gpio::pinMode(SHT3X_VDD, measurement ? Gpio::OUTPUT : Gpio::INPUT); Gpio::digitalWrite(BH1750_VDD, HIGH); Gpio::pinMode(BH1750_VDD, measurement ? Gpio::OUTPUT : Gpio::INPUT); } void loop(void) { // // Read Wind Sensor // readSensor(); // // Start Measurement of BH1750 and SHT31 // if (measurement) { // // Init Wire // Wire::begin(WIRE_SPEED); // // Start BH1750 // bh1750.begin(); bh1750.start(BH1750_ONE_TIME_L_RESOLUTION_MODE); // // Start SHT3x // sht3x.begin(); sht3x.start(); // // End Wire // Wire::end(); } // // Sleep + Wake Timer (0.64uA + 0.05uA + 0.9uA) // WakeTimer::start(WakeTimer::WAKE_0, DATA_INTERVAL, empty); System::sleep(System::SLEEP_OSCON_RAMON); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
/* sht3X.h - Temperatute and Humidity Library for Sensirion SHT3x(30/31/35) Copyright (c) 2024 Sasapea's Lab. All right reserved. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ #pragma once #include <stdint.h> #include <stdbool.h> #include "device.h" typedef enum { SHT3X_SINGLE_H_STRETCH, SHT3X_SINGLE_M_STRETCH, SHT3X_SINGLE_L_STRETCH, SHT3X_SINGLE_H, SHT3X_SINGLE_M, SHT3X_SINGLE_L, } SHT3X_MODE; #define SHT3X_NO_DEVICE 0 #define SHT3X_IDLE 1 #define SHT3X_BUSY 2 #define SHT3X_READY 3 #define SHT3X_DEFAULT_ADDRESS 0x45 #define SHT3X_RESET_TIME ( 1 + 1) #define SHT3X_MEASUREMENT_TIME (15 + 1) #define SHT3X_MEASUREMENT_INTERVAL 100 #define SHT3X_SAMPLING_PERIOD 1000 class SHT3X { private: typedef enum : uint16_t { SINGLE_H_STRETCH = 0x2C06, SINGLE_M_STRETCH = 0x2C0D, SINGLE_L_STRETCH = 0x2C10, SINGLE_H = 0x2400, SINGLE_M = 0x240B, SINGLE_L = 0x2416, REPEAT_H_05MPS = 0x2032, REPEAT_M_05MPS = 0x2024, REPEAT_L_05MPS = 0x202F, REPEAT_H_1MPS = 0x2130, REPEAT_M_1MPS = 0x2126, REPEAT_L_1MPS = 0x212D, REPEAT_H_2MPS = 0x2236, REPEAT_M_2MPS = 0x2220, REPEAT_L_2MPS = 0x222B, REPEAT_H_4MPS = 0x2334, REPEAT_M_4MPS = 0x2322, REPEAT_L_4MPS = 0x2329, REPEAT_H_10MPS = 0x2737, REPEAT_M_10MPS = 0x2721, REPEAT_L_10MPS = 0x272A, REPEAT_ART = 0x2B32, REPEAT_STOP = 0x3093, FETCH_DATA = 0xE000, RESET = 0x30A2, HEATER_ON = 0x306D, HEATER_OFF = 0x3066, STATUS_CLEAR = 0x3041, STATUS_READ = 0xF32D, } INSTRUCTION; uint8_t _slave; uint16_t _interval; INSTRUCTION _cmode; int16_t _temperature; uint16_t _humidity; uint16_t _start; uint8_t _status; uint8_t crc8(const uint8_t *data, int8_t len) { uint8_t crc = 0xFF; while (--len >= 0) { crc ^= *data++; for (int8_t i = 8; --i >= 0;) crc = (crc & 0x80) ? (crc << 1) ^ 0x31 : (crc << 1); } return crc; } bool write0(INSTRUCTION cmd) { uint8_t data[] = {(uint8_t)(cmd >> 8), (uint8_t)(cmd & 0xFF)}; return Wire::write(_slave, data, sizeof(data)); } uint8_t read0(void) { struct __attribute((packed)) { struct __attribute((packed)) { uint8_t high; uint8_t low; } temp; uint8_t temp_crc; struct __attribute((packed)) { uint8_t high; uint8_t low; } humi; uint8_t humi_crc; } data; uint16_t temp, humi; _start += _interval; _status = SHT3X_NO_DEVICE; if (!Wire::read(_slave, (uint8_t *)&data, sizeof(data))) return _status; if (crc8((uint8_t *)&data.temp, sizeof(data.temp)) != data.temp_crc) return _status; if (crc8((uint8_t *)&data.humi, sizeof(data.humi)) != data.humi_crc) return _status; temp = ((uint16_t)data.temp.high << 8) | data.temp.low; humi = ((uint16_t)data.humi.high << 8) | data.humi.low; _temperature = ((uint32_t)temp * 175 * 16 / 65535) - (45 * 16); _humidity = ((uint32_t)humi * 100 * 16 / 65535); _status = SHT3X_IDLE; return SHT3X_READY; } bool measurement(void) { bool rv = false; if (_cmode) { rv = write0(_cmode); _start = System::millis(); _status = rv ? SHT3X_BUSY : SHT3X_NO_DEVICE; } return rv; } bool reset(void) { _cmode = (INSTRUCTION)0; bool rv = write0(RESET); _status = rv ? SHT3X_IDLE : SHT3X_NO_DEVICE; System::delay(SHT3X_RESET_TIME); return rv; } public: void begin(uint8_t addr = SHT3X_DEFAULT_ADDRESS, uint16_t interval = SHT3X_SAMPLING_PERIOD) { _slave = addr; _interval = interval < SHT3X_MEASUREMENT_INTERVAL ? SHT3X_MEASUREMENT_INTERVAL : interval; _cmode = (INSTRUCTION)0; _temperature = 0; _humidity = 0; _start = 0; _status = SHT3X_NO_DEVICE; } void end(void) { stop(); } bool start(SHT3X_MODE mode = SHT3X_SINGLE_L) { const INSTRUCTION MODES[] = { SINGLE_H_STRETCH, SINGLE_M_STRETCH, SINGLE_L_STRETCH, SINGLE_H, SINGLE_M, SINGLE_L, }; _cmode = MODES[mode]; return measurement(); } bool stop(void) { return reset(); } int16_t temperatureF(void) { return _temperature * 9 / 5 + (32 * 16); } int16_t temperatureC(void) { return _temperature; } uint16_t humidity(void) { return _humidity; } uint8_t handle(void) { uint16_t now = System::millis(); switch (_status) { case SHT3X_NO_DEVICE: case SHT3X_IDLE: if ((uint16_t)(now - _start) >= _interval) measurement(); break; case SHT3X_BUSY: if ((uint16_t)(now - _start) >= SHT3X_MEASUREMENT_TIME) return read(); break; } return _status; } uint8_t read(void) { return read0(); } }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
/* bh1750.h - Light Sensor Library for Rohme BH1750 Copyright (c) 2024 Sasapea's Lab. All right reserved. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. */ #pragma once #include <stdint.h> #include <stdbool.h> #include "device.h" typedef enum { BH1750_ONE_TIME_H_RESOLUTION_MODE, BH1750_ONE_TIME_H_RESOLUTION_MODE2, BH1750_ONE_TIME_L_RESOLUTION_MODE, } BH1750_MODE; #define BH1750_NO_DEVICE 0 #define BH1750_IDLE 1 #define BH1750_BUSY 2 #define BH1750_READY 3 #define BH1750_DEFAULT_SLAVE_ADDRESS 0x23 #define BH1750_DEFAULT_MTREG_VALUE 69 // this value range (31 - 254) #define BH1750_DEFAULT_ACCURACY 120 // this value range (96 - 144) #define BH1750_FIXED_POINT_NBITS 4 #define BH1750_INTERVAL 1000 class BH1750 { private: typedef enum : uint8_t { POWER_DOWN = 0x00, POWER_ON = 0x01, RESET = 0x07, CONTINUOUSLY_H_RESOLUTION_MODE = 0x10, CONTINUOUSLY_H_RESOLUTION_MODE2 = 0x11, CONTINUOUSLY_L_RESOLUTION_MODE = 0x13, ONE_TIME_H_RESOLUTION_MODE = 0x20, ONE_TIME_H_RESOLUTION_MODE2 = 0x21, ONE_TIME_L_RESOLUTION_MODE = 0x23, CHANGE_MEASUREMENT_TIME_H_BIT = 0x40, CHANGE_MEASUREMENT_TIME_L_BIT = 0x60, } INSTRUCTION; uint8_t _slave; uint16_t _interval; INSTRUCTION _cmode; uint16_t _result; uint8_t _fppos; bool _mtchg; uint8_t _mtreg; uint16_t _ctime; uint16_t _dtime; uint16_t _start; uint8_t _status; bool write0(INSTRUCTION cmd) { return Wire::write(_slave, (uint8_t *)&cmd, sizeof(cmd)); } uint8_t read0(void) { uint8_t data[2]; _start += _interval; _status = BH1750_NO_DEVICE; _ctime = _dtime; _fppos = ((_cmode & 0x03) == 0x01 ? 1 : 0); if (_cmode & ONE_TIME_H_RESOLUTION_MODE) _cmode = POWER_DOWN; if (Wire::read(_slave, data, sizeof(data))) { _result = ((uint16_t)data[0] << 8) | data[1]; _status = BH1750_IDLE; return BH1750_READY; } return _status; } bool measurement(void) { bool rv = false; if (_cmode & (CONTINUOUSLY_H_RESOLUTION_MODE | ONE_TIME_H_RESOLUTION_MODE)) { _start = System::millis(); _status = BH1750_NO_DEVICE; _dtime = (_cmode & ONE_TIME_H_RESOLUTION_MODE ? 24 : 180); // TYP 16 : 120, MAX 24 : 180 _ctime = (_mtchg ? _dtime << 1 : _dtime); // Change Measurement Time if (_mtchg) { if (!write0((INSTRUCTION)(CHANGE_MEASUREMENT_TIME_H_BIT | ((_mtreg >> 5) & 0x07)))) return rv; if (!write0((INSTRUCTION)(CHANGE_MEASUREMENT_TIME_L_BIT | (_mtreg & 0x1F)))) return rv; _mtchg = false; } // Start Measurement if ((rv = write0(_cmode))) _status = BH1750_BUSY; } return rv; } bool power(bool on) { _cmode = on ? POWER_ON : POWER_DOWN; bool rv = write0(_cmode); _status = rv ? BH1750_IDLE : BH1750_NO_DEVICE; return rv; } public: void begin(uint8_t addr = BH1750_DEFAULT_SLAVE_ADDRESS, uint16_t interval = BH1750_INTERVAL) { _slave = addr; _interval = interval; _cmode = POWER_DOWN; _result = 0; _fppos = 0; _mtchg = false; _mtreg = BH1750_DEFAULT_MTREG_VALUE; _ctime = 0; _dtime = 0; _start = 0; _status = BH1750_NO_DEVICE; } void end(void) { stop(); } bool reset(void) { return power(true) ? write0(RESET) : false; } bool start(BH1750_MODE mode = BH1750_ONE_TIME_H_RESOLUTION_MODE2) { const INSTRUCTION MODES[] = { ONE_TIME_H_RESOLUTION_MODE, ONE_TIME_H_RESOLUTION_MODE2, ONE_TIME_L_RESOLUTION_MODE, }; _cmode = MODES[mode]; return measurement(); } bool stop(void) { return power(false); } void mtreg(int val = 0) { val += BH1750_DEFAULT_MTREG_VALUE; if (val < 31) val = 31; else if (val > 254) val = 254; _mtchg = _mtreg != val; _mtreg = val; } uint32_t lux(uint8_t accuracy = BH1750_DEFAULT_ACCURACY) { if (accuracy < 96) accuracy = 96; else if (accuracy > 144) accuracy = 144; uint32_t lux = (uint32_t)_result * (BH1750_DEFAULT_MTREG_VALUE * 100) / (accuracy * _mtreg); return BH1750_FIXED_POINT_NBITS > 0 ? lux << (+ BH1750_FIXED_POINT_NBITS - _fppos) : lux >> (- BH1750_FIXED_POINT_NBITS + _fppos); } uint8_t handle(void) { uint16_t now = System::millis(); switch (_status) { case BH1750_NO_DEVICE: case BH1750_IDLE: if ((uint16_t)(now - _start) >= _interval) measurement(); break; case BH1750_BUSY: if ((uint16_t)(now - _start) >= _ctime) return read(); break; } return _status; } uint8_t read(void) { return read0(); } }; |