From a69c2d3364eb75d9c9183a108747eb1f28c4a28d Mon Sep 17 00:00:00 2001 From: Anna Wiggins Date: Mon, 8 Nov 2021 01:37:30 +0000 Subject: [PATCH] Refactor code substantially, moving buttons into separate classes and using a lot more pointers to conserve memory until it is needed. --- Button.cpp | 132 +++++++++++++++++++++++ Button.h | 97 +++++++++++++++++ Joystick.cpp | 152 ++++++--------------------- Joystick.h | 63 ++--------- examples/full/full.ino | 6 +- examples/multiplexer/multiplexer.ino | 20 ++-- 6 files changed, 285 insertions(+), 185 deletions(-) create mode 100644 Button.cpp create mode 100644 Button.h diff --git a/Button.cpp b/Button.cpp new file mode 100644 index 0000000..7ded421 --- /dev/null +++ b/Button.cpp @@ -0,0 +1,132 @@ +#include "Button.h" +#include "Joystick.h" +#include +#include + +using namespace admux; + +Button::Button(uint8_t vbutton) { + this->vbutton = vbutton; +} + +void Button::ReleaseButtons(Joystick* js) { + js->ReleaseButton(vbutton); +} + +SwitchButton::SwitchButton(uint8_t pin, uint8_t vbutton, bool pullup, Mux* mux) : Button(vbutton) { + this->mux = mux; + + uint8_t mode = INPUT; + if (pullup) { + this->inverted = true; + mode = INPUT_PULLUP; + } + + if (mux == NULL) this->bouncer.attach(pin, mode); + else { + this->bouncer.attach(mux->signalPin().pin, mode); + this->channel_id = pin; + } +} + +bool SwitchButton::BouncerUpdate() { + if (mux != NULL) { + mux->channel(channel_id); + } + + return bouncer.update(); +} + +bool SwitchButton::On() { + bool state = bouncer.rose(); + if (inverted) state = bouncer.fell(); + return state; +} + + +PassthruButton::PassthruButton(uint8_t pin, uint8_t vbutton, bool pullup, Mux* mux) : SwitchButton(pin, vbutton, pullup, mux) { + this->type = BUTTON_PASSTHRU; +} + +void PassthruButton::Update(Joystick* js) { + if (!BouncerUpdate()) return; + if (On()) js->PressButton(vbutton); + else js->ReleaseButton(vbutton); +} + + +LatchedButton::LatchedButton(uint8_t pin, uint8_t vbutton, bool pullup, Mux* mux) : SwitchButton(pin, vbutton, pullup, mux) { + this->type = BUTTON_LATCHED_MOMENTARY; + this->pressed = false; +} + +void LatchedButton::Update(Joystick* js) { + if (!BouncerUpdate()) return; + + if (On()) { + if (!pressed) { + js->PressButton(vbutton); + pressed = true; + } else { + js->ReleaseButton(vbutton); + pressed = false; + } + } +} + + +PulsedButton::PulsedButton(uint8_t pin, uint8_t vbutton, bool double_action, bool split, bool pullup, Mux* mux) : SwitchButton(pin, vbutton, pullup, mux) { + if (double_action) { + if (split) { + this->type = BUTTON_PULSED_DOUBLE_ACTION_SPLIT; + this->vbutton2 = vbutton + 1; + } else { + this->type = BUTTON_PULSED_DOUBLE_ACTION; + } + } else { + this->type = BUTTON_PULSED; + } +} + +void PulsedButton::Update(Joystick* js) { + if (!BouncerUpdate()) return; + + switch(type) { + case BUTTON_PULSED: + if (On()) js->PressButton(vbutton); + break; + case BUTTON_PULSED_DOUBLE_ACTION: + js->PressButton(vbutton); + break; + case BUTTON_PULSED_DOUBLE_ACTION_SPLIT: + if (On()) js->PressButton(vbutton); + else js->PressButton(vbutton2); + break; + } +} + +void PulsedButton::ReleaseButtons(Joystick* js) { + Button::ReleaseButtons(js); + if (type == BUTTON_PULSED_DOUBLE_ACTION_SPLIT) js->ReleaseButton(vbutton2); +} + + + +EncoderButton::EncoderButton(uint8_t pin1, uint8_t pin2, uint8_t vbutton) : Button(vbutton) { + this->type = ENCODER_PULSED_SPLIT; + this->vbutton2 = vbutton + 1; + this->encoder = new Encoder(pin1, pin2); + this->last_value = encoder->read(); +} + +void EncoderButton::Update(Joystick* js) { + long new_value = encoder->read(); + if (new_value > last_value) js->PressButton(vbutton); + else if (new_value < last_value) js->PressButton(vbutton2); + last_value = new_value; +} + +void EncoderButton::ReleaseButtons(Joystick* js) { + Button::ReleaseButtons(js); + js->ReleaseButton(vbutton2); +} diff --git a/Button.h b/Button.h new file mode 100644 index 0000000..1e522bc --- /dev/null +++ b/Button.h @@ -0,0 +1,97 @@ +#ifndef _BUTTON_H_ +#define _BUTTON_H_ + +#include +#include +#include + +using namespace admux; + +class Joystick; // forward declaration + +enum ButtonType { + BUTTON_PASSTHRU = 0x1, // always use the (debounced) absolute state of the input + BUTTON_PULSED = 0x2, // on button press, send an on signal followed immediately by an off signal. + BUTTON_PULSED_DOUBLE_ACTION = 0x4, // Send a button press twice - once for press and once for release. + BUTTON_PULSED_DOUBLE_ACTION_SPLIT = 0x8, // Send two separate button presses - one button on press, another on release. + BUTTON_LATCHED_MOMENTARY = 0x10, + ENCODER_PULSED_SPLIT = 0x20 // A rotary encoder that should be treated as two different pulsed/momentary buttons, one for each direction +}; + +// The abstract button base class. A button must have, at a minimum: +// * a button *type* (typically automatically assigned by the constructor) +// * at least one "virtual button" - that is, a Joystick input that it controls +// * an update method. +class Button { + public: + Button(uint8_t vbutton); + virtual void Update(Joystick* js) = 0; + virtual void ReleaseButtons(Joystick* js); + ButtonType type; + + protected: + uint8_t vbutton; +}; + +// An abstract class for a momentary/pushbutton or toggle switch. Special properties: +// * needs to be explicitly debounced +// * may use the microcontroller's built-in pullup resistor mode to eliminate noise +// * may be attached to a multiplexer. In this case, the `pin` value will be treated as a multiplexer channel, +// and the multiplexer logic will be automatically invoked by Update() +class SwitchButton : public Button { + public: + SwitchButton(uint8_t pin, uint8_t vbutton, bool pullup, Mux* mux); + virtual void Update(Joystick* js) = 0; + bool BouncerUpdate(); // returns true if the pin's status has changed + bool On(); + + protected: + Bounce bouncer; + bool inverted; + Mux *mux; + uint8_t channel_id; +}; + +class PassthruButton : public SwitchButton { + public: + PassthruButton(uint8_t pin, uint8_t vbutton, bool pullup = true, Mux* mux = NULL); + void Update(Joystick* js); +}; + +class LatchedButton : public SwitchButton { + public: + LatchedButton(uint8_t pin, uint8_t vbutton, bool pullup = true, Mux* mux = NULL); + void Update(Joystick* js); + + protected: + bool pressed; +}; + +class PulsedButton : public SwitchButton { + public: + PulsedButton(uint8_t pin, uint8_t vbutton, bool double_action = false, bool split = false, bool pullup = true, Mux* mux = NULL); + void Update(Joystick* js); + void ReleaseButtons(Joystick* js); + + protected: + bool double_action; + bool split; + uint8_t vbutton2; +}; + +class EncoderButton : public Button { + public: + EncoderButton(uint8_t pin1, uint8_t pin2, uint8_t vbutton); + void Update(Joystick* js); + void ReleaseButtons(Joystick* js); + + protected: + Encoder* encoder; + long last_value; + uint8_t vbutton2; +}; + +// Internal use only. +#define _BUTTON_PULSED_TYPES (BUTTON_PULSED | BUTTON_PULSED_DOUBLE_ACTION | BUTTON_PULSED_DOUBLE_ACTION_SPLIT | ENCODER_PULSED_SPLIT) + +#endif diff --git a/Joystick.cpp b/Joystick.cpp index 3ecf363..9069195 100644 --- a/Joystick.cpp +++ b/Joystick.cpp @@ -1,4 +1,5 @@ #include "Joystick.h" +#include "Button.h" #include #include @@ -22,7 +23,6 @@ Joystick::Joystick(bool debug) { _debug = debug; _virtual_buttons = 0; _num_axes = 0; - _num_mux = 0; _num_buttons = 0; _have_pulsed_button = false; @@ -39,21 +39,39 @@ void Joystick::Init() { delay(100); } -void Joystick::AddButton(uint8_t pin, ButtonType type, bool pullup) { - _BuildButton(pin, type, pullup); +void Joystick::AddButton(uint8_t pin, ButtonType type, bool pullup, Mux* mux) { + Button *button; + switch (type) { + case BUTTON_PASSTHRU: + button = new PassthruButton(pin, _virtual_buttons, pullup, mux); + _virtual_buttons++; + case BUTTON_PULSED: + button = new PulsedButton(pin, _virtual_buttons, false, false, pullup, mux); + _virtual_buttons++; + case BUTTON_PULSED_DOUBLE_ACTION: + button = new PulsedButton(pin, _virtual_buttons, true, false, pullup, mux); + _virtual_buttons++; + case BUTTON_PULSED_DOUBLE_ACTION_SPLIT: + button = new PulsedButton(pin, _virtual_buttons, true, true, pullup, mux); + _virtual_buttons += 2; + default: + return; + } + + _buttons[_num_buttons] = button; + _num_buttons++; + if (type & _BUTTON_PULSED_TYPES) _have_pulsed_button = true; } -void Joystick::AddEncoder(uint8_t pin0, uint8_t pin1, ButtonType type, bool pullup) { +void Joystick::AddEncoder(uint8_t pin1, uint8_t pin2, ButtonType type) { + Button *button; switch (type) { case ENCODER_PULSED_SPLIT: // add an encoder button. _BuildButton() doesn't do everything we need, however... - Button *button = _BuildButton(pin0, type, pullup); - - // ... so we set up the encoder stuff here. button->pin and button->bouncer will be - // ignored - button->encoder_button = true; - button->encoder = new Encoder(pin0, pin1); - long last_enc = button->encoder->read(); + button = new EncoderButton(pin1, pin2, _virtual_buttons); + _buttons[_num_buttons] = button; + _num_buttons++; + _virtual_buttons += 2; break; default: if (_debug) { @@ -63,70 +81,19 @@ void Joystick::AddEncoder(uint8_t pin0, uint8_t pin1, ButtonType type, bool pull } } -void Joystick::AddMuxButton(uint8_t mux_id, uint8_t mux_channel, ButtonType type, bool pullup) { - uint8_t pin = _mux[mux_id]->signalPin().pin; - Button *button = _BuildButton(pin, type, pullup); - button->mux = true; - button->mux_id = mux_id; - button->mux_channel = mux_channel; -} - -Button* Joystick::_BuildButton(uint8_t pin, ButtonType type, bool pullup) { - uint8_t mode; - if (pullup) mode = INPUT_PULLUP; - else mode = INPUT; - - Button button; - button.type = type; - button.inverted = pullup; - button.bouncer.attach(pin, mode); - uint8_t increment = 1; - button.vbutton0 = _virtual_buttons; - - if (type & (BUTTON_PULSED_DOUBLE_ACTION_SPLIT | ENCODER_PULSED_SPLIT)) { - increment = 2; - button.vbutton1 = _virtual_buttons + 1; - } - - if (_virtual_buttons + increment > JOYSTICK_NUM_BUTTONS) { - // todo: fail here - } - - _buttons[_num_buttons] = button; - _num_buttons++; - _virtual_buttons += increment; - - if (type & _BUTTON_PULSED_TYPES) _have_pulsed_button = true; - - return &button; -} - void Joystick::AddAxis(uint8_t pin) { _axes[_num_axes] = pin; _num_axes++; } -uint8_t Joystick::AddMux(uint8_t signal_pin, Pinset addr_pins, bool pullup) { - uint8_t mux_id = _num_mux; - uint8_t mode = INPUT_PULLUP; - if (!pullup) mode = INPUT; - Mux mux(Pin(signal_pin, mode, PinType::Digital), addr_pins); - _mux[mux_id] = &mux; - _num_mux++; - return mux_id; -} - void Joystick::Update() { JoyReport oldReport = _joyReport; for (uint8_t i = 0; i < _num_buttons; i++) { - if (_buttons[i].type == ENCODER_PULSED_SPLIT) { // todo: make this check for any encoder type - _UpdateEncoder(i); - } else { - _UpdateButton(i); - } + _buttons[i]->Update(this); } + // TODO: implement this and also refactor it into a class or classes for (uint8_t i = 0; i < _num_axes; i++) { _UpdateAxis(i); } @@ -167,9 +134,8 @@ void Joystick::ReleaseAllButtons() { void Joystick::_ReleasePulsedButtons() { for (uint8_t i = 0; i < _num_buttons; i++ ) { - Button button = _buttons[i]; - if (button.type & _BUTTON_PULSED_TYPES) ReleaseButton(button.vbutton0); - if (button.type & BUTTON_PULSED_DOUBLE_ACTION_SPLIT) ReleaseButton(button.vbutton1); + Button* button = _buttons[i]; + if (button->type & _BUTTON_PULSED_TYPES) button->ReleaseButtons(this); } } @@ -192,59 +158,7 @@ void Joystick::Write() { delay(250); } -// todo: bite the bullet and use inheritance here, this is getting out of hand -void Joystick::_UpdateButton(uint8_t button_num) { - Button *button = &_buttons[button_num]; - if (button->mux) { - _mux[button->mux_id]->channel(button->mux_channel); - } - bool changed = button->bouncer.update(); - if (!changed) return; - bool on = button->bouncer.rose(); - if (button->inverted) on = button->bouncer.fell(); - - switch (button->type) { - case BUTTON_PASSTHRU: - if (on) PressButton(button->vbutton0); - else ReleaseButton(button->vbutton0); - break; - case BUTTON_PULSED: - if (on) PressButton(button->vbutton0); - break; - case BUTTON_PULSED_DOUBLE_ACTION: - PressButton(button->vbutton0); - break; - case BUTTON_PULSED_DOUBLE_ACTION_SPLIT: - if (on) PressButton(button->vbutton0); - else PressButton(button->vbutton1); - break; - case BUTTON_LATCHED_MOMENTARY: - if (on) { - if (!button->pressed) { - PressButton(button->vbutton0); - button->pressed = true; - } else { - ReleaseButton(button->vbutton0); - button->pressed = false; - } - } - break; - default: - if (_debug) { - Serial.print("DEBUG: Unhandled button type: "); - Serial.println(button->type); - } - } -} - -void Joystick::_UpdateEncoder(uint8_t index) { - Button *button = &_buttons[index]; - long new_value = button->encoder->read(); - if (new_value > button->last_enc) PressButton(button->vbutton0); - else if (new_value < button->last_enc) PressButton(button->vbutton1); - button->last_enc = new_value; -} void Joystick::_UpdateAxis(uint8_t index) { if (_debug) Serial.println("STUB: Joystick::_UpdateAxis"); diff --git a/Joystick.h b/Joystick.h index 39fd0e3..95fb4db 100644 --- a/Joystick.h +++ b/Joystick.h @@ -1,6 +1,7 @@ #ifndef _JOYSTICK_H_ #define _JOYSTICK_H_ +#include "Button.h" #include #include #include @@ -19,44 +20,11 @@ using namespace admux; #endif #define JOYSTICK_NUM_BYTES (JOYSTICK_NUM_BUTTONS+7)/8 -#ifndef MAX_MUX -#define MAX_MUX 3 -#endif - -enum ButtonType { - BUTTON_PASSTHRU = 0x1, // always use the (debounced) absolute state of the input - BUTTON_PULSED = 0x2, // on button press, send an on signal followed immediately by an off signal. - BUTTON_PULSED_DOUBLE_ACTION = 0x4, // Send a button press twice - once for press and once for release. - BUTTON_PULSED_DOUBLE_ACTION_SPLIT = 0x8, // Send two separate button presses - one button on press, another on release. - BUTTON_LATCHED_MOMENTARY = 0x10, - ENCODER_PULSED_SPLIT = 0x20 // A rotary encoder that should be treated as two different pulsed/momentary buttons, one for each direction -}; - struct JoyReport { int16_t axis[JOYSTICK_NUM_AXES]; uint8_t button[JOYSTICK_NUM_BYTES]; }; -struct Button { - ButtonType type; - Bounce bouncer; - uint8_t vbutton0; - uint8_t vbutton1; // only used by BUTTON_PULSED_DOUBLE_ACTION_SPLIT - bool pressed = false; // only used by BUTTON_LATCHED_MOMENTARY - bool inverted = false; // if true, send button press on release and vice versa. - - // multiplexer button settings - // todo: this should probably be abstracted out from this struct... - bool mux; - uint8_t mux_id; - uint8_t mux_channel; - - // encoder button settings - bool encoder_button; - Encoder* encoder; - long last_enc; -}; - bool operator ==(JoyReport a, JoyReport b); bool operator !=(JoyReport a, JoyReport b); @@ -70,35 +38,26 @@ class Joystick { // Button types are documented in the ButtonType enum. // If `pullup` is true, your button should connect the pin to ground. (also be sure that your board supports INPUT_PULLUP on that pin) // If `pullup` is false, your button should connect the pin to VCC. - // Mux parameters are ignored unless `mux` is true. - void AddButton(uint8_t pin, ButtonType type, bool pullup=true); + void AddButton(uint8_t pin, ButtonType type, bool pullup=true, Mux* mux=NULL); + // Add a rotary encoder. ENCODER button types allow you to treat an encoder as a momentary button or an axis (TODO) - void AddEncoder(uint8_t pin0, uint8_t pin1, ButtonType type, bool pullup=true); - void AddMuxButton(uint8_t mux_id, uint8_t mux_channel, ButtonType type, bool pullup=true); + void AddEncoder(uint8_t pin1, uint8_t pin2, ButtonType type); // Add an analog axis to the joystick. THIS METHOD IS NOT CURRENTLY TESTED OR SUPPORTED. It might work, but probably not. void AddAxis(uint8_t pin); - // Add control for a multiplexer. To add a button that's connected via the multiplexer, - // pass the mux's signal pin as the pin parameter to AddButton(), along with the mux* parameters. - // mux_id is the array index of the mux (in the order you added them via AddMux()) - uint8_t AddMux(uint8_t signal_pin, Pinset addr_pins, bool pullup=true); - - private: - void SetAxis(uint8_t axis, int16_t value); void PressButton(uint8_t button); void ReleaseButton(uint8_t button); + + private: + void SetAxis(uint8_t axis, int16_t value); void ReleaseAllButtons(); void Write(); void _ReleasePulsedButtons(); - void _UpdateButton(uint8_t index); - void _UpdateEncoder(uint8_t index); void _UpdateAxis(uint8_t index); - Button* _BuildButton(uint8_t pin, ButtonType type, bool pullup); - - Button _buttons[JOYSTICK_NUM_BUTTONS]; + Button* _buttons[JOYSTICK_NUM_BUTTONS]; uint8_t _num_buttons; uint8_t _virtual_buttons; // a single user-defined button can have multiple virtual buttons. bool _have_pulsed_button; @@ -108,12 +67,6 @@ class Joystick { JoyReport _joyReport; bool _debug; - - Mux *_mux[MAX_MUX]; - uint8_t _num_mux; }; -// Internal use only. -#define _BUTTON_PULSED_TYPES (BUTTON_PULSED | BUTTON_PULSED_DOUBLE_ACTION | BUTTON_PULSED_DOUBLE_ACTION_SPLIT | ENCODER_PULSED_SPLIT) - #endif diff --git a/examples/full/full.ino b/examples/full/full.ino index 9f8e592..e62d88a 100644 --- a/examples/full/full.ino +++ b/examples/full/full.ino @@ -5,7 +5,7 @@ #include #include -using admux::Pinset; +using namespace admux; bool debug = false; Joystick joystick(debug); @@ -26,7 +26,9 @@ void setup() { Pinset addr_pins = Pinset(10, 11, 12, 13); uint8_t mux_id = joystick.AddMux(9, addr_pins); - joystick.AddMuxButton(mux_id, 0, BUTTON_PASSTHRU); + Mux* mux = new Mux(Pin(9, INPUT_PULLUP, PinType::Digital), Pinset(10, 11, 12, 13)); + + joystick.AddButton(0, BUTTON_PASSTHRU, true, mux); // start up serial communication joystick.Init(); diff --git a/examples/multiplexer/multiplexer.ino b/examples/multiplexer/multiplexer.ino index 5817fd4..e299aba 100644 --- a/examples/multiplexer/multiplexer.ino +++ b/examples/multiplexer/multiplexer.ino @@ -9,6 +9,8 @@ #include #include +using namespace admux; + bool debug = false; Joystick joystick(debug); @@ -29,17 +31,17 @@ void setup() { joystick.AddButton(A0, BUTTON_PASSTHRU); // to get more room for our inputs, we add an 8-bit multiplexer - uint8_t mux = joystick.AddMux(A1, admux::Pinset(A2, A3, A4, A5), 4); + Mux* mux = new Mux(Pin(A1, INPUT_PULLUP, PinType::Digital), Pinset(A2, A3, A4, A5)); // now we can add the rest of the buttons - joystick.AddMuxButton(mux, 0, BUTTON_PASSTHRU); - joystick.AddMuxButton(mux, 1, BUTTON_PASSTHRU); - joystick.AddMuxButton(mux, 2, BUTTON_PASSTHRU); - joystick.AddMuxButton(mux, 3, BUTTON_PASSTHRU); - joystick.AddMuxButton(mux, 4, BUTTON_PASSTHRU); - joystick.AddMuxButton(mux, 5, BUTTON_PASSTHRU); - joystick.AddMuxButton(mux, 6, BUTTON_PASSTHRU); - joystick.AddMuxButton(mux, 7, BUTTON_PASSTHRU); + joystick.AddButton(0, BUTTON_PASSTHRU, true, mux); + joystick.AddButton(1, BUTTON_PASSTHRU, true, mux); + joystick.AddButton(2, BUTTON_PASSTHRU, true, mux); + joystick.AddButton(3, BUTTON_PASSTHRU, true, mux); + joystick.AddButton(4, BUTTON_PASSTHRU, true, mux); + joystick.AddButton(5, BUTTON_PASSTHRU, true, mux); + joystick.AddButton(6, BUTTON_PASSTHRU, true, mux); + joystick.AddButton(7, BUTTON_PASSTHRU, true, mux); joystick.Init(); }