From 893e2aed46de55267234c766f905c33f148e4120 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Sat, 28 Nov 2020 20:23:35 -0700 Subject: [PATCH] Add morse code, auto-shutoff --- bounce2.cpp | 138 ++++++++++++++++++++++++++++ bounce2.h | 222 +++++++++++++++++++++++++++++++++++++++++++++ holiday-lights.ino | 211 ++++++++++++++++++++++++++++-------------- morse.cpp | 120 ++++++++++++++++++++++++ morse.h | 40 ++++++++ test | Bin 0 -> 17232 bytes test.cpp | 16 ++++ 7 files changed, 677 insertions(+), 70 deletions(-) create mode 100644 bounce2.cpp create mode 100644 bounce2.h create mode 100644 morse.cpp create mode 100644 morse.h create mode 100755 test create mode 100644 test.cpp diff --git a/bounce2.cpp b/bounce2.cpp new file mode 100644 index 0000000..d175805 --- /dev/null +++ b/bounce2.cpp @@ -0,0 +1,138 @@ +// Please read Bounce2.h for information about the liscence and authors + + +#include "bounce2.h" + + + + +Bounce::Bounce() + : previous_millis(0) + , interval_millis(10) + , state(0) + , pin(0) +{} + +void Bounce::attach(int pin) { + this->pin = pin; + state = 0; + if (readCurrentState()) { + setStateFlag(DEBOUNCED_STATE | UNSTABLE_STATE); + } +#ifdef BOUNCE_LOCK_OUT + previous_millis = 0; +#else + previous_millis = millis(); +#endif +} + +void Bounce::attach(int pin, int mode){ + setPinMode(pin, mode); + this->attach(pin); +} + +void Bounce::interval(uint16_t interval_millis) +{ + this->interval_millis = interval_millis; +} + +bool Bounce::update() +{ + + unsetStateFlag(CHANGED_STATE); +#ifdef BOUNCE_LOCK_OUT + + // Ignore everything if we are locked out + if (millis() - previous_millis >= interval_millis) { + bool currentState = readCurrentState(); + if ( currentState != getStateFlag(DEBOUNCED_STATE) ) { + previous_millis = millis(); + changeState(); + } + } + + +#elif defined BOUNCE_WITH_PROMPT_DETECTION + // Read the state of the switch port into a temporary variable. + bool readState = readCurrentState(); + + + if ( readState != getStateFlag(DEBOUNCED_STATE) ) { + // We have seen a change from the current button state. + + if ( millis() - previous_millis >= interval_millis ) { + // We have passed the time threshold, so a new change of state is allowed. + // set the STATE_CHANGED flag and the new DEBOUNCED_STATE. + // This will be prompt as long as there has been greater than interval_misllis ms since last change of input. + // Otherwise debounced state will not change again until bouncing is stable for the timeout period. + changeState(); + } + } + + // If the readState is different from previous readState, reset the debounce timer - as input is still unstable + // and we want to prevent new button state changes until the previous one has remained stable for the timeout. + if ( readState != getStateFlag(UNSTABLE_STATE) ) { + // Update Unstable Bit to macth readState + toggleStateFlag(UNSTABLE_STATE); + previous_millis = millis(); + } + + +#else + // Read the state of the switch in a temporary variable. + bool currentState = readCurrentState(); + + + // If the reading is different from last reading, reset the debounce counter + if ( currentState != getStateFlag(UNSTABLE_STATE) ) { + previous_millis = millis(); + toggleStateFlag(UNSTABLE_STATE); + } else + if ( millis() - previous_millis >= interval_millis ) { + // We have passed the threshold time, so the input is now stable + // If it is different from last state, set the STATE_CHANGED flag + if (currentState != getStateFlag(DEBOUNCED_STATE) ) { + previous_millis = millis(); + + + changeState(); + } + } + + +#endif + + return changed(); + +} + +// WIP HELD +unsigned long Bounce::previousDuration() { + return durationOfPreviousState; +} + +unsigned long Bounce::duration() { + return (millis() - stateChangeLastTime); +} + +inline void Bounce::changeState() { + toggleStateFlag(DEBOUNCED_STATE); + setStateFlag(CHANGED_STATE) ; + durationOfPreviousState = millis() - stateChangeLastTime; + stateChangeLastTime = millis(); +} + +bool Bounce::read() +{ + return getStateFlag(DEBOUNCED_STATE); +} + +bool Bounce::rose() +{ + return getStateFlag(DEBOUNCED_STATE) && getStateFlag(CHANGED_STATE); +} + +bool Bounce::fell() +{ + return !getStateFlag(DEBOUNCED_STATE) && getStateFlag(CHANGED_STATE); +} diff --git a/bounce2.h b/bounce2.h new file mode 100644 index 0000000..14d4c66 --- /dev/null +++ b/bounce2.h @@ -0,0 +1,222 @@ +/* + The MIT License (MIT) + + Copyright (c) 2013 thomasfredericks + + 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. +*/ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * + Main code by Thomas O Fredericks (tof@t-o-f.info) + Previous contributions by Eric Lowry, Jim Schimpf and Tom Harkaway + * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/** + * @todo Make Bounce2 more abstract. Split it from the hardware layer. + * @body Remove deboucing code from Bounce2 and make a new Debounce class from that code. + * Bounce2 should extend Debounce. + */ + + +#ifndef Bounce2_h +#define Bounce2_h + +#if defined(ARDUINO) && ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +// Uncomment the following line for "LOCK-OUT" debounce method +//#define BOUNCE_LOCK_OUT + +// Uncomment the following line for "BOUNCE_WITH_PROMPT_DETECTION" debounce method +//#define BOUNCE_WITH_PROMPT_DETECTION + +#include + +/** + @example bounce.ino + Simple example of the Bounce library that switches the debug LED when a button is pressed. +*/ + +/** + @example change.ino + This example toggles the debug LED (pin 13) on or off when a button on pin 2 is pressed. +*/ + +/** + @example bounce_multiple.ino + Detect the falling edge of multiple buttons. Eight buttons with internal pullups. Toggles a + LED when any button is pressed. Buttons on pins 2,3,4,5,6,7,8,9 +*/ + +/** + @example bounce2buttons.ino + Example of two instances of the Bounce class that switches the debug LED when either one of + the two buttons is pressed. + */ + +static const uint8_t DEBOUNCED_STATE = 0b00000001; +static const uint8_t UNSTABLE_STATE = 0b00000010; +static const uint8_t CHANGED_STATE = 0b00000100; + +/** + The Bounce class. + */ +class Bounce +{ + public: + +/*! + @brief Create an instance of the Bounce class. + + @code + + // Create an instance of the Bounce class. + Bounce() button; + + @endcode +*/ + Bounce(); + + +/*! + @brief Attach to a pin and sets that pin's mode (INPUT, INPUT_PULLUP or OUTPUT). + + @param pin + The pin that is to be debounced. + @param mode + A valid Arduino pin mode (INPUT, INPUT_PULLUP or OUTPUT). +*/ + void attach(int pin, int mode); + + /** + Attach to a pin for advanced users. Only attach the pin this way once you have previously + set it up. Otherwise use attach(int pin, int mode). + */ + void attach(int pin); + + + /** + @brief Sets the debounce interval in milliseconds. + + @param interval_millis + The interval time in milliseconds. + + */ + void interval(uint16_t interval_millis); + + +/*! + @brief Updates the pin's state. + + Because Bounce does not use interrupts, you have to "update" the object before reading its + value and it has to be done as often as possible (that means to include it in your loop()). + Only call update() once per loop(). + + @return True if the pin changed state. +*/ + + bool update(); + + /** + @brief Returns the pin's state (HIGH or LOW). + + @return HIGH or LOW. + */ + bool read(); + + /** + @brief Returns true if pin signal transitions from high to low. + */ + bool fell(); + + /** + @brief Returns true if pin signal transitions from low to high. + */ + bool rose(); + + + /** + @brief Deprecated (i.e. do not use). Included for partial compatibility for programs written + with Bounce version 1 + */ + bool risingEdge() { return rose(); } + /** + @brief Deprecated (i.e. do not use). Included for partial compatibility for programs written + with Bounce version 1 + */ + bool fallingEdge() { return fell(); } + /** + @brief Deprecated (i.e. do not use). Included for partial compatibility for programs written + with Bounce version 1 + */ + Bounce(uint8_t pin, unsigned long interval_millis ) : Bounce() { + attach(pin); + interval(interval_millis); + } + + /** + @brief Returns the duration in milliseconds of the current state. + + Is reset to 0 once the pin rises ( rose() ) or falls ( fell() ). + + @return The duration in milliseconds (unsigned long) of the current state. + */ + + unsigned long duration(); + + /** + @brief Returns the duration in milliseconds of the previous state. + + Takes the values of duration() once the pin changes state. + + @return The duration in milliseconds (unsigned long) of the previous state. + */ + unsigned long previousDuration(); + + protected: + unsigned long previous_millis; + uint16_t interval_millis; + uint8_t state; + uint8_t pin; + unsigned long stateChangeLastTime; + unsigned long durationOfPreviousState; + virtual bool readCurrentState() { return digitalRead(pin); } + virtual void setPinMode(int pin, int mode) { +#if defined(ARDUINO_ARCH_STM32F1) + pinMode(pin, (WiringPinMode)mode); +#else + pinMode(pin, mode); +#endif + } + + private: + inline void changeState(); + inline void setStateFlag(const uint8_t flag) {state |= flag;} + inline void unsetStateFlag(const uint8_t flag) {state &= ~flag;} + inline void toggleStateFlag(const uint8_t flag) {state ^= flag;} + inline bool getStateFlag(const uint8_t flag) {return((state & flag) != 0);} + + public: + bool changed( ) { return getStateFlag(CHANGED_STATE); } + +}; + +#endif diff --git a/holiday-lights.ino b/holiday-lights.ino index f1ab783..d99c932 100644 --- a/holiday-lights.ino +++ b/holiday-lights.ino @@ -1,102 +1,173 @@ #include -#ifdef __AVR__ - #include -#endif +#include +#include "bounce2.h" +#include "morse.h" + +// Do you want it to run forever, or cycle every 24 hours? +#define FOREVER false // Which pin your LED strip is connected to -#ifdef Attiny85 -# define PIN 3 -#else -# define PIN 6 -#endif +#define NEOPIXEL_PIN 6 -// Which pin to pull to go full white. Comment to disable this feature. -#define WHITE_PIN 4 - -// Order of the lights you got +// Which pin has the momentary switch to toggle full white. +#define BUTTON_PIN 4 // How many LEDs you have. It's okay if this is too big. -#ifdef Attiny85 -# define NUM_LEDS 80 -#else -# define NUM_LEDS 200 -#endif +#define LEDS_PER_GROUP 10 +#define NUM_GROUPS 20 + +// How many milliseconds between activity in one group +#define DELAY_MS 600 // What percentage chance a chosen light has of being on -#define ACTIVITY 50 +#define ACTIVITY 40 -// Debug LED -#define LED_PIN 1 +// Which pixel to flash messages in morse code: -1 = disable +#define MORSE_PIXEL 8 +#define MORSE_COLOR 0x880000 +// How long a dit lasts +#define DIT_DURATION_MS 100 -Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, PIN, NEO_RGB | NEO_KHZ800); +// Color for all-white mode +#define WHITE 0x886655 -const uint32_t colors[] = { - 0x004400, // Green - 0xdd4400, // Yellow - 0xdd4400, // Yellow - 0xdd2200, // Amber - 0xdd2200, // Amber - 0x0000ff, // Red - 0x880044, // Purple - 0x000088, // Blue +// The Neopixel library masks interrupts while writing. +// This means you lose time. +// How much time do you lose? +// I'm guessing 10 minutes a day. +#define SNOSSLOSS (10 * 60 * 1000) +#define DAY (24 * 60 * 60 * 1000 - SNOSSLOSS) +#define ON_FOR (5 * 60 * 60 * 1000) + +const char *messages[] = { + "happy holiday ARK", + "seasons greetings ARK", + "happy festivus ARK", + "merry christmas ARK", + "hanukkah sameach ARK", + "happy solstice ARK", + "happy new year ARK", }; +const int nmessages = sizeof(messages) / sizeof(*messages); +// These colors mirror pretty closely some cheapo LED lights we have +const uint32_t colors[] = { + 0xdd4400, // Yellow + 0xff0000, // Red + 0xdd2200, // Amber + 0x004400, // Green + + 0xdd4400, // Yellow + 0xff0000, // Red + 0xdd2200, // Amber + 0x880044, // Purple + + 0xdd4400, // Yellow + 0xff0000, // Red + 0xdd2200, // Amber + 0x000088, // Blue +}; const int ncolors = sizeof(colors) / sizeof(*colors); +Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_GROUPS * LEDS_PER_GROUP, NEOPIXEL_PIN, NEO_RGB | NEO_KHZ800); +Bounce button; + +int mode; +int nextMode; + void setup() { strip.begin(); strip.show(); -#ifdef WHITE_PIN - pinMode(WHITE_PIN, INPUT_PULLUP); -#endif - -#ifdef LED_PIN - pinMode(LED_PIN, OUTPUT); -#endif + button.attach(BUTTON_PIN, INPUT_PULLUP); + pinMode(LED_BUILTIN, OUTPUT); + mode = EEPROM.read(0); } -void loop_color() { - for (int i = 0; i < NUM_LEDS/12; i += 1) { - int pos = random(NUM_LEDS); - if (random(100) < ACTIVITY) { - int color = random(ncolors); - strip.setPixelColor(pos, colors[color]); - } else { - strip.setPixelColor(pos, 0); - } +bool strandUpdate(bool white) { + static unsigned long nextEventMillis = 0; + unsigned long now = millis(); + static int group = 0; + + if (now < nextEventMillis) { + return false; } - strip.show(); - delay(240); -} -void loop_white() { - for (int i = 0; i < NUM_LEDS/12; i += 1) { - int pos = random(NUM_LEDS); - if (random(100) < 5) { - strip.setPixelColor(pos, 0x0); + int pos = (group * LEDS_PER_GROUP) + random(LEDS_PER_GROUP); + if (random(100) < ACTIVITY) { + uint32_t color; + + if (white) { + color = WHITE; } else { - strip.setPixelColor(pos, 0x99ffaa); + color = colors[random(ncolors)]; } + strip.setPixelColor(pos, color); + } else { + strip.setPixelColor(pos, 0); } - strip.show(); - delay(24); + group = (group + 1) % NUM_GROUPS; + + nextEventMillis = now + (DELAY_MS / NUM_GROUPS); + return true; +} + +bool morseUpdate() { + static MorseEncoder enc; + static unsigned long nextEventMillis = 0; + unsigned long now = millis(); + bool ret = false; + + if (now >= nextEventMillis) { + nextEventMillis = now + DIT_DURATION_MS; + if (!enc.Tick()) { + char *message = messages[random(nmessages)]; + enc.SetText(message); + enc.Quiet(200); + } + ret = true; + } + strip.setPixelColor(MORSE_PIXEL, enc.Transmitting ? MORSE_COLOR : 0); + return ret; +} + +bool black() { + static unsigned long nextEventMillis = 0; + unsigned long now = millis(); + + if (now < nextEventMillis) { + return false; + } + + strip.clear(); + + nextEventMillis = now + (DELAY_MS / NUM_GROUPS); // Keep timing consistent + return true; } void loop() { -#ifdef WHITE_PIN - if (! digitalRead(WHITE_PIN)) { - loop_white(); - } else { -#else - { -#endif - loop_color(); + static bool white = false; + bool update = false; + + button.update(); + if (button.fell()) { + white = !white; } -#ifdef LED_PIN - static bool led = true; - digitalWrite(LED_PIN, led); - led = !led; -#endif + if (forever || white || (millis() % DAY < ON_FOR)) { + update |= strandUpdate(white); + update |= morseUpdate(); + } else { + update |= black(); + } + + if (update) { + strip.show(); + } + + // blink the heart for a little bit + if (millis() < 30 * 1000) { + bool on = (millis() % 1000) < 500; + digitalWrite(LED_BUILTIN, on); + } } diff --git a/morse.cpp b/morse.cpp new file mode 100644 index 0000000..cb472ab --- /dev/null +++ b/morse.cpp @@ -0,0 +1,120 @@ +#include "morse.h" + +// Each morse dit/dah is stored as a nybble + +#define dit 1 +#define dah 3 + +#define Pack(a, b, c, d) ((a << 0) | (b << 2) | (c << 4) | (d << 6)) +#define Unpack(m, pos) ((m >> (pos * 2)) & 0b11) + +const uint8_t MorseLetters[] = { + Pack(dit, dah, 0, 0), // A + Pack(dah, dit, dit, dit), // B + Pack(dah, dit, dah, dit), // C + Pack(dah, dit, dit, 0), // D + Pack(dit, 0, 0, 0), // E + Pack(dit, dit, dah, dit), // F + Pack(dah, dah, dit, 0), // G + Pack(dit, dit, dit, dit), // H + Pack(dit, dit, 0, 0), // I + Pack(dit, dah, dah, dah), // J + Pack(dah, dit, dah, 0), // K + Pack(dit, dah, dit, dit), // L + Pack(dah, dah, 0, 0), // M + Pack(dah, dit, 0, 0), // N + Pack(dah, dah, dah, 0), // O + Pack(dit, dah, dah, dit), // P + Pack(dah, dah, dit, dah), // Q + Pack(dit, dah, dit, 0), // R + Pack(dit, dit, dit, 0), // S + Pack(dah, 0, 0, 0), // T + Pack(dit, dit, dah, 0), // U + Pack(dit, dit, dit, dah), // V + Pack(dit, dah, dah, 0), // W + Pack(dah, dit, dit, dah), // X + Pack(dah, dit, dah, dah), // Y + Pack(dah, dah, dit, dit), // Z +}; + +MorseEncoder::MorseEncoder() { + SetText(""); +} +MorseEncoder::MorseEncoder(const char *s) { + SetText(s); +} + +void MorseEncoder::SetText(const char *s) { + p = s; + crumb = 0; + ticksLeft = 0; + Transmitting = false; +} + +void MorseEncoder::Quiet(int ticks) { + Transmitting = false; + ticksLeft = ticks; +} + +bool MorseEncoder::Tick() { + --ticksLeft; + if (ticksLeft > 0) { + return true; + } + + // We're out of ticks + + if (!p || !*p) { + return false; + } + + // If we were just transmitting, we have to stop for at least one dit + if (Transmitting) { + Transmitting = false; + ticksLeft = MORSE_DIT; + return true; + } + + // If that was the end of the letter, we have to pause more + if (crumb == 4) { + crumb = 0; + ++p; + ticksLeft = MORSE_PAUSE_LETTER - MORSE_DIT; + return true; + } + + switch (*p) { + case '\0': + return false; + case 'a' ... 'z': + Transmitting = true; + ticksLeft = Unpack(MorseLetters[*p - 'a'], crumb++); + break; + case 'A' ... 'Z': + Transmitting = true; + ticksLeft = Unpack(MorseLetters[*p - 'A'], crumb++); + break; + case ' ': + crumb = 0; + ++p; + Transmitting = false; + ticksLeft = MORSE_PAUSE_WORD - MORSE_DIT; + break; + default: // this should never happen! Transmit for a word pause to indicate weirdness. + crumb = 0; + ++p; + Transmitting = true; + ticksLeft = MORSE_PAUSE_WORD; + break; + } + if (0 == ticksLeft) { + // Unpack can return 0 if there are fewer than 4 emissions for a letter. + // In that case, we 're done with the letter. + crumb = 0; + ++p; + Transmitting = false; + ticksLeft = MORSE_PAUSE_LETTER - MORSE_DIT; + } + + return true; +} diff --git a/morse.h b/morse.h new file mode 100644 index 0000000..ca2af3d --- /dev/null +++ b/morse.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#define MORSE_DIT 1 +#define MORSE_DAH 3 +#define MORSE_PAUSE_LETTER 3 +#define MORSE_PAUSE_WORD 6 + +class MorseEncoder { + public: + MorseEncoder(); + MorseEncoder(const char *s); + + /** SetText resets state with new text. + */ + void SetText(const char *s); + + /** Tick tells the encoder that a dit has elapsed. + * + * Returns true if there's data left to transmit. + * If it returns false, you need to feed it more data. + * + * You should call this every time your dit duration ends. + */ + bool Tick(); + + /** Quiet stops transmitting for this many ticks. + */ + void Quiet(int ticks); + + /** Transmitting is true if you should be transmitting right now. + */ + bool Transmitting; + + private: + const char *p; + uint8_t crumb; + int ticksLeft; +}; diff --git a/test b/test new file mode 100755 index 0000000000000000000000000000000000000000..bac98ff8cf3ed3297fad68b4d265ef1dccd0fc39 GIT binary patch literal 17232 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 tRUmAJi|fPpUwpsEb+_7YyvgLQv +#include "morse.h" + +// gcc -D test_main=main -o test morse.cpp test.cpp && ./test + +int test_main(int argc, char *argv[]) { + MorseEncoder enc = MorseEncoder("SOS ck ck ark"); + while (enc.Tick()) { + if (enc.Transmitting) { + printf("#"); + } else { + printf(" "); + } + } + printf("\n"); +}