From e910e57e2838436c082ede29463dcb5810d00b55 Mon Sep 17 00:00:00 2001 From: Abu Anas Shuvom Date: Wed, 15 Mar 2017 03:39:37 +0600 Subject: [PATCH 1/5] Create DrawingInTheAir_MotionActivated Replacing hardware button with the build in motion detection activation and audible feedback. --- .../DrawingInTheAir_MotionActivated | 290 ++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 examples/DrawingInTheAir/DrawingInTheAir_MotionActivated diff --git a/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated b/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated new file mode 100644 index 0000000..d7242cf --- /dev/null +++ b/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated @@ -0,0 +1,290 @@ +/* + * This example demonstrates using the pattern matching engine (CuriePME) + * to classify streams of accelerometer data from CurieIMU. + * + * First, the sketch will prompt you to draw some letters in the air (just + * imagine you are writing on an invisible whiteboard, using your board as the + * pen), and the IMU data from these motions is used as training data for the + * PME. Once training is finished, you can keep drawing letters and the PME + * will try to guess which letter you are drawing. + * + * This example requires a button to be connected to digital pin 4 + * https://www.arduino.cc/en/Tutorial/Button + * + * NOTE: For best results, draw big letters, at least 1-2 feet tall. + * + * Copyright (c) 2016 Intel Corporation. All rights reserved. + * See license notice at end of file. + */ + +#include "CurieIMU.h" +#include "CuriePME.h" + +/* This controls how many times a letter must be drawn during training. + * Any higher than 4, and you may not have enough neurons for all 26 letters + * of the alphabet. Lower than 4 means less work for you to train a letter, + * but the PME may have a harder time classifying that letter. */ +const unsigned int trainingReps = 4; + +/* Increase this to 'A-Z' if you like-- it just takes a lot longer to train */ +const unsigned char trainingStart = 'A'; +const unsigned char trainingEnd = 'D'; + +/* The input pin used to signal when a letter is being drawn- you'll + * need to make sure a button is attached to this pin */ +const unsigned int buttonPin = 4; + +/* Sample rate for accelerometer */ +const unsigned int sampleRateHZ = 200; + +/* No. of bytes that one neuron can hold */ +const unsigned int vectorNumBytes = 128; + +/* Number of processed samples (1 sample == accel x, y, z) + * that can fit inside a neuron */ +const unsigned int samplesPerVector = (vectorNumBytes / 3); + +/* This value is used to convert ASCII characters A-Z + * into decimal values 1-26, and back again. */ +const unsigned int upperStart = 0x40; + +const unsigned int sensorBufSize = 2048; +const int IMULow = -32768; +const int IMUHigh = 32767; + +boolean ledState = false; // Motion Activation Flag + +void setup() +{ + Serial.begin(9600); + while(!Serial); + + pinMode(buttonPin, INPUT); + + /* Start the IMU (Intertial Measurement Unit) */ + CurieIMU.begin(); +CurieIMU.attachInterrupt(eventCallback); + + /* Enable Zero Motion Detection */ + CurieIMU.setDetectionThreshold(CURIE_IMU_ZERO_MOTION, 50); // 50mg + CurieIMU.setDetectionDuration(CURIE_IMU_ZERO_MOTION, 2); // 2s + CurieIMU.interrupts(CURIE_IMU_ZERO_MOTION); + + /* Enable Motion Detection */ + CurieIMU.setDetectionThreshold(CURIE_IMU_MOTION, 20); // 20mg + CurieIMU.setDetectionDuration(CURIE_IMU_MOTION, 10); // trigger times of consecutive slope data points + CurieIMU.interrupts(CURIE_IMU_MOTION); + + Serial.println("IMU initialisation complete, waiting for events..."); + /* Start the PME (Pattern Matching Engine) */ + CuriePME.begin(); + + CurieIMU.setAccelerometerRate(sampleRateHZ); + CurieIMU.setAccelerometerRange(2); +pinMode(11, OUTPUT); +digitalWrite(11, LOW); + trainLetters(); + Serial.println("Training complete. Now, draw some letters (remember to "); + Serial.println("hold the button) and see if the PME can classify them."); +} + +void loop () +{ + byte vector[vectorNumBytes]; + unsigned int category; + char letter; + + /* Record IMU data while button is being held, and + * convert it to a suitable vector */ + readVectorFromIMU(vector); + + /* Use the PME to classify the vector, i.e. return a category + * from 1-26, representing a letter from A-Z */ + category = CuriePME.classify(vector, vectorNumBytes); + + if (category == CuriePME.noMatch) { + Serial.println("Don't recognise that one-- try again."); + } else { + letter = category + upperStart; + Serial.println(letter); + } +} + +/* Simple "moving average" filter, removes low noise and other small + * anomalies, with the effect of smoothing out the data stream. */ +byte getAverageSample(byte samples[], unsigned int num, unsigned int pos, + unsigned int step) +{ + unsigned int ret; + unsigned int size = step * 2; + + if (pos < (step * 3) || pos > (num * 3) - (step * 3)) { + ret = samples[pos]; + } else { + ret = 0; + pos -= (step * 3); + for (unsigned int i = 0; i < size; ++i) { + ret += samples[pos - (3 * i)]; + } + + ret /= size; + } + + return (byte)ret; +} + +/* We need to compress the stream of raw accelerometer data into 128 bytes, so + * it will fit into a neuron, while preserving as much of the original pattern + * as possible. Assuming there will typically be 1-2 seconds worth of + * accelerometer data at 200Hz, we will need to throw away over 90% of it to + * meet that goal! + * + * This is done in 2 ways: + * + * 1. Each sample consists of 3 signed 16-bit values (one each for X, Y and Z). + * Map each 16 bit value to a range of 0-255 and pack it into a byte, + * cutting sample size in half. + * + * 2. Undersample. If we are sampling at 200Hz and the button is held for 1.2 + * seconds, then we'll have ~240 samples. Since we know now that each + * sample, once compressed, will occupy 3 of our neuron's 128 bytes + * (see #1), then we know we can only fit 42 of those 240 samples into a + * single neuron (128 / 3 = 42.666). So if we take (for example) every 5th + * sample until we have 42, then we should cover most of the sample window + * and have some semblance of the original pattern. */ +void undersample(byte samples[], int numSamples, byte vector[]) +{ + unsigned int vi = 0; + unsigned int si = 0; + unsigned int step = numSamples / samplesPerVector; + unsigned int remainder = numSamples - (step * samplesPerVector); + + /* Centre sample window */ + samples += (remainder / 2) * 3; + for (unsigned int i = 0; i < samplesPerVector; ++i) { + for (unsigned int j = 0; j < 3; ++j) { + vector[vi + j] = getAverageSample(samples, numSamples, si + j, step); + } + + si += (step * 3); + vi += 3; + } +} + +void readVectorFromIMU(byte vector[]) +{ + byte accel[sensorBufSize]; + int raw[3]; + + unsigned int samples = 0; + unsigned int i = 0; + + /* Wait until Motion detection inturrupt flag */ + while (ledState == false) +{ + digitalWrite(11, LOW); // Optional Beeper +} + /* While Motion detection inturrupt flag being held... */ + while (ledState == true) { + + if (CurieIMU.accelDataReady()) { + + CurieIMU.readAccelerometer(raw[0], raw[1], raw[2]); + + /* Map raw values to 0-255 */ + accel[i] = (byte) map(raw[0], IMULow, IMUHigh, 0, 255); + accel[i + 1] = (byte) map(raw[1], IMULow, IMUHigh, 0, 255); + accel[i + 2] = (byte) map(raw[2], IMULow, IMUHigh, 0, 255); + + i += 3; + ++samples; + + /* If there's not enough room left in the buffers + * for the next read, then we're done */ + if (i + 3 > sensorBufSize) { + break; + } + + } +digitalWrite(11, HIGH); +delay(50); +digitalWrite(11, LOW); + } + + undersample(accel, samples, vector); +} + +void trainLetter(char letter, unsigned int repeat) +{ + unsigned int i = 0; + + while (i < repeat) { + byte vector[vectorNumBytes]; + + if (i) Serial.println("And again..."); + + readVectorFromIMU(vector); + CuriePME.learn(vector, vectorNumBytes, letter - upperStart); + + Serial.println("Got it!"); + delay(1000); + ++i; + } +} + +void trainLetters() +{ + for (char i = trainingStart; i <= trainingEnd; ++i) { + Serial.print("Initiate Motion and draw the letter '"); + Serial.print(String(i) + "' in the air. Here is no button, CURIE ZERO MOTION DETECTION activation automatically after drawing the letter "); + Serial.println("as you are done."); + + trainLetter(i, trainingReps); + Serial.println("OK, finished with this letter."); + delay(2000); + } +} + + + +// MOTION DETECTION interrupt subroutine + +// global variable motion flag + + +static void eventCallback(void){ + if (CurieIMU.getInterruptStatus(CURIE_IMU_ZERO_MOTION)) { + ledState = false; + // Serial.println("zero motion detected..."); + } + if (CurieIMU.getInterruptStatus(CURIE_IMU_MOTION)) { + ledState = true; + /* if (CurieIMU.motionDetected(X_AXIS, POSITIVE)) + Serial.println("Negative motion detected on X-axis"); + if (CurieIMU.motionDetected(X_AXIS, NEGATIVE)) + Serial.println("Positive motion detected on X-axis"); + if (CurieIMU.motionDetected(Y_AXIS, POSITIVE)) + Serial.println("Negative motion detected on Y-axis"); + if (CurieIMU.motionDetected(Y_AXIS, NEGATIVE)) + Serial.println("Positive motion detected on Y-axis"); + if (CurieIMU.motionDetected(Z_AXIS, POSITIVE)) + Serial.println("Negative motion detected on Z-axis"); + if (CurieIMU.motionDetected(Z_AXIS, NEGATIVE)) + Serial.println("Positive motion detected on Z-axis");*/ + } +} +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ From 68cd92b7806e9ff777082b0b4f68ac6bf7efb195 Mon Sep 17 00:00:00 2001 From: Abu Anas Shuvom Date: Wed, 22 Mar 2017 02:37:14 -0700 Subject: [PATCH 2/5] Update DrawingInTheAir_MotionActivated --- .../DrawingInTheAir_MotionActivated | 46 +++++++------------ 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated b/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated index d7242cf..26ff620 100644 --- a/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated +++ b/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated @@ -8,8 +8,8 @@ * PME. Once training is finished, you can keep drawing letters and the PME * will try to guess which letter you are drawing. * - * This example requires a button to be connected to digital pin 4 - * https://www.arduino.cc/en/Tutorial/Button + * The build in motion detection interrupt is being used to detect whether u are writing or not. + * While drawing in the air, the buzzer is HIGH, so wait until the buzzer turns low after drawing a letter and repeat the cycle. * * NOTE: For best results, draw big letters, at least 1-2 feet tall. * @@ -32,7 +32,7 @@ const unsigned char trainingEnd = 'D'; /* The input pin used to signal when a letter is being drawn- you'll * need to make sure a button is attached to this pin */ -const unsigned int buttonPin = 4; +const unsigned int buzzerPin = 4; /* Sample rate for accelerometer */ const unsigned int sampleRateHZ = 200; @@ -52,15 +52,15 @@ const unsigned int sensorBufSize = 2048; const int IMULow = -32768; const int IMUHigh = 32767; -boolean ledState = false; // Motion Activation Flag +boolean motionFlag = false; // Motion Activation Flag void setup() { Serial.begin(9600); while(!Serial); - pinMode(buttonPin, INPUT); - + pinMode(buzzerPin, OUTPUT); + digitalWrite(buzzerPin, LOW); /* Start the IMU (Intertial Measurement Unit) */ CurieIMU.begin(); CurieIMU.attachInterrupt(eventCallback); @@ -81,11 +81,11 @@ CurieIMU.attachInterrupt(eventCallback); CurieIMU.setAccelerometerRate(sampleRateHZ); CurieIMU.setAccelerometerRange(2); -pinMode(11, OUTPUT); -digitalWrite(11, LOW); + + trainLetters(); Serial.println("Training complete. Now, draw some letters (remember to "); - Serial.println("hold the button) and see if the PME can classify them."); + Serial.println(" Draw and see if the PME can classify them."); } void loop () @@ -180,12 +180,12 @@ void readVectorFromIMU(byte vector[]) unsigned int i = 0; /* Wait until Motion detection inturrupt flag */ - while (ledState == false) + while (motionFlag == false) { - digitalWrite(11, LOW); // Optional Beeper + digitalWrite(11, LOW); // Optional Buzzer } /* While Motion detection inturrupt flag being held... */ - while (ledState == true) { + while (motionFlag == true) { if (CurieIMU.accelDataReady()) { @@ -207,8 +207,7 @@ void readVectorFromIMU(byte vector[]) } digitalWrite(11, HIGH); -delay(50); -digitalWrite(11, LOW); + } undersample(accel, samples, vector); @@ -249,28 +248,17 @@ void trainLetters() // MOTION DETECTION interrupt subroutine -// global variable motion flag + static void eventCallback(void){ if (CurieIMU.getInterruptStatus(CURIE_IMU_ZERO_MOTION)) { - ledState = false; + motionFlag = false; // Serial.println("zero motion detected..."); } if (CurieIMU.getInterruptStatus(CURIE_IMU_MOTION)) { - ledState = true; - /* if (CurieIMU.motionDetected(X_AXIS, POSITIVE)) - Serial.println("Negative motion detected on X-axis"); - if (CurieIMU.motionDetected(X_AXIS, NEGATIVE)) - Serial.println("Positive motion detected on X-axis"); - if (CurieIMU.motionDetected(Y_AXIS, POSITIVE)) - Serial.println("Negative motion detected on Y-axis"); - if (CurieIMU.motionDetected(Y_AXIS, NEGATIVE)) - Serial.println("Positive motion detected on Y-axis"); - if (CurieIMU.motionDetected(Z_AXIS, POSITIVE)) - Serial.println("Negative motion detected on Z-axis"); - if (CurieIMU.motionDetected(Z_AXIS, NEGATIVE)) - Serial.println("Positive motion detected on Z-axis");*/ + motionFlag = true; + } } /* From 30454ff4255a983f5cd830806196f8f77c0dc383 Mon Sep 17 00:00:00 2001 From: Abu Anas Shuvom Date: Wed, 22 Mar 2017 03:09:02 -0700 Subject: [PATCH 3/5] Update DrawingInTheAir_MotionActivated --- .../DrawingInTheAir/DrawingInTheAir_MotionActivated | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated b/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated index 26ff620..cc5eb52 100644 --- a/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated +++ b/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated @@ -8,8 +8,8 @@ * PME. Once training is finished, you can keep drawing letters and the PME * will try to guess which letter you are drawing. * - * The build in motion detection interrupt is being used to detect whether u are writing or not. - * While drawing in the air, the buzzer is HIGH, so wait until the buzzer turns low after drawing a letter and repeat the cycle. + * The built in motion detection interrupt is being used to detect whether u are writing or not. + * While drawing in the air, the buzzer sounds, so wait until the buzzer turns off after drawing a letter and repeat the cycle. * * NOTE: For best results, draw big letters, at least 1-2 feet tall. * @@ -30,8 +30,8 @@ const unsigned int trainingReps = 4; const unsigned char trainingStart = 'A'; const unsigned char trainingEnd = 'D'; -/* The input pin used to signal when a letter is being drawn- you'll - * need to make sure a button is attached to this pin */ +/* An optional buzzer or LED might be attached to a digital pin for feedback */ + const unsigned int buzzerPin = 4; /* Sample rate for accelerometer */ @@ -182,7 +182,7 @@ void readVectorFromIMU(byte vector[]) /* Wait until Motion detection inturrupt flag */ while (motionFlag == false) { - digitalWrite(11, LOW); // Optional Buzzer + digitalWrite(buzzerPin, LOW); // Optional Buzzer off } /* While Motion detection inturrupt flag being held... */ while (motionFlag == true) { @@ -206,7 +206,7 @@ void readVectorFromIMU(byte vector[]) } } -digitalWrite(11, HIGH); + digitalWrite(buzzerPin, HIGH); // Optional buzzer rings } From 74a89b4a225fe5087550baf65655274011998f42 Mon Sep 17 00:00:00 2001 From: Abu Anas Shuvom Date: Wed, 22 Mar 2017 03:16:31 -0700 Subject: [PATCH 4/5] Update DrawingInTheAir_MotionActivated --- .../DrawingInTheAir_MotionActivated | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated b/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated index cc5eb52..fdcb30b 100644 --- a/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated +++ b/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated @@ -1,6 +1,7 @@ /* * This example demonstrates using the pattern matching engine (CuriePME) * to classify streams of accelerometer data from CurieIMU. + *** ENHANCEMENT: Automatic data capture with build in MOTION DETECTION functionality, no need of button or any extra input hardware. * * First, the sketch will prompt you to draw some letters in the air (just * imagine you are writing on an invisible whiteboard, using your board as the @@ -63,12 +64,12 @@ void setup() digitalWrite(buzzerPin, LOW); /* Start the IMU (Intertial Measurement Unit) */ CurieIMU.begin(); -CurieIMU.attachInterrupt(eventCallback); + CurieIMU.attachInterrupt(eventCallback); /* Enable Zero Motion Detection */ - CurieIMU.setDetectionThreshold(CURIE_IMU_ZERO_MOTION, 50); // 50mg - CurieIMU.setDetectionDuration(CURIE_IMU_ZERO_MOTION, 2); // 2s - CurieIMU.interrupts(CURIE_IMU_ZERO_MOTION); + CurieIMU.setDetectionThreshold(CURIE_IMU_ZERO_MOTION, 50); // 50mg + CurieIMU.setDetectionDuration(CURIE_IMU_ZERO_MOTION, 2); // 2s + CurieIMU.interrupts(CURIE_IMU_ZERO_MOTION); /* Enable Motion Detection */ CurieIMU.setDetectionThreshold(CURIE_IMU_MOTION, 20); // 20mg @@ -234,9 +235,9 @@ void trainLetter(char letter, unsigned int repeat) void trainLetters() { for (char i = trainingStart; i <= trainingEnd; ++i) { - Serial.print("Initiate Motion and draw the letter '"); - Serial.print(String(i) + "' in the air. Here is no button, CURIE ZERO MOTION DETECTION activation automatically after drawing the letter "); - Serial.println("as you are done."); + Serial.print(" Draw the letter in Air while the buzzer rings'"); + Serial.print(String(i) + "' Detecting a motion automatically initiate data capture and the optional buzzer rings "); + Serial.println("while you are done."); trainLetter(i, trainingReps); Serial.println("OK, finished with this letter."); From 815bca72b2befd5d1b14ed01939467599a28c36e Mon Sep 17 00:00:00 2001 From: Abu Anas Shuvom Date: Wed, 22 Mar 2017 03:18:01 -0700 Subject: [PATCH 5/5] Updated Updated with the suggestions provided. --- examples/DrawingInTheAir/DrawingInTheAir_MotionActivated | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated b/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated index fdcb30b..2043df0 100644 --- a/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated +++ b/examples/DrawingInTheAir/DrawingInTheAir_MotionActivated @@ -33,7 +33,7 @@ const unsigned char trainingEnd = 'D'; /* An optional buzzer or LED might be attached to a digital pin for feedback */ -const unsigned int buzzerPin = 4; +const unsigned int buzzerPin = 11; /* Sample rate for accelerometer */ const unsigned int sampleRateHZ = 200;