forked from FlorinAndrei/WeatherStation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
nano33.ino
258 lines (222 loc) · 7.37 KB
/
nano33.ino
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
/*
* This code is supposed to run on an Arduino Nano 33 BLE Sense.
* It makes full use of all sensors on that device.
*
* It reads atmospheric parameters such as:
* - temperature
* - pressure
* - humidity
*
* It reads other environmental parameters such as light intensity.
*
* It reads ambient sound via the onboard microphone. It performs FFT
* (Fast Fourier Transform) on the sound samples. From the spectrum generated by FFT
* some frequencies are discarded (those that appear to be outliers).
* The rest are transformed via a logarithmic function that mimics the human ear
* and then are summmed up. The sum is taken to be a measure of ambient noise.
*
* Finally, accelerometer and gyroscope data are recorded, along with magnetic field data.
*
* All these measurements are taken several times per second. Individual samples are
* averaged over one second, then are sent via USB/serial out.
*
* That's all this code does. Take all measurements from all sensors
* as fast as possible, and send those out (one line = one batch of measurements).
*
* It's up to the receiver side to parse, interpret, and store that data.
*
* Care was taken to prevent system freeze
* by allowing brief pauses between sensor reads.
*
* Probably not all sensor reads need to be braketed by delay().
* But it's time-consuming to figure out which need delay().
* It's probably easier to intersperse delay() among all sensor reads
* and just tweak the value of the delay() parameter.
* A minimmum of one week of uninterrupted work is considered the gold standard
* for stability.
*
* TBD: In the future some kind of watchdog needs to be implemented
* since not all lock-up situations can be avoided with code shims such as delay().
*
* A very solid code that doesn't seem to need a watchdog would be
* a solid base for everything else. A watchdog would then be more like insurance.
*/
// HTS221 sensor library
// https://www.arduino.cc/en/Reference/ArduinoHTS221
// https://github.com/arduino-libraries/Arduino_HTS221
#include <Arduino_HTS221.h>
// LPS22HB sensor library
// https://www.arduino.cc/en/Reference/ArduinoLPS22HB
// https://github.com/arduino-libraries/Arduino_LPS22HB
#include <Arduino_LPS22HB.h>
// LSM9DS1 sensor library
// https://www.arduino.cc/en/Reference/ArduinoLSM9DS1
// https://github.com/arduino-libraries/Arduino_LSM9DS1
#include <Arduino_LSM9DS1.h>
// APDS9960 sensor library
// https://www.arduino.cc/en/Reference/ArduinoAPDS9960
// https://github.com/arduino-libraries/Arduino_APDS9960
#include <Arduino_APDS9960.h>
// MP34DT05 sensor library
// https://www.arduino.cc/en/Reference/PDM
// https://github.com/arduino/ArduinoCore-nRF528x-mbedos/tree/master/libraries/PDM
#include <PDM.h>
// TBD
#include <arduinoFFT.h>
arduinoFFT FFT = arduinoFFT();
// store readings from sensors
float temperature, humidity, pressure;
float acc_x, acc_y, acc_z;
float gyro_x, gyro_y, gyro_z;
float magnet_x, magnet_y, magnet_z;
// line output to serial
char linebuf_all[200];
// store readings from light sensor
int r, g, b, w;
// define FFT parameters
#define SAMPLES 256
#define SAMPLING_FREQUENCY 16000
// buffer to read samples into, each sample is 16-bits
short wform[SAMPLES];
// FFT real and imaginary vectors
double vReal[SAMPLES];
double vImag[SAMPLES];
// number of samples read
volatile int samplesRead;
// constrain the APDS readiness loop
short apds_loop;
#define APDS_MAX 50
// final result from FFT
double ftsum = 0.0;
// short pause between sensor reads
short srelax = 40;
int ledState = LOW;
// watchdog timeout in seconds
// 1 second is shorter than sketch upload time
// so uploads will fail when this sketch is loaded
// tap the Reset button twice quickly
// and the LED will start pulsing slowly
// then do the sketch upload
int wdt = 1;
void setup() {
Serial.begin(115200);
delay(srelax);
// configure watchdog
NRF_WDT->CONFIG = 0x01; // Configure WDT to run when CPU is asleep
NRF_WDT->CRV = int(wdt * 32768 + 1); // CRV = timeout * 32768 + 1
NRF_WDT->RREN = 0x01; // Enable the RR[0] reload register
NRF_WDT->TASKS_START = 1; // Start WDT
delay(srelax);
// temperature and humidity
HTS.begin();
delay(srelax);
// pressure
BARO.begin();
delay(srelax);
// The baro sensor reads wrong first time after init
// so let's do a throw-away read here.
pressure = BARO.readPressure(MILLIBAR);
delay(srelax);
// acceleration, gyroscope, magnetic field
IMU.begin();
delay(srelax);
// light
APDS.begin();
delay(srelax);
// sound
PDM.onReceive(onPDMdata);
delay(srelax);
PDM.begin(1, SAMPLING_FREQUENCY);
delay(srelax);
pinMode(LED_BUILTIN, OUTPUT);
// Let's allow things to settle down.
delay(srelax);
}
void loop() {
// Reload the WDTs RR[0] reload register
// this will reset the watchdog every loop
// as long as all goes well
NRF_WDT->RR[0] = WDT_RR_RR_Reload;
apds_loop = 0;
// always check if sensor is available before reading from it
while (! APDS.colorAvailable()) {
// always wait a bit after APDS.colorAvailable()
delay(srelax);
// don't get stuck
if (++apds_loop > APDS_MAX) {
break;
}
}
if (apds_loop <= APDS_MAX) {
APDS.readColor(r, g, b, w);
delay(srelax);
} else {
// failed to read, move on
r = 0;
g = 0;
b = 0;
w = 0;
}
temperature = HTS.readTemperature();
delay(srelax);
humidity = HTS.readHumidity();
delay(srelax);
pressure = BARO.readPressure(MILLIBAR);
delay(srelax);
IMU.readAcceleration(acc_x, acc_y, acc_z);
delay(srelax);
IMU.readGyroscope(gyro_x, gyro_y, gyro_z);
delay(srelax);
IMU.readMagneticField(magnet_x, magnet_y, magnet_z);
delay(srelax);
// wait for sound samples to be read
if (samplesRead) {
delay(srelax);
for (int i = 0; i < SAMPLES; i++) {
// load the waveform into the FFT real vector
vReal[i] = double(wform[i]);
// FFT imaginary vector is zero
vImag[i] = 0.0;
}
// compute the spectrum
// at the end of the sequence, vReal will contain the spectrum
FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);
// calculate the sum of all spectral components
// with log10() to adjust for perceptual scale
ftsum = 0.0;
// don't start i at 0, low frequencies are too noisy
// stop at sR / 2 since the spectrum is repeated symmetrically after that
// (that's how FFT works)
for (int i = 8; i < samplesRead / 2; i++) {
ftsum += log10(vReal[i]);
}
// clear the samples read count
samplesRead = 0;
}
// prepare the line output with all data
sprintf(linebuf_all,
"a,%.2f,%.1f,%.2f,g,%.2f,%.2f,%.2f,r,%.2f,%.2f,%.2f,m,%.1f,%.1f,%.1f,l,%u,%u,%u,%u,n,%u",
temperature, humidity, pressure,
acc_x, acc_y, acc_z,
gyro_x, gyro_y, gyro_z,
magnet_x, magnet_y, magnet_z,
r, g, b, w,
int(ftsum));
// send data out
Serial.println(linebuf_all);
// blink the LED every cycle
// (heartbeat indicator)
ledState = ledState ? LOW: HIGH;
digitalWrite(LED_BUILTIN, ledState);
delay(srelax);
}
void onPDMdata() {
// query the number of bytes available
int bytesAvailable = PDM.available();
// read into the sample buffer
PDM.read(wform, bytesAvailable);
// 16-bit, 2 bytes per sample
samplesRead = bytesAvailable / 2;
}