//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2017 Peter Walsh, Milford, NH 03055 // All Rights Reserved under the MIT license as outlined below. // // FILE // Amulet.ino - Arduino sketch to simulate the android amulets from "I, Mudd" // // SYNOPSIS // // D2 => LEDs , through a 100 ohm resistor // D4 => Speaker, through a 100 ohm resistor // // D11 => Switch // D12 => Switch // // Quick press/release => Begin random light and beep sequence // Quick press/release => Stop random light and beep sequence // Press and hold => Continuous light and beep // // Some serial commands are added for debugging. If you're having trouble with the system, check the // serial monitor while pressing the button for more information. // // DESCRIPTION // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // MIT LICENSE // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies // of the Software, and to permit persons to whom the Software is furnished to do // so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const int LEDPin = 2; // D2 is LED output const int TONEPin = 4; // D4 is speaker output const int BTNPin = 12; // D12 is button input const int GNDPin = 11; // D11 is ground for button return const int TONEFreq = 1063; // Tone to play on speaker const int BTN_DEB = 5; // Debounce time, in ms const int BTN_HOLD = 1000; // Hold time, in ms (== 1 sec) unsigned long CurrentMillis; ////////////////////////////////////////////////////////////////////////////////////////// // // Button state machine // typedef enum { BTN_OFF = 17, // Button is off, waiting for press BTN_ONDEB, // Button is on , counting down for debounce BTN_ON, // Button is on , waiting for off or long press BTN_OFFDEB, // Button is off, counting down for debounce BTN_ONLONG, // Button is on , long press detected BTN_LONGDEB, // Button is on , debounce from long press } BUTTON_STATE; BUTTON_STATE ButtonState; // Current button state (see above) int ButtonTimer; // Ticks remaining in debounce bool ButtonChanged; // TRUE when state has changed #define ON LOW // For the sake of sanity #define OFF HIGH ////////////////////////////////////////////////////////////////////////////////////////// // // Amulet state machine // typedef enum { AMU_OFF = 23, // Amulet is off AMU_BEEPON, // Amulet is beeping and on AMU_BEEPOFF, // Amulet is beeping and off (between beeps) AMU_ON, // Amulet is on continuous } AMULET_STATE; AMULET_STATE AmuletState; // Current amulet state (see above) int AmuletTimer; // Ticks remaining in debounce ////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////// void setup() { Serial.begin(57600); pinMode(LEDPin,OUTPUT); // Set LEDPin to be an output pinMode(BTNPin,INPUT_PULLUP); // Set BTNPin to be an input, with pullup resistor pinMode(GNDPin,OUTPUT); // Set GNDPin to be an output ground return digitalWrite(GNDPin,LOW); // Set to GND CurrentMillis = millis(); ButtonState = BTN_OFF; // Button begins OFF ButtonChanged = false; AmuletState = AMU_OFF; // Amulet begins OFF Serial.println("\n\nReset Amulet"); // For debugging } void loop() { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Wait for the next millisecond tick // while(millis() == CurrentMillis); // Wait for next timer tick CurrentMillis = millis(); //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Update the button debounce logic // ButtonUpdate(); // Update the button if( ButtonChanged ) { // Some debug info if( ButtonState == BTN_OFF ) Serial.println("BTN_OFF"); if( ButtonState == BTN_ONDEB ) Serial.println("BTN_ONDEB"); if( ButtonState == BTN_ON ) Serial.println("BTN_ON"); if( ButtonState == BTN_OFFDEB ) Serial.println("BTN_OFFDEB"); if( ButtonState == BTN_ONLONG ) Serial.println("BTN_ONLONG"); if( ButtonState == BTN_LONGDEB ) Serial.println("BTN_LONGDEB"); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Update the amulet logic // AMULET_STATE PrevState = AmuletState;// For debugging AmuletUpdate(); // Update the amulet logic if( PrevState != AmuletState ) { // Some more debug info if( AmuletState == AMU_OFF ) Serial.println("AMU_OFF"); if( AmuletState == AMU_BEEPON ) Serial.println("AMU_BEEPON"); if( AmuletState == AMU_BEEPOFF ) Serial.println("AMU_BEEPOFF"); if( AmuletState == AMU_ON ) Serial.println("AMU_ON"); } ButtonChanged = false; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // AmuletUpdate - Change the amulet behaviour, based on button events. // // Inputs: None. // // Outputs: None. // void AmuletUpdate() { switch( AmuletState ) { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // AMU_OFF: Amulet is off, waiting for button press event // case AMU_OFF: if( !ButtonChanged ) return; AmuletOn(); if( ButtonState == BTN_OFF ) AmuletState = AMU_BEEPON; if( ButtonState == BTN_ONLONG ) AmuletState = AMU_ON; return; break; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // AMU_BEEPON: Amulet is on, check for button press and also manage beeping time // case AMU_BEEPON: if( ButtonChanged ) { // // Quick press while beeping => Turn amulet off // if( ButtonState == BTN_OFF ) { AmuletState = AMU_OFF; AmuletOff(); return; } // // Long press while beeping => Turn amulet on continuously // if( ButtonState == BTN_ONLONG ) { AmuletState = AMU_ON; return; } } // // If amulet hasn't been on long enough, continue waiting. // AmuletTimer--; if( AmuletTimer > 0 ) return; // // Toggle amulet off // AmuletOff(); AmuletState = AMU_BEEPOFF; return; break; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // AMU_BEEPOFF: Amulet is on, but silent, check for button press and also manage silence time // case AMU_BEEPOFF: if( ButtonChanged ) { // // Quick press while beeping => Turn amulet off // if( ButtonState == BTN_OFF ) { AmuletState = AMU_OFF; AmuletOff(); return; } // // Long press while beeping => Turn amulet on continuously // if( ButtonState == BTN_ONLONG ) { AmuletState = AMU_ON; AmuletOn(); return; } } // // If amulet hasn't been off long enough, continue waiting. // AmuletTimer--; if( AmuletTimer > 0 ) return; // // Toggle amulet on // AmuletOn(); AmuletState = AMU_BEEPON; return; break; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // AMU_ON: Amulet is on continuously. Wait for button press event // case AMU_ON: // // There is only 1 button press event possible after a long press, which is BTN_OFF. // if( !ButtonChanged ) return; AmuletOn(); // Reset timer for AMU_BEEPON state AmuletState = AMU_BEEPON; return; break; } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // ButtonUpdate - Manage the button press finite state machine // // Read the button inputs and process debounce timing. Set the "ButtonChanged" variable when either 1) the button // is released (+debounce), or 2) The button is held longer than 1 second. // // Inputs: None. // // Outputs: None. Sets the "ButtonChanged" external var for significant button state change // // Note that it is the button *release* that constitutes a short press. This is how the algorithm distinguishes // between a short press and a long press. // void ButtonUpdate() { switch( ButtonState ) { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // BTN_OFF: Button is off, waiting for press // case BTN_OFF: if( digitalRead(BTNPin) == OFF ) return; ButtonState = BTN_ONDEB; ButtonTimer = BTN_DEB; return; break; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // BTN_ONDEB: Button is on, counting down for debounce // case BTN_ONDEB: // // If the button goes off again before the debounce time, go back to the "off" state // if( digitalRead(BTNPin) == OFF ) { ButtonState = BTN_OFF; return; } // // If button hasn't been on long enough, continue waiting. // ButtonTimer--; if( ButtonTimer > 0 ) return; ButtonState = BTN_ON; ButtonTimer = BTN_HOLD; return; break; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // BTN_ON: Button is on, waiting for off or long press // case BTN_ON: // // Detect button off, before the long hold // if( digitalRead(BTNPin) == OFF ) { ButtonState = BTN_OFFDEB; ButtonTimer = BTN_DEB; return; } // // Detect long hold, move to new state when detected // ButtonTimer--; if( ButtonTimer > 0 ) return; ButtonState = BTN_ONLONG; ButtonChanged = true; return; break; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // BTN_OFFDEB: Button is off, counting down for debounce // case BTN_OFFDEB: // // If the button goes on again before the debounce time, go back to the "on" state // if( digitalRead(BTNPin) == ON ) { ButtonState = BTN_ON; ButtonTimer = BTN_HOLD; return; } // // If button hasn't been off long enough, continue waiting. // ButtonTimer--; if( ButtonTimer > 0 ) return; ButtonState = BTN_OFF; ButtonChanged = true; return; break; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // BTN_ONLONG: Button is on, long press detected // case BTN_ONLONG: // // Stay in state as long as button is pressed // if( digitalRead(BTNPin) == ON ) return; ButtonState = BTN_LONGDEB; ButtonTimer = BTN_DEB; return; break; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // BTN_LONGDEB: Button is off, long press was released // case BTN_LONGDEB: // // If the button goes on again before the debounce time, go back to the "on-long" state // if( digitalRead(BTNPin) == ON ) { ButtonState = BTN_ONLONG; return; } // // If button hasn't been off long enough, continue waiting. // ButtonTimer--; if( ButtonTimer > 0 ) return; ButtonState = BTN_OFF; ButtonChanged = true; return; } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // Amulet on - Turn on the LEDs and play the tone through the speaker // Amulet off - Turn off the LEDs and stop the tone through the speaker // // Inputs: None. // // Outputs: None. // // In addition to turning the outputs on and off, set the BeepTimer to the nominal value. This can used by the // (AMU_BEEP mode) or ignored (AMY_ON mode) as needed. // void AmuletOn() { digitalWrite(LEDPin,HIGH); // Turn on LED tone(TONEPin,TONEFreq); // Turn on tone AmuletTimer = 120+random(4)*50; // Delay with random duration (AMU_BEEP mode) } void AmuletOff() { digitalWrite(LEDPin,LOW); // Turn off LED noTone(TONEPin); // Turn off tone AmuletTimer = 75; // Delay for silence (AMU_BEEP mode) }