-
Notifications
You must be signed in to change notification settings - Fork 1
/
model_baro.h
313 lines (277 loc) · 12.7 KB
/
model_baro.h
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
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
#pragma once // Please format this file with clang before check-in to GitHub
/*
File: model_baro.h
Software: Barry Hansen, K7BWH, [email protected], Seattle, WA
Hardware: John Vanderbeck, KM7O, Seattle, WA
The interface to this class provides:
1. Constructor BarometerModel baroModel();
2. Init hardware begin();
3. Read barometer for ongoing display baro.getBaroPressure();
4. Read-and-save barometer for data logger baro.logPressure( rightnow );
5. Load history from NVR baro.loadHistory();
6. Save history to NVR baro.saveHistory();
7. A few minor functions for unit tests
Data logger reads BMP280 or BMP388 or BMP390 hardware for pressure.
We get time-of-day from the caller. We don't read the realtime clock.
Barometric Sensor:
Goal is to hide hardware from the caller via conditional compile.
Adafruit BMP280 Barometric Pressure https://www.adafruit.com/product/2651
Adafruit BMP388 Barometric Pressure https://www.adafruit.com/product/3966
Adafruit BMP390 https://www.adafruit.com/product/4816
Bosch BMP280 datasheet: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp280-ds001.pdf
Bosch BMP388 datasheet: https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp388-ds001.pdf
Pressure History:
This class is basically a data logger for barometric pressure.
Q: How many data points should we store?
A: 288 samples
. Assume we want 288-pixel wide graph which leaves room for graph labels on the 320-pixel TFT display
. Assume we want one pixel for each sample, and yes this makes a pretty dense graph
. Assume we want a 3-day display, which means 288/3 = 96 pixels (samples) per day
. Then 24 hours / 96 pixels = 4 samples/hour = 15 minutes per sample
Units of Time:
This relies on "TimeLib.h" which uses "time_t" to represent time.
The basic unit of time (time_t) is the number of seconds since Jan 1, 1970,
a compact 4-byte integer.
https://github.com/PaulStoffregen/Time
Units of Pressure:
hPa is the abbreviated name for hectopascal (100 x 1 pascal) pressure
units which are exactly equal to millibar pressure unit (mb or mbar):
100 Pascals = 1 hPa = 1 millibar.
The hectopascal or millibar is the preferred unit for reporting barometric
or atmospheric pressure in European and many other countries.
The Adafruit BMP388 Precision Barometric Pressure sensor reports pressure
in 'float' values of Pascals.
In the USA and other backward countries that failed to adopt SI units,
barometric pressure is reported as inches-mercury (inHg).
1 pascal = 0.000295333727 inches of mercury, or
1 inch Hg = 3386.39 Pascal
So if you take the Pascal value of say 100734 and divide by 3386.39 you'll get 29.72 inHg.
The BMP388 sensor has a relative accuracy of 8 Pascals, which translates to
about +/- 0.5 meter of altitude.
*/
#if defined(ARDUINO_ADAFRUIT_FEATHER_RP2040)
#include <Wire.h>
#include <Adafruit_BMP280.h> // Precision barometric and temperature sensor
#include <Adafruit_Sensor.h>
#else
#include <Adafruit_BMP3XX.h> // Precision barometric and temperature sensor
#endif
#include "constants.h" // Griduino constants, colors, typedefs
#include "logger.h" // conditional printing to Serial port
#include "date_helper.h" // date/time conversions
// ========== extern ===========================================
extern Logger logger; // Griduino.ino
extern Dates date; // for "datetimeToString()", Griduino.ino
// ------------ definitions
#define MILLIBARS_PER_INCHES_MERCURY (0.02953)
#define BARS_PER_INCHES_MERCURY (0.0338639)
#define PASCALS_PER_INCHES_MERCURY (3386.39)
#define PASCALS_PER_HPA (100.0)
#define HPA_PER_PASCAL (0.01)
#define HPA_PER_INCHES_MERCURY (33.8639)
#define INCHES_MERCURY_PER_PASCAL (0.0002953)
// ========== class BarometerModel ======================
class BarometerModel {
public:
// Class member variables
#if defined(ARDUINO_ADAFRUIT_FEATHER_RP2040)
// BMP280 Constructor
Adafruit_BMP280 baro; // has-a hardware-managing class object, use I2C interface
BarometerModel() : baro(&Wire1) {}
#else
// BMP388 and BMP390 Constructor
Adafruit_BMP3XX baro; // has-a hardware-managing class object, use SPI interface
BarometerModel(int vChipSelect = BMP_CS) {
bmp_cs = vChipSelect;
}
#endif
int bmp_cs; // Chip Select for BMP388 / BMP390 hardware
float gPressure; // pressure in Pascals
float inchesHg; // same pressure in inHg
float celsius; // internal case temperature
#define maxReadings 384 // 384 = (4 readings/hour)*(24 hours/day)*(4 days)
#define lastIndex (maxReadings - 1) // index to the last element in pressure array
BaroReading pressureStack[maxReadings] = {}; // array to hold pressure data, init filled with zeros
// float elevCorr = 4241; // elevation correction in Pascals
// use difference between altimeter setting and station pressure: https://www.weather.gov/epz/wxcalc_altimetersetting
float elevCorr = 0; // todo: unused for now, review and change if needed
// init BMP388 or BMP390 barometer
int begin(void) {
int rc = 1; // assume success
#if defined(ARDUINO_ADAFRUIT_FEATHER_RP2040)
Wire1.begin();
bool initialized = baro.begin(0x76, 0x58); // Griduino v7 pcb, I2C
#else
//ol initialized = baro.begin_SPI(bmp_cs); // Griduino v4 pcb, SPI
bool initialized = baro.begin_I2C(); // Griduino v11 pcb, I2C
#endif
if (initialized) {
// IIR:
// An "infinite impulse response" filter intended to remove short-term
// fluctuations in pressure, e.g. caused by slamming a door or wind blowing
// on the sensor.
// Oversampling:
// Each oversampling step reduces noise and increases output resolution
// by one bit.
//
// ----- Settings recommended by Bosch based on use case for "handheld device dynamic"
// https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp388-ds001.pdf
// Section 3.5 Filter Selection, page 17
/*
baro->setTemperatureOversampling(BMP3_NO_OVERSAMPLING);
baro->setPressureOversampling(BMP3_OVERSAMPLING_4X);
baro->setIIRFilterCoeff(BMP3_IIR_FILTER_COEFF_7); // was 3, too busy
baro->setOutputDataRate(BMP3_ODR_50_HZ);
// logger.fencepost("model_baro.h", __LINE__);
/*****
// ----- Settings from Adafruit example
// https://github.com/adafruit/Adafruit_BMP3XX/blob/master/examples/bmp3xx_simpletest/bmp3xx_simpletest.ino
// Set up oversampling and filter initialization
baro->setTemperatureOversampling(BMP3_OVERSAMPLING_8X);
baro->setPressureOversampling(BMP3_OVERSAMPLING_4X);
baro->setIIRFilterCoeff(BMP3_IIR_FILTER_COEFF_3);
baro->setOutputDataRate(BMP3_ODR_50_HZ);
*****/
/*****
// ----- Settings from original Barograph example
// Set up BMP388 oversampling and filter initialization
baro->setTemperatureOversampling(BMP3_OVERSAMPLING_2X);
baro->setPressureOversampling(BMP3_OVERSAMPLING_32X);
baro->setIIRFilterCoeff(BMP3_IIR_FILTER_COEFF_127);
// baro->setOutputDataRate(BMP3_ODR_50_HZ);
*****/
// Get and discard the first data point
// Repeated because first reading is always bad, until iir oversampling buffers are populated
// for (int ii = 0; ii < 4; ii++) {
// baro->performReading(); // read hardware
// delay(50);
// }
//
} else {
logger.error("Error, unable to initialize Bosch pressure sensor");
#if defined(ARDUINO_ADAFRUIT_FEATHER_RP2040)
uint8_t id = baro.sensorID();
switch (id) {
case 0x00:
Serial.println(" ID of 0x00 means no response from hardware");
break;
case 0xFF:
Serial.println(" ID of 0xFF probably means a bad address, a BMP 180 or BMP 085");
break;
case 0x56:
case 0x57:
case 0x58:
Serial.println(" ID of 0x56-0x58 represents a BMP 280");
break;
case 0x60:
Serial.println(" ID of 0x60 represents a BME 280");
break;
case 0x61:
Serial.println(" ID of 0x61 represents a BME 680");
break;
default:
Serial.println(" ID is not recognized");
break;
}
#endif
rc = 0; // return failure
}
return rc;
}
float getAltitude(float sealevelPa) {
// input: sea level air pressure, Pascals
// returns: altitude, meters
return baro.readAltitude(sealevelPa / 100.0);
}
float getTemperature() {
// updates: celsius (public class var)
// query temperature from the C++ object, Celsius, not the hardware
celsius = baro.readTemperature();
return celsius;
}
float getBaroPressure() {
// returns: float Pascals
// updates: gPressure (public class var)
// inchesHg (public class var)
// continue anyway, for demo
gPressure = baro.readPressure(); // Pressure is returned in SI units of Pascals. 100 Pascals = 1 hPa = 1 millibar
inchesHg = gPressure * INCHES_MERCURY_PER_PASCAL;
return gPressure;
}
// the schedule is determined by the Controller
// controller should call this every 15 minutes
void logPressure(time_t rightnow) {
if (logger.print_info) {
float pressure = getBaroPressure(); // read
rememberPressure(pressure, rightnow); // push onto stack
Serial.print("logPressure( "); // debug
Serial.print(pressure, 1); // debug
Serial.println(" )"); // debug
}
saveHistory(); // write stack to NVR
}
// ========== load/save barometer pressure history =============
// Filenames MUST match between Griduino and Baroduino example program
// To erase and rewrite a new data file, change the version string below.
const char PRESSURE_HISTORY_FILE[25] = CONFIG_FOLDER "/barometr.dat";
const char PRESSURE_HISTORY_VERSION[15] = "Pressure v02";
int loadHistory() {
return true; // debug!!
SaveRestore history(PRESSURE_HISTORY_FILE, PRESSURE_HISTORY_VERSION);
BaroReading tempStack[maxReadings] = {}; // array to hold pressure data, fill with zeros
int result = history.readConfig((byte *)&tempStack, sizeof(tempStack));
if (result) {
int numNonZero = 0;
for (int ii = 0; ii < maxReadings; ii++) {
// pressureStack[ii] = tempStack[ii]; // debug!
if (pressureStack[ii].pressure > 0) {
numNonZero++;
}
}
logger.info(". Loaded barometric pressure history file, %d readings", numNonZero);
}
dumpPressureHistory(); // debug
return result;
}
void saveHistory() {
SaveRestore history(PRESSURE_HISTORY_FILE, PRESSURE_HISTORY_VERSION);
history.writeConfig((byte *)&pressureStack, sizeof(pressureStack));
logger.info("Saved pressure history to non-volatile memory");
}
void testRememberPressure(float pascals, time_t time) {
rememberPressure(pascals, time);
}
protected:
void rememberPressure(float pascals, time_t time) {
// interface for unit test
// push the given barometer reading onto the stack (without reading hardware)
// shift existing stack to the left
for (int ii = 0; ii < lastIndex; ii++) {
pressureStack[ii] = pressureStack[ii + 1];
}
// put the latest pressure onto the stack
pressureStack[lastIndex].pressure = pascals;
pressureStack[lastIndex].time = time;
}
void dumpPressureHistory() { // debug
// return; // debug debug
// format the barometric pressure array and write it to the Serial console log
// entire subroutine is for debug purposes
logger.info("Pressure history stack, non-zero values [line %d]", __LINE__);
if (logger.print_info) {
for (int ii = 0; ii < maxReadings; ii++) {
BaroReading item = pressureStack[ii];
if (item.pressure > 0) {
Serial.print("Stack[");
Serial.print(ii);
Serial.print("] = ");
Serial.print(item.pressure);
Serial.print(" ");
char msg[24];
Serial.println(date.datetimeToString(msg, sizeof(msg), item.time)); // debug
}
}
}
return;
}
}; // end class BarometerModel