From ba58eb93c00d93301d9a0e85d1336834f61db78f Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Sun, 4 Dec 2016 19:53:43 +0100 Subject: [PATCH] Moved away from proof of concepts, started prototyping for the final software - Button library - State handling and transitions - Default state: dual counter display - User unknown state: static for now - Lost the game state: static for now --- Button.cpp | 48 ++++++++++++++++ Button.h | 37 +++++++++++++ Globals.cpp | 23 ++++++++ Globals.h | 21 +++++++ NerfStatTrek.ino | 121 ++++++++++------------------------------- NerfStatTrekConfig.h | 44 +++++++++++++++ SegmentDisplay.cpp | 4 +- SegmentDisplay.h | 1 - SegmentDisplayConfig.h | 2 +- StateDefault.cpp | 82 ++++++++++++++++++++++++++++ StateDefault.h | 33 +++++++++++ StateHandler.h | 11 ++++ StateLoseTheGame.cpp | 19 +++++++ StateLoseTheGame.h | 17 ++++++ StateUserUnknown.cpp | 19 +++++++ StateUserUnknown.h | 19 +++++++ 16 files changed, 405 insertions(+), 96 deletions(-) create mode 100644 Button.cpp create mode 100644 Button.h create mode 100644 Globals.cpp create mode 100644 Globals.h create mode 100644 NerfStatTrekConfig.h create mode 100644 StateDefault.cpp create mode 100644 StateDefault.h create mode 100644 StateHandler.h create mode 100644 StateLoseTheGame.cpp create mode 100644 StateLoseTheGame.h create mode 100644 StateUserUnknown.cpp create mode 100644 StateUserUnknown.h 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