├── .gitignore ├── README.md ├── examples ├── DrawingInTheAir │ ├── DrawingInTheAir.ino │ ├── Makefile │ ├── README.md │ └── images │ │ ├── a1.jpg │ │ ├── a1.png │ │ ├── a2.jpg │ │ ├── a2.png │ │ ├── a3.jpg │ │ ├── a3.png │ │ ├── a_compressed.jpeg │ │ ├── a_compressed.png │ │ ├── a_filtered.jpeg │ │ ├── a_filtered.png │ │ ├── a_points.png │ │ ├── a_raw.jpeg │ │ ├── a_raw.png │ │ ├── b1.jpg │ │ ├── b1.png │ │ ├── c1.jpg │ │ ├── c1.png │ │ ├── circle.jpg │ │ ├── circle.png │ │ ├── shake.jpg │ │ ├── shake.png │ │ ├── still.jpg │ │ └── still.png ├── a_SimplePatternMatching │ ├── Makefile │ └── a_SimplePatternMatching.ino ├── b_SavingKnowledge │ ├── Makefile │ └── b_SavingKnowledge.ino ├── c_RestoringKnowledge │ ├── Makefile │ └── c_RestoringKnowledge.ino └── d_k-nearest-neighbor │ ├── Makefile │ └── d_k-nearest-neighbor.ino ├── library.properties ├── plot_accel.sh └── src ├── CuriePME.cpp └── CuriePME.h /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DISCONTINUATION OF PROJECT. 2 | 3 | This project will no longer be maintained by Intel. 4 | 5 | Intel has ceased development and contributions including, but not limited to, maintenance, bug fixes, new releases, or updates, to this project. 6 | 7 | Intel no longer accepts patches to this project. 8 | 9 | If you have an ongoing need to use this project, are interested in independently developing it, or would like to maintain patches for the open source software community, please create your own fork of this project. 10 | Table of Contents 11 | ================= 12 | 13 | * [Intel® Pattern Matching Technology](#intel-pattern-matching-technology) 14 | * [Development Environments](#development-environments) 15 | * [About the Library](#about-the-library) 16 | * [About the Intel® Curie™ Pattern Matching Engine](#about-the-intel-curie-pattern-matching-engine) 17 | * [CuriePME API reference](#curiepme-api-reference) 18 | * [Constants](#constants) 19 | * [Initialization Functions](#initialization-functions) 20 | * [CuriePME.begin()](#curiepmebegin) 21 | * [CuriePME.forget()](#curiepmeforget) 22 | * [Basic Functions](#basic-functions) 23 | * [CuriePME.learn()](#curiepmelearn) 24 | * [CuriePME.classify()](#curiepmeclassify) 25 | * [Saving Knowledge](#saving-knowledge) 26 | * [CuriePME.beginSaveMode()](#curiepmebeginsavemode) 27 | * [CuriePME.iterateNeuronsToSave()](#curiepmeiterateneuronstosave) 28 | * [CuriePME.endSaveMode()](#curiepmeendsavemode) 29 | * [Restoring Knowledge](#restoring-knowledge) 30 | * [CuriePME.beginRestoreMode()](#curiepmebeginrestoremode) 31 | * [CuriePME.iterateNeuronsToRestore()](#curiepmeiterateneuronstorestore) 32 | * [CuriePME.endRestoreMode()](#curiepmeendrestoremode) 33 | * [Configuraton Functions](#configuraton-functions) 34 | * [CuriePME.setClassifierMode()](#curiepmesetclassifiermode) 35 | * [CuriePME.getClassifierMode()](#curiepmegetclassifiermode) 36 | * [CuriePME.setDistanceMode()](#curiepmesetdistancemode) 37 | * [CuriePME.getDistanceMode()](#curiepmegetdistancemode) 38 | * [CuriePME.setGlobalContext()](#curiepmesetglobalcontext) 39 | * [CuriePME.getGlobalContext()](#curiepmegetglobalcontext) 40 | * [Other Functions](#other-functions) 41 | * [CuriePME.getCommittedCount()](#curiepmegetcommittedcount) 42 | * [CuriePME.readNeuron()](#curiepmereadneuron) 43 | * [CuriePME.writeVector() (KNN_Mode only)](#curiepmewritevector-knn_mode-only) 44 | 45 | # Intel® Pattern Matching Technology 46 | 47 | This repository contains the CuriePME library, which provides access to the 48 | Pattern Matching Engine (PME) within the Intel® Curie™ Compute Module. 49 | 50 | Supported Curie™ hardware platforms: 51 | 52 | * [Arduino/Genuino 101](https://www.arduino.cc/en/Main/ArduinoBoard101) 53 | 54 | * [tinyTILE (Farnell)](https://www.element14.com/community/docs/DOC-82913/l/tinytile-intel-curie-module-based-board) 55 | 56 | ## Development Environments 57 | 58 | This library can be used in the following development environments: 59 | 60 | * On Ubuntu 14.04 LTS or 16.04 LTS, using the latest version of the 61 | [Curie Open Developer Kit](https://intel.com/curieodk) 62 | 63 | * In the Arduino IDE ("Download as a ZIP", unzip, place inside your libraries 64 | folder) 65 | 66 | ## About the Library 67 | the PME is a hardware engine capable of learning and recognizing patterns in 68 | arbitrary sets of data. The CuriePME library provides access to the PME, 69 | making it possible to implement machine-learning pattern matching or 70 | classification algorithms which are accelerated by the pattern-matching 71 | capabilities of the PME. 72 | 73 | + Basic Functions Supported: 74 | * Learning Patterns 75 | * Recognizing Patterns 76 | * Storing Pattern Memory (Knowledge) [Requires [SerialFlash](https://github.com/PaulStoffregen/SerialFlash) Library] 77 | * Retrieving Pattern Memory (Knowledge) [Requires [SerialFlash](https://github.com/PaulStoffregen/SerialFlash) Library] 78 | 79 | ## About the Intel® Curie™ Pattern Matching Engine 80 | 81 | The Pattern Matching Engine (PME) is a parallel data recognition engine with the following features: 82 | + 128 parallel Processing Elements (PE) each with" 83 | 84 | - 128 byte input vector 85 | - 128 byte model memory. 86 | - 8-Bit Arithmetic Units 87 | - Two distance evaluation norms with 16-bit resolution: 88 | 89 | * L1 norm (Manhattan Distance) 90 | * Lsup (Supremum) norm (Chebyshev Distance) 91 | 92 | - Support for up to 32,768 Categories 93 | - Classification states: 94 | 95 | * ID - Identified 96 | * UNC - Uncertain 97 | * UNK - Unknown 98 | 99 | + Two Classification Functions: 100 | 101 | * k-nearest neighbors (KNN) 102 | * Radial Basis Function (RBF) 103 | 104 | + Support for up to 127 Contexts 105 | 106 | # CuriePME API reference 107 | 108 | # *Constants* 109 | 110 | * ``CuriePME.noMatch (uint32_t)``: The value returned by classify() 111 | to indicate a pattern could not be classified 112 | * ``CuriePME.minContext (uint16_t)``: Minimum context value 113 | * ``CuriePME.maxContext (uint16_t)``: Maximum context value 114 | * ``CuriePME.maxVectorSize (int32_t)``: Maximum pattern size (in bytes) that can 115 | be accepted by learn() and classify() 116 | * ``CuriePME.firstNeuronID (int32_t)``: ID of first neuron in network 117 | * ``CuriePME.lastNeuronID (int32_t)``: ID of last neuron in network 118 | * ``CuriePME.maxNeurons (int32_t)``: Number of neurons in the network 119 | 120 | # *Initialization Functions* 121 | 122 | ### ``CuriePME.begin()`` 123 | 124 | ``` 125 | void CuriePME.begin(void) 126 | ``` 127 | 128 | Initialize the PME so it is ready for operation 129 | 130 | *Parameters* 131 | 132 | none 133 | 134 | *Return value* 135 | 136 | none 137 | 138 | ### ``CuriePME.forget()`` 139 | 140 | ``` 141 | void CuriePME.forget(void) 142 | ``` 143 | 144 | Clear any data committed to the network, making the network ready to learn again. 145 | 146 | *Parameters* 147 | 148 | none 149 | 150 | *Return value* 151 | 152 | none 153 | 154 | # *Basic Functions* 155 | 156 | ### ``CuriePME.learn()`` 157 | 158 | ``` 159 | uint16_t CuriePME.learn(uint8_t *pattern_vector, int32_t vector_length, uint8_t category) 160 | ``` 161 | 162 | Takes a pattern `pattern_vector` of size `vector_length`, and commits it to the 163 | network as training data. The `category` parameter indicates to the PME which 164 | category this training vector belongs to- that is, if a future input has a 165 | sufficiently similar pattern, it will be classified as the same category passed 166 | with this pattern. 167 | 168 | *Parameters* 169 | 170 | 1. `uint8_t *pattern_vector` : Pointer to the training data. Training data must 171 | be no longer than 128 bytes 172 | 2. `int32_t vector_length` : The size (in bytes) of your training vector 173 | 3. `uint8_t category` : The category that should be assigned to this data 174 | 175 | *Return value* 176 | 177 | Total number of committed neurons in the network after the learning 178 | operation is complete 179 | 180 | ### ``CuriePME.classify()`` 181 | 182 | ``` 183 | uint16_t CuriePME.classify(uint8_t *pattern_vector, int32_t vector_length) 184 | ``` 185 | 186 | Takes a pattern `pattern_vector` of size `vector_length`, and uses the 187 | committed neurons in the network to classify the pattern 188 | 189 | *Parameters* 190 | 191 | 1. `uint8_t *pattern_vector` : Pointer to the data to be classified. Pattern 192 | data must be no longer than 128 bytes 193 | 2. `int32_t vector_length` : The size (in bytes) of the data to be classified 194 | 195 | *Return value* 196 | 197 | `CuriePME.noMatch` if the input data did not match any of the trained categories. 198 | Otherwise, the trained category assigned by the network will be returned 199 | 200 | # *Saving Knowledge* 201 | 202 | ### ``CuriePME.beginSaveMode()`` 203 | 204 | ``` 205 | void CuriePME.beginSaveMode(void) 206 | ``` 207 | 208 | Puts the network into a state that allows the neuron contents to be read 209 | 210 | *Parameters* 211 | 212 | none 213 | 214 | *Return value* 215 | 216 | none 217 | 218 | ### ``CuriePME.iterateNeuronsToSave()`` 219 | 220 | ``` 221 | uint16_t CuriePME.iterateNeuronsToSave(neuronData& data_array) 222 | ``` 223 | 224 | When in save mode, this method can be called to obtain the data for the next 225 | committed neuron. Each successive call will increment an internal pointer and 226 | return the data for successive committed neurons, until all committed neurons 227 | have been read. 228 | 229 | *Parameters* 230 | 231 | 1. `neuronData& data_array` : a `neuronData` type in which the neuron data will 232 | be placed 233 | 234 | *Return value* 235 | 236 | 0 when all committed neurons have been read. Otherwise, this method returns 237 | the trained category of the neuron being read 238 | 239 | ### ``CuriePME.endSaveMode()`` 240 | 241 | ``` 242 | void CuriePME.endSaveMode(void) 243 | ``` 244 | 245 | Takes the network out of save mode 246 | 247 | *Parameters* 248 | 249 | none 250 | 251 | *Return value* 252 | 253 | none 254 | 255 | # *Restoring Knowledge* 256 | 257 | ### ``CuriePME.beginRestoreMode()`` 258 | 259 | ``` 260 | void CuriePME.beginRestoreMode(void) 261 | ``` 262 | 263 | Puts the network into a state that allows the neuron contents to be restored 264 | from a file 265 | 266 | *Parameters* 267 | 268 | none 269 | 270 | *Return value* 271 | 272 | none 273 | 274 | ### ``CuriePME.iterateNeuronsToRestore()`` 275 | 276 | ``` 277 | void CuriePME.iterateNeuronsToRestore(neuronData& data_array) 278 | ``` 279 | 280 | When in restore mode, this method can be called to write data to the next 281 | available neuron. Each successive call will increment an internal pointer until 282 | all the neurons in the network have been written. 283 | 284 | *Parameters* 285 | 286 | 1. `neuronData& data_array` : a `neuronData` type containing the neuron data 287 | 288 | *Return value* 289 | 290 | none 291 | 292 | ### ``CuriePME.endRestoreMode()`` 293 | 294 | ``` 295 | void CuriePME.endRestoreMode(void) 296 | ``` 297 | 298 | Takes the network out of restore mode 299 | 300 | *Parameters* 301 | 302 | none 303 | 304 | *Return value* 305 | 306 | none 307 | 308 | # *Configuraton Functions* 309 | 310 | ### ``CuriePME.setClassifierMode()`` 311 | 312 | ``` 313 | void CuriePME.setClassifierMode(PATTERN_MATCHING_CLASSIFICATION_MODE mode) 314 | ``` 315 | 316 | Sets the classifying function to be used by the network 317 | 318 | *Parameters* 319 | 320 | 1. `PATTERN_MATCHING_CLASSIFICATION_MODE mode` The classifying function to use. 321 | Valid values are: 322 | * `RBF_Mode` (default) 323 | * `KNN_Mode` 324 | 325 | *Return value* 326 | 327 | none 328 | 329 | ### ``CuriePME.getClassifierMode()`` 330 | 331 | ``` 332 | PATTERN_MATCHING_CLASSIFICATION_MODE CuriePME.getClassifierMode(void) 333 | ``` 334 | 335 | Gets the classifying function being used by the network 336 | 337 | *Parameters* 338 | 339 | none 340 | 341 | *Return value* 342 | 343 | `PATTERN_MATCHING_CLASSIFICATION_MODE mode` The classifying function being used. 344 | Possible values are: 345 | 346 | * `RBF_Mode` 347 | * `KNN_Mode` 348 | 349 | ### ``CuriePME.setDistanceMode()`` 350 | 351 | ``` 352 | void CuriePME.setDistanceMode(PATTERN_MATCHING_DISTANCE_MODE mode) 353 | ``` 354 | 355 | Sets the distance function to be used by the network 356 | 357 | *Parameters* 358 | 359 | 1. `PATTERN_MATCHING_DISTANCE_MODE mode` The distance function to use. 360 | Valid values are: 361 | 362 | * `LSUP_Distance` (default) 363 | * `L1_Distance` 364 | 365 | *Return value* 366 | 367 | none 368 | 369 | ### ``CuriePME.getDistanceMode()`` 370 | 371 | ``` 372 | PATTERN_MATCHING_DISTANCE_MODE CuriePME.getDistanceMode(void) 373 | ``` 374 | 375 | Gets the distance function being used by the network 376 | 377 | *Parameters* 378 | 379 | none 380 | 381 | *Return value* 382 | 383 | `PATTERN_MATCHING_DISTANCE_MODE mode` The distance function being used. 384 | Possible values are: 385 | 386 | * `LSUP_Distance` 387 | * `L1_Distance` 388 | 389 | ### ``CuriePME.setGlobalContext()`` 390 | 391 | ``` 392 | void CuriePME.setGlobalContext(uint16_t context) 393 | ``` 394 | 395 | Writes a value to the Global Context Register. Valid context values range 396 | between 1-127. A context value of 0 enables all neurons, with no regard to 397 | their context 398 | 399 | *Parameters* 400 | 401 | 1. `uint16_t context` : a value between 0-127 representing the desired context. 402 | A context value of 0 selects all neurons, regardless of their context value. 403 | 404 | *Return value* 405 | 406 | none 407 | 408 | ### ``CuriePME.getGlobalContext()`` 409 | 410 | ``` 411 | uint16_t CuriePME.getGlobalContext(void) 412 | ``` 413 | 414 | Reads the Global Context Register 415 | 416 | *Parameters* 417 | 418 | none 419 | 420 | *Return value* 421 | 422 | `uint16_t`, the contents of the Global Context Register (a value between 0-127) 423 | 424 | # *Other Functions* 425 | 426 | ### ``CuriePME.getCommittedCount()`` 427 | 428 | ``` 429 | uint16_t CuriePME.getCommittedCount(void) 430 | ``` 431 | 432 | Gets the number of committed neurons in the network (NOTE: this method should 433 | not be used in save/restore mode, because it will give inaccurate results) 434 | 435 | *Parameters* 436 | 437 | none 438 | 439 | *Return value* 440 | 441 | `uint16_t`, the number of comitted neurons in the network 442 | 443 | ### ``CuriePME.readNeuron()`` 444 | 445 | ``` 446 | void CuriePME.readNeuron(int32_t neuronID, neuronData& data_array) 447 | ``` 448 | 449 | Read a specific neuron by its ID 450 | 451 | *Parameters* 452 | 453 | 1. `int32_t neuronID` : value between 1-128 representing a specific neuron 454 | 2. `neuronData& data_array` : neuronData type in which to write the neuron data 455 | 456 | *Return value* 457 | 458 | none 459 | 460 | ### ``CuriePME.writeVector()`` (KNN_Mode only) 461 | 462 | ``` 463 | uint16_t CuriePME.writeVector(uint8_t *pattern_vector, int32_t vector_length) 464 | ``` 465 | 466 | (Should only be used in `KNN_Mode`) Takes a pattern `pattern_vector` of size 467 | `vector_length`, and uses the committed neurons in the network to classify the 468 | pattern 469 | 470 | *Parameters* 471 | 472 | 1. `uint8_t *pattern_vector` : Pointer to the data to be classified. Pattern 473 | data must be no longer than 128 bytes 474 | 2. `int32_t vector_length` : The size (in bytes) of the data to be classified 475 | 476 | *Return value* 477 | 478 | `CuriePME.noMatch` if the input data did not match any of the trained categories. 479 | Otherwise, the trained category assigned by the network will be returned 480 | 481 | -------------------------------------------------------------------------------- /examples/DrawingInTheAir/DrawingInTheAir.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * This example demonstrates using the pattern matching engine (CuriePME) 3 | * to classify streams of accelerometer data from CurieIMU. 4 | * 5 | * First, the sketch will prompt you to draw some letters in the air (just 6 | * imagine you are writing on an invisible whiteboard, using your board as the 7 | * pen), and the IMU data from these motions is used as training data for the 8 | * PME. Once training is finished, you can keep drawing letters and the PME 9 | * will try to guess which letter you are drawing. 10 | * 11 | * This example requires a button to be connected to digital pin 4 12 | * https://www.arduino.cc/en/Tutorial/Button 13 | * 14 | * NOTE: For best results, draw big letters, at least 1-2 feet tall. 15 | * 16 | * Copyright (c) 2016 Intel Corporation. All rights reserved. 17 | * See license notice at end of file. 18 | */ 19 | 20 | #include "CurieIMU.h" 21 | #include "CuriePME.h" 22 | 23 | /* This controls how many times a letter must be drawn during training. 24 | * Any higher than 4, and you may not have enough neurons for all 26 letters 25 | * of the alphabet. Lower than 4 means less work for you to train a letter, 26 | * but the PME may have a harder time classifying that letter. */ 27 | const unsigned int trainingReps = 4; 28 | 29 | /* Increase this to 'A-Z' if you like-- it just takes a lot longer to train */ 30 | const unsigned char trainingStart = 'A'; 31 | const unsigned char trainingEnd = 'F'; 32 | 33 | /* The input pin used to signal when a letter is being drawn- you'll 34 | * need to make sure a button is attached to this pin */ 35 | const unsigned int buttonPin = 4; 36 | 37 | /* Sample rate for accelerometer */ 38 | const unsigned int sampleRateHZ = 200; 39 | 40 | /* No. of bytes that one neuron can hold */ 41 | const unsigned int vectorNumBytes = 128; 42 | 43 | /* Number of processed samples (1 sample == accel x, y, z) 44 | * that can fit inside a neuron */ 45 | const unsigned int samplesPerVector = (vectorNumBytes / 3); 46 | 47 | /* This value is used to convert ASCII characters A-Z 48 | * into decimal values 1-26, and back again. */ 49 | const unsigned int upperStart = 0x40; 50 | 51 | const unsigned int sensorBufSize = 2048; 52 | const int IMULow = -32768; 53 | const int IMUHigh = 32767; 54 | 55 | void setup() 56 | { 57 | Serial.begin(9600); 58 | while(!Serial); 59 | 60 | pinMode(buttonPin, INPUT); 61 | 62 | /* Start the IMU (Intertial Measurement Unit), enable the accelerometer */ 63 | CurieIMU.begin(ACCEL); 64 | 65 | /* Start the PME (Pattern Matching Engine) */ 66 | CuriePME.begin(); 67 | 68 | CurieIMU.setAccelerometerRate(sampleRateHZ); 69 | CurieIMU.setAccelerometerRange(2); 70 | 71 | trainLetters(); 72 | Serial.println("Training complete. Now, draw some letters (remember to "); 73 | Serial.println("hold the button) and see if the PME can classify them."); 74 | } 75 | 76 | void loop () 77 | { 78 | byte vector[vectorNumBytes]; 79 | unsigned int category; 80 | char letter; 81 | 82 | /* Record IMU data while button is being held, and 83 | * convert it to a suitable vector */ 84 | readVectorFromIMU(vector); 85 | 86 | /* Use the PME to classify the vector, i.e. return a category 87 | * from 1-26, representing a letter from A-Z */ 88 | category = CuriePME.classify(vector, vectorNumBytes); 89 | 90 | if (category == CuriePME.noMatch) { 91 | Serial.println("Don't recognise that one-- try again."); 92 | } else { 93 | letter = category + upperStart; 94 | Serial.println(letter); 95 | } 96 | } 97 | 98 | /* Simple "moving average" filter, removes low noise and other small 99 | * anomalies, with the effect of smoothing out the data stream. */ 100 | byte getAverageSample(byte samples[], unsigned int num, unsigned int pos, 101 | unsigned int step) 102 | { 103 | unsigned int ret; 104 | unsigned int size = step * 2; 105 | 106 | if (pos < (step * 3) || pos > (num * 3) - (step * 3)) { 107 | ret = samples[pos]; 108 | } else { 109 | ret = 0; 110 | pos -= (step * 3); 111 | for (unsigned int i = 0; i < size; ++i) { 112 | ret += samples[pos - (3 * i)]; 113 | } 114 | 115 | ret /= size; 116 | } 117 | 118 | return (byte)ret; 119 | } 120 | 121 | /* We need to compress the stream of raw accelerometer data into 128 bytes, so 122 | * it will fit into a neuron, while preserving as much of the original pattern 123 | * as possible. Assuming there will typically be 1-2 seconds worth of 124 | * accelerometer data at 200Hz, we will need to throw away over 90% of it to 125 | * meet that goal! 126 | * 127 | * This is done in 2 ways: 128 | * 129 | * 1. Each sample consists of 3 signed 16-bit values (one each for X, Y and Z). 130 | * Map each 16 bit value to a range of 0-255 and pack it into a byte, 131 | * cutting sample size in half. 132 | * 133 | * 2. Undersample. If we are sampling at 200Hz and the button is held for 1.2 134 | * seconds, then we'll have ~240 samples. Since we know now that each 135 | * sample, once compressed, will occupy 3 of our neuron's 128 bytes 136 | * (see #1), then we know we can only fit 42 of those 240 samples into a 137 | * single neuron (128 / 3 = 42.666). So if we take (for example) every 5th 138 | * sample until we have 42, then we should cover most of the sample window 139 | * and have some semblance of the original pattern. */ 140 | void undersample(byte samples[], int numSamples, byte vector[]) 141 | { 142 | unsigned int vi = 0; 143 | unsigned int si = 0; 144 | unsigned int step = numSamples / samplesPerVector; 145 | unsigned int remainder = numSamples - (step * samplesPerVector); 146 | 147 | /* Centre sample window */ 148 | samples += (remainder / 2) * 3; 149 | for (unsigned int i = 0; i < samplesPerVector; ++i) { 150 | for (unsigned int j = 0; j < 3; ++j) { 151 | vector[vi + j] = getAverageSample(samples, numSamples, si + j, step); 152 | } 153 | 154 | si += (step * 3); 155 | vi += 3; 156 | } 157 | } 158 | 159 | void readVectorFromIMU(byte vector[]) 160 | { 161 | byte accel[sensorBufSize]; 162 | int raw[3]; 163 | 164 | unsigned int samples = 0; 165 | unsigned int i = 0; 166 | 167 | /* Wait until button is pressed */ 168 | while (digitalRead(buttonPin) == LOW); 169 | 170 | /* While button is being held... */ 171 | while (digitalRead(buttonPin) == HIGH) { 172 | if (CurieIMU.dataReady()) { 173 | 174 | CurieIMU.readAccelerometer(raw[0], raw[1], raw[2]); 175 | 176 | /* Map raw values to 0-255 */ 177 | accel[i] = (byte) map(raw[0], IMULow, IMUHigh, 0, 255); 178 | accel[i + 1] = (byte) map(raw[1], IMULow, IMUHigh, 0, 255); 179 | accel[i + 2] = (byte) map(raw[2], IMULow, IMUHigh, 0, 255); 180 | 181 | i += 3; 182 | ++samples; 183 | 184 | /* If there's not enough room left in the buffers 185 | * for the next read, then we're done */ 186 | if (i + 3 > sensorBufSize) { 187 | break; 188 | } 189 | } 190 | } 191 | 192 | undersample(accel, samples, vector); 193 | } 194 | 195 | void trainLetter(char letter, unsigned int repeat) 196 | { 197 | unsigned int i = 0; 198 | 199 | while (i < repeat) { 200 | byte vector[vectorNumBytes]; 201 | 202 | if (i) Serial.println("And again..."); 203 | 204 | readVectorFromIMU(vector); 205 | CuriePME.learn(vector, vectorNumBytes, letter - upperStart); 206 | 207 | Serial.println("Got it!"); 208 | delay(1000); 209 | ++i; 210 | } 211 | } 212 | 213 | void trainLetters() 214 | { 215 | for (char i = trainingStart; i <= trainingEnd; ++i) { 216 | Serial.print("Hold down the button and draw the letter '"); 217 | Serial.print(String(i) + "' in the air. Release the button as soon "); 218 | Serial.println("as you are done."); 219 | 220 | trainLetter(i, trainingReps); 221 | Serial.println("OK, finished with this letter."); 222 | delay(2000); 223 | } 224 | } 225 | 226 | /* 227 | * This library is free software; you can redistribute it and/or 228 | * modify it under the terms of the GNU Lesser General Public 229 | * License as published by the Free Software Foundation; either 230 | * version 2.1 of the License, or (at your option) any later version. 231 | * 232 | * This library is distributed in the hope that it will be useful, 233 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 234 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 235 | * Lesser General Public License for more details. 236 | * 237 | * You should have received a copy of the GNU Lesser General Public 238 | * License along with this library; if not, write to the Free Software 239 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 240 | */ 241 | -------------------------------------------------------------------------------- /examples/DrawingInTheAir/Makefile: -------------------------------------------------------------------------------- 1 | ifeq ("$(strip $(CODK_DIR))", "") 2 | $(error Please set the CODK_DIR variable.) 3 | endif 4 | 5 | ARDUINOSW_DIR ?= $(CODK_DIR)/arc 6 | 7 | current_dir = $(shell pwd) 8 | 9 | VERBOSE = true 10 | 11 | LIBDIRS = $(ARDUINOSW_DIR)/corelibs/libraries/Intel-Pattern-Matching-Technology/src \ 12 | $(ARDUINOSW_DIR)/corelibs/libraries/CurieIMU/src 13 | 14 | include $(ARDUINOSW_DIR)/Makefile.inc 15 | 16 | all: compile 17 | 18 | .DEFAULT_GOAL := all 19 | -------------------------------------------------------------------------------- /examples/DrawingInTheAir/README.md: -------------------------------------------------------------------------------- 1 | Table of Contents 2 | ================= 3 | 4 | * [CuriePME example sketch tutorial](#curiepme-example-sketch-tutorial) 5 | * [Learning](#learning) 6 | * [Classifying](#classifying) 7 | * [Tutorial sketch #1: use PME to convert hex string to ASCII character](#tutorial-sketch-1-use-pme-to-convert-hex-string-to-ascii-character) 8 | * [Tutorial sketch #1: breakdown](#tutorial-sketch-1-breakdown) 9 | * [Tutorial sketch #2: use IMU data to create a more complex pattern](#tutorial-sketch-2-use-imu-data-to-create-a-more-complex-pattern) 10 | * [Tutorial sketch #2: breakdown](#tutorial-sketch-2-breakdown) 11 | * [Problem 1: There's too much data](#problem-1-theres-too-much-data) 12 | * [Undersampling](#undersampling) 13 | * [Problem 2: The data is too noisy](#problem-2-the-data-is-too-noisy) 14 | * [Use an averaging filter!](#use-an-averaging-filter) 15 | 16 | This tutorial covers the use of the ``learn`` and ``classify`` functions. See 17 | the [CuriePME API reference](https://github.com/01org/Intel-Pattern-Matching-Technology/blob/master/README.md) 18 | for full API details. 19 | 20 | # CuriePME example sketch tutorial 21 | 22 | The Curie module used on the Arduino 101 board contains a Pattern Matching 23 | Engine (PME), which is a hardware device capable of learning and recognizing 24 | patterns in arbitrary sets of data. The DrawingInTheAir example sketch allows a 25 | user to draw letters in the air, using their Arduino 101 board as an imaginary 26 | pen, and can recognize which letter is being drawn by recognizing patterns in 27 | accelerometer data from the CurieIMU library. 28 | 29 | Don't worry if that sounds daunting. This tutorial will build the 30 | DrawingInTheAir example from scratch, starting with a much simpler sketch. By 31 | the end, you will understand all the individual pieces that go into the final 32 | program, and you'll have the knowledge to start using the CuriePME library in 33 | your own projects. 34 | 35 | Firstly, we'll cover briefly how the PME works. We've said the PME can "learn" 36 | and "recognize" patterns in data, but what does that mean for you, the 37 | programmer? 38 | 39 | All of the learning and classifying functions that the PME provides can be 40 | controlled using the CuriePME library. Let's start with learning... 41 | 42 | # Learning 43 | 44 | Before the PME can be used to recognize (or "classify") any patterns, we first 45 | have to give the PME some examples of the pattern (or patterns) we are looking 46 | for. The CuriePME library provides the `learn()` function for this: 47 | 48 | ```cpp 49 | uint16_t CuriePME.learn (uint8_t vector[], int32t vector_length, uint16_t category) 50 | ``` 51 | 52 | `vector` is an array containing the data that represents our pattern. `category` 53 | is the category that should be assigned to that pattern. So by calling 54 | `learn()`, say for example, with a category value of 1, we are effectively 55 | saying to the PME "Here is an example of a pattern that belongs in category 1. 56 | If you see another pattern that looks like this, then you should know it's 57 | category 1". 58 | 59 | The PME is a network of 128 individual memory units, called neurons. Each neuron 60 | can hold 128 bytes of data. Each time you call `learn()`, it writes your 61 | pattern to a new neuron in the network. This means the maximum pattern size that 62 | can be accepted by `learn()` (i.e. maximum value for `vector_length`) is 128 bytes, 63 | since the entire pattern must fit inside a single neuron. 64 | 65 | And, because we only have a certain number of neurons (128 to be exact), you 66 | will eventually run out if you call `learn()` enough times. You can check how 67 | many neurons have been used up using the `getComittedCount()` function, e.g. 68 | 69 | ```cpp 70 | if (CuriePME.getComittedCount() == CuriePME.MaxNeurons) { 71 | /* Network is fully committed */ 72 | } 73 | ``` 74 | 75 | # Classifying 76 | 77 | Classifying, as you may already have guessed, allows you to pass a new pattern 78 | to the PME, and the PME will report whether the new pattern matches any of its 79 | learned categories. The CuriePME library provides the `classify()` function for 80 | this: 81 | 82 | ```cpp 83 | uint16_t CuriePME.classify (uint8_t vector[], int32_t vector_length) 84 | ``` 85 | 86 | The best way to illustrate this is with a simple example sketch: 87 | 88 | # Tutorial sketch #1: use PME to convert hex string to ASCII character 89 | 90 | This sketch trains the PME to associate a very simple pattern with each letter 91 | of the alphabet. The pattern for each letter is simply a string of two 92 | ASCII characters, representing the hex digits that make up that character's 93 | place in the ASCII table. For example, the letter 'a' has a numerical value of 94 | decimal 97, or hex 61. So to train for the letter 'a', we will give the pattern 95 | "61" to the PME, and indicate that it belongs in category 97. Repeat for all 96 | letters from 'a' to 'z'. 97 | 98 | Then, when the training is complete, users can enter strings via the Serial 99 | monitor (e.g. "7a"), which will be written directly to the PME as a pattern to 100 | be classified. The PME will respond with the corresponding ASCII character 101 | (e.g. "z"). Copy-and-paste the following code into the Arduino IDE and 102 | give it a try: 103 | 104 | ```cpp 105 | #include "CuriePME.h" 106 | 107 | void setup() 108 | { 109 | Serial.begin(9600); 110 | while(!Serial); 111 | 112 | /* Start the PME (Pattern Matching Engine) */ 113 | CuriePME.begin(); 114 | trainLetters(); 115 | 116 | Serial.println("Training complete."); 117 | 118 | Serial.print("Write a hex value (e.g. 6e), and the PME will return the"); 119 | Serial.println(" corresponding ASCII character"); 120 | } 121 | 122 | void loop () 123 | { 124 | char buf[5]; 125 | uint8_t vector[4]; 126 | unsigned int category; 127 | 128 | while (readNumFromUser(buf, 5) < 2) { 129 | Serial.println("Expecting 2 hex characters, e.g. 6f"); 130 | } 131 | 132 | vector[0] = vector[2] = buf[0]; 133 | vector[1] = vector[3] = buf[1]; 134 | 135 | category = CuriePME.classify(vector, 4); 136 | 137 | if (category == CuriePME.noMatch) { 138 | Serial.println("Don't recognize that one-- try again."); 139 | } else { 140 | Serial.println(String((char)category)); 141 | } 142 | } 143 | 144 | int isLineEnding (char c) 145 | { 146 | return (c == '\n' || c == '\r'); 147 | } 148 | 149 | int readNumFromUser (char buf[], int bufsize) 150 | { 151 | int i = 0; 152 | char c; 153 | 154 | /* Wait for something to read */ 155 | while (Serial.available() == 0); 156 | 157 | /* Read new characters into buffer until full, or until line ending is seen */ 158 | while (Serial.available() > 0) { 159 | while (i < (bufsize - 1) && !isLineEnding(c = Serial.read())) { 160 | buf[i++] = c; 161 | } 162 | 163 | Serial.read(); 164 | } 165 | 166 | buf[i] = 0; 167 | return i; 168 | } 169 | 170 | void trainLetter (const char *buf, uint8_t category) 171 | { 172 | uint8_t vector[4]; 173 | 174 | /* Write pattern twice, to ensure a large-enough distance 175 | between categories */ 176 | vector[0] = vector[2] = buf[0]; 177 | vector[1] = vector[3] = buf[1]; 178 | 179 | CuriePME.learn(vector, 4, category); 180 | } 181 | 182 | void trainLetters (void) 183 | { 184 | trainLetter("61", 'a'); 185 | trainLetter("62", 'b'); 186 | trainLetter("63", 'c'); 187 | trainLetter("64", 'd'); 188 | trainLetter("65", 'e'); 189 | trainLetter("66", 'f'); 190 | trainLetter("67", 'g'); 191 | trainLetter("68", 'h'); 192 | trainLetter("69", 'i'); 193 | trainLetter("6a", 'j'); 194 | trainLetter("6b", 'k'); 195 | trainLetter("6c", 'l'); 196 | trainLetter("6d", 'm'); 197 | trainLetter("6e", 'n'); 198 | trainLetter("6f", 'o'); 199 | trainLetter("70", 'p'); 200 | trainLetter("71", 'q'); 201 | trainLetter("72", 'r'); 202 | trainLetter("73", 's'); 203 | trainLetter("74", 't'); 204 | trainLetter("75", 'u'); 205 | trainLetter("76", 'v'); 206 | trainLetter("77", 'w'); 207 | trainLetter("78", 'x'); 208 | trainLetter("79", 'y'); 209 | trainLetter("7a", 'z'); 210 | } 211 | ``` 212 | 213 | ## Tutorial sketch #1: breakdown 214 | 215 | Let's look at some parts of this sketch in detail. We had said that we were 216 | going to read two characters from the user, and write them directly to the PME 217 | to be classified. You may have noticed that we're not quite doing that. Here's 218 | the function that takes the input string, something like "6f", and writes it 219 | to the PME for learning: 220 | 221 | ```cpp 222 | void trainLetter (const char *buf, uint8_t category) 223 | { 224 | uint8_t vector[4]; 225 | 226 | /* Write pattern twice, to ensure a large-enough distance 227 | between categories */ 228 | vector[0] = vector[2] = buf[0]; 229 | vector[1] = vector[3] = buf[1]; 230 | 231 | CuriePME.learn(vector, 4, category); 232 | } 233 | ``` 234 | 235 | You'll notice that the actual pattern that is passed to the `learn()` function 236 | is double the size- 4 bytes. So we start with an input pattern like this 237 | 238 | ```cpp 239 | contents of buf = "6f" 240 | ``` 241 | 242 | But this is what we actually write to the PME 243 | 244 | ```cpp 245 | contents of vector[] = {'6', 'f', '6', 'f'} 246 | ``` 247 | 248 | *Why do we need to do this?* 249 | 250 | The basic reason is that if we don't, the categories for each letter will not 251 | be distinct enough, and we'll get unexpected results when we try to classify. 252 | 253 | Take the first two categories, 'a' and 'b', whose patterns are "61" and "62", 254 | respectively. Now, think about what the raw data looks like for each string; 255 | "61", two ASCII characters '6' and '1'. So the hex representation of these two 256 | bytes is 0x3631. Similarly, "62" is 0x3632. So there is only a difference of 1 257 | between categories 'a' and 'b'-- this is not enough! by using that simple trick 258 | of writing the value twice successively, e.g. 0x36313631 and 0x36323632, now 259 | there is a much bigger difference between these two values-- 1001 to be exact. 260 | 261 | While it's an interesting example to illustrate the basic use of the PME, this 262 | problem we encountered would probably not occur when working with "real" 263 | data. When larger and more complex patterns are used, patterns derived from 264 | sensors or other real-world inputs, it is quite unlikely that there would be 265 | such a small difference between neurons of different categories. 266 | 267 | Now, why would you go to all this trouble just to convert some strings? 268 | You could quite easily write some code that does the same conversion using 269 | some simple calculations, or even by comparing the strings. The input strings 270 | are very simple, and they never change: so it is easy to test for equality. 271 | Either the string matches "61", and it represents 'a', or it does not match. 272 | There's no in-between. 273 | 274 | Indeed, this example sketch is totally overkill. The PME is designed for more 275 | complex patterns, and we're kind of abusing it here by using it to avoid such 276 | a simple arithmetic task. 277 | 278 | But what if the pattern *wasn't* so simple? what if we needed, instead of a way 279 | to test exact equality, as we so often do in everyday programming tasks, 280 | a way to know if something was "kind of" or "almost" equal? What if we need a 281 | more abstract definition of equality, that is not so rigid? That is where the 282 | real power of the PME becomes apparent. The `classify()` function lets you ask 283 | "are these two things close enough to be considered equal?" and the `learn()` 284 | function allows you to define what "close enough" means. 285 | 286 | # Tutorial sketch #2: use IMU data to create a more complex pattern 287 | 288 | **Extra hardware required**: For this sketch, you will need a button connected 289 | to your Arduino 101 (https://www.arduino.cc/en/tutorial/pushbutton), or a shield 290 | with a button built-in. I am using the Danger Shield from Sparkfun 291 | (https://www.sparkfun.com/products/11649), which includes a button connected 292 | to digital pin 12. 293 | 294 | Now, we're going to forget about the PME for just a few minutes. We just 295 | learned how to use the `learn()` and `classify()` functions, and you probably 296 | want to start using them for some more interesting applications. Don't worry, 297 | we will, but we need to move away from them for a bit and concentrate on another 298 | piece first. 299 | 300 | In order to use more complex patterns with the PME, we need to first analyze the 301 | data that we have, and try to identify the patterns ourselves. Since the 302 | DrawingInTheAir example uses accelerometer data from the CurieIMU library, we 303 | will start by collecting some accelerometer data. 304 | 305 | The following sketch will wait until you press and hold the connected button, 306 | and while the button is being held, accelerometer data is saved into a buffer 307 | at a rate of 100Hz. That's 100 X, Y and Z values per second. 308 | 309 | ```cpp 310 | #include "CurieIMU.h" 311 | 312 | const unsigned int buttonPin = 12; 313 | 314 | /* reading the accelerometer 100 times per second */ 315 | const unsigned int sampleRateHZ = 100; 316 | 317 | void setup() 318 | { 319 | Serial.begin(9600); 320 | while(!Serial); 321 | 322 | /* Set button pin as input */ 323 | pinMode(buttonPin, INPUT); 324 | 325 | /* Start the IMU (Inertial Measurement Unit), enable accelerometer only */ 326 | CurieIMU.begin(ACCEL); 327 | 328 | CurieIMU.setAccelerometerRate(sampleRateHZ); 329 | CurieIMU.setAccelerometerRange(4); 330 | } 331 | 332 | void loop () 333 | { 334 | int buf[1000]; 335 | unsigned int numSamples = 0; 336 | 337 | /* Record IMU data while button is being held */ 338 | numSamples = readFromIMU(buf, sizeof(buf)); 339 | Serial.println("Read " + String(numSamples) + " samples from IMU"); 340 | } 341 | 342 | unsigned int readFromIMU(int buf[], unsigned int buf_size) 343 | { 344 | unsigned int i = 0; 345 | 346 | /* Wait until button is pressed */ 347 | while (digitalRead(buttonPin) == HIGH); 348 | 349 | Serial.println("Recording motion... "); 350 | 351 | /* While button is being held... */ 352 | while (digitalRead(buttonPin) == LOW) { 353 | 354 | /* Wait for new accelerometer data to be ready */ 355 | if (CurieIMU.dataReady()) { 356 | 357 | /* Read the new x, y & z values into the buffer */ 358 | CurieIMU.readAccelerometer(buf[i], buf[i + 1], buf[i + 2]); 359 | 360 | i += 3; 361 | 362 | /* If the buffer doesn't have enough space for the 363 | * next x, y & z values, we're finished. */ 364 | if (i + 3 > buf_size) { 365 | break; 366 | } 367 | } 368 | } 369 | 370 | Serial.println("Got it!"); 371 | return (i + 1) / 3; 372 | } 373 | ``` 374 | 375 | ## Tutorial sketch #2: breakdown 376 | 377 | This sketch doesn't do very much right now. It reads accelerometer data into a 378 | buffer, while the button is held, and when the button is released, we just print 379 | the number of bytes that were read from the IMU. In order to identify any 380 | patterns in the data, we'll need to visualize it somehow. So, I'm going to 381 | wave my board around in the air in some specific ways, make some pretty pictures 382 | out of the data, and let's see if we can identify some patterns. 383 | 384 | 385 | *Note: In the graphs that follow, the x-axis represents time in terms of* 386 | *samples recorded. Since we are sampling at 100Hz, 230 total samples* 387 | *(for example) would mean the button was held for approximately 2.3 seconds.* 388 | 389 | *The y-axis represents the value read from the accelerometer, which will* 390 | *be a value between -32768 and 32767.* 391 | 392 | **Keeping the board still & flat on my desk while holding the button** 393 | 394 | ![still](images/still.png) 395 | 396 | 397 | **Gently shaking the board up and down, as if shaking hands, while holding the button** 398 | 399 | ![shake](images/shake.png) 400 | 401 | 402 | **Drawing one full circle in the air while holding the button** 403 | 404 | ![circle](images/circle.png) 405 | 406 | 407 | OK, so it's pretty easy to recognize the different patterns for those 3 very 408 | different movements. Now, let's try drawing some letters (note that everybody 409 | draws letters differently, so the following patterns will be what *my* letters 410 | look like) 411 | 412 | 413 | **Drawing 'A' in the air while holding the button** 414 | 415 | ![drawing A](images/a1.png) 416 | 417 | 418 | **Drawing 'B' in the air while holding the button** 419 | 420 | ![drawing B](images/b1.png) 421 | 422 | 423 | **Drawing 'C' in the air while holding the button** 424 | 425 | ![drawing C](images/c1.png) 426 | 427 | 428 | You can see that the data from drawing the letters A, B and C produce fairly 429 | different patterns for each letter. But is it repeatable? let's try drawing 430 | the letter A 3 times, and see if the patterns look similar: 431 | 432 | 433 | **Drawing 'A', first time** 434 | 435 | ![drawing A1](images/a1.png) 436 | 437 | 438 | **Drawing 'A', second time** 439 | 440 | ![drawing A2](images/a2.png) 441 | 442 | 443 | **Drawing 'A', third time** 444 | 445 | ![drawing A3](images/a3.png) 446 | 447 | Alright, so drawing the letter 'A' produces a pretty consistent pattern. With 448 | the exception of some low noise and minor variations, it's easy for us to see 449 | that those are 3 different versions of the same pattern. 450 | 451 | 452 | So, is that it? Can we just write this data directly to the PME for training 453 | and classification? Unfortunately, not yet. We have two big problems to solve 454 | first. The first problem... 455 | 456 | 457 | ## Problem 1: There's too much data 458 | 459 | As we can see from the graphs above, drawing the letter 'A' takes an average of 460 | 1.6 seconds, which is 160 samples at 100Hz. Each sample (i.e. each call to 461 | `CurieIMU.readAccelerometer()` takes up 3 'int' values, which are 4 bytes each, 462 | so 12 bytes per sample. 463 | 464 | ```cpp 465 | int X, Y, Z; 466 | 467 | /* Read raw IMU data, 14 bytes in total for all 3 values */ 468 | CurieIMU.readAccelerometer(X, Y, Z); 469 | ``` 470 | 471 | This makes for a total of 1,920 bytes that we have to capture to represent the 472 | letter 'A', which is *way* too much. Remember that our pattern can be no larger 473 | than 128 bytes, so we need to figure out a clever way to throw away **over 90%** 474 | of the raw data, without destroying the original pattern. 475 | 476 | One way we can do this is by decreasing the range of sample values. The 477 | accelerometer returns values in the range of -32,768 to 32,767, and we are 478 | currently using a four-byte int to store each one. This is quite a big range, 479 | and we probably don't need such high precision for this application. So we could 480 | map each value from the original range to 0-255, meaning it can fit in a single 481 | byte, so we will only need 3 bytes to store a single sample of x, y and z 482 | values. We can do this quite easily with the Arduino `map()` function: 483 | 484 | ```cpp 485 | int X, Y, Z; 486 | byte x, y, z; 487 | 488 | /* Read raw IMU data, 14 bytes in total for all 3 values */ 489 | CurieIMU.readAccelerometer(X, Y, Z); 490 | 491 | /* Map to smaller range, now 3 bytes in total for all 3 values */ 492 | x = (byte) map(X, -32768, 32767, 0, 255); 493 | y = (byte) map(Y, -32768, 32767, 0, 255); 494 | z = (byte) map(Z, -32768, 32767, 0, 255); 495 | ``` 496 | 497 | OK, so let's modify the ``readFromIMU()`` function from example sketch #2, to 498 | map the X,Y and Z values from the accelerometer, and store them in a ``byte`` 499 | array instead of an ``int`` array: 500 | 501 | ```cpp 502 | /* Use some named constants to make life easier */ 503 | const int IMULow = -32768; 504 | const int IMUHigh = 32767; 505 | 506 | unsigned int readFromIMU(byte buf[], unsigned int buf_size) 507 | { 508 | unsigned int i = 0; 509 | int temp[3]; 510 | 511 | /* Wait until button is pressed */ 512 | while (digitalRead(buttonPin) == HIGH); 513 | 514 | Serial.println("Recording motion... "); 515 | 516 | /* While button is being held... */ 517 | while (digitalRead(buttonPin) == LOW) { 518 | 519 | /* Wait for new accelerometer data to be ready */ 520 | if (CurieIMU.dataReady()) { 521 | 522 | /* Read the new x, y & z values into the buffer */ 523 | CurieIMU.readAccelerometer(temp[0], temp[1], temp[2]); 524 | 525 | buf[i] = (byte) map(temp[0], IMULow, IMUHigh, 0, 255); 526 | buf[i + 1] = (byte) map(temp[1], IMULow, IMUHigh, 0, 255); 527 | buf[i + 2] = (byte) map(temp[2], IMULow, IMUHigh, 0, 255); 528 | 529 | i += 3; 530 | 531 | /* If the buffer doesn't have enough space for the 532 | * next x, y & z values, we're finished. */ 533 | if (i + 3 > buf_size) { 534 | break; 535 | } 536 | } 537 | } 538 | 539 | Serial.println("Got it!"); 540 | return (i + 1) / 3; 541 | } 542 | ``` 543 | 544 | Now, ``readFromIMU()`` crushes each ``int`` value into a byte, and our data is 545 | 75% smaller. Have a look at the accelerometer data for drawing the letter 'A', 546 | before and after using the ``map()`` function (notice the smaller scale on the 547 | X-axis in the second picture- it only goes up to 255): 548 | 549 | **Drawing the letter A, raw accelerometer data** 550 | ![drawing A, raw](images/a_raw.png) 551 | 552 | **Drawing the letter A, accelerometer data mapped to a range of 0-255** 553 | ![drawing A, raw](images/a_compressed.png) 554 | 555 | They both look the same, right? That's good news! The original pattern is still 556 | almost completely intact, and we threw away 75% of the data. But we still have 557 | some problems; even though each value is now 1 byte instead of 4 bytes, there is 558 | still too much data. In the picture above showing the compressed data, we still 559 | have 198 samples (I held the button for about 2 seconds), and given that each 560 | sample holds one byte each for X, Y and Z, the total size of the data captured 561 | here is 594 bytes (198 * 3 = 594). That's *still* a lot more than the 128 bytes 562 | that we have available in a neuron! 563 | 564 | So, what else can we do? We could compress the accelerometer values *even more*, 565 | so each value takes up less than a byte, but that will make things really 566 | complicated. Let's stick with 1 byte per accelerometer value, which also means 567 | that our pattern size is limited to 42 samples, or 126 bytes (42 * 3 = 568 | 126) -- just two bytes shy of a neuron's full capacity. 569 | 570 | To summarize, we are sampling the accelerometer at 100Hz. In most cases, the 571 | button will be held down for anywhere between 1 and 3 seconds-- possibly even 572 | longer-- meaning ``readFromIMU()`` will usually collect 100-300 samples. But no 573 | matter how many samples are captured, *we can only use 42 of them*. This means 574 | we have to be smart about picking those 42 samples, which brings us to... 575 | 576 | ### Undersampling 577 | 578 | Undersampling is pretty much what it sounds like; storing only a small number 579 | of samples, at a lower rate than the actual sample rate. Actually, it's a bit 580 | more than that-- Wikipedia says 581 | 582 | > undersampling is a technique where one samples a bandpass-filtered signal at 583 | > a sample rate below its Nyquist rate (twice the upper cutoff frequency), 584 | > but is still able to reconstruct the signal 585 | 586 | For us, this just means we'll take only the samples that we need to 587 | accurately reproduce our pattern. To make things even simpler, we already know 588 | that we can only take 42 samples. So we will simply stretch those 42 samples 589 | over the entire sample window. 590 | 591 | Taking as an example the pattern from drawing the letter 'A' in the previous 592 | section, which was 198 samples in total, we would need to take every 4th sample 593 | to cover the sample window with only 42 samples (198 / 42 = 4.71). 594 | 595 | To show you what that would look like, here is the same 198 samples we saw in 596 | the previous section, but with every 4th point highlighted, so you can get an 597 | idea of what the final 42 samples would look like (Note that I've only 598 | highlighted points in the Z axis, since the graph would be too crowded 599 | otherwise. But we will of course be taking the same points from the X and Y 600 | axes, as well): 601 | 602 | **Drawing the letter 'A', with undersample points highlighted** 603 | 604 | ![drawing the letter A, undersampled](images/a_points.png) 605 | 606 | Now, we are going to write a function that does the undersampling. This function 607 | will take the full buffer of samples, determine how many samples must be skipped 608 | to cover the whole window with 42 samples, then iterate through the sample 609 | buffer (skipping the calculated number of samples with every step) and place 610 | the resulting 42 samples in an output buffer. 611 | 612 | ```cpp 613 | 614 | /* Number of processed samples (1 sample == accel x, y, z) 615 | * that can fit inside a neuron */ 616 | const unsigned int samplesPerVector = (CuriePME.maxVectorSize / 3); 617 | 618 | void undersample(byte input[], int numSamples, byte output[]) 619 | { 620 | unsigned int oi = 0; /* Current position in output sample buffer */ 621 | unsigned int ii = 0; /* Current position in input sample buffer */ 622 | 623 | /* No. of samples to skip for each iteration */ 624 | unsigned int step = numSamples / samplesPerVector; 625 | 626 | for (unsigned int i = 0; i < samplesPerVector; ++i) { 627 | for (unsigned int j = 0; j < 3; ++j) { 628 | /* Cherry-pick the (ii + j)th sample from sample window */ 629 | output[oi + j] = input[ii + j]; 630 | } 631 | 632 | /* Skip 'step' samples */ 633 | ii += (step * 3); 634 | oi += 3; 635 | } 636 | } 637 | ``` 638 | 639 | By passing the output of ``readFromIMU()`` into this new ``undersample()`` 640 | function, we will always get 42 samples out. This brings us to the second 641 | problem, which is... 642 | 643 | ## Problem 2: The data is too noisy 644 | 645 | If you look back at the last graph of accelerometer data, or any of the data 646 | graphs we've looked at so far, you'll notice that all 3 axes consist of quite 647 | wobbly and shaky lines. The aren't smooth at all, like they might be if I'd 648 | drawn them with a pen. In fact, in all of them, it looks like my hand is 649 | constantly shaking. 650 | 651 | There are a few reasons for this. First of all, all the muscles in my hand and 652 | fingers are constantly making little movements and adjustments as I'm holding 653 | the board-- even though it may look to me like I've moved my hand in a 654 | straight line, the accelerometer is able to pick up a lot of little movements 655 | that my eyes can't. Additionally, the accelerometer is not perfect, so 656 | it will not be 100% accurate, and there will always be a little bit of noise in 657 | the signal. 658 | 659 | The bottom line is, these little signal anomalies will make it more difficult 660 | for the PME to classify patterns, and we need to clean them up somehow. 661 | 662 | ### Use an averaging filter! 663 | 664 | An [averaging filter](https://www.arduino.cc/en/tutorial/smoothing) is a simple 665 | and effective way of "smoothing out" a data set, commonly used for streams of 666 | ADC or accelerometer readings. I'm going to explain how we can modify the 667 | ``undersample()`` function to incorporate an averaging filter. 668 | 669 | As was shown with the ``undersample()`` function (continuing with the same 670 | example data set of 198 samples), we are only taking every 4th sample from the 671 | sample window, and throwing away the rest. But instead of throwing away the 672 | in-between samples, we can use them! Have a look at the following lines from 673 | the ``undersample()`` function, where we copy a sample from the ``input`` 674 | buffer to the ``output`` buffer: 675 | 676 | ```cpp 677 | /* Cherry-pick the (ii + j)th sample from sample window */ 678 | output[oi + j] = input[ii + j]; 679 | ``` 680 | 681 | Instead of just copying the sample as-is from the ``input`` buffer, we can 682 | calculate the sum of this sample and the 4 skipped samples before it. Then we 683 | take the average of these samples, to create new sample which is the average 684 | of the previous 5 samples, and write *that* sample to the ``output`` buffer. 685 | 686 | Now, we will write a new function called ``getAverageSample()``, which can be 687 | used by ``undersample()`` to get an average sample: 688 | 689 | ```cpp 690 | /* Number of processed samples (1 sample == accel x, y, z) 691 | * that can fit inside a neuron */ 692 | const unsigned int samplesPerVector = (CuriePME.maxVectorSize / 3); 693 | 694 | byte getAverageSample(byte samples[], unsigned int num, unsigned int pos, unsigned int step) 695 | { 696 | unsigned int ret; 697 | 698 | /* This is the number of samples that will be used to create each 699 | * average sample; i.e. all the skipped samples before and after 700 | * the current sample */ 701 | unsigned int size = step * 2; 702 | 703 | /* Don't do any averaging, if we are at the beginning or end 704 | * of the sample window */ 705 | if (pos < (step * 3) || pos > (num * 3) - (step * 3)) { 706 | ret = samples[pos]; 707 | } else { 708 | ret = 0; 709 | pos -= (step * 3); 710 | 711 | /* Calculate the sum of 'step' samples before, and after, 712 | * the current sample */ 713 | for (unsigned int i = 0; i < size; ++i) { 714 | ret += samples[pos - (3 * i)]; 715 | } 716 | 717 | ret /= size; 718 | } 719 | 720 | return (byte)ret; 721 | } 722 | 723 | void undersample(byte input[], int numSamples, byte output[]) 724 | { 725 | unsigned int oi = 0; /* Current position in output sample buffer */ 726 | unsigned int ii = 0; /* Current position in input sample buffer */ 727 | 728 | /* No. of samples to skip for each iteration */ 729 | unsigned int step = numSamples / samplesPerVector; 730 | 731 | for (unsigned int i = 0; i < samplesPerVector; ++i) { 732 | for (unsigned int j = 0; j < 3; ++j) { 733 | /* Get an average sample for the current position 734 | * in the sample window */ 735 | output[oi + j] = getAverageSample(input, numSamples, ii + j, step); 736 | } 737 | 738 | /* Skip 'step' samples */ 739 | ii += (step * 3); 740 | oi += 3; 741 | } 742 | } 743 | ``` 744 | 745 | 746 | This does a pretty good job of removing the worst of the noise, while leaving 747 | the original pattern intact. Let's compare the raw data to the final compressed, 748 | undersampled and smoothed signal: 749 | 750 | **Drawing 'A', raw accelerometer data (2,376 bytes in total)** 751 | 752 | ![Drawing 'A', raw data](images/a_raw.png) 753 | 754 | **Drawing 'A', compressed, undersampled & smoothed (126 bytes in total)** 755 | 756 | ![Drawing 'A', fully processed](images/a_filtered.png) 757 | 758 | We threw away **94.7%** of the original data-- and we still have a recognizable 759 | pattern! not bad! And now, finally, we are finished processing the data. We 760 | have the tools that we need to turn raw accelerometer data into patterns 761 | that the PME can learn and recognize. 762 | 763 | As a final step for the pattern-processing part, let's modify our 764 | ``readFromIMU()`` function to use the new undersampling function on the 765 | collected accelerometer data: 766 | 767 | ```cpp 768 | void readFromIMU(byte vector[]) 769 | { 770 | byte accel[sensorBufSize]; 771 | int raw[3]; 772 | 773 | unsigned int i = 0; 774 | 775 | /* Wait until button is pressed */ 776 | while (digitalRead(buttonPin) == LOW); 777 | 778 | /* While button is being held... */ 779 | while (digitalRead(buttonPin) == HIGH) { 780 | if (CurieIMU.dataReady()) { 781 | 782 | CurieIMU.readAccelerometer(raw[0], raw[1], raw[2]); 783 | 784 | /* Map raw values to 0-255 */ 785 | accel[i] = (byte) map(raw[0], IMULow, IMUHigh, 0, 255); 786 | accel[i + 1] = (byte) map(raw[1], IMULow, IMUHigh, 0, 255); 787 | accel[i + 2] = (byte) map(raw[2], IMULow, IMUHigh, 0, 255); 788 | 789 | i += 3; 790 | 791 | /* If there's not enough room left in the buffers 792 | * for the next read, then we're done */ 793 | if (i + 3 > sensorBufSize) { 794 | break; 795 | } 796 | } 797 | } 798 | 799 | undersample(accel, i / 3, vector); 800 | } 801 | ``` 802 | 803 | The rest is easy; we'll write a function called ``trainLetters()``, which 804 | will use ``readFromIMU()`` to obtain a 126-byte pattern from the IMU. 805 | Then, we will call ``CuriePME.learn()`` using that pattern as input 806 | (and using letters of the alphabet as categories). Here is the code for 807 | the ``trainLetters()`` function: 808 | 809 | ```cpp 810 | /* This function trains a single letter, by asking the user to 811 | * draw it 'repeat' times */ 812 | void trainLetter(char letter, unsigned int repeat) 813 | { 814 | unsigned int i = 0; 815 | 816 | while (i < repeat) { 817 | byte vector[vectorNumBytes]; 818 | 819 | if (i) Serial.println("And again..."); 820 | 821 | /* Read IMU data and convert to 126-byte filtered pattern */ 822 | readFromIMU(vector); 823 | 824 | /* Train the PME with the pattern. Convert letter from normal ASCII 825 | * range (65-90) to 1-based range (1-26). 826 | * 827 | * If you don't want to do that conversion, you don't need to-- 828 | * things will still work if the categories range from 65-90 */ 829 | CuriePME.learn(vector, vectorNumBytes, letter - upperStart); 830 | 831 | Serial.println("Got it!"); 832 | delay(1000); 833 | ++i; 834 | } 835 | } 836 | 837 | /* This function calls 'trainLetter()' on each letter of the alphabet */ 838 | void trainLetters() 839 | { 840 | /* You can change this to 'Z', if you want... it just takes a long time 841 | * to train the whole alphabet. My arm gets tired :) */ 842 | for (char i = 'A'; i <= 'F'; ++i) { 843 | Serial.print("Hold down the button and draw the letter '"); 844 | Serial.print(String(i) + "' in the air. Release the button as soon "); 845 | Serial.println("as you are done."); 846 | 847 | /* Train the current letter, and prompt user to draw the 848 | * letter 4 times in a row */ 849 | trainLetter(i, 4); 850 | 851 | Serial.println("OK, finished with this letter."); 852 | delay(2000); 853 | } 854 | } 855 | ``` 856 | 857 | That's it! we finished the whole DrawingInTheAir example. While we did not 858 | discuss code in the ``setup()`` and ``loop()`` functions, it should be pretty 859 | easy to figure out, so it is left as an exercise for you. 860 | 861 | Congratulations on making it all the way to the end of this (quite long) 862 | tutorial; hopefully now you feel confident about using the CuriePME library 863 | in your own projects! 864 | -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/a1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/a1.jpg -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/a1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/a1.png -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/a2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/a2.jpg -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/a2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/a2.png -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/a3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/a3.jpg -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/a3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/a3.png -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/a_compressed.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/a_compressed.jpeg -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/a_compressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/a_compressed.png -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/a_filtered.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/a_filtered.jpeg -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/a_filtered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/a_filtered.png -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/a_points.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/a_points.png -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/a_raw.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/a_raw.jpeg -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/a_raw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/a_raw.png -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/b1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/b1.jpg -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/b1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/b1.png -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/c1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/c1.jpg -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/c1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/c1.png -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/circle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/circle.jpg -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/circle.png -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/shake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/shake.jpg -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/shake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/shake.png -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/still.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/still.jpg -------------------------------------------------------------------------------- /examples/DrawingInTheAir/images/still.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/Intel-Pattern-Matching-Technology/be22b6d7b19b701e0c6686f3bbceaedc37a9c105/examples/DrawingInTheAir/images/still.png -------------------------------------------------------------------------------- /examples/a_SimplePatternMatching/Makefile: -------------------------------------------------------------------------------- 1 | ifeq ("$(strip $(CODK_DIR))", "") 2 | $(error Please set the CODK_DIR variable.) 3 | endif 4 | 5 | ARDUINOSW_DIR ?= $(CODK_DIR)/arc 6 | 7 | current_dir = $(shell pwd) 8 | 9 | VERBOSE = true 10 | 11 | LIBDIRS = $(ARDUINOSW_DIR)/corelibs/libraries/Intel-Pattern-Matching-Technology/src 12 | 13 | include $(ARDUINOSW_DIR)/Makefile.inc 14 | 15 | all: compile 16 | 17 | .DEFAULT_GOAL := all 18 | -------------------------------------------------------------------------------- /examples/a_SimplePatternMatching/a_SimplePatternMatching.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016 Intel Corporation. All rights reserved. 3 | See license notice at end of file. 4 | */ 5 | 6 | // This Example illustrates how to train the Intel(r) Curie(tm) pattern matching engine with 7 | // example data and how to tell how many neurons are committed in the network. 8 | 9 | #include "CuriePME.h" 10 | 11 | void setup() { 12 | // put your setup code here, to run once: 13 | Serial.begin(9600); // initialize Serial communication 14 | while (!Serial); // wait for the serial port to open 15 | 16 | // initialize the engine 17 | CuriePME.begin(); 18 | 19 | trainNeuronsWithData(); 20 | } 21 | 22 | void loop() { 23 | 24 | uint8_t vector[3]; 25 | // now we'll classify some unknown data and let the 26 | // engine decide which pattern of 3 numbers most closely match 27 | // what it has been taught, or if they don't match anything it knows about. 28 | 29 | int x, y, z; 30 | while (Serial.available() > 0) { 31 | 32 | x = Serial.parseInt(); 33 | y = Serial.parseInt(); 34 | z = Serial.parseInt(); 35 | 36 | // Wait until we see newline or carriage return 37 | while (!isLineEnding(Serial.read())); 38 | 39 | // Then wait until we stop seeing then 40 | while (isLineEnding(Serial.peek())) { 41 | Serial.read(); 42 | } 43 | 44 | vector[0] = constrain(x, 0, 255); 45 | vector[1] = constrain(y, 0, 255); 46 | vector[2] = constrain(z, 0, 255); 47 | 48 | int answer = CuriePME.classify(vector, 3 ); 49 | 50 | Serial.print("You entered: "); 51 | printVector(vector); 52 | 53 | if( answer == CuriePME.noMatch ) { 54 | Serial.print("Which didn't match any of the trained categories.\n"); 55 | } else { 56 | Serial.print("The closest match to the trained data \n"); 57 | Serial.print("is category: "); 58 | Serial.print( answer ); 59 | Serial.print("\n"); 60 | } 61 | } 62 | } 63 | 64 | void printVector (uint8_t vector[]) 65 | { 66 | Serial.print(vector[0]); 67 | Serial.print(","); 68 | Serial.print(vector[1]); 69 | Serial.print(","); 70 | Serial.println(vector[2]); 71 | } 72 | 73 | bool isLineEnding (char c) 74 | { 75 | return (c == '\r' || c == '\n') ? true : false; 76 | } 77 | 78 | // The pattern matching engine will commit a new neuron for each new category. 79 | // Adding additional data to a category may or may not commit more neurons. 80 | // If the training data is relatively close together and well separated 81 | // from other committed neurons, it usually will not commit an additional 82 | // neuron. Widely separated data sets will usually commit more neurons per category. 83 | void trainNeuronsWithData( void ) 84 | { 85 | Serial.print("Neurons committed before learning = "); 86 | Serial.print( CuriePME.getCommittedCount()); 87 | Serial.print("\n"); 88 | 89 | commitThreeSamples(1, 11, 24, 29); // Category 1 90 | commitThreeSamples(2, 18, 75, 38); // Category 2 91 | commitThreeSamples(3, 2, 56, 35); // Category 3 92 | commitThreeSamples(4, 111, 224, 229); // Category 4 93 | commitThreeSamples(5, 128, 200, 255); // Category 5 94 | commitThreeSamples(6, 99, 180, 201); // Category 6 95 | 96 | Serial.print("Neurons committed after learning = "); 97 | Serial.print( CuriePME.getCommittedCount()); 98 | Serial.print("\n"); 99 | 100 | Serial.print("Now enter 3 numbers, between 0 and 255, separated by a comma. \n"); 101 | Serial.print("Like 11, 24, 29 \n"); 102 | } 103 | 104 | void commitThreeSamples (int category, uint8_t s1, uint8_t s2, uint8_t s3) 105 | { 106 | uint8_t vector[3]; 107 | 108 | vector[0] = s1; 109 | vector[1] = s2; 110 | vector[2] = s3; 111 | 112 | // give the data, the number of elements and the category it belongs to. 113 | CuriePME.learn(vector, 3, category); 114 | 115 | Serial.print("Category "); 116 | Serial.print( category ); 117 | Serial.print(" trained with: "); 118 | printVector(vector); 119 | } 120 | 121 | /* 122 | This library is free software; you can redistribute it and/or 123 | modify it under the terms of the GNU Lesser General Public 124 | License as published by the Free Software Foundation; either 125 | version 2.1 of the License, or (at your option) any later version. 126 | 127 | This library is distributed in the hope that it will be useful, 128 | but WITHOUT ANY WARRANTY; without even the implied warranty of 129 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 130 | Lesser General Public License for more details. 131 | 132 | You should have received a copy of the GNU Lesser General Public 133 | License along with this library; if not, write to the Free Software 134 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 135 | 136 | */ 137 | -------------------------------------------------------------------------------- /examples/b_SavingKnowledge/Makefile: -------------------------------------------------------------------------------- 1 | ifeq ("$(strip $(CODK_DIR))", "") 2 | $(error Please set the CODK_DIR variable.) 3 | endif 4 | 5 | ARDUINOSW_DIR ?= $(CODK_DIR)/arc 6 | 7 | current_dir = $(shell pwd) 8 | 9 | VERBOSE = true 10 | 11 | LIBDIRS = $(ARDUINOSW_DIR)/corelibs/libraries/Intel-Pattern-Matching-Technology/src \ 12 | $(ARDUINOSW_DIR)/corelibs/libraries/SPI/src \ 13 | $(ARDUINOSW_DIR)/corelibs/libraries/SerialFlash 14 | 15 | include $(ARDUINOSW_DIR)/Makefile.inc 16 | 17 | all: compile 18 | 19 | .DEFAULT_GOAL := all 20 | -------------------------------------------------------------------------------- /examples/b_SavingKnowledge/b_SavingKnowledge.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016 Intel Corporation. All rights reserved. 3 | See license notice at end of file. 4 | */ 5 | 6 | // This Example illustrates how to train the Intel(r) Curie(tm) pattern matching engine with 7 | // example data and how to tell how many neurons are committed in the network. 8 | 9 | #include "CuriePME.h" 10 | 11 | #include 12 | #include 13 | 14 | void setup() { 15 | // put your setup code here, to run once: 16 | Serial.begin(9600); // initialize Serial communication 17 | while (!Serial); // wait for the serial port to open 18 | 19 | // initialize the engine 20 | CuriePME.begin(); 21 | 22 | trainNeuronsWithData(); 23 | 24 | // Init. SPI Flash chip 25 | if (!SerialFlash.begin(ONBOARD_FLASH_SPI_PORT, ONBOARD_FLASH_CS_PIN)) { 26 | Serial.println("Unable to access SPI Flash chip"); 27 | } 28 | 29 | saveNetworkKnowledge(); 30 | 31 | Serial.print("\n Now enter 3 numbers, between 0 and 255, separated by a comma. \n"); 32 | Serial.print("Like 11, 24, 29 \n"); 33 | 34 | 35 | } 36 | 37 | void loop() { 38 | 39 | uint8_t vector[3]; 40 | // now we'll classify some unknown data and let the 41 | // engine decide which pattern of 3 numbers most closely match 42 | // what it has been taught, or if they don't match anything it knows about. 43 | 44 | int x, y, z; 45 | while (Serial.available() > 0) { 46 | 47 | x = Serial.parseInt(); 48 | y = Serial.parseInt(); 49 | z = Serial.parseInt(); 50 | 51 | // Wait until we see newline or carriage return 52 | while (!isLineEnding(Serial.read())); 53 | 54 | // Then wait until we stop seeing then 55 | while (isLineEnding(Serial.peek())) { 56 | Serial.read(); 57 | } 58 | 59 | vector[0] = constrain(x, 0, 255); 60 | vector[1] = constrain(y, 0, 255); 61 | vector[2] = constrain(z, 0, 255); 62 | 63 | int answer = CuriePME.classify(vector, 3 ); 64 | 65 | Serial.print("You entered: "); 66 | printVector(vector); 67 | 68 | if( answer == CuriePME.noMatch ) { 69 | Serial.print("Which didn't match any of the trained categories.\n"); 70 | } else { 71 | Serial.print("The closest match to the trained data \n"); 72 | Serial.print("is category: "); 73 | Serial.print( answer ); 74 | Serial.print("\n"); 75 | } 76 | } 77 | } 78 | 79 | void printVector (uint8_t vector[]) 80 | { 81 | Serial.print(vector[0]); 82 | Serial.print(","); 83 | Serial.print(vector[1]); 84 | Serial.print(","); 85 | Serial.println(vector[2]); 86 | } 87 | 88 | bool isLineEnding (char c) 89 | { 90 | return (c == '\r' || c == '\n') ? true : false; 91 | } 92 | 93 | // The pattern matching engine will commit a new neuron for each new category. 94 | // Adding additional data to a category may or may not commit more neurons. 95 | // If the training data is relatively close together and well separated 96 | // from other committed neurons, it usually will not commit an additional 97 | // neuron. Widely separated data sets will usually commit more neurons per category. 98 | void trainNeuronsWithData( void ) 99 | { 100 | Serial.print("Neurons committed before learning = "); 101 | Serial.print( CuriePME.getCommittedCount()); 102 | Serial.print("\n"); 103 | 104 | commitThreeSamples(1, 11, 24, 29); // Category 1 105 | commitThreeSamples(2, 18, 75, 38); // Category 2 106 | commitThreeSamples(3, 2, 56, 35); // Category 3 107 | commitThreeSamples(4, 111, 224, 229); // Category 4 108 | commitThreeSamples(5, 128, 200, 255); // Category 5 109 | commitThreeSamples(6, 99, 180, 201); // Category 6 110 | 111 | Serial.print("Neurons committed after learning = "); 112 | Serial.print( CuriePME.getCommittedCount()); 113 | Serial.print("\n"); 114 | } 115 | 116 | void commitThreeSamples (int category, uint8_t s1, uint8_t s2, uint8_t s3) 117 | { 118 | uint8_t vector[3]; 119 | 120 | vector[0] = s1; 121 | vector[1] = s2; 122 | vector[2] = s3; 123 | 124 | // give the data, the number of elements and the category it belongs to. 125 | CuriePME.learn(vector, 3, category); 126 | 127 | Serial.print("Category "); 128 | Serial.print( category ); 129 | Serial.print(" trained with: "); 130 | printVector(vector); 131 | } 132 | 133 | void saveNetworkKnowledge ( void ) 134 | { 135 | const char *filename = "NeurData.dat"; 136 | SerialFlashFile file; 137 | 138 | Intel_PMT::neuronData neuronData; 139 | uint32_t fileSize = 128 * sizeof(neuronData); 140 | 141 | Serial.print( "File Size to save is = "); 142 | Serial.print( fileSize ); 143 | Serial.print("\n"); 144 | 145 | create_if_not_exists( filename, fileSize ); 146 | // Open the file and write test data 147 | file = SerialFlash.open(filename); 148 | file.erase(); 149 | 150 | CuriePME.beginSaveMode(); 151 | if (file) { 152 | // iterate over the network and save the data. 153 | while( uint16_t nCount = CuriePME.iterateNeuronsToSave(neuronData)) { 154 | if( nCount == 0x7FFF) 155 | break; 156 | 157 | Serial.print("Saving Neuron: "); 158 | Serial.print(nCount); 159 | Serial.print("\n"); 160 | uint16_t neuronFields[4]; 161 | 162 | neuronFields[0] = neuronData.context; 163 | neuronFields[1] = neuronData.influence; 164 | neuronFields[2] = neuronData.minInfluence; 165 | neuronFields[3] = neuronData.category; 166 | 167 | file.write( (void*) neuronFields, 8); 168 | file.write( (void*) neuronData.vector, 128 ); 169 | } 170 | } 171 | 172 | CuriePME.endSaveMode(); 173 | Serial.print("Knowledge Set Saved. \n"); 174 | } 175 | 176 | 177 | bool create_if_not_exists (const char *filename, uint32_t fileSize) { 178 | if (!SerialFlash.exists(filename)) { 179 | Serial.println("Creating file " + String(filename)); 180 | return SerialFlash.createErasable(filename, fileSize); 181 | } 182 | 183 | Serial.println("File " + String(filename) + " already exists"); 184 | return true; 185 | } 186 | 187 | /* 188 | This library is free software; you can redistribute it and/or 189 | modify it under the terms of the GNU Lesser General Public 190 | License as published by the Free Software Foundation; either 191 | version 2.1 of the License, or (at your option) any later version. 192 | 193 | This library is distributed in the hope that it will be useful, 194 | but WITHOUT ANY WARRANTY; without even the implied warranty of 195 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 196 | Lesser General Public License for more details. 197 | 198 | You should have received a copy of the GNU Lesser General Public 199 | License along with this library; if not, write to the Free Software 200 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 201 | 202 | */ 203 | 204 | -------------------------------------------------------------------------------- /examples/c_RestoringKnowledge/Makefile: -------------------------------------------------------------------------------- 1 | ifeq ("$(strip $(CODK_DIR))", "") 2 | $(error Please set the CODK_DIR variable.) 3 | endif 4 | 5 | ARDUINOSW_DIR ?= $(CODK_DIR)/arc 6 | 7 | current_dir = $(shell pwd) 8 | 9 | VERBOSE = true 10 | 11 | LIBDIRS = $(ARDUINOSW_DIR)/corelibs/libraries/Intel-Pattern-Matching-Technology/src \ 12 | $(ARDUINOSW_DIR)/corelibs/libraries/SPI/src \ 13 | $(ARDUINOSW_DIR)/corelibs/libraries/SerialFlash 14 | 15 | include $(ARDUINOSW_DIR)/Makefile.inc 16 | 17 | all: compile 18 | 19 | .DEFAULT_GOAL := all 20 | -------------------------------------------------------------------------------- /examples/c_RestoringKnowledge/c_RestoringKnowledge.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016 Intel Corporation. All rights reserved. 3 | See license notice at end of file. 4 | 5 | This example restores the neural network from a file saved in the storage memory 6 | on the Arduino/Genuino 101 board that was saved by the previous example sketch. 7 | When the network is restored, it is able to use the training learned from prior 8 | examples without retraining it. Thus the prior knowledge is restored. 9 | */ 10 | 11 | #include "CuriePME.h" 12 | 13 | #include 14 | #include 15 | 16 | void setup() { 17 | // put your setup code here, to run once: 18 | Serial.begin(9600); // initialize Serial communication 19 | while (!Serial); // wait for the serial port to open 20 | 21 | // initialize the engine 22 | CuriePME.begin(); 23 | 24 | // Init. SPI Flash chip 25 | if (!SerialFlash.begin(ONBOARD_FLASH_SPI_PORT, ONBOARD_FLASH_CS_PIN)) { 26 | Serial.println("Unable to access SPI Flash chip"); 27 | } 28 | 29 | restoreNetworkKnowledge(); 30 | 31 | Serial.print("\n Now enter 3 numbers, between 0 and 255, separated by a comma. \n"); 32 | Serial.print("Like 11, 24, 29 \n"); 33 | } 34 | 35 | void loop() { 36 | 37 | uint8_t vector[3]; 38 | // now we'll classify some unknown data and let the 39 | // engine decide which pattern of 3 numbers most closely match 40 | // what it has been taught, or if they don't match anything it knows about. 41 | 42 | int x, y, z; 43 | while (Serial.available() > 0) { 44 | 45 | x = Serial.parseInt(); 46 | y = Serial.parseInt(); 47 | z = Serial.parseInt(); 48 | 49 | // Wait until we see newline or carriage return 50 | while (!isLineEnding(Serial.read())); 51 | 52 | // Then wait until we stop seeing then 53 | while (isLineEnding(Serial.peek())) { 54 | Serial.read(); 55 | } 56 | 57 | vector[0] = constrain(x, 0, 255); 58 | vector[1] = constrain(y, 0, 255); 59 | vector[2] = constrain(z, 0, 255); 60 | 61 | int answer = CuriePME.classify(vector, 3 ); 62 | 63 | Serial.print("You entered: "); 64 | printVector(vector); 65 | 66 | if( answer == CuriePME.noMatch ) { 67 | Serial.print("Which didn't match any of the trained categories.\n"); 68 | } else { 69 | Serial.print("The closest match to the trained data \n"); 70 | Serial.print("is category: "); 71 | Serial.print( answer ); 72 | Serial.print("\n"); 73 | } 74 | } 75 | } 76 | 77 | void printVector (uint8_t vector[]) 78 | { 79 | Serial.print(vector[0]); 80 | Serial.print(","); 81 | Serial.print(vector[1]); 82 | Serial.print(","); 83 | Serial.println(vector[2]); 84 | } 85 | 86 | bool isLineEnding (char c) 87 | { 88 | return (c == '\r' || c == '\n') ? true : false; 89 | } 90 | 91 | // This function reads the file saved by the previous example 92 | // The file contains all the data that was learned, then saved before. 93 | // Once the network is restored, it is able to classify patterns again without 94 | // having to be retrained. 95 | 96 | void restoreNetworkKnowledge ( void ) 97 | { 98 | const char *filename = "NeurData.dat"; 99 | SerialFlashFile file; 100 | int32_t fileNeuronCount = 0; 101 | 102 | Intel_PMT::neuronData neuronData; 103 | 104 | // Open the file and write test data 105 | file = SerialFlash.open(filename); 106 | 107 | CuriePME.beginRestoreMode(); 108 | if (file) { 109 | // iterate over the network and save the data. 110 | while(1) { 111 | Serial.print("Reading Neuron: "); 112 | 113 | uint16_t neuronFields[4]; 114 | file.read( (void*) neuronFields, 8); 115 | file.read( (void*) neuronData.vector, 128 ); 116 | 117 | neuronData.context = neuronFields[0] ; 118 | neuronData.influence = neuronFields[1] ; 119 | neuronData.minInfluence = neuronFields[2] ; 120 | neuronData.category = neuronFields[3]; 121 | 122 | if (neuronFields[0] == 0 || neuronFields[0] > 127) 123 | break; 124 | 125 | fileNeuronCount++; 126 | 127 | // this part just prints each neuron as it is restored, 128 | // so you can see what is happening. 129 | Serial.print(fileNeuronCount); 130 | Serial.print("\n"); 131 | 132 | Serial.print( neuronFields[0] ); 133 | Serial.print( "\t"); 134 | Serial.print( neuronFields[1] ); 135 | Serial.print( "\t"); 136 | Serial.print( neuronFields[2] ); 137 | Serial.print( "\t"); 138 | Serial.print( neuronFields[3] ); 139 | Serial.print( "\t"); 140 | 141 | Serial.print( neuronData.vector[0] ); 142 | Serial.print( "\t"); 143 | Serial.print( neuronData.vector[1] ); 144 | Serial.print( "\t"); 145 | Serial.print( neuronData.vector[2] ); 146 | 147 | Serial.print( "\n"); 148 | CuriePME.iterateNeuronsToRestore( neuronData ); 149 | } 150 | } 151 | 152 | CuriePME.endRestoreMode(); 153 | Serial.print("Knowledge Set Restored. \n"); 154 | } 155 | 156 | 157 | /* 158 | This library is free software; you can redistribute it and/or 159 | modify it under the terms of the GNU Lesser General Public 160 | License as published by the Free Software Foundation; either 161 | version 2.1 of the License, or (at your option) any later version. 162 | 163 | This library is distributed in the hope that it will be useful, 164 | but WITHOUT ANY WARRANTY; without even the implied warranty of 165 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 166 | Lesser General Public License for more details. 167 | 168 | You should have received a copy of the GNU Lesser General Public 169 | License along with this library; if not, write to the Free Software 170 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 171 | 172 | */ 173 | -------------------------------------------------------------------------------- /examples/d_k-nearest-neighbor/Makefile: -------------------------------------------------------------------------------- 1 | ifeq ("$(strip $(CODK_DIR))", "") 2 | $(error Please set the CODK_DIR variable.) 3 | endif 4 | 5 | ARDUINOSW_DIR ?= $(CODK_DIR)/arc 6 | 7 | current_dir = $(shell pwd) 8 | 9 | VERBOSE = true 10 | 11 | LIBDIRS = $(ARDUINOSW_DIR)/corelibs/libraries/Intel-Pattern-Matching-Technology/src \ 12 | $(ARDUINOSW_DIR)/corelibs/libraries/SPI/src \ 13 | $(ARDUINOSW_DIR)/corelibs/libraries/SerialFlash 14 | 15 | include $(ARDUINOSW_DIR)/Makefile.inc 16 | 17 | all: compile 18 | 19 | .DEFAULT_GOAL := all 20 | -------------------------------------------------------------------------------- /examples/d_k-nearest-neighbor/d_k-nearest-neighbor.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016 Intel Corporation. All rights reserved. 3 | See license notice at end of file. 4 | */ 5 | 6 | #include "CuriePME.h" 7 | 8 | #include 9 | #include 10 | 11 | void setup() { 12 | // put your setup code here, to run once: 13 | Serial.begin(9600); // initialize Serial communication 14 | while (!Serial); // wait for the serial port to open 15 | 16 | // initialize the engine 17 | CuriePME.begin(); 18 | 19 | //trainNeuronsWithData(); 20 | 21 | // Init. SPI Flash chip 22 | if (!SerialFlash.begin(ONBOARD_FLASH_SPI_PORT, ONBOARD_FLASH_CS_PIN)) { 23 | Serial.println("Unable to access SPI Flash chip"); 24 | } 25 | 26 | restoreNetworkKnowledge(); 27 | 28 | // set to knn mode for this example: 29 | 30 | CuriePME.setClassifierMode( CuriePME.KNN_Mode ); 31 | 32 | Serial.print("\n Now enter 3 numbers, between 0 and 255, separated by a comma. \n"); 33 | Serial.print("Like 11, 24, 29 \n"); 34 | } 35 | 36 | void loop() { 37 | 38 | uint8_t vector[3]; 39 | // now we'll classify some unknown data and let the 40 | // engine decide which pattern of 3 numbers most closely match 41 | // what it has been taught, or if they don't match anything it knows about. 42 | 43 | 44 | int x, y, z; 45 | while (Serial.available() > 0) { 46 | 47 | x = Serial.parseInt(); 48 | y = Serial.parseInt(); 49 | z = Serial.parseInt(); 50 | 51 | // Wait until we see newline or carriage return 52 | while (!isLineEnding(Serial.read())); 53 | 54 | // Then wait until we stop seeing then 55 | while (isLineEnding(Serial.peek())) { 56 | Serial.read(); 57 | } 58 | 59 | vector[0] = constrain(x, 0, 255); 60 | vector[1] = constrain(y, 0, 255); 61 | vector[2] = constrain(z, 0, 255); 62 | 63 | CuriePME.writeVector(vector, 3 ); 64 | 65 | Serial.print("You entered: "); 66 | printVector(vector); 67 | Serial.print("Now searching k-nearest neighbor\n"); 68 | 69 | while(1) { 70 | uint16_t distance = CuriePME.getIDX_DIST(); 71 | uint16_t category = CuriePME.getCAT(); 72 | 73 | if(category == 0 || category > 127) 74 | break; 75 | 76 | Serial.print( "Distance = "); 77 | Serial.print( distance ); 78 | Serial.print(" Category = "); 79 | Serial.print(category); 80 | Serial.print("\n"); 81 | } 82 | } 83 | } 84 | 85 | void printVector (uint8_t vector[]) 86 | { 87 | Serial.print(vector[0]); 88 | Serial.print(","); 89 | Serial.print(vector[1]); 90 | Serial.print(","); 91 | Serial.println(vector[2]); 92 | } 93 | 94 | bool isLineEnding (char c) 95 | { 96 | return (c == '\r' || c == '\n') ? true : false; 97 | } 98 | 99 | // The pattern matching engine will commit a new neuron for each new category. 100 | // Adding additional data to a category may or may not commit more neurons. 101 | // If the training data is relatively close together and well separated 102 | // from other committed neurons, it usually will not commit an additional 103 | // neuron. Widely separated data sets will usually commit more neurons per category. 104 | void trainNeuronsWithData( void ) 105 | { 106 | Serial.print("Neurons committed before learning = "); 107 | Serial.print( CuriePME.getCommittedCount()); 108 | Serial.print("\n"); 109 | 110 | commitThreeSamples(1, 11, 24, 29); // Category 1 111 | commitThreeSamples(2, 18, 75, 38); // Category 2 112 | commitThreeSamples(3, 2, 56, 35); // Category 3 113 | commitThreeSamples(4, 111, 224, 229); // Category 4 114 | commitThreeSamples(5, 128, 200, 255); // Category 5 115 | commitThreeSamples(6, 99, 180, 201); // Category 6 116 | 117 | Serial.print("Neurons committed after learning = "); 118 | Serial.print( CuriePME.getCommittedCount()); 119 | Serial.print("\n"); 120 | 121 | Serial.print("Now enter 3 numbers, between 0 and 255, separated by a comma. \n"); 122 | Serial.print("Like 11, 24, 29 \n"); 123 | } 124 | 125 | void commitThreeSamples (int category, uint8_t s1, uint8_t s2, uint8_t s3) 126 | { 127 | uint8_t vector[3]; 128 | 129 | vector[0] = s1; 130 | vector[1] = s2; 131 | vector[2] = s3; 132 | 133 | // give the data, the number of elements and the category it belongs to. 134 | CuriePME.learn(vector, 3, category); 135 | 136 | Serial.print("Category "); 137 | Serial.print( category ); 138 | Serial.print(" trained with: "); 139 | printVector(vector); 140 | } 141 | 142 | void restoreNetworkKnowledge ( void ) 143 | { 144 | const char *filename = "NeurData.dat"; 145 | SerialFlashFile file; 146 | int32_t fileNeuronCount = 0; 147 | 148 | CuriePME.beginRestoreMode(); 149 | Intel_PMT::neuronData neuronData; 150 | 151 | // Open the file and read test data 152 | file = SerialFlash.open(filename); 153 | 154 | if (file) { 155 | // iterate over the network and restore the data. 156 | while(1) { 157 | 158 | Serial.print("Restoring Neuron: "); 159 | uint16_t neuronFields[4]; 160 | 161 | file.read( (void*) neuronFields, 8); 162 | file.read( (void*) neuronData.vector, 128 ); 163 | 164 | neuronData.context = neuronFields[0] ; 165 | neuronData.influence = neuronFields[1] ; 166 | neuronData.minInfluence = neuronFields[2] ; 167 | neuronData.category = neuronFields[3]; 168 | 169 | if (neuronFields[0] == 0 || neuronFields[0] > 127) 170 | break; 171 | 172 | fileNeuronCount++; 173 | Serial.print(fileNeuronCount); 174 | Serial.print("\n"); 175 | /* Serial.print( neuronFields[0] ); 176 | Serial.print( "\t"); 177 | Serial.print( neuronFields[1] ); 178 | Serial.print( "\t"); 179 | Serial.print( neuronFields[2] ); 180 | Serial.print( "\t"); 181 | Serial.print( neuronFields[3] ); 182 | Serial.print( "\t"); 183 | */ 184 | Serial.print( neuronData.vector[0] ); 185 | Serial.print( "\t"); 186 | Serial.print( neuronData.vector[1] ); 187 | Serial.print( "\t"); 188 | Serial.print( neuronData.vector[2] ); 189 | 190 | Serial.print( "\n"); 191 | CuriePME.iterateNeuronsToRestore( neuronData ); 192 | } 193 | } 194 | 195 | CuriePME.endRestoreMode(); 196 | Serial.print("Knowledge Set Restored. \n"); 197 | } 198 | 199 | 200 | /* 201 | This library is free software; you can redistribute it and/or 202 | modify it under the terms of the GNU Lesser General Public 203 | License as published by the Free Software Foundation; either 204 | version 2.1 of the License, or (at your option) any later version. 205 | 206 | This library is distributed in the hope that it will be useful, 207 | but WITHOUT ANY WARRANTY; without even the implied warranty of 208 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 209 | Lesser General Public License for more details. 210 | 211 | You should have received a copy of the GNU Lesser General Public 212 | License along with this library; if not, write to the Free Software 213 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 214 | 215 | */ 216 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=CuriePME 2 | version=0.1 3 | author=Intel Corporation 4 | maintainer=Intel Corporation 5 | sentence=Intel Pattern Matching Technology Library. 6 | paragraph= 7 | category=Other 8 | url= 9 | architectures=arc32 10 | -------------------------------------------------------------------------------- /plot_accel.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | 3 | if [ $# -ne 2 ] 4 | then 5 | echo "Usage: $0 " 6 | exit 1 7 | fi 8 | 9 | gnuplot -e \ 10 | "set terminal png;\ 11 | set title '';\ 12 | set output '$2';\ 13 | set xlabel 'sample number'; 14 | set ylabel 'value of sample'; 15 | set yrange [-32767:32768]; 16 | plot '$1' using 1:2 with lines title 'x',\ 17 | '' using 1:3 with lines title 'y',\ 18 | '' using 1:4 with lines title 'z'" 19 | -------------------------------------------------------------------------------- /src/CuriePME.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016 Intel Corporation. All rights reserved. 3 | 4 | This library is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU Lesser General Public 6 | License as published by the Free Software Foundation; either 7 | version 2.1 of the License, or (at your option) any later version. 8 | 9 | This library is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this library; if not, write to the Free Software 16 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | 18 | */ 19 | 20 | #include "CuriePME.h" 21 | 22 | Intel_PMT CuriePME; 23 | 24 | // default constructor use a begin() method to initialize the instance 25 | Intel_PMT::Intel_PMT() 26 | { 27 | 28 | } 29 | 30 | // Default initializer 31 | void Intel_PMT::begin(void) 32 | { 33 | uint16_t savedNSR = regRead16( NSR ); 34 | forget(); 35 | 36 | regWrite16( NSR, (uint16_t) NSR_NET_MODE); 37 | 38 | for( int i = 0; i < maxNeurons; i++) 39 | { 40 | regWrite16( TESTCOMP, 0 ); 41 | } 42 | 43 | regWrite16( TESTCAT, 0); 44 | regWrite16( NSR, savedNSR ); 45 | 46 | } 47 | 48 | // custom initializer for the neural network 49 | void Intel_PMT::begin( uint16_t global_context, 50 | PATTERN_MATCHING_DISTANCE_MODE distance_mode, 51 | PATTERN_MATCHING_CLASSIFICATION_MODE classification_mode, 52 | uint16_t minAIF, uint16_t maxAIF ) 53 | { 54 | this->begin(); 55 | 56 | configure( global_context, 57 | distance_mode, 58 | classification_mode, 59 | minAIF, 60 | maxAIF ); 61 | 62 | 63 | } 64 | 65 | void Intel_PMT::configure( uint16_t global_context, 66 | PATTERN_MATCHING_DISTANCE_MODE distance_mode, 67 | PATTERN_MATCHING_CLASSIFICATION_MODE classification_mode, 68 | uint16_t minAIF, uint16_t maxAIF ) 69 | { 70 | 71 | regWrite16( GCR , (global_context | (distance_mode << 7))); 72 | regWrite16( NSR , regRead16( NSR) | (classification_mode << 5) ); 73 | regWrite16( MINIF, minAIF); 74 | regWrite16( MAXIF, maxAIF); 75 | } 76 | 77 | // clear all commits in the network and make it ready to learn 78 | void Intel_PMT::forget( void ) 79 | { 80 | regWrite16( FORGET_NCOUNT, 0 ); 81 | } 82 | 83 | // mark --learn and classify-- 84 | 85 | uint16_t Intel_PMT::learn(uint8_t *pattern_vector, int32_t vector_length, uint16_t category) 86 | { 87 | if( vector_length > maxVectorSize ) 88 | vector_length = maxVectorSize; 89 | 90 | for( int i = 0; i < vector_length -1; i++ ) 91 | { 92 | regWrite16( COMP , pattern_vector[i] ); 93 | } 94 | 95 | regWrite16( LCOMP, pattern_vector[ vector_length - 1 ] ); 96 | 97 | /* Mask off the 15th bit-- valid categories range from 1-32766, 98 | * and bit 15 is used to indicate if a firing neuron has degenerated */ 99 | regWrite16(CAT, (regRead16(CAT) & ~CAT_CATEGORY) | (category & CAT_CATEGORY)); 100 | return regRead16( FORGET_NCOUNT ); 101 | 102 | } 103 | 104 | uint16_t Intel_PMT::classify(uint8_t *pattern_vector, int32_t vector_length) 105 | { 106 | 107 | 108 | uint8_t *current_vector = pattern_vector; 109 | uint8_t index = 0; 110 | 111 | if (vector_length > maxVectorSize) return -1; 112 | 113 | for (index = 0; index < (vector_length - 1); index++) 114 | { 115 | regWrite16(COMP, current_vector[index]); 116 | } 117 | regWrite16( LCOMP , current_vector[vector_length - 1] ); 118 | 119 | // Sort matching categories by (We don't care about the returned value here; 120 | // reading the distance register sorts matched categories by distance) 121 | regRead16(IDX_DIST); 122 | 123 | // Return the category with the lowest distance to point 124 | return (regRead16(CAT) & CAT_CATEGORY); 125 | } 126 | 127 | // write vector is used for kNN recognition and does not alter 128 | // the CAT register, which moves the chain along. 129 | uint16_t Intel_PMT::writeVector(uint8_t *pattern_vector, int32_t vector_length) 130 | { 131 | 132 | 133 | uint8_t *current_vector = pattern_vector; 134 | uint8_t index = 0; 135 | 136 | if (vector_length > maxVectorSize) return -1; 137 | 138 | for (index = 0; index < (vector_length - 1); index++) 139 | { 140 | regWrite16(COMP, current_vector[index]); 141 | } 142 | regWrite16( LCOMP , current_vector[vector_length - 1] ); 143 | 144 | return 0; 145 | 146 | } 147 | 148 | // retrieve the data of a specific neuron element by ID, between 1 and 128. 149 | uint16_t Intel_PMT::readNeuron( int32_t neuronID, neuronData& data_array) 150 | { 151 | uint16_t dummy = 0; 152 | 153 | // range check the ID - technically, this should be an error. 154 | 155 | if( neuronID < firstNeuronID ) 156 | neuronID = firstNeuronID; 157 | if(neuronID > lastNeuronID ) 158 | neuronID = lastNeuronID; 159 | 160 | // use the beginSaveMode method 161 | beginSaveMode(); 162 | 163 | //iterate over n elements in order to reach the one we want. 164 | 165 | for( int i = 0; i < (neuronID -1); i++) 166 | { 167 | 168 | dummy = regRead16( CAT ); 169 | } 170 | 171 | // retrieve the data using the iterateToSave method 172 | 173 | iterateNeuronsToSave( data_array); 174 | 175 | //restore the network to how we found it. 176 | endSaveMode(); 177 | 178 | return 0; 179 | } 180 | 181 | // mark --save and restore network-- 182 | 183 | // save and restore knowledge 184 | void Intel_PMT::beginSaveMode(void) 185 | { 186 | nsr_save = regRead16(NSR); 187 | 188 | // set save/restore mode in the NSR 189 | regWrite16( NSR, regRead16(NSR) | NSR_NET_MODE); 190 | // reset the chain to 0th neuron 191 | regWrite16( RSTCHAIN, 0); 192 | } 193 | 194 | // pass the function a structure to save data into 195 | uint16_t Intel_PMT::iterateNeuronsToSave(neuronData& array ) 196 | { 197 | array.context = regRead16( NCR ); 198 | for( int i=0; i < saveRestoreSize; i++) 199 | { 200 | array.vector[i] = regRead16(COMP); 201 | } 202 | 203 | array.influence = regRead16( AIF ); 204 | array.minInfluence = regRead16( MINIF ); 205 | array.category = regRead16( CAT ); 206 | 207 | return array.category; 208 | } 209 | 210 | void Intel_PMT::endSaveMode(void) 211 | { 212 | //restore the network to how we found it. 213 | regWrite16(NSR, (nsr_save & ~NSR_NET_MODE)); 214 | } 215 | 216 | 217 | void Intel_PMT::beginRestoreMode(void) 218 | { 219 | nsr_save = regRead16(NSR); 220 | 221 | forget(); 222 | // set save/restore mode in the NSR 223 | regWrite16( NSR, regRead16(NSR) | NSR_NET_MODE); 224 | // reset the chain to 0th neuron 225 | regWrite16( RSTCHAIN, 0); 226 | } 227 | 228 | uint16_t Intel_PMT::iterateNeuronsToRestore(neuronData& array ) 229 | { 230 | regWrite16( NCR, array.context ); 231 | for( int i=0; i < saveRestoreSize; i++) 232 | { 233 | regWrite16(COMP, array.vector[i]); 234 | } 235 | 236 | regWrite16( AIF, array.influence ); 237 | regWrite16( MINIF, array.minInfluence ); 238 | regWrite16( CAT, array.category ); 239 | 240 | return 0; 241 | } 242 | 243 | void Intel_PMT::endRestoreMode(void) 244 | { 245 | //restore the network to how we found it. 246 | regWrite16(NSR, (nsr_save & ~NSR_NET_MODE)); 247 | } 248 | 249 | // mark -- getter and setters-- 250 | 251 | Intel_PMT::PATTERN_MATCHING_DISTANCE_MODE // L1 or LSup 252 | Intel_PMT::getDistanceMode(void) 253 | { 254 | return (GCR_DIST & regRead16(GCR)) ? LSUP_Distance : L1_Distance; 255 | } 256 | 257 | void 258 | Intel_PMT::setDistanceMode( Intel_PMT::PATTERN_MATCHING_DISTANCE_MODE mode) // L1 or LSup 259 | { 260 | uint16_t rd = regRead16(GCR); 261 | 262 | // do a read modify write on the GCR register 263 | regWrite16(GCR, (mode == LSUP_Distance) ? rd | GCR_DIST : rd & ~GCR_DIST); 264 | } 265 | 266 | uint16_t Intel_PMT::getGlobalContext(void) 267 | { 268 | return (GCR_GLOBAL & regRead16(GCR)); 269 | } 270 | 271 | // A valid context value is in the range of 1-127. A context 272 | // value of 0 enables all neurons, without regard to their context 273 | void Intel_PMT::setGlobalContext(uint16_t context) 274 | { 275 | uint16_t gcrMask = ~GCR_GLOBAL & regRead16(GCR); 276 | gcrMask |= (context & GCR_GLOBAL); 277 | regWrite16(GCR, gcrMask); 278 | } 279 | 280 | uint16_t Intel_PMT::getNeuronContext(void) 281 | { 282 | return (NCR_CONTEXT & regRead16(NCR)); 283 | } 284 | 285 | // valid range is 1-127 286 | void Intel_PMT::setNeuronContext(uint16_t context) 287 | { 288 | uint16_t ncrMask = ~NCR_CONTEXT & regRead16(NCR); 289 | ncrMask |= (context & NCR_CONTEXT); 290 | regWrite16(NCR, ncrMask); 291 | } 292 | 293 | // NOTE: getCommittedCount() will give inaccurate value if the network is in Save/Restore mode. 294 | // It should not be called between the beginSaveMode() and endSaveMode() or between 295 | // beginRestoreMode() and endRestoreMode() 296 | uint16_t 297 | Intel_PMT::getCommittedCount( void ) 298 | { 299 | return (getFORGET_NCOUNT() & 0xff ); 300 | } 301 | 302 | Intel_PMT::PATTERN_MATCHING_CLASSIFICATION_MODE 303 | Intel_PMT::getClassifierMode( void ) // RBF or KNN 304 | { 305 | if( regRead16( NSR ) & NSR_CLASS_MODE ) 306 | return KNN_Mode; 307 | 308 | return RBF_Mode; 309 | } 310 | 311 | void 312 | Intel_PMT::setClassifierMode( Intel_PMT::PATTERN_MATCHING_CLASSIFICATION_MODE mode ) 313 | { 314 | uint16_t mask = regRead16(NSR ); 315 | mask &= ~NSR_CLASS_MODE; 316 | 317 | if( mode == KNN_Mode ) 318 | mask |= NSR_CLASS_MODE; 319 | 320 | regWrite16( NSR, mask); 321 | } 322 | 323 | // mark --register access-- 324 | //getter and setters 325 | uint16_t Intel_PMT::getNCR( void ) 326 | { 327 | return regRead16(NCR); 328 | } 329 | 330 | uint16_t Intel_PMT::getCOMP( void ) 331 | { 332 | return regRead16(COMP); 333 | } 334 | 335 | uint16_t Intel_PMT::getLCOMP( void ) 336 | { 337 | return regRead16(LCOMP); 338 | } 339 | 340 | uint16_t Intel_PMT::getIDX_DIST( void ) 341 | { 342 | return regRead16(IDX_DIST); 343 | } 344 | 345 | uint16_t Intel_PMT::getCAT( void ) 346 | { 347 | return regRead16(CAT); 348 | } 349 | 350 | uint16_t Intel_PMT::getAIF( void ) 351 | { 352 | return regRead16(AIF); 353 | } 354 | 355 | uint16_t Intel_PMT::getMINIF( void ) 356 | { 357 | return regRead16(MINIF); 358 | } 359 | 360 | uint16_t Intel_PMT::getMAXIF( void ) 361 | { 362 | return regRead16(MAXIF); 363 | } 364 | 365 | uint16_t Intel_PMT::getNID( void ) 366 | { 367 | return regRead16(NID); 368 | } 369 | 370 | uint16_t Intel_PMT::getGCR( void ) 371 | { 372 | return regRead16(GCR); 373 | } 374 | 375 | uint16_t Intel_PMT::getRSTCHAIN( void ) 376 | { 377 | return regRead16(RSTCHAIN); 378 | } 379 | 380 | uint16_t Intel_PMT::getNSR( void ) 381 | { 382 | return regRead16(NSR); 383 | } 384 | 385 | uint16_t Intel_PMT::getFORGET_NCOUNT( void ) 386 | { 387 | return regRead16(FORGET_NCOUNT); 388 | } 389 | -------------------------------------------------------------------------------- /src/CuriePME.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2016 Intel Corporation. All rights reserved. 3 | 4 | This library is free software; you can redistribute it and/or 5 | modify it under the terms of the GNU Lesser General Public 6 | License as published by the Free Software Foundation; either 7 | version 2.1 of the License, or (at your option) any later version. 8 | 9 | This library is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | Lesser General Public License for more details. 13 | 14 | You should have received a copy of the GNU Lesser General Public 15 | License along with this library; if not, write to the Free Software 16 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | 18 | */ 19 | 20 | #ifndef _CURIE_PME_H_ 21 | #define _CURIE_PME_H_ 22 | 23 | extern "C" 24 | { 25 | #include 26 | } 27 | 28 | class Intel_PMT 29 | { 30 | 31 | 32 | public: 33 | 34 | static const uint32_t noMatch = 0x7fff; 35 | static const uint16_t minContext = 0; 36 | static const uint16_t maxContext = 127; 37 | static const int32_t maxVectorSize = 128; 38 | static const int32_t firstNeuronID = 1; 39 | static const int32_t lastNeuronID = 128; 40 | static const int32_t maxNeurons = 128; 41 | static const int32_t saveRestoreSize = 128; 42 | 43 | enum PATTERN_MATCHING_CLASSIFICATION_MODE 44 | { 45 | RBF_Mode = 0, 46 | KNN_Mode = 1 47 | } ; 48 | 49 | enum PATTERN_MATCHING_DISTANCE_MODE 50 | { 51 | L1_Distance = 0, 52 | LSUP_Distance = 1 53 | } ; 54 | 55 | typedef struct neuronData 56 | { 57 | uint16_t context; 58 | uint16_t influence; 59 | uint16_t minInfluence; 60 | uint16_t category; 61 | 62 | uint8_t vector[saveRestoreSize]; 63 | 64 | } neuronData; 65 | 66 | // constructor - the semantic is to construct, then initialise with a begin() method 67 | Intel_PMT(); 68 | 69 | // Default initializer 70 | void begin(void); 71 | 72 | // custom initializer for the neural network 73 | void begin( uint16_t global_context, 74 | PATTERN_MATCHING_DISTANCE_MODE distance_mode, 75 | PATTERN_MATCHING_CLASSIFICATION_MODE classification_mode, 76 | uint16_t minAIF, uint16_t maxAIF ); 77 | 78 | void forget( void ); 79 | 80 | void configure( uint16_t global_context, 81 | PATTERN_MATCHING_DISTANCE_MODE distance_mode, 82 | PATTERN_MATCHING_CLASSIFICATION_MODE classification_mode, 83 | uint16_t minAIF, uint16_t maxAIF ); 84 | 85 | uint16_t learn(uint8_t *pattern_vector, int32_t vector_length, uint16_t category); 86 | uint16_t classify(uint8_t *pattern_vector, int32_t vector_length); 87 | 88 | uint16_t readNeuron( int32_t neuronID, neuronData& data_array); 89 | 90 | // save and restore knowledge 91 | void beginSaveMode(void); // saves the contents of the NSR register 92 | uint16_t iterateNeuronsToSave( neuronData& data_array ); 93 | void endSaveMode(void); // restores the NSR value saved by beginSaveMode 94 | 95 | void beginRestoreMode(void); 96 | uint16_t iterateNeuronsToRestore( neuronData& data_array ); 97 | void endRestoreMode(void); 98 | 99 | //getter and setters 100 | PATTERN_MATCHING_DISTANCE_MODE getDistanceMode(void); 101 | void setDistanceMode( PATTERN_MATCHING_DISTANCE_MODE mode); 102 | uint16_t getGlobalContext( void ); 103 | void setGlobalContext( uint16_t context ); // valid range is 1-127 104 | uint16_t getNeuronContext( void ); 105 | void setNeuronContext( uint16_t context ); // valid range is 1-127 106 | 107 | // NOTE: getCommittedCount() will give inaccurate value if the network is in Save/Restore mode. 108 | // It should not be called between the beginSaveMode() and endSaveMode() or between 109 | // beginRestoreMode() and endRestoreMode() 110 | uint16_t getCommittedCount( void ); 111 | 112 | PATTERN_MATCHING_CLASSIFICATION_MODE getClassifierMode( void ); // RBF or KNN 113 | void setClassifierMode( PATTERN_MATCHING_CLASSIFICATION_MODE mode ); 114 | 115 | // write vector is used for kNN recognition and does not alter 116 | // the CAT register, which moves the chain along. 117 | uint16_t writeVector(uint8_t *pattern_vector, int32_t vector_length); 118 | 119 | // raw register access - not recommended. 120 | uint16_t getNCR( void ); 121 | uint16_t getCOMP( void ); 122 | uint16_t getLCOMP( void ); 123 | uint16_t getIDX_DIST( void ); 124 | uint16_t getCAT( void ); 125 | uint16_t getAIF( void ); 126 | uint16_t getMINIF( void ); 127 | uint16_t getMAXIF( void ); 128 | uint16_t getNID( void ); 129 | uint16_t getGCR( void ); 130 | uint16_t getRSTCHAIN( void ); 131 | uint16_t getNSR( void ); 132 | uint16_t getFORGET_NCOUNT( void ); 133 | 134 | protected: 135 | 136 | // base address of the pattern matching accelerator in Intel(r) Curie(tm) and QuarkSE(tm) 137 | static const uint32_t baseAddress = 0xB0600000L; 138 | 139 | enum Registers 140 | { 141 | NCR = 0x00, // Neuron Context Register 142 | COMP = 0x04, // Component Register 143 | LCOMP = 0x08, // Last Component 144 | IDX_DIST = 0x0C, // Write Component Index / Read Distance 145 | CAT = 0x10, // Category Register 146 | AIF = 0x14, // Active Influence Field 147 | MINIF = 0x18, // Minimum Influence Field 148 | MAXIF = 0x1C, // Maximum Influence Field 149 | TESTCOMP = 0x20, // Write Test Component 150 | TESTCAT = 0x24, // Write Test Category 151 | NID = 0x28, // Network ID 152 | GCR = 0x2C, // Global Context Register 153 | RSTCHAIN = 0x30, // Reset Chain 154 | NSR = 0x34, // Network Status Register 155 | FORGET_NCOUNT = 0x3C // Forget Command / Neuron Count 156 | }; 157 | 158 | enum Masks 159 | { 160 | NCR_ID = 0xFF00, // Upper 8-bit of Neuron ID 161 | NCR_NORM = 0x0040, // 1 = LSUP, 0 = L1 162 | NCR_CONTEXT = 0x007F, // Neuron Context 163 | CAT_DEGEN = 0x8000, // Indicates neuron is degenerate 164 | CAT_CATEGORY = 0x7FFF, // the category associated with a neuron 165 | GCR_DIST = 0x0080, // distance type, 1 = Lsup, 0 = L1 166 | GCR_GLOBAL = 0x007F, // the context of the neuron, used to segment the network 167 | NSR_CLASS_MODE = 0x0020, // Classifier mode 1 = KNN, 0 = RBF (KNN not for learning mode) 168 | NSR_NET_MODE = 0x0010, // 1 = SR (save/restore) 0 = LR (learn/recognize) 169 | NSR_ID_FLAG = 0x0008, // Indicates positive identification 170 | NSR_UNCERTAIN_FLAG = 0x0004,// Indicates uncertain identification 171 | }; 172 | 173 | // all pattern matching accelerator registers are 16-bits wide, memory-addressed 174 | // define efficient inline register access 175 | inline volatile uint16_t *regAddress (Registers reg) 176 | { 177 | return reinterpret_cast(baseAddress + reg); 178 | } 179 | 180 | inline uint16_t regRead16 (Registers reg) 181 | { 182 | return *regAddress(reg); 183 | } 184 | 185 | inline void regWrite16 (Registers reg, uint16_t value) 186 | { 187 | *regAddress(reg) = value; 188 | } 189 | 190 | inline void regWrite16 (Registers reg, uint8_t value) 191 | { 192 | *regAddress(reg) = value; 193 | } 194 | 195 | inline void regWrite16 (Registers reg, int value) 196 | { 197 | *regAddress(reg) = value; 198 | } 199 | 200 | private: 201 | uint16_t nsr_save; 202 | 203 | }; 204 | 205 | extern Intel_PMT CuriePME; 206 | 207 | #endif 208 | --------------------------------------------------------------------------------