/
stepcount.c
295 lines (251 loc) · 8.56 KB
/
stepcount.c
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
/*
* This file is part of Espruino, a JavaScript interpreter for Microcontrollers
*
* Copyright (C) 2021 Gordon Williams <gw@pur3.co.uk>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* ----------------------------------------------------------------------------
* Step Counter
* ----------------------------------------------------------------------------
*/
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include "stepcount.h"
// a1bc34f9a9f5c54b9d68c3c26e973dba195e2105 HughB-walk-1834.csv 1446
// oxford filter 1584
// peak detection 1752
// state machine 1751
// STEPCOUNT_CONFIGURABLE is for use with https://github.com/gfwilliams/step-count
// to test/configure the step counter offline
/*
==========================================================
FIR filter designed with http://t-filter.engineerjs.com/
AccelFilter_get modified to return 8 bits of fractional
data.
==========================================================
*/
#define ACCELFILTER_TAP_NUM 7
typedef struct {
int8_t history[ACCELFILTER_TAP_NUM];
unsigned int last_index;
} AccelFilter;
const static int8_t filter_taps[ACCELFILTER_TAP_NUM] = {
-11, -15, 44, 68, 44, -15, -11
};
static void AccelFilter_init(AccelFilter* f) {
int i;
for(i = 0; i < ACCELFILTER_TAP_NUM; ++i)
f->history[i] = 0;
f->last_index = 0;
}
static void AccelFilter_put(AccelFilter* f, int8_t input) {
f->history[f->last_index++] = input;
if(f->last_index == ACCELFILTER_TAP_NUM)
f->last_index = 0;
}
static int AccelFilter_get(AccelFilter* f) {
int acc = 0;
int index = f->last_index, i;
for(i = 0; i < ACCELFILTER_TAP_NUM; ++i) {
index = index != 0 ? index-1 : ACCELFILTER_TAP_NUM-1;
acc += (int)f->history[index] * (int)filter_taps[i];
};
return acc >> 2;
}
AccelFilter accelFilter;
// ===============================================================
// These were calculated based on contributed data
// using https://github.com/gfwilliams/step-count
#define STEPCOUNTERTHRESHOLD_DEFAULT 600
// These are the ranges of values we test over
// when calculating the best data offline
#define STEPCOUNTERTHRESHOLD_MIN 0
#define STEPCOUNTERTHRESHOLD_MAX 2000
#define STEPCOUNTERTHRESHOLD_STEP 100
// This is a bit of a hack to allow us to configure
// variables which would otherwise have been compiler defines
#ifdef STEPCOUNT_CONFIGURABLE
int stepCounterThreshold = STEPCOUNTERTHRESHOLD_DEFAULT;
// These are handy values used for graphing
int accScaled;
#else
#define stepCounterThreshold STEPCOUNTERTHRESHOLD_DEFAULT
#endif
// ===============================================================
/** stepHistory allows us to check for repeated step counts.
Rather than registering each instantaneous step, we now only
measure steps if there were at least 3 steps (including the
current one) in 3 seconds
For each step it contains the number of iterations ago it occurred. 255 is the maximum
*/
int16_t accFiltered; // last accel reading, after running through filter
int16_t accFilteredHist[2]; // last 2 accel readings, 1=newest
// ===============================================================
/**
* (4) State Machine
*
* The state machine ensure all steps are checked that they fall
* between T_MIN_STEP and T_MAX_STEP. The 2v9.90 firmare uses X steps
* in Y seconds but this just enforces that the step X steps ago was
* within 6 seconds (75 samples). It is possible to have 4 steps
* within 1 second and then not get the 5th until T5 seconds. This
* could mean that the F/W would would be letting through 2 batches
* of steps that actually would not meet the threshold as the step at
* T5 could be the last. The F/W version also does not give back the
* X steps detected whilst it is waiting for X steps in Y seconds.
* After 100 cycles of the algorithm this would amount to 500 steps
* which is a 5% error over 10K steps. In practice the number of
* passes through the step machine from STEP_1 state to STEPPING
* state can be as high as 500 events. So using the state machine
* approach avoids this source of error.
*
*/
typedef enum {
S_STILL = 0, // just created state m/c no steps yet
S_STEP_1 = 1, // first step recorded
S_STEP_22N = 2, // counting 2-X steps
S_STEPPING = 3, // we've had X steps in X seconds
} StepState;
// In periods of 12.5Hz
#define T_MIN_STEP 4 // ~333ms
#define T_MAX_STEP 16 // ~1300ms
#define X_STEPS 5 // steps in a row needed
#define RAW_THRESHOLD 10
#define N_ACTIVE_SAMPLES 3
StepState stepState;
unsigned char holdSteps; // how many steps are we holding back?
unsigned char stepLength; // how many poll intervals since the last step?
int active_sample_count = 0;
bool gate_open = false; // start closed
// ===============================================================
// quick integer square root
// https://stackoverflow.com/questions/31117497/fastest-integer-square-root-in-the-least-amount-of-instructions
unsigned short int int_sqrt32(unsigned int x) {
unsigned short int res=0;
unsigned short int add= 0x8000;
int i;
for(i=0;i<16;i++) {
unsigned short int temp=res | add;
unsigned int g2=temp*temp;
if (x>=g2)
res=temp;
add>>=1;
}
return res;
}
// Init step count
void stepcount_init() {
AccelFilter_init(&accelFilter);
accFiltered = 0;
accFilteredHist[0] = 0;
accFilteredHist[1] = 0;
stepState = S_STILL;
holdSteps = 0;
stepLength = 0;
}
int stepcount_had_step() {
StepState st = stepState;
switch (st) {
case S_STILL:
stepState = S_STEP_1;
holdSteps = 1;
return 0;
case S_STEP_1:
holdSteps = 1;
// we got a step within expected period
if (stepLength <= T_MAX_STEP && stepLength >= T_MIN_STEP) {
//stepDebug(" S_STEP_1 -> S_STEP_22N");
stepState = S_STEP_22N;
holdSteps = 2;
} else {
// we stay in STEP_1 state
//stepDebug(" S_STEP_1 -> S_STEP_1");
//reject_count++;
}
return 0;
case S_STEP_22N:
// we got a step within expected time range
if (stepLength <= T_MAX_STEP && stepLength >= T_MIN_STEP) {
holdSteps++;
if (holdSteps >= X_STEPS) {
stepState = S_STEPPING;
//pass_count++; // we are going to STEPPING STATE
//stepDebug(" S_STEP_22N -> S_STEPPING");
return X_STEPS;
}
//stepDebug(" S_STEP_22N -> S_STEP_22N");
} else {
// we did not get the step in time, back to STEP_1
stepState = S_STEP_1;
//stepDebug(" S_STEP_22N -> S_STEP_1");
//reject_count++;
}
return 0;
case S_STEPPING:
// we got a step within the expected window
if (stepLength <= T_MAX_STEP && stepLength >= T_MIN_STEP) {
stepState = S_STEPPING;
//stepDebug(" S_STEPPING -> S_STEPPING");
return 1;
} else {
// we did not get the step in time, back to STEP_1
stepState = S_STEP_1;
//stepDebug(" S_STEPPING -> S_STEP_1");
//reject_count++;
}
return 0;
}
// should never get here
//stepDebug(" ERROR");
return 0;
}
// do calculations
int stepcount_new(int accMagSquared) {
// square root accelerometer data
int accMag = int_sqrt32(accMagSquared);
// scale to fit and clip
int v = (accMag-8192)>>5;
//printf("v %d\n",v);
//if (v>127 || v<-128) printf("Out of range %d\n", v);
if (v>127) v = 127;
if (v<-128) v = -128;
#ifdef STEPCOUNT_CONFIGURABLE
accScaled = v;
#endif
// do filtering
AccelFilter_put(&accelFilter, v);
accFilteredHist[0] = accFilteredHist[1];
accFilteredHist[1] = accFiltered;
int a = AccelFilter_get(&accelFilter);
if (a>32767) a=32767;
if (a<-32768) a=32768;
accFiltered = a;
if (v > RAW_THRESHOLD || v < -1*RAW_THRESHOLD) {
if (active_sample_count < N_ACTIVE_SAMPLES)
active_sample_count++;
if (active_sample_count == N_ACTIVE_SAMPLES)
gate_open = true;
} else {
if (active_sample_count > 0)
active_sample_count--;
if (active_sample_count == 0)
gate_open = false;
}
// increment step count history counters
if (stepLength<255)
stepLength++;
int stepsCounted = 0;
// check for a peak in the last sample - in which case report a step
if (accFilteredHist[1] > accFilteredHist[0] &&
accFilteredHist[1] > accFiltered &&
accFiltered > stepCounterThreshold) {
// We now have something resembling a step!
stepsCounted = stepcount_had_step();
stepLength = 0;
}
return stepsCounted;
}