muwerk mupplet Sensor Library
muwerk applets; mupplets: functional units that support specific hardware or reusable applications for sensors
Loading...
Searching...
No Matches
mup_presstemp_bmp280.h
1// mup_presstemp_bmp280.h
2#pragma once
3
4#ifndef tiny_twi_h
5#include <Wire.h>
6#else
7typedef TinyWire TwoWire;
8#endif
9
10#include "scheduler.h"
11#include "sensors.h"
12
13#include "helper/mup_i2c_registers.h"
14
15namespace ustd {
16
17// clang-format off
109// clang-format on
111 private:
112 String BMP280_VERSION = "0.1.0";
113 Scheduler *pSched;
114 TwoWire *pWire;
115 I2CRegisters *pI2C;
116 int tID;
117 String name;
118 double temperatureValue, pressureValue, pressureNNValue;
119 unsigned long stateMachineClock;
120 int32_t rawTemperature;
121 double calibratedTemperature;
122 int32_t rawPressure;
123 double calibratedPressure;
124 double baseRelativeNNPressure;
125 bool relativeAltitudeStarted;
126 bool captureRelative = false;
127
128 // BMP Sensor calibration data
129 int16_t dig_T2, dig_T3, dig_P2, dig_P3, dig_P4, dig_P5, dig_P6, dig_P7, dig_P8, dig_P9;
130 uint16_t dig_T1, dig_P1, dig_reserved;
131
132 public:
133 enum BMPSensorState { UNAVAILABLE,
134 IDLE,
135 MEASUREMENT_WAIT,
136 WAIT_NEXT_MEASUREMENT };
137
145 };
146 BMPSensorState sensorState;
147 unsigned long errs = 0;
148 unsigned long oks = 0;
149 unsigned long basePollRate = 500000L;
150 uint32_t pollRateMs = 2000;
151 uint32_t lastPollMs = 0;
152 uint16_t oversampleMode = 3; // 1..5, see BMPSampleMode.
153 uint16_t oversampleModePressure = 3;
154 uint16_t oversampleModeTemperature = 1;
155 enum FilterMode { FAST,
156 MEDIUM,
157 LONGTERM };
158#define MUP_BMP_INVALID_ALTITUDE -1000000.0
159 double referenceAltitudeMeters;
160 FilterMode filterMode;
161 uint8_t i2cAddress;
162 ustd::sensorprocessor temperatureSensor = ustd::sensorprocessor(4, 600, 0.005);
163 ustd::sensorprocessor pressureSensor = ustd::sensorprocessor(4, 600, 0.005);
164 bool bActive = false;
165
166 PressTempBMP280(String name, FilterMode filterMode = FilterMode::MEDIUM, uint8_t i2cAddress = 0x76)
167 : name(name), filterMode(filterMode), i2cAddress(i2cAddress) {
173 sensorState = BMPSensorState::UNAVAILABLE;
174 referenceAltitudeMeters = MUP_BMP_INVALID_ALTITUDE;
175 relativeAltitudeStarted = false;
176 captureRelative = false;
177 pI2C = nullptr;
178 setFilterMode(filterMode, true);
179 }
180
182 if (pI2C) {
183 delete pI2C;
184 }
185 }
186
187 void setReferenceAltitude(double _referenceAltitudeMeters) {
191 referenceAltitudeMeters = _referenceAltitudeMeters;
192 }
193
200 if (referenceAltitudeMeters != MUP_BMP_INVALID_ALTITUDE) {
201 captureRelative = true;
202 }
203 }
204
205 double getTemperature() {
209 return temperatureValue;
210 }
211
212 double getPressure() {
216 return pressureValue;
217 }
218
219 double getPressureNN(double _pressure) {
227 if (referenceAltitudeMeters != MUP_BMP_INVALID_ALTITUDE) {
228 double prNN = _pressure / pow((1.0 - (referenceAltitudeMeters / 44330.0)), 5.255);
229 return prNN;
230 }
231 return 0.0;
232 }
233
234 void setSampleMode(BMPSampleMode _sampleMode) {
235 oversampleMode = (uint16_t)_sampleMode;
236 oversampleModePressure = oversampleMode;
237 if (oversampleModePressure == BMPSampleMode::ULTRA_HIGH_RESOLUTION) {
238 oversampleModeTemperature = 2; // as per datasheet recommendations.
239 } else {
240 oversampleModeTemperature = 1;
241 }
242 }
243
244 bool initBmpSensorConstants() {
245 // Bosch made a long, winded paragraph about calibration data,
246 // but didn't manage to mention that this uses the other endianness :-/ .
247 if (!pI2C->readRegisterWordLE(0x88, (uint16_t *)&dig_T1)) return false;
248 if (!pI2C->readRegisterWordLE(0x8A, (uint16_t *)&dig_T2)) return false;
249 if (!pI2C->readRegisterWordLE(0x8C, (uint16_t *)&dig_T3)) return false;
250 if (!pI2C->readRegisterWordLE(0x8E, (uint16_t *)&dig_P1)) return false;
251 if (!pI2C->readRegisterWordLE(0x90, (uint16_t *)&dig_P2)) return false;
252 if (!pI2C->readRegisterWordLE(0x92, (uint16_t *)&dig_P3)) return false;
253 if (!pI2C->readRegisterWordLE(0x94, (uint16_t *)&dig_P4)) return false;
254 if (!pI2C->readRegisterWordLE(0x96, (uint16_t *)&dig_P5)) return false;
255 if (!pI2C->readRegisterWordLE(0x98, (uint16_t *)&dig_P6)) return false;
256 if (!pI2C->readRegisterWordLE(0x9A, (uint16_t *)&dig_P7)) return false;
257 if (!pI2C->readRegisterWordLE(0x9C, (uint16_t *)&dig_P8)) return false;
258 if (!pI2C->readRegisterWordLE(0x9E, (uint16_t *)&dig_P9)) return false;
259 // if (!pI2C->readRegisterWordLE(0xA0,(uint16_t*)&dig_reserved)) return false;
260 dig_reserved = 0xffff; // Don't try to read.
261 return true;
262 }
263
264 void begin(Scheduler *_pSched, TwoWire *_pWire = &Wire, uint32_t _pollRateMs = 2000, BMPSampleMode _sampleMode = BMPSampleMode::STANDARD) {
265 pSched = _pSched;
266 pWire = _pWire;
267 uint8_t data;
268
269 pollRateMs = _pollRateMs;
270 pI2C = new I2CRegisters(pWire, i2cAddress);
271 setSampleMode(_sampleMode);
272
273 auto ft = [=]() { this->loop(); };
274 tID = pSched->add(ft, name, basePollRate); // 500us
275
276 auto fnall = [=](String topic, String msg, String originator) {
277 this->subsMsg(topic, msg, originator);
278 };
279 pSched->subscribe(tID, name + "/sensor/#", fnall);
280
281 pI2C->lastError = pI2C->checkAddress(i2cAddress);
282 if (pI2C->lastError == I2CRegisters::I2CError::OK) {
283 if (!pI2C->readRegisterByte(0xd0, &data)) { // 0xd0: chip-id register
284#ifdef USE_SERIAL_DBG
285 Serial.print("Failed to inquire BMP280 chip-id at address 0x");
286 Serial.println(i2cAddress, HEX);
287#endif
288 bActive = false;
289 } else {
290 if (data == 0x58) { // 0x58: signature of BMP280
291 if (!initBmpSensorConstants()) {
292#ifdef USE_SERIAL_DBG
293 Serial.print("Failed to read calibration data for sensor BMP280 at address 0x");
294 Serial.println(i2cAddress, HEX);
295#endif
296 pI2C->lastError = I2CRegisters::I2CError::I2C_HW_ERROR;
297 bActive = false;
298 } else {
299#ifdef USE_SERIAL_DBG
300 Serial.print("BMP280 sensor active at address 0x");
301 Serial.println(i2cAddress, HEX);
302#endif
303 sensorState = BMPSensorState::IDLE;
304 bActive = true;
305 }
306 } else {
307#ifdef USE_SERIAL_DBG
308 Serial.print("Wrong hardware (not BMP280) found at address 0x");
309 Serial.print(i2cAddress, HEX);
310 Serial.print(" chip-id is ");
311 Serial.print(data, HEX);
312 Serial.println(" expected: 0x58 for BMP280.");
313#endif
314 pI2C->lastError = I2CRegisters::I2CError::I2C_WRONG_HARDWARE_AT_ADDRESS;
315 bActive = false;
316 }
317 }
318 } else {
319#ifdef USE_SERIAL_DBG
320 Serial.print("No BMP280 sensor found at address 0x");
321 Serial.println(i2cAddress, HEX);
322#endif
323 bActive = false;
324 }
325 }
326
327 void setFilterMode(FilterMode mode, bool silent = false) {
328 switch (mode) {
329 case FAST:
330 filterMode = FAST;
331 temperatureSensor.update(1, 2, 0.05);
332 pressureSensor.update(1, 2, 0.1);
333 break;
334 case MEDIUM:
335 filterMode = MEDIUM;
336 temperatureSensor.update(4, 30, 0.1);
337 pressureSensor.update(4, 30, 0.5);
338 break;
339 case LONGTERM:
340 default:
341 filterMode = LONGTERM;
342 temperatureSensor.update(10, 600, 0.1);
343 pressureSensor.update(50, 600, 0.5);
344 break;
345 }
346 if (!silent)
347 publishFilterMode();
348 }
349
350 private:
351 void publishTemperature() {
352 char buf[32];
353 sprintf(buf, "%6.2f", temperatureValue);
354 pSched->publish(name + "/sensor/temperature", buf);
355 }
356
357 void publishPressure() {
358 char buf[32];
359 sprintf(buf, "%7.2f", pressureValue);
360 pSched->publish(name + "/sensor/pressure", buf);
361 if (referenceAltitudeMeters != MUP_BMP_INVALID_ALTITUDE) {
362 sprintf(buf, "%7.2f", pressureNNValue);
363 pSched->publish(name + "/sensor/pressureNN", buf);
364 }
365 }
366
367 void publishError(String errMsg) {
368 pSched->publish(name + "/sensor/error", errMsg);
369 }
370
371 void publishFilterMode() {
372 switch (filterMode) {
373 case FilterMode::FAST:
374 pSched->publish(name + "/sensor/mode", "FAST");
375 break;
376 case FilterMode::MEDIUM:
377 pSched->publish(name + "/sensor/mode", "MEDIUM");
378 break;
379 case FilterMode::LONGTERM:
380 pSched->publish(name + "/sensor/mode", "LONGTERM");
381 break;
382 }
383 }
384
385 void publishOversampling() {
386 switch ((BMPSampleMode)oversampleMode) {
388 pSched->publish(name + "/sensor/oversampling", "ULTRA_LOW_POWER");
389 break;
391 pSched->publish(name + "/sensor/oversampling", "LOW_POWER");
392 break;
394 pSched->publish(name + "/sensor/oversampling", "STANDARD");
395 break;
397 pSched->publish(name + "/sensor/oversampling", "HIGH_RESOLUTION");
398 break;
400 pSched->publish(name + "/sensor/oversampling", "ULTRA_HIGH_RESOLUTION");
401 break;
402 default:
403 pSched->publish(name + "/sensor/oversampling", "INVALID");
404 break;
405 }
406 }
407
408 void publishCalibrationData() {
409 char msg[256];
410 sprintf(msg, "dig_T1=%u, dig_T2=%d, dig_T3=%d, dig_P1=%u, dig_P2=%d, dig_P3=%d, dig_P4=%d, dig_P5=%d, dig_P6=%d, dig_P7=%d, dig_P8=%d, dig_P9=%d, dig_reserved=%u",
411 dig_T1, dig_T2, dig_T3, dig_P1, dig_P2, dig_P3, dig_P4, dig_P5, dig_P6, dig_P7, dig_P8, dig_P9, dig_reserved);
412 pSched->publish("sensor/calibrationdata", msg);
413 }
414
415 void publishReferenceAltitude() {
416 if (referenceAltitudeMeters != MUP_BMP_INVALID_ALTITUDE) {
417 char buf[32];
418 sprintf(buf, "%7.2f", referenceAltitudeMeters);
419 pSched->publish(name + "/sensor/referencealtitude", buf);
420 } else {
421 pSched->publish(name + "/sensor/referencealtitude", "unknown");
422 }
423 }
424
425 void publishRelativeAltitude() {
426 char buf[32];
427 if (relativeAltitudeStarted) {
428 double ralt = 44330.0 * (1.0 - pow(pressureValue / baseRelativeNNPressure, 1. / 5.255));
429 sprintf(buf, "%7.2f", ralt);
430 pSched->publish(name + "/sensor/relativealtitude", buf);
431 double dalt = ralt - referenceAltitudeMeters;
432 sprintf(buf, "%7.2f", dalt);
433 pSched->publish(name + "/sensor/deltaaltitude", buf);
434 }
435 }
436
437 bool sensorStateMachine() {
438 bool newData = false;
439 uint32_t rt;
440 uint32_t rp;
441 uint8_t reg_data;
442 const uint8_t status_register = 0xf3;
443 const uint8_t measure_mode_register = 0xf4;
444 const uint8_t config_register = 0xf5;
445 const uint8_t temperature_registers = 0xfa;
446 const uint8_t pressure_registers = 0xf7;
447 uint8_t status;
448 uint8_t normalmodeInactivity = 0, IIRfilter = 0; // not used.
449 switch (sensorState) {
450 case BMPSensorState::UNAVAILABLE:
451 break;
452 case BMPSensorState::IDLE:
453 reg_data = (normalmodeInactivity << 5) + (IIRfilter << 2) + 0;
454 if (!pI2C->writeRegisterByte(config_register, reg_data)) {
455 ++errs;
456 sensorState = BMPSensorState::WAIT_NEXT_MEASUREMENT;
457 stateMachineClock = micros();
458 break;
459 }
460 reg_data = (oversampleModeTemperature << 5) + (oversampleModePressure << 2) + 0x1; // 0x3: normal mode, 0x1 one-shot
461 if (!pI2C->writeRegisterByte(measure_mode_register, reg_data)) {
462 ++errs;
463 sensorState = BMPSensorState::WAIT_NEXT_MEASUREMENT;
464 stateMachineClock = micros();
465 break;
466 } else { // Start temperature read
467 sensorState = BMPSensorState::MEASUREMENT_WAIT;
468 stateMachineClock = micros();
469 }
470 break;
471 case BMPSensorState::MEASUREMENT_WAIT:
472 if (!pI2C->readRegisterByte(status_register, &status)) {
473 // no status
474 ++errs;
475 sensorState = BMPSensorState::WAIT_NEXT_MEASUREMENT;
476 stateMachineClock = micros();
477 break;
478 }
479 status = status & 0x09;
480 if (timeDiff(stateMachineClock, micros()) > 1 && status == 0) { // 1ms for meas, no status set.
481 rt = 0;
482 rp = 0;
483 if (pI2C->readRegisterTripple(temperature_registers, &rt) && pI2C->readRegisterTripple(pressure_registers, &rp)) {
484 rawTemperature = rt >> 4;
485 rawPressure = rp >> 4;
486 sensorState = BMPSensorState::WAIT_NEXT_MEASUREMENT;
487 stateMachineClock = micros();
488 ++oks;
489 newData = true;
490 } else {
491 ++errs;
492 sensorState = BMPSensorState::WAIT_NEXT_MEASUREMENT;
493 stateMachineClock = micros();
494 }
495 }
496 break;
497 case BMPSensorState::WAIT_NEXT_MEASUREMENT:
498 if (timeDiff(lastPollMs, millis()) > pollRateMs) {
499 sensorState = BMPSensorState::IDLE; // Start next cycle.
500 lastPollMs = millis();
501 }
502 break;
503 }
504 return newData;
505 }
506
507 // From BOSCH datasheet BMA150 data sheet Rev. 1.4
508 // Returns temperature in DegC, double precision. Output value of “51.23” equals 51.23 DegC.
509 // t_fine carries fine temperature used by pressure
510 // int32_t t_fine;
511 double bmp280_compensate_T_double(int32_t adc_T, int32_t *pt_fine) {
512 double var1, var2, T;
513 var1 = (((double)adc_T) / 16384.0 - ((double)dig_T1) / 1024.0) * ((double)dig_T2);
514 var2 = ((((double)adc_T) / 131072.0 - ((double)dig_T1) / 8192.0) *
515 (((double)adc_T) / 131072.0 - ((double)dig_T1) / 8192.0)) *
516 ((double)dig_T3);
517 *pt_fine = (int32_t)(var1 + var2);
518 T = (var1 + var2) / 5120.0;
519 return T;
520 }
521
522 // From BOSCH datasheet BMA150 data sheet Rev. 1.4
523 // Returns pressure in Pa as double. Output value of “96386.2” equals 96386.2 Pa = 963.862 hPa
524 double bmp280_compensate_P_double(int32_t adc_P, int32_t t_fine) {
525 double var1, var2, p;
526 var1 = ((double)t_fine / 2.0) - 64000.0;
527 var2 = var1 * var1 * ((double)dig_P6) / 32768.0;
528 var2 = var2 + var1 * ((double)dig_P5) * 2.0;
529 var2 = (var2 / 4.0) + (((double)dig_P4) * 65536.0);
530 var1 = (((double)dig_P3) * var1 * var1 / 524288.0 + ((double)dig_P2) * var1) / 524288.0;
531 var1 = (1.0 + var1 / 32768.0) * ((double)dig_P1);
532 if (var1 == 0.0) {
533 return 0; // avoid exception caused by division by zero
534 }
535 p = 1048576.0 - (double)adc_P;
536 p = (p - (var2 / 4096.0)) * 6250.0 / var1;
537 var1 = ((double)dig_P9) * p * p / 2147483648.0;
538 var2 = p * ((double)dig_P8) / 32768.0;
539 p = p + (var1 + var2 + ((double)dig_P7)) / 16.0;
540 return p;
541 }
542
543 bool calibrateRawData() {
544 int32_t t_fine;
545 // Temperature
546 calibratedTemperature = bmp280_compensate_T_double(rawTemperature, &t_fine);
547 // Pressure
548 calibratedPressure = bmp280_compensate_P_double(rawPressure, t_fine) / 100.0;
549 return true;
550 }
551
552 void loop() {
553 double tempVal, pressVal;
554 if (bActive) {
555 if (!sensorStateMachine()) return; // no new data
556 calibrateRawData();
557 tempVal = (double)calibratedTemperature;
558 pressVal = (double)calibratedPressure;
559 if (temperatureSensor.filter(&tempVal)) {
560 temperatureValue = tempVal;
561 publishTemperature();
562 }
563 if (pressureSensor.filter(&pressVal)) {
564 pressureValue = pressVal;
565 if (referenceAltitudeMeters != MUP_BMP_INVALID_ALTITUDE) {
566 pressureNNValue = getPressureNN(pressureValue);
567 if (captureRelative) {
568 baseRelativeNNPressure = pressureNNValue;
569 relativeAltitudeStarted = true;
570 captureRelative = false;
571 }
572 }
573 publishPressure();
574 if (relativeAltitudeStarted) publishRelativeAltitude();
575 }
576 }
577 }
578
579 void subsMsg(String topic, String msg, String originator) {
580 if (topic == name + "/sensor/temperature/get") {
581 publishTemperature();
582 } else if (topic == name + "/sensor/pressure/get") {
583 publishPressure();
584 } else if (topic == name + "/sensor/mode/get") {
585 publishFilterMode();
586 } else if (topic == name + "/sensor/calibrationdata/get") {
587 publishCalibrationData();
588 } else if (topic == name + "/sensor/referencealtitude/get") {
589 publishReferenceAltitude();
590 } else if (topic == name + "/sensor/relativealtitude/get") {
591 publishRelativeAltitude();
592 } else if (topic == name + "/sensor/relativealtitude/set") {
594 } else if (topic == name + "/sensor/oversampling/get") {
595 publishOversampling();
596 } else if (topic == name + "/sensor/referencealtitude/set") {
597 double alt = atof(msg.c_str());
599 } else if (topic == name + "/sensor/mode/set") {
600 if (msg == "fast" || msg == "FAST") {
601 setFilterMode(FilterMode::FAST);
602 } else {
603 if (msg == "medium" || msg == "MEDIUM") {
604 setFilterMode(FilterMode::MEDIUM);
605 } else {
606 setFilterMode(FilterMode::LONGTERM);
607 }
608 }
609 } else if (topic == name + "/sensor/oversampling/set") {
610 if (msg == "ULTRA_LOW_POWER") {
611 setSampleMode(BMPSampleMode::ULTRA_LOW_POWER);
612 } else {
613 if (msg == "LOW_POWER") {
614 setSampleMode(BMPSampleMode::LOW_POWER);
615 } else {
616 if (msg == "STANDARD") {
617 setSampleMode(BMPSampleMode::STANDARD);
618 } else {
619 if (msg == "HIGH_RESOLUTION") {
620 setSampleMode(BMPSampleMode::HIGH_RESOLUTION);
621 } else {
623 }
624 }
625 }
626 }
627 }
628 }
629
630}; // PressTempBMP
631
632} // namespace ustd
mupplet-sensor temperature and pressure with Bosch BMP280
Definition: mup_presstemp_bmp280.h:110
double getTemperature()
Definition: mup_presstemp_bmp280.h:205
BMPSampleMode
Definition: mup_presstemp_bmp280.h:139
@ HIGH_RESOLUTION
8 samples, pressure resolution 19bit / 0.33 Pa, rec temperature oversampling: x1
Definition: mup_presstemp_bmp280.h:143
@ ULTRA_HIGH_RESOLUTION
16 samples, pressure resolution 20bit / 0.16 Pa, rec temperature oversampling: x2
Definition: mup_presstemp_bmp280.h:144
@ ULTRA_LOW_POWER
1 samples, pressure resolution 16bit / 2.62 Pa, rec temperature oversampling: x1
Definition: mup_presstemp_bmp280.h:140
@ LOW_POWER
2 samples, pressure resolution 17bit / 1.31 Pa, rec temperature oversampling: x1
Definition: mup_presstemp_bmp280.h:141
@ STANDARD
4 samples, pressure resolution 18bit / 0.66 Pa, rec temperature oversampling: x1
Definition: mup_presstemp_bmp280.h:142
void startRelativeAltitude()
Definition: mup_presstemp_bmp280.h:194
double getPressureNN(double _pressure)
Definition: mup_presstemp_bmp280.h:219
double getPressure()
Definition: mup_presstemp_bmp280.h:212
PressTempBMP280(String name, FilterMode filterMode=FilterMode::MEDIUM, uint8_t i2cAddress=0x76)
Definition: mup_presstemp_bmp280.h:166
void setReferenceAltitude(double _referenceAltitudeMeters)
Definition: mup_presstemp_bmp280.h:187