Fun application for FRDM-KL25Z. Uses the on-board accelerometer's built-in tap detection to detect a sequence of knocks on the table the board is sitting on. If the knock sequence matches the secret knock, it makes it happy (blinks green). Otherwise it gets angry (blinks red).

Dependencies:   MMA8451Q_ext mbed

Fun application for FRDM-KL25Z. Uses the on-board accelerometer's built-in tap detection to detect a sequence of knocks on the table the board is sitting on. If the knock sequence matches the secret knock, it makes it happy (blinks green). Otherwise it gets angry (blinks red).

Right now it doesn't support recording new secret knocks. It only knows, "shave and a hair cut, two bits". There is commented-out code in there to do the recording, but it hasn't been enabled or tested yet.

Files at this revision

API Documentation at this revision

Comitter:
maclobdell
Date:
Fri Mar 15 19:06:22 2013 +0000
Commit message:
public release

Changed in this revision

MMA8451Q_ext.lib Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed.bld Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MMA8451Q_ext.lib	Fri Mar 15 19:06:22 2013 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/maclobdell/code/MMA8451Q_ext/#018aea85c0db
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Fri Mar 15 19:06:22 2013 +0000
@@ -0,0 +1,348 @@
+#include "mbed.h"
+#include "MMA8451Q.h"
+
+#define MMA8451_I2C_ADDRESS (0x1d<<1)
+
+/* Secret Knock Demo for FRDM-KL25Z by Mac Lobdell */
+/* Several bits and pieces taken from code by Steve Hoefer (http://grathio.com) under a creative commons share-alike license */
+/* Revison 1.0  
+/* Programming not supported yet - only knows "shave and a hair cut, two bits" (or another pre-set sequence if set in secretCode variable initialization) */
+ 
+void INT2ISR(void);
+void INT1ISR(void);
+void d2ISR(void);
+int validateKnock(void);
+void triggerSuccessfulAction(void);
+void triggerFailedAction(void);
+int map(int x, int in_min, int in_max, int out_min, int out_max);
+
+Serial pc(USBTX,USBRX);
+MMA8451Q acc(PTE25, PTE24, MMA8451_I2C_ADDRESS);
+DigitalOut rled(LED_RED);
+DigitalOut gled(LED_GREEN);
+DigitalOut bled(LED_BLUE);
+     
+DigitalOut d2(PTD4);
+Timer timer;
+
+const int maximumKnocks = 8;       // Maximum number of knocks to listen for.
+int secretCode[maximumKnocks] = {
+  50, 25, 25, 50, 100, 50, 0, 0 }; //, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};  // Initial setup: "Shave and a Hair Cut, two bits." 100=full note, 50=half note, 25=quarter note, etc.
+int secretKnockMax = 6; 
+const int rejectValue = 30;        // If an individual knock is off by this percentage of a knock we ignore. (30 is pretty lose. 10 is strict)
+const int averageRejectValue = 20; // If the average timing of the knocks is off by this percent we ignore. (20 is pretty lose, 10 is strict.)
+int knockTime[maximumKnocks];    // When someone knocks this array fills with delays between knocks. (A correct knock looks a lot like the line above).
+int nKnockTime[maximumKnocks]; 
+int knocking = 0;
+int counter;
+int knockCount = 0;  //starts at 0 for first knock
+int i = 0;
+int programButtonPressed = false;
+int startTime;
+int dummyKnock = 1;
+int unlocked = 0;
+
+int main(void) {
+
+    DigitalIn int1(PTA14);  //data ready interrupt
+    int1.mode(PullUp);
+    DigitalIn int2(PTA15);  //tap or Portrat/Landscape interrupt
+    int2.mode(PullUp);
+
+     /* initialize all variables */   
+   knocking = 0;
+   counter = 0;
+   knockCount = 0;
+   startTime = 0;
+   dummyKnock = 1;
+   unlocked = 0;
+    for(i = 0; i<maximumKnocks; i++)
+    {
+         knockTime[i] = 0;  
+         nKnockTime[i] = 0;
+    }
+    rled = 1; //off
+    gled = 0; //on  
+    bled = 1; //off
+         
+    //To Do - need to check if programming
+    //capacitive touch slider swipe?     
+  if (programButtonPressed==true){  
+    for (i=0;i<3;i++){
+      wait_ms(100);
+      gled = 0;
+      bled = 0;
+      wait_ms(100);
+      gled = 1;
+      bled = 1;      
+    }
+  }
+
+   //enable interrupts
+    InterruptIn int1i(PTA14);
+    int1i.fall(&INT1ISR);
+    InterruptIn int2i(PTA15);
+    int2i.fall(&INT2ISR);
+
+    while(1)
+    {
+         //just hang out and wait for interrupts
+       if(knocking == 1)
+       {
+        counter++;    //increment here, it will start over to 0 if knock happens in ISR
+        bled = 0;  //blue on
+        gled = 1;  //green off
+        wait_ms(10);  //wait 10 ms
+        if ((counter > 1000) || ((knockCount) >= secretKnockMax ))  //timeout after 100*10ms = 1 sec, or if max knocks ocurred
+         {
+           timer.stop();
+ 
+            //report what happened
+            if(counter > 100) 
+            {
+              pc.printf("timout\n\r");
+            }else
+            {
+              pc.printf("max knocks\n\r");
+            }
+            
+        /* check if secret code */
+            if (validateKnock() == true){      
+                triggerSuccessfulAction(); 
+                unlocked = 1;
+            } 
+            else {
+                triggerFailedAction(); 
+                unlocked = 0;
+            }
+
+        /* re-initialize all variables */
+           counter = 0;
+           knocking = 0;
+           knockCount = 0;
+           unlocked = 0;          
+           for(i = 0; i<maximumKnocks; i++)
+           {
+             knockTime[i] = 0;  
+           }
+          rled = 1; //off
+          gled = 0; //on  
+          bled = 1; //off
+
+
+         }
+   
+       }             
+    }    
+    
+    
+}
+void d2ISR(void)
+{
+   pc.printf("d2 ISR\n\r");
+   
+}
+void INT1ISR(void)
+{
+  /*Acceleration data Ready ISR */
+  
+     //   rled = 1.0 - abs(acc.getAccX());
+     //   gled = 1.0 - abs(acc.getAccY());
+     //   bled = 1.0 - abs(acc.getAccZ());           
+}
+
+void INT2ISR(void)
+{
+  /* tap occurred - make sure only single taps in one direction are enabled */
+  
+  uint8_t dummy;
+ 
+  dummy = acc.tapSource();  //seems like you need to read the source to clear the interrupt
+  
+//  pc.printf("tap isr\n\r");
+
+ //if(dummyKnock == 1)
+ //{
+ //  //always get a bad knock on reset
+ //   dummyKnock = 0;
+ //   pc.printf("dummy knock \n");
+ //}else
+ {
+
+    
+  //  pc.printf("Tap \n");            
+    counter = 0;  //reset the timeout counter
+
+  if(knocking ==0)
+  {
+    //this is the first knock
+    pc.printf("first knock\n\r");   
+    knockCount = 0;
+    knocking = 1;
+    pc.printf("start timer\n\r");   
+    timer.start();
+    timer.reset();
+    startTime = timer.read_ms(); 
+       
+  }else if(knocking == 1)
+  {
+    //this is not the first knock
+    pc.printf("not first knock\n\r");
+    int now;
+    now = timer.read_ms();
+    pc.printf("startTime = %d\n\r",startTime);
+    pc.printf("now = %d\n\r",now);
+    knockTime[knockCount] = (now - startTime);  
+    pc.printf("knockTime[knockCount] : %d\n\r",knockTime[knockCount]);
+    
+    knockCount++;
+    pc.printf("knockCount : %d\n\r",knockCount);
+
+    startTime = now;  //start time for next knock period
+    
+
+    
+  }    
+  
+ }
+
+}
+
+
+// We got a good knock, so do something!
+void triggerSuccessfulAction(){
+  pc.printf("Success!\n\r");
+
+  rled = 1;  //off  
+  gled = 0;  //on
+  bled = 1;  //off 
+
+  for (int i=0;i<16;i++){                    
+    gled = 0;
+    wait_ms(100);
+    gled = 1;
+    wait_ms(100);
+  }
+
+}
+
+// We didn't like the knock.  Indicate displeasure.
+void triggerFailedAction(){
+  pc.printf("Secret knock failed\n");
+  rled = 0;  //on  
+  gled = 1;  //off
+  bled = 1;  //off 
+   
+  for (int i=0;i<16;i++){                    
+    rled = 0;
+    wait_ms(100);
+    rled = 1;
+    wait_ms(100);
+  }
+}
+
+// Checks if our knock matches the secret.
+// Returns true if it's a good knock, false if it's not.
+int validateKnock(){
+  int i=0;
+  // Simplest check first: Did we get the right number of knocks?
+  int currentKnockCount = 0;
+  int secretKnockCount = 0;
+  int maxKnockInterval = 0;             // We use this later to normalize the times.
+  
+  int codeFound=true;
+  int totaltimeDifferences=0;
+  int timeDiff=0;
+  
+  pc.printf("validating knock sequence \n\r");
+
+
+  for (i=0;i<maximumKnocks;i++){
+    if (knockTime[i] > 0){
+      currentKnockCount++;
+    }
+    if (secretCode[i] > 0){                     
+      secretKnockCount++;
+    }
+
+    if (knockTime[i] > maxKnockInterval){   // Collect normalization data while we're looping.
+      maxKnockInterval = knockTime[i];
+    }
+  }
+   pc.printf("max knock interval: \n\r",maxKnockInterval);
+  
+  // If we're recording a new knock, save the relevant info and get out of here.
+  /*if (programButtonPressed==true){
+    for (i=0;i<maximumKnocks;i++){ // Normalize the knock timing
+      secretCode[i]= map(knockTime[i],0, maxKnockInterval, 0, 100);
+    }
+    // And flash the lights in the recorded pattern to let us know it's been programmed.
+    bled = 0;
+    rled = 0;
+    wait_ms(750);
+
+    //Start playing back the knocks
+    bled = 1;
+    rled = 1;
+    wait_ms(40);
+    for (i = 0; i < maximumKnocks ; i++){
+      bled = 0;
+      rled = 0;
+
+      if (programButtonPressed==true){  // Only turn it on if there's a delay
+        if (secretCode[i] > 0){                                   
+          wait_ms(map(secretCode[i],0, 100, 0, maxKnockInterval)); // Expand the time back out to what it was.  Roughly. 
+          bled = 1;
+          rled = 1;  
+        }
+      }
+      wait_ms(40);
+      bled = 0;
+      rled = 0;
+    }
+    return false;   // We don't do anything when we are recording a new knock.
+  }
+*/
+    pc.printf("currentKnockCount: %d secretKnockCount %d\n\r",currentKnockCount,secretKnockCount);
+        
+  if (currentKnockCount != secretKnockCount){
+    pc.printf("wrong number of knocks\n\r");
+    return false;   // Return false if the number of knocks are wrong.
+  }
+
+  /*  Now we compare the relative intervals of our knocks, not the absolute time between them.
+   (ie: if you do the same pattern slow or fast it should still work.)
+   This makes it less picky, which does make it less secure but also makes it
+   less of a pain to use if you're tempo is a little slow or fast. 
+   */
+  for (i=0;i<maximumKnocks;i++){ 
+    nKnockTime[i] = 0;  //reinitialize
+  }  
+  for (i=0;i<maximumKnocks;i++){ // Normalize the times
+    nKnockTime[i]= map(knockTime[i],0, maxKnockInterval, 0, 100);      
+    pc.printf("knock time: %d, normalized knock time: %d, secret code time %d \n\r",knockTime[i], nKnockTime[i], secretCode[i]);
+    timeDiff = abs(nKnockTime[i]-secretCode[i]);
+    if (timeDiff > rejectValue){ // Individual value too far out of whack
+      codeFound=false;
+    }
+    totaltimeDifferences += timeDiff;
+  }
+  // It can also fail if the whole thing is too inaccurate.
+  if (totaltimeDifferences/secretKnockCount>averageRejectValue){
+    codeFound = false;
+  }
+
+  if (codeFound==false){
+    return false;
+  } 
+  else {
+    return true;
+  }
+
+}
+
+
+int map(int x, int in_min, int in_max, int out_min, int out_max)
+{
+  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed.bld	Fri Mar 15 19:06:22 2013 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/mbed_official/code/mbed/builds/5e5da4a5990b
\ No newline at end of file