├── src ├── pin │ └── pin.h ├── Controlino.h ├── mode │ └── mode.h ├── selector │ ├── selector.h │ └── selector.cpp ├── multiplexer │ ├── multiplexer.h │ └── multiplexer.cpp ├── control │ ├── control.h │ └── control.cpp ├── key │ ├── key.h │ └── key.cpp ├── pinmode │ ├── pinmode.h │ └── pinmode.cpp ├── potentiometer │ ├── potentiometer.h │ └── potentiometer.cpp └── button │ ├── button.h │ └── button.cpp ├── library.properties ├── keywords.txt ├── LICENSE ├── examples ├── Potentiometer │ └── Potentiometer.ino ├── Button │ └── Button.ino ├── Key │ └── Key.ino └── Complex │ └── Complex.ino └── README.md /src/pin/pin.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace controlino 4 | { 5 | 6 | using Pin = char; 7 | 8 | } // controlino 9 | -------------------------------------------------------------------------------- /src/Controlino.h: -------------------------------------------------------------------------------- 1 | #include "button/button.h" 2 | #include "key/key.h" 3 | #include "multiplexer/multiplexer.h" 4 | #include "potentiometer/potentiometer.h" 5 | #include "selector/selector.h" 6 | -------------------------------------------------------------------------------- /src/mode/mode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace controlino 6 | { 7 | 8 | enum class Mode : char 9 | { 10 | Input = INPUT, // pull down 11 | Output = OUTPUT, 12 | Pullup = INPUT_PULLUP, 13 | }; 14 | 15 | } // controlino 16 | -------------------------------------------------------------------------------- /src/selector/selector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../pin/pin.h" 4 | 5 | namespace controlino 6 | { 7 | 8 | struct Selector 9 | { 10 | Selector(Pin S0, Pin S1, Pin S2); // 8 channels (3 bits) 11 | Selector(Pin S0, Pin S1, Pin S2, Pin S3); // 16 channels (4 bits) 12 | 13 | void select(Pin pin); 14 | 15 | private: 16 | Pin _pins[4]; // [lsb...msb] 17 | }; 18 | 19 | } // controlino 20 | -------------------------------------------------------------------------------- /src/multiplexer/multiplexer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../pinmode/pinmode.h" 4 | #include "../selector/selector.h" 5 | 6 | namespace controlino 7 | { 8 | 9 | struct Multiplexer 10 | { 11 | Multiplexer(Pin comm, Selector & selector); 12 | 13 | int digitalRead(Pin pin); 14 | int analogRead(Pin pin); 15 | 16 | void pinMode(Mode mode); 17 | 18 | private: 19 | Pinmode _comm; 20 | Selector & _selector; 21 | }; 22 | 23 | } // controlino 24 | -------------------------------------------------------------------------------- /src/control/control.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../multiplexer/multiplexer.h" 4 | #include "../pinmode/pinmode.h" 5 | 6 | namespace controlino 7 | { 8 | 9 | struct Control 10 | { 11 | protected: 12 | Control(Pin pin, Mode mode); 13 | Control(Multiplexer & multiplexer, Pin pin, Mode mode); 14 | 15 | int digitalRead(); 16 | int analogRead(); 17 | 18 | private: 19 | Pinmode _pinmode; 20 | Multiplexer * _multiplexer; 21 | }; 22 | 23 | } // controlino 24 | -------------------------------------------------------------------------------- /src/key/key.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../control/control.h" 4 | 5 | namespace controlino 6 | { 7 | 8 | struct Key : Control 9 | { 10 | enum class Event : char 11 | { 12 | None, // nothing has happened 13 | 14 | Down, 15 | Up, 16 | Hold, 17 | }; 18 | 19 | Key(Pin pin); 20 | Key(Multiplexer & multiplexer, Pin pin); 21 | 22 | Event check(); 23 | 24 | private: 25 | bool _previous = false; 26 | }; 27 | 28 | } // controlino 29 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Controlino 2 | version=1.2.1 3 | author=Raz Haleva 4 | maintainer=Raz Haleva 5 | sentence=A library for using direct or multiplexed input controls easily. 6 | paragraph=Written in C++ and offers simple and complex potentiometer and button gestures such as down/up/click/double-click/press/click-and-press. All controls can be connected through a multiplexer. 7 | category=Signal Input/Output 8 | url=https://github.com/levosos/Controlino 9 | architectures=* 10 | includes=Controlino.h 11 | -------------------------------------------------------------------------------- /src/pinmode/pinmode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../mode/mode.h" 4 | #include "../pin/pin.h" 5 | 6 | namespace controlino 7 | { 8 | 9 | struct Pinmode 10 | { 11 | Pinmode(Pin pin, Mode mode); 12 | 13 | // setters 14 | void pin(Pin value); 15 | void mode(Mode value); 16 | 17 | // getters 18 | Pin pin() const; 19 | Mode mode() const; 20 | 21 | private: 22 | // 2 msbs are the mode and are enough to represent the possible 3 modes 23 | // 6 lsbs are the pin and are enough to represent numbers up to 64 24 | char _data; 25 | }; 26 | 27 | } // controlino 28 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | # Datatypes (KEYWORD1) 2 | 3 | Button KEYWORD1 4 | Control KEYWORD1 5 | Event KEYWORD1 6 | Key KEYWORD1 7 | Mode KEYWORD1 8 | Multiplexer KEYWORD1 9 | Pin KEYWORD1 10 | Potentiometer KEYWORD1 11 | Selector KEYWORD1 12 | 13 | # Methods and Functions (KEYWORD2) 14 | 15 | check KEYWORD2 16 | select KEYWORD2 17 | 18 | # Constants (LITERAL1) 19 | 20 | None LITERAL1 21 | Down LITERAL1 22 | Up LITERAL1 23 | Click LITERAL1 24 | Press LITERAL1 25 | ClickClick LITERAL1 26 | ClickPress LITERAL1 27 | Hold LITERAL1 28 | Changed LITERAL1 29 | 30 | Input LITERAL1 31 | Output LITERAL1 32 | Pullup LITERAL1 33 | -------------------------------------------------------------------------------- /src/pinmode/pinmode.cpp: -------------------------------------------------------------------------------- 1 | #include "pinmode.h" 2 | 3 | namespace controlino 4 | { 5 | 6 | Pinmode::Pinmode(Pin pin, Mode mode) 7 | { 8 | this->pin(pin); 9 | this->mode(mode); 10 | } 11 | 12 | void Pinmode::pin(Pin value) 13 | { 14 | _data &= 0b11000000; 15 | _data |= (value & 0b00111111); 16 | } 17 | 18 | void Pinmode::mode(Mode value) 19 | { 20 | _data &= 0b00111111; 21 | _data |= (((char)value << 6) & 0b11000000); 22 | } 23 | 24 | Pin Pinmode::pin() const 25 | { 26 | return (Pin)(_data & 0b00111111); 27 | } 28 | 29 | Mode Pinmode::mode() const 30 | { 31 | return (Mode)((_data >> 6) & 0b00000011); 32 | } 33 | 34 | } // controlino 35 | -------------------------------------------------------------------------------- /src/key/key.cpp: -------------------------------------------------------------------------------- 1 | #include "key.h" 2 | 3 | namespace controlino 4 | { 5 | 6 | Key::Key(Pin pin) : Control(pin, Mode::Pullup) 7 | {} 8 | 9 | Key::Key(Multiplexer & multiplexer, Pin pin) : Control(multiplexer, pin, Mode::Pullup) 10 | {} 11 | 12 | Key::Event Key::check() 13 | { 14 | auto event = Event::None; 15 | 16 | const auto pressed = digitalRead() == LOW; 17 | 18 | if (pressed == true && _previous == false) 19 | { 20 | event = Event::Down; 21 | } 22 | else if (pressed == false && _previous == true) 23 | { 24 | event = Event::Up; 25 | } 26 | else if (pressed == true && _previous == true) 27 | { 28 | event = Event::Hold; 29 | } 30 | 31 | _previous = pressed; 32 | return event; 33 | } 34 | 35 | } // controlino 36 | -------------------------------------------------------------------------------- /src/multiplexer/multiplexer.cpp: -------------------------------------------------------------------------------- 1 | #include "multiplexer.h" 2 | 3 | namespace controlino 4 | { 5 | 6 | Multiplexer::Multiplexer(Pin comm, Selector & selector) : 7 | _comm(comm, (Mode)0b11), // 0b11 (3) is not a valid mode and will cause the pin to be configured at the first usage 8 | _selector(selector) 9 | {} 10 | 11 | int Multiplexer::digitalRead(Pin pin) 12 | { 13 | _selector.select(pin); 14 | return ::digitalRead(_comm.pin()); 15 | } 16 | 17 | int Multiplexer::analogRead(Pin pin) 18 | { 19 | _selector.select(pin); 20 | return ::analogRead(_comm.pin()); 21 | } 22 | 23 | void Multiplexer::pinMode(Mode mode) 24 | { 25 | if (mode != _comm.mode()) 26 | { 27 | ::pinMode(_comm.pin(), (char)mode); 28 | _comm.mode(mode); 29 | } 30 | } 31 | 32 | } // controlino 33 | -------------------------------------------------------------------------------- /src/selector/selector.cpp: -------------------------------------------------------------------------------- 1 | #include "selector.h" 2 | 3 | #include 4 | 5 | namespace controlino 6 | { 7 | 8 | Selector::Selector(Pin S0, Pin S1, Pin S2) : Selector(S0, S1, S2, -1) 9 | {} 10 | 11 | Selector::Selector(Pin S0, Pin S1, Pin S2, Pin S3) 12 | { 13 | _pins[0] = S0; 14 | _pins[1] = S1; 15 | _pins[2] = S2; 16 | _pins[3] = S3; 17 | 18 | for (unsigned i = 0; i < sizeof(_pins) / sizeof(_pins[0]); ++i) 19 | { 20 | if (_pins[i] != -1) 21 | { 22 | pinMode(_pins[i], OUTPUT); 23 | } 24 | } 25 | } 26 | 27 | void Selector::select(Pin pin) 28 | { 29 | for (unsigned i = 0; i < sizeof(_pins) / sizeof(_pins[0]); ++i) 30 | { 31 | if (_pins[i] != -1) 32 | { 33 | digitalWrite(_pins[i], (pin & (1 << i)) ? HIGH : LOW); 34 | } 35 | } 36 | } 37 | 38 | } // controlino 39 | -------------------------------------------------------------------------------- /src/control/control.cpp: -------------------------------------------------------------------------------- 1 | #include "control.h" 2 | 3 | #include 4 | 5 | namespace controlino 6 | { 7 | 8 | Control::Control(Pin pin, Mode mode) : 9 | _pinmode(pin, mode), 10 | _multiplexer(nullptr) 11 | { 12 | pinMode(pin, (char)mode); 13 | } 14 | 15 | Control::Control(Multiplexer & multiplexer, Pin pin, Mode mode) : 16 | _pinmode(pin, mode), 17 | _multiplexer(&multiplexer) 18 | {} 19 | 20 | int Control::digitalRead() 21 | { 22 | if (_multiplexer != nullptr) 23 | { 24 | _multiplexer->pinMode(_pinmode.mode()); 25 | return _multiplexer->digitalRead(_pinmode.pin()); 26 | } 27 | else 28 | { 29 | return ::digitalRead(_pinmode.pin()); 30 | } 31 | } 32 | 33 | int Control::analogRead() 34 | { 35 | if (_multiplexer != nullptr) 36 | { 37 | _multiplexer->pinMode(_pinmode.mode()); 38 | return _multiplexer->analogRead(_pinmode.pin()); 39 | } 40 | else 41 | { 42 | return ::analogRead(_pinmode.pin()); 43 | } 44 | } 45 | 46 | } // controlino 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Raz Rotenberg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/Potentiometer/Potentiometer.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // `MUXED` should be defined if the potentiometer is connected through a multiplexer 4 | // #define MUXED 5 | 6 | namespace controlino 7 | { 8 | 9 | namespace 10 | { 11 | namespace pin 12 | { 13 | 14 | #ifdef MUXED 15 | 16 | constexpr Pin S0 = 2; 17 | constexpr Pin S1 = 3; 18 | constexpr Pin S2 = 4; 19 | constexpr Pin S3 = 5; 20 | 21 | constexpr Pin SIG = A0; 22 | 23 | constexpr Pin Potentiometer = 0; 24 | 25 | #else 26 | 27 | constexpr Pin Potentiometer = A0; 28 | 29 | #endif 30 | 31 | }; // pin 32 | }; // 33 | 34 | extern "C" void setup() 35 | { 36 | Serial.begin(9600); 37 | } 38 | 39 | extern "C" void loop() 40 | { 41 | #ifdef MUXED 42 | static auto __selector = Selector(pin::S0, pin::S1, pin::S2, pin::S3); 43 | static auto __multiplexer = Multiplexer(pin::SIG, __selector); 44 | static auto __potentiometer = Potentiometer(__multiplexer, pin::Potentiometer); 45 | #else 46 | static auto __potentiometer = Potentiometer(pin::Potentiometer); 47 | #endif 48 | 49 | if (__potentiometer.check() == Potentiometer::Event::Changed) 50 | { 51 | Serial.println(__potentiometer.read()); 52 | } 53 | } 54 | 55 | } // controlino 56 | -------------------------------------------------------------------------------- /src/potentiometer/potentiometer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../control/control.h" 4 | 5 | #include 6 | 7 | namespace controlino 8 | { 9 | 10 | struct Potentiometer : Control 11 | { 12 | struct Noise 13 | { 14 | // encapsulating all configurable parameters 15 | // that are dependent on the setup itself 16 | 17 | Noise() : Noise(0.8, 1.1, 2) 18 | {} 19 | 20 | Noise(float down, float up, unsigned char fluctuation) : 21 | down(down), 22 | up(up), 23 | fluctuation(fluctuation) 24 | {} 25 | 26 | float down; // a multiplier for lowering 'min' 27 | float up; // a multiplier for uppering 'max' 28 | unsigned char fluctuation; // how much noise is acceptible 29 | }; 30 | 31 | enum class Event : char 32 | { 33 | None, // nothing has happened 34 | 35 | Changed, 36 | }; 37 | 38 | Potentiometer(Pin pin, const Noise & noise = {}); 39 | Potentiometer(Pin pin, int min = -1, int max = -1, const Noise & noise = {}); 40 | Potentiometer(Multiplexer & multiplexer, Pin pin, const Noise & noise = {}); 41 | Potentiometer(Multiplexer & multiplexer, Pin pin, int min = -1, int max = -1, const Noise & noise = {}); 42 | 43 | Event check(); 44 | int read(); 45 | 46 | private: 47 | int _min; 48 | int _max; 49 | Noise _noise; 50 | int _previous; 51 | }; 52 | 53 | } // controlino 54 | -------------------------------------------------------------------------------- /src/potentiometer/potentiometer.cpp: -------------------------------------------------------------------------------- 1 | #include "potentiometer.h" 2 | 3 | namespace controlino 4 | { 5 | 6 | Potentiometer::Potentiometer(Pin pin, const Noise & noise) : Potentiometer(pin, -1, -1, noise) 7 | {} 8 | 9 | Potentiometer::Potentiometer(Pin pin, int min, int max, const Noise & noise) : Control(pin, Mode::Input), 10 | _min(min), 11 | _max(max), 12 | _noise(noise), 13 | _previous(analogRead()) 14 | {} 15 | 16 | Potentiometer::Potentiometer(Multiplexer & multiplexer, Pin pin, const Noise & noise) : Potentiometer(multiplexer, pin, -1, -1, noise) 17 | {} 18 | 19 | Potentiometer::Potentiometer(Multiplexer & multiplexer, Pin pin, int min, int max, const Noise & noise) : Control(multiplexer, pin, Mode::Input), 20 | _min(min), 21 | _max(max), 22 | _noise(noise), 23 | _previous(analogRead()) 24 | {} 25 | 26 | Potentiometer::Event Potentiometer::check() 27 | { 28 | auto event = Event::None; 29 | 30 | const int now = analogRead(); 31 | 32 | if (abs(now - _previous) > _noise.fluctuation) 33 | { 34 | _previous = now; 35 | event = Event::Changed; 36 | } 37 | 38 | return event; 39 | } 40 | 41 | int Potentiometer::read() 42 | { 43 | int value = _previous; 44 | 45 | if (_min != -1 && _max != -1) 46 | { 47 | value = constrain( 48 | map(value, 0, 1023, (int)((float)_min * _noise.down), (int)((float)_max * _noise.up)), 49 | _min, _max); 50 | } 51 | 52 | return value; 53 | } 54 | 55 | } // controlino 56 | -------------------------------------------------------------------------------- /examples/Button/Button.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // `MUXED` should be defined if the button is connected through a multiplexer 4 | // #define MUXED 5 | 6 | namespace controlino 7 | { 8 | 9 | namespace 10 | { 11 | namespace pin 12 | { 13 | 14 | #ifdef MUXED 15 | 16 | constexpr Pin S0 = 2; 17 | constexpr Pin S1 = 3; 18 | constexpr Pin S2 = 4; 19 | constexpr Pin S3 = 5; 20 | 21 | constexpr Pin SIG = 6; 22 | 23 | constexpr Pin Button = 0; 24 | 25 | #else 26 | 27 | constexpr Pin Button = 7; 28 | 29 | #endif 30 | 31 | }; // pin 32 | }; // 33 | 34 | extern "C" void setup() 35 | { 36 | Serial.begin(9600); 37 | } 38 | 39 | extern "C" void loop() 40 | { 41 | #ifdef MUXED 42 | static auto __selector = Selector(pin::S0, pin::S1, pin::S2, pin::S3); 43 | static auto __multiplexer = Multiplexer(pin::SIG, __selector); 44 | static auto __button = Button(__multiplexer, pin::Button); 45 | #else 46 | static auto __button = Button(pin::Button); 47 | #endif 48 | 49 | const auto event = __button.check(); 50 | 51 | if (event == Button::Event::Down) { Serial.println("Down"); } 52 | else if (event == Button::Event::Up) { Serial.println("Up"); } 53 | else if (event == Button::Event::Click) { Serial.println("Click"); } 54 | else if (event == Button::Event::Press) { Serial.println("Press"); } 55 | else if (event == Button::Event::ClickClick) { Serial.println("ClickClick"); } 56 | else if (event == Button::Event::ClickPress) { Serial.println("ClickPress"); } 57 | } 58 | 59 | } // controlino 60 | -------------------------------------------------------------------------------- /examples/Key/Key.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // `MUXED` should be defined if the key is connected through a multiplexer 4 | // #define MUXED 5 | 6 | namespace controlino 7 | { 8 | 9 | namespace 10 | { 11 | namespace pin 12 | { 13 | 14 | #ifdef MUXED 15 | 16 | constexpr Pin S0 = 2; 17 | constexpr Pin S1 = 3; 18 | constexpr Pin S2 = 4; 19 | constexpr Pin S3 = 5; 20 | 21 | constexpr Pin SIG = 6; 22 | 23 | constexpr Pin Key = 0; 24 | 25 | #else 26 | 27 | constexpr Pin Key = 7; 28 | 29 | #endif 30 | 31 | }; // pin 32 | }; // 33 | 34 | extern "C" void setup() 35 | { 36 | Serial.begin(9600); 37 | pinMode(LED_BUILTIN, OUTPUT); 38 | } 39 | 40 | extern "C" void loop() 41 | { 42 | #ifdef MUXED 43 | static auto __selector = Selector(pin::S0, pin::S1, pin::S2, pin::S3); 44 | static auto __multiplexer = Multiplexer(pin::SIG, __selector); 45 | static auto __key = Key(__multiplexer, pin::Key); 46 | #else 47 | static auto __key = Key(pin::Key); 48 | #endif 49 | 50 | const auto event = __key.check(); 51 | 52 | if (event == Key::Event::Down) 53 | { 54 | digitalWrite(LED_BUILTIN, HIGH); 55 | Serial.println("Down"); 56 | } 57 | else if (event == Key::Event::Hold) 58 | { 59 | static unsigned long __millis = -1; 60 | 61 | if (__millis == -1 || millis() - __millis > 200) 62 | { 63 | Serial.println("Hold"); 64 | __millis = millis(); 65 | } 66 | } 67 | else if (event == Key::Event::Up) 68 | { 69 | digitalWrite(LED_BUILTIN, LOW); 70 | Serial.println("Up"); 71 | } 72 | } 73 | 74 | } // controlino 75 | -------------------------------------------------------------------------------- /src/button/button.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../control/control.h" 4 | 5 | namespace controlino 6 | { 7 | 8 | struct Button : Control 9 | { 10 | enum class Event : char 11 | { 12 | None, // nothing has happened 13 | 14 | Down, 15 | Up, 16 | Click, 17 | Press, 18 | ClickClick, 19 | ClickPress, 20 | }; 21 | 22 | Button(Pin pin); 23 | Button(Multiplexer & multiplexer, Pin pin); 24 | 25 | Event check(); 26 | 27 | private: 28 | enum class What : char 29 | { 30 | Idle, 31 | Touch, 32 | Press, 33 | Slip, 34 | Release, 35 | SecondPress, 36 | SecondRelease, 37 | Drain, 38 | }; 39 | 40 | What _what = What::Idle; 41 | 42 | // 16 bits can hold 65,535 possible values, which is enough for storing information for ~65 seconds 43 | // Arduino's `millis()` returns an `unsigned long` which overflows back to (0) after approximately 50 days. 44 | // when we take the 16 lsbs (2 bytes) we receive a number that will overflow after every ~65 seconds. 45 | // therefore, in order to be able to correctly compare time differences, we have to calculate the time 46 | // difference only at one side of the furmula: 47 | // good: `now - _when > 1000` 48 | // bad: `now > _whan + 1000` 49 | // this will cause the overflow to "correct itself" when it's in the same side of the formula 50 | // 51 | // example: 52 | // unsigned short a = 1; 53 | // unsigned short b = USHRT_MAX; 54 | // unsigned short c = a - b; 55 | // std::cout << c << std::endl; // will print 2 56 | // 57 | unsigned short _when; 58 | }; 59 | 60 | } // controlino 61 | -------------------------------------------------------------------------------- /examples/Complex/Complex.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace controlino; 4 | 5 | namespace pin 6 | { 7 | constexpr auto S0 = 2; 8 | constexpr auto S1 = 3; 9 | constexpr auto S2 = 4; 10 | constexpr auto S3 = 5; 11 | 12 | constexpr auto SIG = A5; 13 | }; // pin 14 | 15 | void setup() 16 | { 17 | Serial.begin(9600); 18 | } 19 | 20 | void loop() 21 | { 22 | static Selector __selector(pin::S0, pin::S1, pin::S2, pin::S3); 23 | static Multiplexer __multiplexer(pin::SIG, __selector); 24 | 25 | static Button __buttons[] = { 26 | { __multiplexer, 0 }, 27 | { 10 }, 28 | }; 29 | 30 | for (auto & button : __buttons) 31 | { 32 | const auto event = button.check(); 33 | 34 | if (event == Button::Event::Down) { Serial.println("Down"); } 35 | else if (event == Button::Event::Up) { Serial.println("Up"); } 36 | else if (event == Button::Event::Click) { Serial.println("Click"); } 37 | else if (event == Button::Event::Press) { Serial.println("Press"); } 38 | else if (event == Button::Event::ClickClick) { Serial.println("ClickClick"); } 39 | else if (event == Button::Event::ClickPress) { Serial.println("ClickPress"); } 40 | } 41 | 42 | static Key __keys[] = { 43 | { __multiplexer, 5 }, 44 | { 9 }, 45 | }; 46 | 47 | for (auto & key : __keys) 48 | { 49 | const auto event = key.check(); 50 | 51 | if (event == Key::Event::Down) { Serial.println("Down"); } 52 | else if (event == Key::Event::Up) { Serial.println("Up"); } 53 | } 54 | 55 | using Potentiometer = Potentiometer<20, 230>; 56 | 57 | static Potentiometer __potentiometers[] = { 58 | { __multiplexer, 10 }, 59 | { A0 }, 60 | }; 61 | 62 | for (auto & potentiometer : __potentiometers) 63 | { 64 | if (potentiometer.check() == Potentiometer::Event::Changed) 65 | { 66 | Serial.println(potentiometer.read()); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/button/button.cpp: -------------------------------------------------------------------------------- 1 | #include "button.h" 2 | 3 | #include 4 | 5 | namespace controlino 6 | { 7 | 8 | namespace duration 9 | { 10 | 11 | // values are in ms 12 | 13 | constexpr unsigned short Touch = 20; 14 | constexpr unsigned short Slip = 20; 15 | constexpr unsigned short Press = 800; 16 | constexpr unsigned short DoubleClick = 220; 17 | 18 | } // duration 19 | 20 | Button::Button(Pin pin) : Control(pin, Mode::Pullup) 21 | {} 22 | 23 | Button::Button(Multiplexer & multiplexer, Pin pin) : Control(multiplexer, pin, Mode::Pullup) 24 | {} 25 | 26 | Button::Event Button::check() 27 | { 28 | const auto now = static_cast(millis()); 29 | 30 | static_assert(sizeof(now) == sizeof(Button::_when), "Sizes must match"); // otherwise calculating the difference would be misleading 31 | 32 | const auto pressed = digitalRead() == LOW; 33 | 34 | if (pressed == true && _what == What::Idle) 35 | { 36 | // we tell the user nothing as we want to make sure this is not just a touch but an actual press 37 | 38 | _what = What::Touch; 39 | _when = now; 40 | return Event::None; 41 | } 42 | 43 | if (pressed == false && _what == What::Touch) 44 | { 45 | // go back to idle mode as the touch was probably just a glitch 46 | 47 | _what = What::Idle; 48 | return Event::None; 49 | } 50 | 51 | if (pressed == true && _what == What::Touch && (unsigned short)(now - _when) > duration::Touch) 52 | { 53 | // now we are sure this is an actual press 54 | 55 | _what = What::Press; 56 | // don't change '_when' as we want to count from the touch 57 | return Event::Down; 58 | } 59 | 60 | if (pressed == true && _what == What::Press && (unsigned short)(now - _when) >= duration::Press) 61 | { 62 | // the button is being pressed long enough for it to count as a long press 63 | 64 | _what = What::Drain; 65 | return Event::Press; 66 | } 67 | 68 | if (pressed == false && _what == What::Press) 69 | { 70 | // we tell the user nothing as we want to make sure this is not just a slip but an actual release 71 | 72 | _what = What::Slip; 73 | _when = now; 74 | return Event::None; 75 | } 76 | 77 | if (pressed == true && _what == What::Slip) 78 | { 79 | // ignore this slip and act as if the button is still being pressed as it was repressed right away 80 | 81 | _what = What::Press; 82 | // although '_when' was modified when the slip took place, it is the best we could do 83 | return Event::None; 84 | } 85 | 86 | if (pressed == false && _what == What::Slip && (unsigned short)(now - _when) > duration::Slip) 87 | { 88 | // now we are sure this is an actual release 89 | 90 | _what = What::Release; 91 | // don't change '_when' as we want to count from the slip 92 | return Event::Up; 93 | } 94 | 95 | if (pressed == false && _what == What::Release && (unsigned short)(now - _when) > duration::DoubleClick) 96 | { 97 | // this is a single click as there was no second click after waiting for a long enough period of time 98 | 99 | _what = What::Idle; 100 | return Event::Click; 101 | } 102 | 103 | if (pressed == true && _what == What::Release) 104 | { 105 | // a second click in the allowed period of time for a double click 106 | 107 | _what = What::SecondPress; 108 | _when = now; 109 | return Event::Down; 110 | } 111 | 112 | if (pressed == true && _what == What::SecondPress && (unsigned short)(now - _when) >= duration::Press) 113 | { 114 | // the button is being pressed long enough for it to count as a long press 115 | 116 | _what = What::Drain; 117 | return Event::ClickPress; 118 | } 119 | 120 | if (pressed == false && _what == What::SecondPress) 121 | { 122 | _what = What::SecondRelease; 123 | return Event::Up; 124 | } 125 | 126 | if (pressed == false && _what == What::SecondRelease) 127 | { 128 | // although there is no need to wait after the 'Up' event, 129 | // as we do not support more than two clicks, we implement this 130 | // stage in order to fire both 'Up' and 'ClickClick' events 131 | 132 | _what = What::Idle; 133 | return Event::ClickClick; 134 | } 135 | 136 | if (pressed == false && _what == What::Drain) 137 | { 138 | _what = What::Idle; 139 | return Event::Up; 140 | } 141 | 142 | return Event::None; 143 | } 144 | 145 | } // controlino 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Controlino 2 | 3 | *Controlino* is a library that eases the use of physical input controls connected the Arduino directly or through a multiplexer. 4 | 5 | Written in C++ and offering easy control of potentiometers and buttons, while supporting simple and complex gestures of buttons such as down/up/click/double-click/press/click-and-press. 6 | 7 | --- 8 | 9 | ## Overview 10 | 11 | *Controlino* is written in a C++ manner. 12 | It supports several input controls (all documented below). All input controls can be connected directly to the Arduino or through a multiplexer, and their usage is kept the same for the user. 13 | 14 | Currently, there is support for 8-channel and 16-channel multiplexers, but additional support should be easy to add. 15 | 16 | The user should periodically call the `check()` method of all controls. A good example is to do it in an interrupt handler every a few milliseconds, or from the global `loop()` method. 17 | `Control` objects store their current state and update it on every call to `check()`. 18 | 19 | The method `check()` returns an `Event` enum to indicate events that happen to the control (e.h. `Click`, `Down`, `Hold`). 20 | The enum `Event` is a different one for every class, and has different options. All options are documented below, for every control. 21 | 22 | ## Controls 23 | 24 | ### Key 25 | 26 | A simple button switch control. 27 | 28 | A `Key` control should be connected to the builtin pullup resistors. 29 | Therefore, in case of a physical button switch, on leg should be connected to GND, while the other one should be connected to the Arduino. 30 | 31 | The following table lists the options of `Key::Event`: 32 | 33 | | Event | Description | Details | 34 | |-------|-------------|---------| 35 | | `None` | The key is still not pressed | The key was previously not pressed and it is now not pressed | 36 | | `Down` | The key was just pressed | The key was previously not pressed and it is now pressed | 37 | | `Up` | The key was just released | The key was previously pressed and it is now not pressed | 38 | | `Hold` | The key is still pressed | The key was previously pressed and it is now pressed | 39 | 40 | > Take a look at the "key" [example](examples/Key/Key.ino) for a usage example. 41 | 42 | ### Button 43 | 44 | A complex button switch control. 45 | 46 | A `Button` control should be connected to the builtin pullup resistors. 47 | Therefore, in case of a physical button switch, on leg should be connected to GND, while the other one should be connected to the Arduino. 48 | 49 | The following table lists the options of `Button::Event`: 50 | 51 | | Event | Description | Details | 52 | |-------|-------------|---------| 53 | | `None` | The button is still not pressed | The button was previously not pressed and it is now not pressed | 54 | | `Down` | The button was just pressed | The button was previously not pressed and it is now pressed | 55 | | `Up` | The button was just released | The button was previously pressed and it is now not pressed | 56 | | `Click` | The button was clicked | The button was just released after being pressed once for a short duration | 57 | | `Press` | The button was pressed | The button was pressed once and is still pressed for a long duration | 58 | | `ClickClick` | The button was clicked twice | The button was just released after being pressed twice for a short duration | 59 | | `ClickPress` | The button was clicked and pressed | The button was clicked for a short duration and then pressed and is still pressed for a long duration | 60 | 61 | > Take a look at the "button" [example](examples/Button/Button.ino) for a usage example. 62 | 63 | ### Potentiometer 64 | 65 | A potentiometer control. 66 | 67 | A `Potentiometer` control should be either connected directly to an analog pin of the Arduino, or through a multiplexer which its communication pin is connected to an analog pin. 68 | 69 | The following table lists the options of `Potentiometer::Event`: 70 | 71 | | Event | Description | Details | 72 | |-------|-------------|---------| 73 | | `None` | The potentiometer was not moved | The current value of the potentiometer is equal to the previous one | 74 | | `Changed` | The potentiometer was moved | The current value of the potentiometer is different than the previous one | 75 | 76 | In addition, `Potentiometer` exposes the method `int read()` to read the current value of the control. 77 | 78 | Upon creation, `Potentiometer` may receive minimum and maximum values, and the value returned from `read()` will be limited by them. 79 | 80 | During development and because of the usage of a solderless breadboard, it's common for potentiometers to be unstable. 81 | For this reason, `Potentiometer` receives parameters, encapsulated in `Potentiometer::Noise`, that could be configured according to the current setup, to improve the stability: 82 | - `down` - a floating point multiplier for `min` 83 | - `up` - a floating point multiplier for `max` 84 | - `fluctuation` - the noise threshold 85 | 86 | Both `down` and `up` are used for multiplying `min` and `max`, respectively. 87 | They are used for decreasing and increasing `min` and `max` to improve the mapping of the raw values of the potentiometer from (0..1023) to (min..max) 88 | 89 | > Take a look at the "potentiometer" [example](examples/Potentiometer/Potentiometer.ino) for a usage example. 90 | 91 | ## Classes 92 | 93 | ### Pin 94 | 95 | A typedef of `char` to be used for pin numbers. 96 | 97 | ### Pinmode 98 | 99 | A utility class to hold information of both a pin number and a pin behavior (mode). 100 | 101 | `Pinmode` stores the information in a single byte (a `char`). It stores the pin mode in the 2 msbs, and the pin number in the 6 lsbs. 102 | 2 bits are enough for storing the 3 possible modes (`INPUT`, `OUTPUT`, `INPUT_PULLUP`), and 6 bits are enough for storing pin numbers up to 64. 103 | 104 | ### Selector 105 | 106 | A utility class which is used by `Multiplexer` to easily control the select pins of a multiplexer. Currently supporting 8 channel (3 bit) and 16 channel (4 bit) multiplexers. 107 | 108 | Upon creation, the select pin numbers should be passed to the object. Three pin numbers for a 8-channel (3-bit) selector, and four pin numbers for a 16-channel (4-bit) one. 109 | The least-significant pin should be passed as the first argument, and the most-significant pin should be passed as the last argument. 110 | 111 | `Selector` exposes the method `select(Pin pin)` to write values to the select pins in order for pin `pin` to be passed by the multiplexer. 112 | 113 | ### Multiplexer 114 | 115 | A class to ease digital and analog reads of multiplexed pins. 116 | 117 | Upon creation, a `Selector` object must be passed to `Multiplexer`, as well as the communication pin number. 118 | The `Multiplexer` class holds a weak reference to a `Selector` object, to allow several `Multiplexer`s can share the same `Selector` object. 119 | 120 | Currently, `Multiplexer` supports input communication only. Output communication sould be easy to add in the future as well. 121 | 122 | `Multiplexer` supports passing both digital and analog communication, and exposes two methods with an API similar to the Arduino: 123 | * `int digitalRead(Pin pin)` 124 | * `int analogRead(Pin pin)` 125 | 126 | Both methods receive an argument `pin` which is the pin number behind the multiplexer. This value will be selected using `Selector` before reading the value of the communication pin. 127 | 128 | Pins behind the multiplexer can be of different input controls. There could be digital and analog pins being multiplexed at the same time, as well as pins that should be connected to the builtin pullup resistors. 129 | Therefore, every time one wants to read the value that is passed by the multiplexer (using one ot the two methods above), he or she should configure the communication pin to the desired behavior. 130 | This could be done by using the method `piMode(Mode mode)` of `Multiplexer` that configures the behavior of the communication pin to the specified mode. 131 | To avoid reconfiguring the communication pin if it is already configured as desired, `pinMode()` will configure its behavior only if the pin is currently configured to a different behavior, or if it was never configured. 132 | 133 | ### Control 134 | 135 | The base class from which all controls inherit. 136 | 137 | Every `Control` object represent a physical control on the board, connected to the Arduino, and should be of a specific pin behavior. Pin behaviors cannot be changed during runtime. 138 | 139 | Every physical control could be behind a multiplexer or not. Upon creation, a pin number and a pin mode (behavior) are passed to `Control`. 140 | In case the control is connected through a multiplexer, the respective `Multiplexer` object is to be passed upon creation, and a weak reference to it is stored in the object. 141 | 142 | If the control is connected through a multiplexer, the pin number represents the channel number of the multiplexer, and not the multiplexer's communication pin number. 143 | Otherwise, if the control is connected directly, the pin number represents the actual pin number to which the control is connected. 144 | 145 | If the control is connected through a multiplexer, the multiplexer's communication pin is configured - using `Multiplexer::pinMode()` - before every read of its value. 146 | Otherwise, if the control is connected directly, the pin is configured exactly once, upon creation. 147 | --------------------------------------------------------------------------------