-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathFallDetection.ino
386 lines (290 loc) · 9.47 KB
/
FallDetection.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
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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_ADXL345_U.h>
/**
Code Written By: Tendai Makumire
Last Updated: 09-May-2020
Code Description:
Code for wearable(on the waist) device designed to detect falls. Uses the ADXL345
to detect orientation and measure the horizontal vector magnitude (HZM). The ADXl345
is oriented so that the X axis is downwards and the Y axis points to the right.
Thresholds are extracted from SisFall Dataset.
TODO:
- Use proper MAX values for checks
- Automatic Calibration using Magnetometer
- Improve Orientation estimate with gyroscope
- Develop interrupt based I2C driver for sensor
- Reduce memory usage
**/
/************************* Defines *******************************************************/
#define CONSTANT_G 9.81
#define ACCEL_THRESHOLD (1.82 * CONSTANT_G)
#define SETTLE_THRESHOLD (0.30 * CONSTANT_G)
#define ANGLE_THRESHOLD 40.0
#define BUFFER_LEN 127
#define SAMPLE_TIME_MS 40
#define MAX_UINT16 1000
#define MAX_INT16 1000
#define MAX_FLOAT 1000.0
#define OFFSET_X 0.32;
#define OFFSET_Y 0.56;
#define OFFSET_Z 1.06;
/************************* Structs ****************************************************************/
typedef struct {
float roll;
float pitch;
} Imu_rotation;
typedef struct {
float x;
float y;
float z;
} Imu_accel;
/************************* Enums ****************************************************************/
enum States {
stateMeasureAccel,
stateSetTriggers,
stateResetTriggers,
stateCheckSignal,
stateCheckOrientation,
stateFallDetected
};
/************************* Function Prototypes ****************************************************/
Imu_accel ReadAccel();
Imu_rotation CalcTilt(Imu_accel* accel);
Imu_rotation AverageRotation(uint16_t start_index, uint8_t samples);
float CalcRoll(Imu_accel* accel);
float CalcPitch(Imu_accel* accel);
float CalcHoriz_Mag(Imu_accel* accel);
float CalcPkPk();
float FilterLowPass(float new_input, float old_output);
uint16_t GetBufferPosition(uint16_t current_pos, int8_t samples);
/************************* Global Variables ***********************************************/
States mState;
Imu_rotation mTilt[BUFFER_LEN];
Imu_rotation mCurrent_orientation;
float mVector_mag[10] = {0.0};
uint8_t mMag_index = 0;
uint16_t mBuffer_index = 0;
uint16_t mAccel_trigger = MAX_UINT16;
int16_t mTilt_trigger = MAX_INT16;
float mY_rotF = 0.0;
float mZ_rotF = 0.0;
unsigned long mCurrent_time;
Adafruit_ADXL345_Unified adxl = Adafruit_ADXL345_Unified(1);
/************************* Main ****************************************************/
void setup() {
Serial.begin(115200);
// Disable pullups
digitalWrite(SDA, LOW);
digitalWrite(SCL, LOW);
// Enable LED
pinMode(LED_BUILTIN, OUTPUT);
/* Initialise the ADXL345 */
while (!adxl.begin()) {
/* Flash the LED a couple of times times then try again */
blinkLED();
}
// Initialise ADXL345 parameters
adxl.setRange(ADXL345_RANGE_16_G);
adxl.setDataRate(ADXL345_DATARATE_25_HZ);
// Set initial values for orientation
Imu_accel initAccel = ReadAccel();
mTilt[mBuffer_index] = CalcTilt(&initAccel);
mBuffer_index++;
mCurrent_time = millis();
// Start measuring accel
mState = stateMeasureAccel;
}
void loop() {
switch(mState) {
case stateMeasureAccel:
{
// Wait 40ms until next sample
while((unsigned long)(millis() - mCurrent_time) <= SAMPLE_TIME_MS);
Imu_accel accel = ReadAccel();
mCurrent_time = millis();
// Calculate Horizontal acceleration vector magnitude
float accel_mag = CalcHoriz_Mag(&accel);
if (mBuffer_index > mAccel_trigger + 50) {
mVector_mag[mMag_index] = accel_mag;
mMag_index++;
}
// Get Current roll/pitch angles
Imu_rotation filteredTilt;
Imu_rotation currentTilt = CalcTilt(&accel);
// Apply Low Pass IIR Filter to roll/pitch angles
mY_rotF = FilterLowPass(currentTilt.pitch, mY_rotF);
mZ_rotF = FilterLowPass(currentTilt.roll, mZ_rotF);
filteredTilt.roll = mZ_rotF;
filteredTilt.pitch = mY_rotF;
mTilt[mBuffer_index] = filteredTilt;
/* ------------ DEBUG Roll and Pitch ------------*/
Serial.print(mTilt[mBuffer_index].pitch); Serial.print(" ");
Serial.println(mTilt[mBuffer_index].roll);
// Check if acceleration above the threshold
if (accel_mag > ACCEL_THRESHOLD) {
mState = stateSetTriggers;
}
// Check if enough time has passed after the fall trigger
if (mBuffer_index == mTilt_trigger) {
mState = stateCheckSignal;
}
break;
}
case stateSetTriggers:
{
mMag_index = 0;
mAccel_trigger = mBuffer_index;
mTilt_trigger = GetBufferPosition(mAccel_trigger, 60);
// Get Orientation 2.4s before acceleration trigger and set as current Orientation
// Take an average of the samples before from -2.4s to -2.0s
uint16_t index = GetBufferPosition(mAccel_trigger, -60);
mCurrent_orientation = AverageRotation(index, 10);
/* ------------ DEBUG Roll and Pitch after High Accel ------------*/
Serial.print("Current Orientation is (Forward/Backward | Left/Right: ");
Serial.print(mCurrent_orientation.pitch); Serial.print(" ");
Serial.println(mCurrent_orientation.roll);
// Go back to measuring the accel
mState = stateMeasureAccel;
break;
}
case stateCheckSignal:
{
if (CalcPkPk() < SETTLE_THRESHOLD) {
/* ------------------------ DEBUG ------------------------*/
Serial.println("Signal has settled, check change in orientation");
mState = stateCheckOrientation;
}
else {
// No Fall Detected so reset the trigger points
mState = stateResetTriggers;
}
break;
}
case stateResetTriggers:
{
mMag_index = 0;
mTilt_trigger = MAX_INT16;
mAccel_trigger = MAX_UINT16;
// Continue measuring accel
mState = stateMeasureAccel;
break;
}
case stateCheckOrientation:
{
uint16_t index = GetBufferPosition(mBuffer_index, -10);
Imu_rotation new_orientation = AverageRotation(index, 10);
/* ------------ DEBUG Roll and Pitch after High Accel ------------*/
Serial.print("New Orientation is (Forward/Backward | Left/Right: ");
Serial.print(new_orientation.pitch); Serial.print(" ");
Serial.println(new_orientation.roll);
float orientation_change;
float roll_change = abs(new_orientation.roll - mCurrent_orientation.roll);
float pitch_change = abs(new_orientation.pitch - mCurrent_orientation.pitch);
if (pitch_change > roll_change) {
orientation_change = pitch_change;
}else {
orientation_change = roll_change;
}
if (orientation_change > ANGLE_THRESHOLD) {
// Fall has been detected
mState = stateFallDetected;
}else {
// Fall not detected due to low change in orientation
mState = stateResetTriggers;
}
break;
}
case stateFallDetected:
{
blinkLED();
Serial.println("Fall Detected");
mState = stateResetTriggers;
break;
}
}
// Increment buffer index only if we are about measure the acceleration
if (mState == stateMeasureAccel) {
mBuffer_index = GetBufferPosition(mBuffer_index, 1);
}
}
/************************* Functions ***********************************************/
Imu_rotation AverageRotation(uint16_t start_index, uint8_t samples) {
Imu_rotation average_rot = {0.0, 0.0};
uint8_t i = 0;
for (i = 0; i < samples; i++) {
uint16_t tilt_index = GetBufferPosition(start_index, i);
average_rot.roll += mTilt[tilt_index].roll;
average_rot.pitch += mTilt[tilt_index].pitch;
}
average_rot.roll = average_rot.roll / samples;
average_rot.pitch = average_rot.pitch / samples;
return average_rot;
}
Imu_accel ReadAccel() {
/* Get a new sensor event */
sensors_event_t event;
adxl.getEvent(&event);
Imu_accel accel;
accel.x = event.acceleration.x + OFFSET_X;
accel.y = event.acceleration.y + OFFSET_Y;
accel.z = event.acceleration.z + OFFSET_Z;
return accel;
}
float CalcHoriz_Mag(Imu_accel* accel) {
float horiz_mag = sqrt((accel->y * accel->y) + (accel->z * accel->z));
return horiz_mag;
}
float CalcRoll(Imu_accel* accel) {
float z_rot = atan(-1 * accel->y / sqrt(pow(accel->x, 2) + pow(accel->z, 2))) * 180 / PI;
return z_rot;
}
float CalcPitch(Imu_accel* accel) {
float y_rot = atan(-1 * accel->z / sqrt(pow(accel->x, 2) + pow(accel->y, 2))) * 180 / PI;
return y_rot;
}
Imu_rotation CalcTilt(Imu_accel* accel) {
Imu_rotation tilt;
tilt.roll = CalcRoll(accel);
tilt.pitch = CalcPitch(accel);
return tilt;
}
float CalcPkPk() {
float minVal = mVector_mag[0];
float maxVal = mVector_mag[0];
uint8_t i;
// Find Min/Max in array
for (i = 1; i < 10; i++) {
if (mVector_mag[i] > maxVal) {
maxVal = mVector_mag[i];
}
else if (mVector_mag[i] < minVal) {
minVal = mVector_mag[i];
}
}
return (maxVal - minVal);
}
float FilterLowPass(float new_input, float old_output) {
// IIR Low pass Filter y[n] = 0.90y[n-1] + 0.10x[n]
return (0.9 * old_output + 0.10 * new_input);
}
uint16_t GetBufferPosition(uint16_t current_pos, int8_t samples) {
int16_t buffer_pos = current_pos + samples;
if (buffer_pos > BUFFER_LEN - 1) {
buffer_pos -= BUFFER_LEN;
}
if (buffer_pos < 0) {
buffer_pos += BUFFER_LEN;
}
return (uint16_t) buffer_pos;
}
void blinkLED() {
unsigned long current_time = millis();
while (current_time + 2000 > millis()) {
digitalWrite(LED_BUILTIN, HIGH);
delay(200);
digitalWrite(LED_BUILTIN, LOW);
delay(200);
}
Serial.println("LED BLINKED");
}