Skip to content

Commit 453a5a8

Browse files
committed
Added filtering for 60Hz and 50Hz noise. Updated buffer data to be stored as ints. Added filter buffer (couldn't do filtering correctly without both buffers). Devlpr is now configurable as performing filtering and defaults to no filtering. All functions that return EMG values now take a boolean to indicate if they should return filtered or raw data. Flex detection based on filtered data if configured.
1 parent e2e2628 commit 453a5a8

2 files changed

Lines changed: 148 additions & 60 deletions

File tree

src/Devlpr.cpp

Lines changed: 129 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
#include "Arduino.h"
22
#include "Devlpr.h"
33

4-
Devlpr::Devlpr(int pin)
4+
Devlpr::Devlpr(int pin, int filterType)
55
{
66
emgPin = pin;
7-
emgRunningSum = 0;
7+
rawEmgRunningSum = 0;
88
bufInd = BUFSIZE - 1;
99
numFuncs = 0;
10-
lastFiltVal = 0;
10+
if (filterType != FILTER_NONE) {
11+
doFilter = true;
12+
initFilter(filterType);
13+
}
1114
}
1215

1316
void Devlpr::tick()
@@ -22,7 +25,10 @@ void Devlpr::tick()
2225
microsSinceEMG += microsDelta;
2326
// check if enough time has passed to read EMG
2427
if (microsSinceEMG >= MICROS_SCHED_EMG) {
28+
unsigned long myStartMicros = micros();
2529
readEMG();
30+
unsigned long myDeltaMicros = micros() - myStartMicros;
31+
Serial.println(myDeltaMicros);
2632
// and update micros since
2733
microsSinceEMG = 0L;
2834
// NOTE just a best effort to run on time
@@ -61,56 +67,86 @@ void Devlpr::tick()
6167
lastTickMicros = currMicros;
6268
}
6369

64-
unsigned int Devlpr::lastValue()
70+
int Devlpr::lastValue(bool filtered)
6571
{
72+
if (filtered) {
73+
return filterBuf[bufInd];
74+
}
6675
return buf[bufInd];
6776
}
6877

69-
unsigned int Devlpr::windowAvg()
78+
int Devlpr::windowAvg(bool filtered)
7079
{
71-
return emgRunningSum / BUFSIZE;
80+
// int math should be good enough
81+
if (filtered) {
82+
return filterEmgRunningSum / BUFSIZE;
83+
}
84+
return rawEmgRunningSum / BUFSIZE;
7285
}
7386

74-
int Devlpr::lastValueCentered()
87+
int Devlpr::lastValueCentered(bool filtered)
7588
{
76-
int lastVal = lastValue();
77-
int wAvg = windowAvg();
89+
int lastVal = lastValue(filtered);
90+
int wAvg = windowAvg(filtered);
7891
return lastVal - wAvg;
7992
}
8093

81-
int Devlpr::lastValueFiltered()
82-
{
83-
return lastFiltVal;
84-
}
85-
86-
unsigned int Devlpr::windowPeakAmplitude()
94+
int Devlpr::windowPeakAmplitude(bool filtered)
8795
{
8896
// use the window average as the reference point (should be close to DC offset)
89-
int wAvg = windowAvg();
97+
int wAvg = windowAvg(filtered);
9098
// and need to find the max absolute value from ref
91-
unsigned int peak = 0;
92-
for (int i = 0; i < BUFSIZE; i++) { // no need to start from bufInd
93-
int currDiff = (int)buf[i] - wAvg;
94-
int currAbs = abs(currDiff);
95-
if (currAbs > peak) {
96-
peak = currAbs;
99+
int peak = 0;
100+
// hear me out, I'm duplicating the loop to not determine the buffer each iteration
101+
// if it helps, I feel just awful about it
102+
if (filtered) {
103+
for (int i = 0; i < BUFSIZE; i++) { // no need to start from bufInd
104+
int currDiff = filterBuf[i] - wAvg;
105+
int currAbs = abs(currDiff);
106+
if (currAbs > peak) {
107+
peak = currAbs;
108+
}
109+
}
110+
}
111+
else {
112+
for (int i = 0; i < BUFSIZE; i++) { // no need to start from bufInd
113+
int currDiff = buf[i] - wAvg;
114+
int currAbs = abs(currDiff);
115+
if (currAbs > peak) {
116+
peak = currAbs;
117+
}
97118
}
98119
}
99120
return peak;
100121
}
101122

102-
unsigned int Devlpr::windowPeakToPeakAmplitude()
123+
int Devlpr::windowPeakToPeakAmplitude(bool filtered)
103124
{
104125
// and need to find the max absolute value from ref
105-
unsigned int peak = 0;
106-
unsigned int trough = 1023;
107-
for (int i = 0; i < BUFSIZE; i++) { // no need to start from bufInd
108-
unsigned int currVal = buf[i];
109-
if (currVal > peak) {
110-
peak = currVal;
126+
int peak = 0;
127+
int trough = 1023;
128+
// hear me out, I'm duplicating the loop to not determine the buffer each iteration
129+
// if it helps, I feel just awful about it
130+
if (filtered) {
131+
for (int i = 0; i < BUFSIZE; i++) { // no need to start from bufInd
132+
int currVal = filterBuf[i];
133+
if (currVal > peak) {
134+
peak = currVal;
135+
}
136+
if (currVal < trough) {
137+
trough = currVal;
138+
}
111139
}
112-
if (currVal < trough) {
113-
trough = currVal;
140+
}
141+
else {
142+
for (int i = 0; i < BUFSIZE; i++) { // no need to start from bufInd
143+
int currVal = buf[i];
144+
if (currVal > peak) {
145+
peak = currVal;
146+
}
147+
if (currVal < trough) {
148+
trough = currVal;
149+
}
114150
}
115151
}
116152
return peak - trough;
@@ -149,23 +185,27 @@ void Devlpr::readEMG()
149185
if (bufInd >= BUFSIZE) { // faster than mod
150186
bufInd = 0;
151187
}
152-
// read our new value
188+
// read our new raw value
153189
emgVal = analogRead(emgPin);
154190
// before replacing the prev tail value though, update running sum
155-
emgRunningSum = emgRunningSum - buf[bufInd] + emgVal;
191+
rawEmgRunningSum = rawEmgRunningSum - buf[bufInd];
192+
rawEmgRunningSum = rawEmgRunningSum + emgVal;
156193
// now replace
157194
buf[bufInd] = emgVal;
158-
// and we need to calculate the filtered value every sample
159-
calcFiltered();
195+
// and handle all the filtering if set
196+
if (doFilter) {
197+
handleFiltered();
198+
}
160199
}
161200

162201
void Devlpr::flexCheck(unsigned long currMicros)
163202
{
164203
// to check cooldown
165204
unsigned long microsDelta = currMicros - prevFlexMicros;
166205
// the actual flex check
167-
unsigned int peakToPeakThresh = prevPeakToPeak * flexThreshMultiple;
168-
unsigned int currPeakToPeak = windowPeakToPeakAmplitude();
206+
int peakToPeakThresh = prevPeakToPeak * flexThreshMultiple;
207+
// if filter is configured, base it on filtered values
208+
int currPeakToPeak = windowPeakToPeakAmplitude(doFilter);
169209
// need an attached function, cooldown passed, and a peak change
170210
if (onFlexFunc && microsDelta >= flexCooldownMicros &&
171211
currPeakToPeak >= peakToPeakThresh) {
@@ -178,18 +218,63 @@ void Devlpr::flexCheck(unsigned long currMicros)
178218
prevPeakToPeak = currPeakToPeak;
179219
}
180220

181-
void Devlpr::calcFiltered()
221+
void Devlpr::handleFiltered()
182222
{
183223
// NOTE: IIR filter is recurrent and needs to run every tick to work
184224
// we need to operate on 0-centered(ish) data and we will be doing float math
185-
float xn = lastValueCentered();
225+
float xn = lastValueCentered(false); // needs to be based on raw data
186226
// compute the recurrence by section
187227
for (int s = 0; s < N_SECTIONS; s++) {
188228
float xn_tmp = xn;
189-
xn = notch60[s][0] * xn_tmp + z[s][0];
190-
z[s][0] = (notch60[s][1] * xn_tmp - notch60[s][4] * xn + z[s][1]);
191-
z[s][1] = (notch60[s][2] * xn_tmp - notch60[s][5] * xn);
229+
xn = filter[s][0] * xn_tmp + z[s][0];
230+
z[s][0] = (filter[s][1] * xn_tmp - filter[s][4] * xn + z[s][1]);
231+
z[s][1] = (filter[s][2] * xn_tmp - filter[s][5] * xn);
232+
}
233+
// and store our filtered value for reference later (as an int now)
234+
int filtVal = (int)xn;
235+
// before replacing the prev tail value though, update running sum
236+
filterEmgRunningSum = filterEmgRunningSum - filterBuf[bufInd];
237+
filterEmgRunningSum = filterEmgRunningSum + filtVal;
238+
// now replace
239+
filterBuf[bufInd] = filtVal;
240+
}
241+
242+
void Devlpr::initFilter(int filterType) {
243+
// NOTE: this is just a dumb way to not have to reference a different
244+
// filter array depending on the filter type selected at the start
245+
246+
// 2nd order Butterworth notch for 50Hz
247+
// {{0.95654323, -1.82035157, 0.95654323, 1., -1.84458768, 0.9536256},
248+
// { 1. , -1.90305207, 1. , 1., -1.87701816, 0.95947072}}
249+
// 2nd order Butterworth notch for 60Hz
250+
// {{0.95654323, -1.77962093, 0.95654323, 1., -1.80093517, 0.95415195},
251+
// { 1. , -1.860471 , 1. , 1., -1.83739919, 0.95894143}}
252+
if (filterType == FILTER_50HZ) {
253+
filter[0][0] = 0.95654323;
254+
filter[0][1] = -1.82035157;
255+
filter[0][2] = 0.95654323;
256+
filter[0][3] = 1.;
257+
filter[0][4] = -1.84458768;
258+
filter[0][5] = 0.9536256;
259+
filter[1][0] = 1.;
260+
filter[1][1] = -1.90305207;
261+
filter[1][2] = 1.;
262+
filter[1][3] = 1.;
263+
filter[1][4] = -1.87701816;
264+
filter[1][5] = 0.95947072;
265+
}
266+
if (filterType == FILTER_60HZ) {
267+
filter[0][0] = 0.95654323;
268+
filter[0][1] = -1.77962093;
269+
filter[0][2] = 0.95654323;
270+
filter[0][3] = 1.;
271+
filter[0][4] = -1.80093517;
272+
filter[0][5] = 0.95415195;
273+
filter[1][0] = 1.;
274+
filter[1][1] = -1.860471;
275+
filter[1][2] = 1.;
276+
filter[1][3] = 1.;
277+
filter[1][4] = -1.83739919;
278+
filter[1][5] = 0.95894143;
192279
}
193-
// and store our most recent value for reference later (as an int now)
194-
lastFiltVal = (int)xn;
195280
}

src/Devlpr.h

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,45 @@
33

44
#include "Arduino.h"
55

6+
#define FILTER_NONE 0
7+
#define FILTER_50HZ 1
8+
#define FILTER_60HZ 2
9+
610
class Devlpr
711
{
812
public:
9-
Devlpr(int pin=A0);
13+
Devlpr(int pin=A0, int filterType=FILTER_NONE);
1014
void tick();
11-
unsigned int lastValue();
12-
int lastValueCentered();
13-
int lastValueFiltered();
14-
unsigned int windowAvg();
15-
unsigned int windowPeakAmplitude();
16-
unsigned int windowPeakToPeakAmplitude();
15+
int lastValue(bool filtered=false);
16+
int lastValueCentered(bool filtered=false);
17+
int windowAvg(bool filtered=false);
18+
int windowPeakAmplitude(bool filtered=false);
19+
int windowPeakToPeakAmplitude(bool filtered=false);
1720
int scheduleFunction(void (*f)(Devlpr *d), unsigned int millisPer);
1821
void setFlexCallback(void (*f)(Devlpr *d), float threshMult=1.5,
1922
unsigned int millisCooldown=400);
2023
private:
2124
// emg buffer bookkeeping
2225
static const byte BUFSIZE = 32; // power of 2 for fast integer avg calc
23-
unsigned int buf[BUFSIZE];
26+
int buf[BUFSIZE];
2427
byte bufInd;
2528
// emg tracking
2629
int emgPin;
27-
unsigned int emgVal; // ATMEGA boards have 10-bit ADC (0-1023)
28-
unsigned int emgRunningSum; // if BUFSIZE is small, uint is fine
30+
int emgVal; // ATMEGA boards have 10-bit ADC (0-1023)
31+
int rawEmgRunningSum; // if BUFSIZE is <= 32, int is fine (max sum=32*1023)
2932
void readEMG();
3033
// emg filtering
31-
void calcFiltered();
32-
int lastFiltVal;
34+
void handleFiltered();
35+
void initFilter(int filterType); // this is dumb
36+
bool doFilter = false;
3337
static const byte N_SECTIONS = 2; // 2nd order
34-
float notch60[2][6] = { // 2nd order Butterworth notch for 60Hz
35-
{0.95654323 ,-1.77962093 ,0.95654323 ,1. ,-1.80093517 ,0.95415195},
36-
{1. ,-1.860471 ,1. ,1. ,-1.83739919 ,0.95894143}
37-
};
38+
float filter[2][6];
3839
float z[2][2] = { // maintain recurrent state
3940
{0.0, 0.0},
4041
{0.0, 0.0}
4142
};
43+
int filterBuf[BUFSIZE]; // need to keep a separate buffer for filtered values
44+
int filterEmgRunningSum; // if BUFSIZE is <= 32, int is fine
4245
// scheduling
4346
unsigned long lastTickMicros = 0L;
4447
// emg scheduling

0 commit comments

Comments
 (0)