diff --git a/Button.cpp b/Button.cpp new file mode 100644 index 0000000..7fc5426 --- /dev/null +++ b/Button.cpp @@ -0,0 +1,48 @@ +#include "Button.h" + + +void Button::init(uint8_t pin, int offState, bool usePullUp) +{ + this->pin = pin; + this->offState = offState; + this->lastState = offState; + + if (offState == HIGH && usePullUp) + pinMode(pin, INPUT_PULLUP); + else + pinMode(pin, INPUT); +} + + +void Button::update(unsigned long referenceTime) +{ + unsigned long currentTime = getTime(referenceTime); + int currentState = digitalRead(pin); + + if (lastChangedTime != 0) + { + if (currentState != lastState && (currentTime - lastChangedTime) > debounceMillis) + { + lastChanged = true; + + if (currentState != offState) + lastPressedTime = currentTime; + else if (blocked) + { + blocked = false; + lastChanged = false; + } + + lastChangedTime = currentTime; + lastState = currentState; + return; + } + } + else + { + lastChangedTime = currentTime; + lastState = currentState; + } + + lastChanged = false; +} \ No newline at end of file diff --git a/Button.h b/Button.h new file mode 100644 index 0000000..a3d9731 --- /dev/null +++ b/Button.h @@ -0,0 +1,37 @@ +#ifndef Button_h +#define Button_h + +#include "Arduino.h" + + +class Button +{ + public: + void init(uint8_t pin, int offState = HIGH, bool usePullUp = true); + void update(unsigned long referenceTime = 0); + void block() { if (lastState != offState) blocked = true; } + + bool changed() { return !blocked && lastChanged; } + bool pressed() { return !blocked && lastState != offState; } + unsigned long pressedMillis(unsigned long referenceTime = 0) { return getTime(referenceTime) - lastPressedTime; } + + unsigned long getDebounceMillis() { return debounceMillis; } + void setDebounceMillis(unsigned long value) { debounceMillis = value; } + + protected: + inline unsigned long getTime(unsigned long referenceTime) { return referenceTime == 0 ? millis() : referenceTime; }; + + private: + uint8_t pin = 1; + int offState = HIGH; + unsigned long debounceMillis = 50; + + unsigned long lastChangedTime = 0; + unsigned long lastPressedTime = 0; + int lastState; + bool lastChanged; + + bool blocked = false; +}; + +#endif \ No newline at end of file diff --git a/Globals.cpp b/Globals.cpp new file mode 100644 index 0000000..0f833a0 --- /dev/null +++ b/Globals.cpp @@ -0,0 +1,23 @@ +#include "Globals.h" +#include "StateHandler.h" + + +SegmentDisplay* display; +Button* buttonA; +Button* buttonB; + +uint32_t shots = 0; +uint32_t hits = 0; + +unsigned long currentTime; +AbstractStateHandler* currentState; + + +void setCurrentState(AbstractStateHandler* value) +{ + buttonA->block(); + buttonB->block(); + + currentState = value; + currentState->setup(); +} diff --git a/Globals.h b/Globals.h new file mode 100644 index 0000000..65c15f8 --- /dev/null +++ b/Globals.h @@ -0,0 +1,21 @@ +#ifndef Globals_h +#define Globals_h + +#include "SegmentDisplay.h" +#include "SegmentDisplayConfig.h" +#include "Button.h" +#include "StateHandler.h" + +extern SegmentDisplay* display; +extern Button* buttonA; +extern Button* buttonB; + +extern uint32_t shots; +extern uint32_t hits; + +extern unsigned long currentTime; +extern AbstractStateHandler* currentState; + +void setCurrentState(AbstractStateHandler* value); + +#endif \ No newline at end of file diff --git a/NerfStatTrek.ino b/NerfStatTrek.ino index 352f83e..055086c 100644 --- a/NerfStatTrek.ino +++ b/NerfStatTrek.ino @@ -1,110 +1,45 @@ -#include "SegmentDisplay.h" +#include "NerfStatTrekConfig.h" +#include "Globals.h" -#define ASCIIUppercaseA 65 -#define ASCIIUppercaseZ 90 - -SegmentDisplay* display; - -char* text = new char[7]; +#include "StateHandler.h" +#include "StateDefault.h" void setup() { - pinMode(2, INPUT_PULLUP); - pinMode(A3, INPUT); - + // Configure display display = new SegmentDisplay(); - //display->setClockSpeed(4000000UL); - display->setClockPin(12); - display->setDataPin(11); - display->setLatchPin(10); - display->setDigits(6); + + #ifdef SDUseSPI + display->setClockSpeed(NerfClockSpeed); + #else + display->setClockPin(NerfClockPin); + display->setDataPin(NerfDataPin); + #endif + + display->setLatchPin(NerfLatchPin); + display->setDigits(NerfDigits); display->begin(); - text[0] = '\0'; - text[1] = '\0'; - text[2] = '\0'; - text[3] = '\0'; - text[4] = '\0'; - text[5] = '\0'; - text[6] = '\0'; + + // Configure buttons + buttonA = new Button(); + buttonA->init(NerfButtonA); + + buttonB = new Button(); + buttonB->init(NerfButtonB); + + + setCurrentState(new DefaultState()); } -unsigned long currentTime; -unsigned long lastCounterTime = 0; -uint32_t counter = 0; -uint32_t delayValue; - -bool showChars = true; -byte character = ASCIIUppercaseA - 1; - -int buttonValue; -bool buttonDown = false; -unsigned long lastButtonChange = 0; - - void loop() { currentTime = millis(); - buttonValue = digitalRead(2); - if (buttonValue == LOW) - { - if (!buttonDown && (currentTime - lastButtonChange) > 50) - { - buttonDown = true; - showChars = !showChars; - lastButtonChange = currentTime; - } - } - else - { - if (buttonDown && (currentTime - lastButtonChange) > 50) - { - buttonDown = false; - lastButtonChange = currentTime; - } - } + buttonA->update(currentTime); + buttonB->update(currentTime); - if (showChars) - { - if (lastCounterTime == 0 || currentTime - lastCounterTime > 250UL) - { - character++; - if (character > ASCIIUppercaseZ) - character = ASCIIUppercaseA; - - lastCounterTime = currentTime; - - delayValue = ((uint32_t)analogRead(A3) * 100); - display->setDigitDelayMicroseconds(delayValue); - } - - for (byte c = 0; c < 6; c++) - { - if (character + c > ASCIIUppercaseZ) - text[c] = ' '; - else - text[c] = character + c; - } - - display->writeTextLeft(text); - } - else - { - if (currentTime - lastCounterTime > 100UL) - { - counter++; - if (counter > 999999) - counter = 0; - - lastCounterTime = currentTime; - - delayValue = ((uint32_t)analogRead(A3) * 100); - display->setDigitDelayMicroseconds(delayValue); - } - - display->writeNumber(counter); - } + currentState->loop(); } diff --git a/NerfStatTrekConfig.h b/NerfStatTrekConfig.h new file mode 100644 index 0000000..29e29f0 --- /dev/null +++ b/NerfStatTrekConfig.h @@ -0,0 +1,44 @@ +#ifndef NerfStatTrekConfig_h +#define NerfStatTrekConfig_h + + +/** + * Display configuration +**/ +#define NerfDigits 6 + + +/** + * Display pin configuration + * + * Clock and data are used only when not using SPI, + * clock speed is only for SPI +**/ +#define NerfLatchPin 10 +#define NerfDataPin 11 +#define NerfClockPin 12 +#define NerfClockSpeed 1600000UL + + +/** + * Button configuration +**/ +#define NerfButtonLongPressThreshold 1000 + + +/** + * Button pin configuration +**/ +#define NerfButtonA 3 +#define NerfButtonB 2 +#define NerfButtonArmed 4 + + + +/** + * Default state configuration +**/ +#define NerfDefaultIntroTime 1000 + + +#endif \ No newline at end of file diff --git a/SegmentDisplay.cpp b/SegmentDisplay.cpp index 1c1e53c..f3d5dfb 100644 --- a/SegmentDisplay.cpp +++ b/SegmentDisplay.cpp @@ -86,11 +86,13 @@ void SegmentDisplay::writeTextLeft(char* value) void SegmentDisplay::writeTextCenter(char* value) { + // TODO } void SegmentDisplay::writeTextRight(char* value) { + // TODO } @@ -120,7 +122,7 @@ inline void SegmentDisplay::writeChar(char value, byte digitMask) } -inline void SegmentDisplay::writeDisplay(byte segmentMask, byte digitMask) +void SegmentDisplay::writeDisplay(byte segmentMask, byte digitMask) { // If no segments should be lit, clear the digit mask as well to prevent // power leaking through (it's probably an issue in my circuit or with the diff --git a/SegmentDisplay.h b/SegmentDisplay.h index 3d8f1fe..d1453e4 100644 --- a/SegmentDisplay.h +++ b/SegmentDisplay.h @@ -58,7 +58,6 @@ class SegmentDisplay protected: void writeDigit(byte value, byte digitMask); void writeChar(char value, byte digitMask); - public: void writeDisplay(byte characterMask, byte digitMask); private: diff --git a/SegmentDisplayConfig.h b/SegmentDisplayConfig.h index 5985d97..f20fba5 100644 --- a/SegmentDisplayConfig.h +++ b/SegmentDisplayConfig.h @@ -21,7 +21,7 @@ * * If not defined, the order is reversed. **/ -//#define SDPushSegmentsFirst +#define SDPushSegmentsFirst /** diff --git a/StateDefault.cpp b/StateDefault.cpp new file mode 100644 index 0000000..b5dbdeb --- /dev/null +++ b/StateDefault.cpp @@ -0,0 +1,82 @@ +#include "StateDefault.h" +#include "NerfStatTrekConfig.h" +#include "Globals.h" +#include "StateLoseTheGame.h" +#include "StateUserUnknown.h" + + +bool DefaultState::ShowHits = true; + + + +void DefaultState::setup() +{ + introStart = currentTime; +} + +void DefaultState::loop() +{ + if (buttonA->pressed() && buttonB->pressed()) + { + // A + B + setCurrentState(new LoseTheGameState()); + return; + } + + if (buttonA->changed() && !buttonA->pressed()) + { + // A (triggered on release) + hits++; + + if (!DefaultState::ShowHits) + setShowHits(true); + } + + // B short (triggered on release) + if (buttonB->changed() && !buttonB->pressed() && buttonB->pressedMillis(currentTime) <= NerfButtonLongPressThreshold) + { + setShowHits(!DefaultState::ShowHits); + } + + // B long + if (buttonB->pressed() && buttonB->pressedMillis(currentTime) > NerfButtonLongPressThreshold) + { + setCurrentState(new UserUnknownState()); + return; + } + + + if (showIntro) + { + if (currentTime - introStart >= NerfDefaultIntroTime) + showIntro = false; + + if (DefaultState::ShowHits) + { + // Prevents warning: deprecated conversion from string constant to 'char*' + char text[] = "Hits"; + display->writeTextLeft(text); + } + else + { + char text[] = "Shots"; + display->writeTextLeft(text); + } + } + else + { + if (DefaultState::ShowHits) + display->writeNumber(hits); + else + display->writeNumber(shots); + } +} + + +void DefaultState::setShowHits(bool value) +{ + showIntro = true; + introStart = currentTime; + + DefaultState::ShowHits = value; +} \ No newline at end of file diff --git a/StateDefault.h b/StateDefault.h new file mode 100644 index 0000000..83cd360 --- /dev/null +++ b/StateDefault.h @@ -0,0 +1,33 @@ +#ifndef StateDefault_h +#define StateDefault_h + +#include "StateHandler.h" + + +/** + * Default state + * + * Start by showing the current counter mode (hits or shots), + * then display the value. + * + * A: Add hit + * B short: Toggle between hits and shots and restart state + * B long: Display "Error user unknwn" + * A + B: Display "You lost the game" animation (losethegame.com - you're welcome!) +**/ +class DefaultState : public AbstractStateHandler +{ + private: + bool showIntro = true; + unsigned long introStart; + + void setShowHits(bool value); + + public: + static bool ShowHits; + + void setup(); + void loop(); +}; + +#endif \ No newline at end of file diff --git a/StateHandler.h b/StateHandler.h new file mode 100644 index 0000000..2de3317 --- /dev/null +++ b/StateHandler.h @@ -0,0 +1,11 @@ +#ifndef StateHandler_h +#define StateHandler_h + +class AbstractStateHandler +{ + public: + virtual void setup() = 0; + virtual void loop() = 0; +}; + +#endif \ No newline at end of file diff --git a/StateLoseTheGame.cpp b/StateLoseTheGame.cpp new file mode 100644 index 0000000..24c72dd --- /dev/null +++ b/StateLoseTheGame.cpp @@ -0,0 +1,19 @@ +#include "StateLoseTheGame.h" +#include "Globals.h" +#include "StateDefault.h" + + +void LoseTheGameState::setup() +{ +} + +void LoseTheGameState::loop() +{ + if (buttonA->pressed() || buttonB->pressed()) + { + setCurrentState(new DefaultState()); + } + + char text[] = "Lost"; + display->writeTextLeft(text); +} \ No newline at end of file diff --git a/StateLoseTheGame.h b/StateLoseTheGame.h new file mode 100644 index 0000000..5aa9c81 --- /dev/null +++ b/StateLoseTheGame.h @@ -0,0 +1,17 @@ +#ifndef StateLoseTheGame_h +#define StateLoseTheGame_h + +#include "StateHandler.h" + + +/** + * Lose the game state +**/ +class LoseTheGameState : public AbstractStateHandler +{ + public: + void setup(); + void loop(); +}; + +#endif \ No newline at end of file diff --git a/StateUserUnknown.cpp b/StateUserUnknown.cpp new file mode 100644 index 0000000..ad2fd49 --- /dev/null +++ b/StateUserUnknown.cpp @@ -0,0 +1,19 @@ +#include "StateUserUnknown.h" +#include "Globals.h" +#include "StateDefault.h" + + +void UserUnknownState::setup() +{ +} + +void UserUnknownState::loop() +{ + if (buttonA->pressed() || buttonB->pressed()) + { + setCurrentState(new DefaultState()); + } + + char text[] = "Unknwn"; + display->writeTextLeft(text); +} \ No newline at end of file diff --git a/StateUserUnknown.h b/StateUserUnknown.h new file mode 100644 index 0000000..9bed0f6 --- /dev/null +++ b/StateUserUnknown.h @@ -0,0 +1,19 @@ +#ifndef StateUserUnknown_h +#define StateUserUnknown_h + +#include "StateHandler.h" + + +/** + * "User unknown" state + * + * Displays "Error user unknwn" until a button is pressed. +**/ +class UserUnknownState : public AbstractStateHandler +{ + public: + void setup(); + void loop(); +}; + +#endif \ No newline at end of file