├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── ESP32_Input_Pulldown │ └── ESP32_Input_Pulldown.ino ├── Eight_buttons │ └── Eight_buttons.ino ├── Input_Bit_Test │ └── Input_Bit_Test.ino ├── Input_Byte │ └── Input_Byte.ino ├── Input_Byte_Test │ └── Input_Byte_Test.ino ├── One_Button_One_Switch │ └── One_Button_One_Switch.ino ├── Press_Code │ └── Press_Code.ino ├── Pressed_For │ └── Pressed_For.ino ├── Released_For │ └── Released_For.ino ├── Retrigger_While_Pressed │ └── Retrigger_While_Pressed.ino ├── Three_Position_Switch │ └── Three_Position_Switch.ino ├── Toggle_Basic │ └── Toggle_Basic.ino └── Use_Button_As_Toggle │ └── Use_Button_As_Toggle.ino ├── include └── README ├── keywords.txt ├── lib └── README ├── library.json ├── library.properties ├── platformio.ini ├── src ├── Toggle.cpp └── Toggle.h └── test └── README /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dlloydev 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Toggle [![arduino-library-badge](https://www.ardu-badge.com/badge/Toggle.svg?)](https://www.ardu-badge.com/Toggle) [![PlatformIO Registry](https://badges.registry.platformio.org/packages/dlloydev/library/Toggle.svg)](https://registry.platformio.org/libraries/dlloydev/Toggle) 2 | 3 | Arduino button debounce library for various switch types, port expanders and other 8-bit data sources. Fast and robust debounce algorithm. 4 | 5 | ## Features 6 | 7 | - Fast, robust and symetrical [debouncing](https://github.com/Dlloydev/Toggle/wiki/Debounce-Algorithm) of both press and release button transitions 8 | - Works with various [switch types and connections](https://github.com/Dlloydev/Toggle/wiki/Switch-Connections). 9 | - Identifies 7 unique transitions for 3-position switches 10 | - Use momentary button as toggle switch (configurable) 11 | - Return up to 225 codes from one button 12 | 13 | ## Examples (Wokwi) 14 | 15 | - Do a test run for evaluation. 16 | - Your suggestions, Issues and Discussions are welcome [here](https://github.com/Dlloydev/Toggle). 17 | 18 | | ESP32-S2 | UNO | 19 | | ------------------------------------------------------------ | ------------------------------------------------------------ | 20 | | [Toggle_Basic](https://wokwi.com/projects/334453650605736531) | [Toggle_Basic](https://wokwi.com/projects/334443631893021266) | 21 | | [Use_Button_As_Toggle.ino](https://wokwi.com/projects/334322217034711635) | [Use_Button_As_Toggle.ino](https://wokwi.com/projects/334321940028195411) | 22 | | [Eight_Buttons.ino](https://wokwi.com/projects/334413532415132244) | [Eight_Buttons.ino](https://wokwi.com/projects/334411953607803475) | 23 | | [Pressed_For.ino](https://wokwi.com/projects/334454109412262483) | [Pressed_For.ino](https://wokwi.com/projects/334457647787934292) | 24 | | [Released_For.ino](https://wokwi.com/projects/334458628485415508) | [Released_For.ino](https://wokwi.com/projects/334458460245590611) | 25 | | [Retrigger_While_Pressed.ino](https://wokwi.com/projects/334459280465855059) | [Retrigger_While_Pressed.ino](https://wokwi.com/projects/334458963671122515) | 26 | | [Press_Code.ino](https://wokwi.com/projects/334320246564323924) | [Press_Code.ino](https://wokwi.com/projects/334284248581145170) | 27 | | [ESP32_Input_Pulldown.ino](https://wokwi.com/projects/334554744838160980) | | 28 | 29 | ## Toggle Library Reference 30 | 31 | To use this library 32 | 33 | ```c++ 34 | #include 35 | ``` 36 | 37 | ## Constructors 38 | 39 | ```c++ 40 | Toggle(); 41 | Toggle(inA); 42 | Toggle(inA, inB); 43 | Toggle(*in); 44 | ``` 45 | 46 | ##### Description 47 | 48 | The constructor defines a button object. If the default constructor is used when declaring an array of pointers to button objects, then it must be followed by a call to begin in setup. 49 | 50 | ##### Required parameter 51 | 52 | **inA:** Arduino pin number that the button or switch is connected to *(byte)* , or 53 | ***in:** Arduino variable representing the input signal *(byte)* 54 | 55 | ##### Optional parameter 56 | 57 | **inB:** Second Arduino pin number *(byte)*. Use 2 inputs when connecting to 3 position switches. 58 | 59 | ##### Returns 60 | 61 | None. 62 | 63 | ##### Example 64 | 65 | ```c++ 66 | Toggle myInput(2); // Button connected pin 2 to GND, INPUT_PULLUP, debounced 67 | Toggle myInput(2, 3); // SPDT switch connected pins 2 and 3 to GND, INPUT_PULLUP, debounced 68 | Toggle myInput(*Input); // Byte variable as input, debounced 69 | ``` 70 | 71 | ## poll() 72 | 73 | ##### Description 74 | 75 | This function is placed at the top of the loop. Initializes the Button object and the pin it is connected to. 76 | 77 | ##### Syntax 78 | 79 | `myInput.poll();` 80 | 81 | ##### Optional parameter 82 | 83 | **bit:** selects the bit number from an input data *(byte)* 84 | 85 | ##### Returns 86 | 87 | None. 88 | 89 | ## Primary Functions 90 | 91 | There are 5 primary functions when using 1 input pin or data bit. Shown below is a plot showing the returned values that are verically offset for clarity: 92 | 93 | ![image](https://user-images.githubusercontent.com/63488701/169307791-e4b02e03-4399-4984-87a2-755dafaf3f47.png) 94 | 95 | ## onChange() 96 | 97 | ##### Description 98 | 99 | This function checks the status register to see if the button or switch has changed state and reports whether the onPress or onRelease flag has been set. Calling onChange() does not modify the status register. 100 | 101 | ##### Syntax 102 | 103 | `myInput.onChange();` 104 | 105 | ##### Parameters 106 | 107 | None. 108 | 109 | ##### Returns 110 | 111 | no change (0) , onPress (1), onRelease (2) *(byte)* 112 | 113 | ##### Example 114 | 115 | ```c++ 116 | if (myInput.onChange() == 2) { 117 | // button was released 118 | } else if (myInput.onChange() == 1) { 119 | // button was pressed 120 | } else { 121 | // no change 122 | } 123 | ``` 124 | 125 | ## onPress() 126 | 127 | ## onRelease() 128 | 129 | ##### Description 130 | 131 | These functions check the the status register to see if the button or switch has set the onPress or onRelease flag. These functions will return the flag status and clear the respective flag. 132 | 133 | ##### Syntax 134 | 135 | `myInput.onPress();` 136 | 137 | `myInput.onRelease();` 138 | 139 | ##### Parameters 140 | 141 | None. 142 | 143 | ##### Returns 144 | 145 | *true* or *false*, *(bool)* 146 | 147 | ##### Example 1 148 | 149 | ```c++ 150 | if (myInput.onPress()) 151 | { 152 | // do something (true only once per press) 153 | } 154 | ``` 155 | 156 | ## isPressed() 157 | 158 | ## isReleased() 159 | 160 | ##### Description 161 | 162 | These functions checks the curent debounced output and its history to see if the button or switch is pressed or released. 163 | 164 | ##### Syntax 165 | 166 | `myInput.isPressed();` 167 | 168 | `myInput.isReleased();` 169 | 170 | ##### Optional parameter 171 | 172 | **bit:** selects the bit number from an input data *(byte)* 173 | 174 | ##### Returns 175 | 176 | *true* or *false*, *(bool)* 177 | 178 | ##### Example 179 | 180 | ```c++ 181 | if (myButton.isPressed()) { 182 | // do something 183 | } 184 | else { 185 | // do something else 186 | } 187 | ``` 188 | 189 | ## toggle() 190 | 191 | ##### Description 192 | 193 | This function can be used to convert a momentary push button to a toggle switch. By default, the retuen value will toggle `true-false` then `false-true` for each `onPress` action of the button. 194 | 195 | - The `setToggleState()` function sets the initial state (*true* or *false*) 196 | - The `setToggleTrigger()` function sets the trigger mode: *false*: `onPress`, *true*: `onRelease` 197 | 198 | ##### Parameters 199 | 200 | None. 201 | 202 | ##### Returns 203 | 204 | *true* or *false*, (*bool*). Toggles as configured by `setToggleTrigger()`. Default is trigger `onPress` 205 | 206 | ## Timer Functions 207 | 208 | ## clearTimer() 209 | 210 | ##### Description 211 | 212 | Simply clears the ms timer used for the timer functions. 213 | 214 | ##### Syntax 215 | 216 | `myInput.clearTimer();` 217 | 218 | ##### Parameters 219 | 220 | None. 221 | 222 | ##### Returns 223 | 224 | None. 225 | 226 | ## blink(ms, mode) 227 | 228 | ##### Description 229 | 230 | This function sets the duration in milliseconds that the returned value is true. The mode parameter sets what blink responds to: onChange (0), onPress( 1) default, onRelease (2). 231 | 232 | ##### Syntax 233 | 234 | `myInput.blink(ms);` 235 | 236 | ##### Parameters 237 | 238 | **ms:** The number of milliseconds *(unsigned int)* 239 | 240 | **mode:** Blink onChange (0), onPress( 1) default, onRelease (2) *(byte)* 241 | 242 | ##### Returns 243 | 244 | *true* or *false*, depending on whether the elapsed time has expired after any state change set by the mode parameter. *(bool)* 245 | 246 | ##### Example 247 | 248 | [Toggle_Basic.ino](https://github.com/Dlloydev/Toggle/blob/main/examples/Toggle_Basic/Toggle_Basic.ino) 249 | 250 | ## pressedFor(ms) 251 | 252 | ## releasedFor(ms) 253 | 254 | ##### Description 255 | 256 | These functions return true if the button or switch has been in the pressedFor or releasedFor state for at least the given number of milliseconds. 257 | 258 | ##### Syntax 259 | 260 | `myInput.pressedFor(ms);` 261 | `myInput.releasedFor(ms);` 262 | 263 | ##### Parameters 264 | 265 | **ms:** The number of milliseconds *(unsigned int)* 266 | 267 | ##### Returns 268 | 269 | *true* or *false*, depending on whether the elapsed time has expired after the state change. *(bool)* 270 | 271 | ##### Example 272 | 273 | ```c++ 274 | if (myInput.pressedFor(500)) { 275 | // true (once only) if button has been pressed for 500ms 276 | } 277 | ``` 278 | 279 | ## retrigger(ms) 280 | 281 | ##### Description 282 | 283 | This function checks the duration in milliseconds that the button or switch is is in the state as selected by timer mode and returns true (once only) each time the given millisecond duration has expired. 284 | 285 | ##### Syntax 286 | 287 | `myInput.retrigger(ms);` 288 | 289 | ##### Parameters 290 | 291 | **ms:** The number of milliseconds *(unsigned int)* 292 | 293 | ##### Returns 294 | 295 | *true* or *false*, returns true (once only) each time the given ms duration has expired while the button is in the state as selected by timer mode. *(bool)* 296 | 297 | ##### Example 298 | 299 | ```c++ 300 | if (retrigger(500)) { 301 | // count every 500ms interval while the button is being pressed 302 | } 303 | ``` 304 | 305 | ## pressCode() 306 | 307 | ##### Description 308 | 309 | - Up to 225 possible codes with one button. The returned code (*byte*) is easy to interpret when viewed in hex format. For example, `47` is 4 long, 7 short presses. `F2` is double-click, `F7` is 7 `F`ast clicks. 310 | - Fast-click mode is detected if the first several clicks (presses) are less than 0.2 sec, then all presses are counted as fast, up to 15 max (code `FF`) 311 | - Detection of long presses occurs if the first press is greater than 0.2 sec, then all presses greater than 0.2 sec are counted as long and all presses less than 0.2 sec are counted as short presses. 312 | - Detect up to 15 short presses 313 | - Detect up to 14 long presses 314 | - Returns code after button is released for 0.5 sec 315 | - simplifies your code while adding maximum functionality to one button 316 | 317 | ##### Example 318 | 319 | ```c++ 320 | byte pCode = sw1.pressCode(1); // (1) serial print results 321 | ``` 322 | 323 | ##### Example Sketch [Press_Code.ino](https://github.com/Dlloydev/Toggle/blob/main/examples/Press_Code/Press_Code.ino) 324 | 325 | [Trial run on *WOKWi*](https://wokwi.com/projects/334284248581145170) 326 | 327 | ## Set and Get Functions 328 | 329 | ## setInputMode() 330 | 331 | ##### Description 332 | 333 | This function sets the various options for the input source. 334 | 335 | ##### Syntax 336 | 337 | `myInput.inMode::input_option;` 338 | 339 | ##### Returns 340 | 341 | None. 342 | 343 | ##### Options 344 | 345 | ```c++ 346 | myInput.setInputMode(sw1.inMode::input_input); // high impedance input 347 | myInput.setInputMode(sw1.inMode::input_pullup); // pullup resistor enabled (default) 348 | myInput.setInputMode(sw1.inMode::input_pulldown); // pulldown resistor enabled (ESP32) 349 | myInput.setInputMode(sw1.inMode::input_byte); // input byte (8-bit) 350 | ``` 351 | 352 | ## setInputInvert() 353 | 354 | ##### Description 355 | 356 | Set true if the button or switch pulls the signal high when pressed. Default is false (button or switch pulls the signal low when pressed). 357 | 358 | ##### Syntax 359 | 360 | `myInput.setInputInvert(true);` 361 | 362 | ##### Returns 363 | 364 | None. 365 | 366 | ## setSampleUs() 367 | 368 | ##### Description 369 | 370 | Sets the sample period in microseconds. Default is 5000 μs. 371 | 372 | ##### Syntax 373 | 374 | `myInput.setSampleUs(us);` 375 | 376 | ##### Returns 377 | 378 | None. 379 | 380 | ## getElapsedMs(() 381 | 382 | ##### Description 383 | 384 | Gets the elapsed ms since the last state change selected by timer mode. 385 | 386 | ##### Syntax 387 | 388 | `myInput.getElapsedMs();` 389 | 390 | ##### Returns 391 | 392 | Elapsed milliseconds *(unsigned int)*. 393 | 394 | --- 395 | 396 | ### References 397 | 398 | - [A Guide To Debouncing](http://www.ganssle.com/item/debouncing-switches-contacts-code.htm) 399 | - [Wetting Current](https://en.wikipedia.org/wiki/Wetting_current) 400 | 401 | -------------------------------------------------------------------------------- /examples/ESP32_Input_Pulldown/ESP32_Input_Pulldown.ino: -------------------------------------------------------------------------------- 1 | /********************************************************************* 2 | ESP32-S2 INPUT_PULLDOWN EXAMPLE: 3 | This example blinks the built in LED as configured below. 4 | Try on Wokwi ESP32-S2: https://wokwi.com/projects/334554744838160980 5 | ********************************************************************/ 6 | 7 | #include 8 | 9 | const byte buttonPin = 4; 10 | const byte ledPin = 16; 11 | 12 | const unsigned int blinkMs = 100; // blink duration (ms) 13 | const byte blinkMode = 1; // 0: on change, 1: on press (default), 2: on release 14 | 15 | Toggle button(buttonPin); 16 | 17 | void setup() { 18 | button.begin(buttonPin); 19 | button.setInputInvert(1); 20 | pinMode(buttonPin, INPUT_PULLDOWN); 21 | pinMode(ledPin, OUTPUT); 22 | } 23 | 24 | void loop() { 25 | button.poll(); 26 | digitalWrite(ledPin, button.blink(blinkMs, blinkMode)); 27 | delay(1); 28 | } 29 | -------------------------------------------------------------------------------- /examples/Eight_buttons/Eight_buttons.ino: -------------------------------------------------------------------------------- 1 | /**************************************************** 2 | EIGHT BUTTONS EXAMPLE: 3 | An example that checks the status of eight buttons. 4 | All input pins will have their pullups enabled. 5 | The LED blinks for each on press transition. 6 | ***************************************************/ 7 | 8 | #include 9 | 10 | const byte num = 8; // number of buttons 11 | const byte pin[num] = {2, 3, 4, 5, 6, 7, 8, 9}; //button pins 12 | const byte ledPin = LED_BUILTIN; 13 | const unsigned int blinkMs = 100; // led blink duration (ms) 14 | 15 | Toggle *sw = new Toggle[num]; 16 | 17 | void setup() { 18 | for (int i = 0; i < num; ++i) { 19 | sw[i].begin(pin[i]); 20 | sw[i] = pin[i]; 21 | } 22 | } 23 | 24 | void loop() { 25 | byte ledState = num; 26 | for (int i = 0; i < num; i++) { 27 | sw[i].poll(); 28 | if (sw[i].blink(blinkMs)) { 29 | ledState++; 30 | } else { 31 | ledState--; 32 | } 33 | } 34 | digitalWrite(ledPin, ledState); 35 | } 36 | -------------------------------------------------------------------------------- /examples/Input_Bit_Test/Input_Bit_Test.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | Input Bit Test Example: 3 | ======================= 4 | This example demonstrates how you can debounce any bit of a byte variable 5 | in the sketch. The Arduino pin functions are not used. 6 | • Open Serial Plotter to view the noisy input and debounced outputs. 7 | • Open Serial Monitor to view/copy the results in CSV format. 8 | • Can be used to monitor, deglitch and debounce an input representing 9 | RPM pulses, flow meter pulses, noisy comparator output signal, etc. 10 | *************************************************************************/ 11 | 12 | #include 13 | 14 | const byte dat[47] = { 15 | 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 16 | }; 17 | 18 | byte Input; 19 | byte bit = 0; 20 | 21 | Toggle myInput(&Input); 22 | 23 | void setup() { 24 | while (!Serial) { }; // Leonardo 25 | Serial.begin(115200); 26 | myInput.begin(Input); 27 | myInput.setInputMode(myInput.inputMode::input_byte); 28 | myInput.setSamplePeriodUs(20); // 0-65535μs 29 | 30 | for (int i = 0; i < 47; i++) { 31 | Input = dat[i] << bit; 32 | myInput.poll(bit); 33 | 34 | for (int i = 0; i < 10; i++) { // zoom horizontal 35 | plot("In", Input + 8, false); 36 | plot("isOFF", myInput.isReleased(bit) + 6, false); 37 | plot("OFF2ON", myInput.onPress() + 4, false); 38 | plot("ON2OFF", myInput.onRelease() + 2, false); 39 | plot("isON", myInput.isPressed(bit), true); 40 | } 41 | } 42 | } 43 | 44 | void loop() { 45 | } 46 | 47 | void plot(String label, float value, bool last) { 48 | Serial.print(label); // can be empty 49 | if (label != "") Serial.print(":"); 50 | Serial.print(value); 51 | if (last == false) Serial.print(", "); 52 | else Serial.println(); 53 | } 54 | -------------------------------------------------------------------------------- /examples/Input_Byte/Input_Byte.ino: -------------------------------------------------------------------------------- 1 | /****************************************************************** 2 | Input Byte Example: 3 | =================== 4 | This example demonstrates how you can debounce any byte variable 5 | in the sketch. The Arduino pin functions are not used. 6 | Open Serial Monitor to view the debounced byte status. 7 | *****************************************************************/ 8 | 9 | #include 10 | 11 | const byte ledPin = LED_BUILTIN; 12 | 13 | byte bit = 0; // choose bit(0-7) 14 | byte Input; 15 | 16 | Toggle myInput(&Input); 17 | 18 | void setup() { 19 | pinMode(ledPin, OUTPUT); 20 | while (!Serial) { }; // Leonardo 21 | Serial.begin(115200); 22 | myInput.begin(Input); 23 | myInput.setInputMode(myInput.inputMode::input_byte); // debounce all bits 24 | myInput.setSamplePeriodUs(20); // 0-65535μs 25 | } 26 | 27 | void loop() { 28 | myInput.poll(); 29 | if (myInput.onPress()) { 30 | Serial.print(F("b")); Serial.print(bit); Serial.println(F(": OFF⇒ON")); 31 | } 32 | if (myInput.onRelease()) { 33 | Serial.print(F("b")); Serial.print(bit); Serial.println(F(": ON⇒OFF")); 34 | } 35 | digitalWrite(ledPin, myInput.toggle()); 36 | } 37 | -------------------------------------------------------------------------------- /examples/Input_Byte_Test/Input_Byte_Test.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************* 2 | Input Byte Debouncer Test Example: 3 | ================================== 4 | This example demonstrates how you can debounce an 8-bit byte (8 signals) 5 | in just one Toggle object. The Arduino pin functions are not used. 6 | The input value is linked to a byte variable in the sketch. 7 | 8 | This is the expected serial print with leading 0's added: 9 | 10 | In: 00000000 Out: 00000000 11 | In: 10000001 Out: 00000000 12 | In: 10100011 Out: 00000000 13 | In: 00100111 Out: 00000001 14 | In: 00001110 Out: 00000011 15 | In: 00011110 Out: 00000111 16 | In: 00101111 Out: 00001111 17 | In: 01110110 Out: 00001111 18 | In: 11111100 Out: 00101111 19 | In: 11011000 Out: 01111110 20 | In: 01110000 Out: 01111100 21 | In: 01100011 Out: 01111000 22 | In: 11000000 Out: 01110000 23 | In: 10000100 Out: 01100000 24 | In: 00000000 Out: 01000000 25 | 26 | Looking at the columns (bit data) top to bottom, it can be seen that the 27 | debounced "Out" data lags by only 2 samples (rows). It also can be seen 28 | that the input debouncer can tolerate a very noisy signal with up to 2 29 | consecutive 1's or 0's that are anomalous or spurious in the In data. 30 | ************************************************************************/ 31 | 32 | #include 33 | 34 | const byte dat[15] = { 35 | 0b00000000, 36 | 0b10000001, 37 | 0b10000011, 38 | 0b00000111, 39 | 0b00001111, 40 | 0b00011111, 41 | 0b00101111, 42 | 0b01110110, 43 | 0b11111100, 44 | 0b11011000, 45 | 0b01110000, 46 | 0b01100000, 47 | 0b11000000, 48 | 0b10000000, 49 | 0b00000000 50 | }; 51 | 52 | byte Input; 53 | 54 | Toggle myInput(&Input); 55 | 56 | void setup() { 57 | while (!Serial) { }; // Leonardo 58 | Serial.begin(115200); 59 | myInput.begin(Input); 60 | myInput.setInputMode(myInput.inputMode::input_byte); 61 | myInput.setSamplePeriodUs(20); // 0-65535μs 62 | 63 | for (int i = 0; i < 15; i++) { 64 | Input = dat[i]; 65 | myInput.poll(); 66 | Serial.print(F("In: ")); 67 | Serial.print(dat[i], BIN); 68 | Serial.print(F(" Out: ")); 69 | Serial.println(myInput.debouncer(), BIN); 70 | } 71 | } 72 | 73 | void loop() { 74 | } 75 | -------------------------------------------------------------------------------- /examples/One_Button_One_Switch/One_Button_One_Switch.ino: -------------------------------------------------------------------------------- 1 | /****************************************************************** 2 | One Button and One Three Position Switch: 3 | ========================================= 4 | This example that blinks an LED each time the button 5 | has transitioned. All input pins will have their pullups enabled. 6 | Open the Serial Monitor to view transition status. 7 | *****************************************************************/ 8 | 9 | #include 10 | 11 | const byte buttonPin = 2; 12 | const byte swPinA = 3; 13 | const byte swPinB = 4; 14 | const byte ledPin = LED_BUILTIN; 15 | 16 | Toggle sw1(buttonPin); // button 17 | Toggle sw2(swPinA, swPinB); // 3-position switch 18 | 19 | void setup() { 20 | pinMode(ledPin, OUTPUT); 21 | while (!Serial) { }; // Leonardo 22 | Serial.begin(115200); 23 | sw1.begin(buttonPin); 24 | sw2.begin(swPinA, swPinB); 25 | } 26 | 27 | void loop() { 28 | sw1.poll(); 29 | sw2.poll(); 30 | 31 | if (sw1.onPress()) Serial.println(F("sw1: OFF⇒ON")); 32 | if (sw1.onRelease()) Serial.println(F("sw1: ON⇒OFF")); 33 | if (sw2.UPtoMID()) Serial.println(F("sw2: UP⇒MID")); 34 | if (sw2.MIDtoDN()) Serial.println(F("sw2: MID⇒DN")); 35 | if (sw2.DNtoMID()) Serial.println(F("sw2: DN⇒MID")); 36 | if (sw2.MIDtoUP()) Serial.println(F("sw2: MID⇒UP")); 37 | digitalWrite(ledPin, sw1.toggle()); 38 | } 39 | -------------------------------------------------------------------------------- /examples/Press_Code/Press_Code.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | Press Code Example: 3 | =================== 4 | A simple example that demonstrates how fast-clicks, short presses and 5 | long presses can be automatically detected and combined into a byte code. 6 | 7 | ⦿ Up to 225 possible codes with one button. The returned code (byte) is easy to 8 | interpret when viewed in hex format. For example, `47` is 4 long, 7 short presses. 9 | F2 is double-click, F7 is 7 `F`ast clicks. 10 | 11 | ⦿ Fast-click mode is detected if the first several presses are less than 0.2 sec, 12 | then all presses are counted as fast, up to 15 max (code `FF`). 13 | 14 | ⦿ Detection of long presses occurs if the first press is greater than 0.2 sec, 15 | then all presses greater than 0.2 sec are counted as long 16 | and all presses less than 0.2 sec are counted as short presses. 17 | 18 | ⦿ Detect up to 15 short presses 19 | ⦿ Detect up to 14 long presses 20 | ⦿ Returns code after button is released for 0.5 sec 21 | ⦿ simplifies your code while adding maximum functionality to one button 22 | 23 | Try on Wokwi UNO: https://wokwi.com/projects/334284248581145170 24 | ESP32-S2: https://wokwi.com/projects/334320246564323924 25 | ***********************************************************************/ 26 | 27 | #include 28 | 29 | const byte buttonPin = 2; 30 | byte code; 31 | 32 | Toggle sw1(buttonPin); 33 | 34 | void setup() { 35 | while (!Serial) { }; // Leonardo 36 | Serial.begin(115200); 37 | sw1.begin(buttonPin); 38 | } 39 | 40 | void loop() { 41 | sw1.poll(); 42 | sw1.pressCode(1); // print: (1) on () off 43 | } 44 | -------------------------------------------------------------------------------- /examples/Pressed_For/Pressed_For.ino: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | PRESSED FOR EXAMPLE: 3 | While a button is pressed, returns true after elapsed ms 4 | Try on Wokwi UNO: https://wokwi.com/projects/334457647787934292 5 | ESP32-S2: https://wokwi.com/projects/334454109412262483 6 | ****************************************************************/ 7 | 8 | #include 9 | 10 | const byte buttonPin = 2; 11 | const byte ledPin = LED_BUILTIN; 12 | const unsigned int ms = 1000; 13 | 14 | Toggle sw1(buttonPin); 15 | 16 | void setup() { 17 | pinMode(ledPin, OUTPUT); 18 | sw1.begin(buttonPin); 19 | } 20 | 21 | void loop() { 22 | sw1.poll(); 23 | digitalWrite(ledPin, sw1.pressedFor(ms)); 24 | } 25 | -------------------------------------------------------------------------------- /examples/Released_For/Released_For.ino: -------------------------------------------------------------------------------- 1 | /***************************************************************** 2 | RELEASED FOR EXAMPLE: 3 | While a button is released, returns true after elapsed ms 4 | Try on Wokwi UNO: https://wokwi.com/projects/334458460245590611 5 | ESP32-S2: https://wokwi.com/projects/334458628485415508 6 | ****************************************************************/ 7 | 8 | #include 9 | 10 | const byte buttonPin = 2; 11 | const byte ledPin = LED_BUILTIN; 12 | const unsigned int ms = 1000; 13 | 14 | Toggle sw1(buttonPin); 15 | 16 | void setup() { 17 | pinMode(ledPin, OUTPUT); 18 | sw1.begin(buttonPin); 19 | } 20 | 21 | void loop() { 22 | sw1.poll(); 23 | digitalWrite(ledPin, sw1.releasedFor(ms)); 24 | } 25 | -------------------------------------------------------------------------------- /examples/Retrigger_While_Pressed/Retrigger_While_Pressed.ino: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | Retrigger While Pressed Example: 3 | ================================ 4 | An example that shows how to retrigger an event while a button is pressed. 5 | The retrigger period (ms) determines the rate. Each expired time period is 6 | counted and displayed on the serial monitor. The built in LED toggles on 7 | each trigger event. 8 | Try on Wokwi UNO: https://wokwi.com/projects/334458963671122515 9 | ESP32-S2: https://wokwi.com/projects/334459280465855059 10 | *************************************************************************/ 11 | 12 | #include 13 | 14 | const byte buttonPin = 2; 15 | const word retriggerMs = 1000; 16 | const byte ledPin = LED_BUILTIN; 17 | bool ledState = false; 18 | byte trigCount = 0; 19 | 20 | Toggle sw1(buttonPin); 21 | 22 | void setup() { 23 | while (!Serial) { }; // Leonardo 24 | Serial.begin(115200); 25 | pinMode(ledPin, OUTPUT); 26 | sw1.begin(buttonPin); 27 | } 28 | 29 | void loop() { 30 | sw1.poll(); 31 | if (sw1.retrigger(retriggerMs)) { 32 | trigCount++; 33 | Serial.println(trigCount); 34 | ledState = !ledState; 35 | digitalWrite(ledPin, ledState); 36 | } 37 | if (sw1.onRelease()) { 38 | Serial.println(F("Released")); 39 | trigCount = 0; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/Three_Position_Switch/Three_Position_Switch.ino: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | Using Three Position Switches: 3 | ============================== 4 | A simple example that toggles an LED each time a 3-position switch has 5 | transitioned. Both input pins will have its pullup enabled. The 6 | switch is in the disconnected MID position if both inpits read high. 7 | Open the Serial Monitor to view transition status. 8 | **********************************************************************/ 9 | 10 | #include 11 | 12 | const byte pinA = 2; 13 | const byte pinB = 3; 14 | const byte ledPin = LED_BUILTIN; 15 | 16 | Toggle sw1(pinA, pinB); // 3-position switch 17 | 18 | void setup() { 19 | pinMode(ledPin, OUTPUT); 20 | while (!Serial) { }; // Leonardo 21 | Serial.begin(115200); 22 | sw1.begin(pinA, pinB); 23 | } 24 | 25 | void loop() { 26 | sw1.poll(); 27 | if (sw1.UPtoMID()) Serial.println(F("sw1: UP⇒MID")); 28 | if (sw1.MIDtoDN()) Serial.println(F("sw1: MID⇒DN")); 29 | if (sw1.DNtoMID()) Serial.println(F("sw1: DN⇒MID")); 30 | if (sw1.MIDtoUP()) Serial.println(F("sw1: MID⇒UP")); 31 | digitalWrite(ledPin, sw1.toggle()); // toggles on MID⇒UP transition only 32 | } 33 | -------------------------------------------------------------------------------- /examples/Toggle_Basic/Toggle_Basic.ino: -------------------------------------------------------------------------------- 1 | /**************************************************************** 2 | TOGGLE BASIC EXAMPLE: 3 | This example blinks the built in LED as configured below. 4 | INPUT_PULLUP and debouncing are pre-configured. 5 | Try on Wokwi UNO: https://wokwi.com/projects/334443631893021266 6 | ESP32-S2: https://wokwi.com/projects/334453650605736531 7 | ***************************************************************/ 8 | 9 | #include 10 | 11 | const byte buttonPin = 2; 12 | const byte ledPin = LED_BUILTIN; 13 | 14 | const unsigned int blinkMs = 100; // blink duration (ms) 15 | const byte blinkMode = 1; // 0: on change, 1: on press (default), 2: on release 16 | 17 | Toggle button(buttonPin); 18 | 19 | void setup() { 20 | button.begin(buttonPin); 21 | pinMode(ledPin, OUTPUT); 22 | } 23 | 24 | void loop() { 25 | button.poll(); 26 | digitalWrite(ledPin, button.blink(blinkMs, blinkMode)); 27 | } 28 | -------------------------------------------------------------------------------- /examples/Use_Button_As_Toggle/Use_Button_As_Toggle.ino: -------------------------------------------------------------------------------- 1 | /*************************************************************** 2 | USE A MOMENTARY PUSHBUTTON AS A TOGGLE SWITCH: 3 | The button is connected from input pin to GND. 4 | INPUT_PULLUP and debouncing are pre-configured. 5 | The built in LED changes state as configured. 6 | Try on Wokwi UNO: https://wokwi.com/projects/334321940028195411 7 | ESP32-S2: https://wokwi.com/projects/334322217034711635 8 | ****************************************************************/ 9 | 10 | #include 11 | 12 | const byte buttonPin = 2; 13 | const byte ledPin = LED_BUILTIN; 14 | 15 | Toggle button(buttonPin); 16 | 17 | void setup() { 18 | button.begin(buttonPin); 19 | button.setToggleState(0); // set initial state 0 or 1 20 | button.setToggleTrigger(0); // set trigger onPress: 0, or onRelease: 1 21 | 22 | pinMode(ledPin, OUTPUT); 23 | } 24 | 25 | void loop() { 26 | button.poll(); 27 | digitalWrite(ledPin, button.toggle()); 28 | } 29 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ########################################## 2 | # Syntax Coloring Map For Toggle Library 3 | ########################################## 4 | 5 | ########################################## 6 | # Datatypes (KEYWORD1) 7 | ########################################## 8 | 9 | Toggle KEYWORD1 10 | myInput KEYWORD1 11 | myButton KEYWORD1 12 | sw KEYWORD1 13 | sw0 KEYWORD1 14 | sw1 KEYWORD1 15 | sw2 KEYWORD1 16 | sw3 KEYWORD1 17 | sw4 KEYWORD1 18 | sw5 KEYWORD1 19 | sw6 KEYWORD1 20 | sw7 KEYWORD1 21 | sw8 KEYWORD1 22 | sw9 KEYWORD1 23 | 24 | ########################################## 25 | # Methods and Functions (KEYWORD2) 26 | ########################################## 27 | 28 | poll KEYWORD2 29 | setInputMode KEYWORD2 30 | setInputInvert KEYWORD2 31 | toggle KEYWORD2 32 | setToggleState KEYWORD2 33 | setToggleTrigger KEYWORD2 34 | setSamplePeriodUs KEYWORD2 35 | clearTimer KEYWORD2 36 | getElapsedMs KEYWORD2 37 | isPressed KEYWORD2 38 | isReleased KEYWORD2 39 | onPress KEYWORD2 40 | onRelease KEYWORD2 41 | onChange KEYWORD2 42 | blink KEYWORD2 43 | pressedFor KEYWORD2 44 | releasedFor KEYWORD2 45 | retrigger KEYWORD2 46 | pressCode KEYWORD2 47 | debouncer KEYWORD2 48 | isUP KEYWORD2 49 | isMID KEYWORD2 50 | isDN KEYWORD2 51 | UPtoMID KEYWORD2 52 | MIDtoDN KEYWORD2 53 | DNtoMID KEYWORD2 54 | MIDtoUP KEYWORD2 55 | 56 | ########################################## 57 | # Constants (LITERAL1) 58 | ########################################## 59 | 60 | input LITERAL1 61 | input_pullup LITERAL1 62 | input_pulldown LITERAL1 63 | input_bit LITERAL1 64 | input_byte LITERAL1 65 | FAST LITERAL1 66 | DONE LITERAL1 67 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Toggle", 3 | "version": "3.1.8", 4 | "description": "Arduino button debounce library for various switch types, port expanders and other 8-bit data sources. Fast and robust debounce algorithm.", 5 | "keywords": "debounce, toggle, button, switch, data, deglitch", 6 | "repository": 7 | { 8 | "type": "git", 9 | "url": "https://github.com/Dlloydev/Toggle" 10 | }, 11 | "authors": 12 | [ 13 | { 14 | "name": "David Lloyd", 15 | "email": "dlloydev@testcor.ca", 16 | "url": "https://github.com/Dlloydev/Toggle", 17 | "maintainer": true 18 | } 19 | ], 20 | "license": "MIT", 21 | "homepage": "https://github.com/Dlloydev/Toggle", 22 | "frameworks": "arduino", 23 | "platforms": "*" 24 | } -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Toggle 2 | version=3.1.8 3 | author=David Lloyd 4 | maintainer=David Lloyd 5 | sentence=AArduino button debounce library for various switch types, port expanders and other 8-bit data sources. 6 | paragraph= Fast and robust debounce algorithm. 7 | category=Signal Input/Output 8 | url=https://github.com/Dlloydev/Toggle 9 | architectures=* -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | -------------------------------------------------------------------------------- /src/Toggle.cpp: -------------------------------------------------------------------------------- 1 | /************************************************ 2 | Toggle Library for Arduino - Version 3.1.8 3 | by dlloydev https://github.com/Dlloydev/Toggle 4 | Licensed under the MIT License. 5 | ************************************************/ 6 | 7 | #include "Toggle.h" 8 | #include 9 | 10 | Toggle::Toggle() : _inA(255), _inB(255) {} 11 | Toggle::Toggle(uint8_t *in) : _in(in) {} 12 | 13 | void Toggle::begin(uint8_t inA, uint8_t inB) { 14 | if (csr & 0b00000001) { // firstRun 15 | csr &= ~0b00000001; // clear firstRun 16 | _inA = inA; 17 | _inB = inB; 18 | us_timestamp = micros(); 19 | startUs = us_timestamp; 20 | setInputMode(inputMode::input_pullup); 21 | } 22 | } 23 | 24 | void Toggle::poll(uint8_t bit) { 25 | begin(_inA, _inB); // runs one only 26 | if (micros() - us_timestamp > us_period) { 27 | us_timestamp += us_period; 28 | if (_inputMode == inputMode::input || _inputMode == inputMode::input_pullup || _inputMode == inputMode::input_pulldown) { 29 | if (_inA) dat = digitalRead(_inA); 30 | if (_inB) dat += digitalRead(_inB) * 2; 31 | } 32 | if (_inputMode == inputMode::input_byte) dat = *_in; 33 | if (getInputInvert()) dat = ~dat; 34 | if (bit > 7) bit = 0; 35 | debouncer(bit); 36 | } // run sample 37 | } // poll 38 | 39 | void Toggle::setInputMode(inputMode inputMode) { 40 | _inputMode = inputMode; 41 | if (_inA != 255) { 42 | if (_inputMode == inputMode::input_pullup) pinMode(_inA, INPUT_PULLUP); 43 | else if (_inputMode == inputMode::input) pinMode(_inA, INPUT); 44 | #if (defined(ESP32) || defined(ARDUINO_ARCH_ESP32)) 45 | else if (_inputMode == inputMode::input_pulldown) pinMode(_inA, INPUT_PULLDOWN); 46 | #endif 47 | } 48 | if (_inB != 255) { 49 | if (_inputMode == inputMode::input_pullup) pinMode(_inB, INPUT_PULLUP); 50 | else if (_inputMode == inputMode::input) pinMode(_inB, INPUT); 51 | #if (defined(ESP32) || defined(ARDUINO_ARCH_ESP32)) 52 | else if (_inputMode == inputMode::input_pulldown) pinMode(_inB, INPUT_PULLDOWN); 53 | #endif 54 | } 55 | } 56 | 57 | void Toggle::setSamplePeriodUs(uint16_t period) { 58 | us_period = period; 59 | } 60 | 61 | /************* button state functions ****************/ 62 | 63 | bool Toggle::isPressed(uint8_t bit) { 64 | return !isReleased(bit); 65 | } 66 | 67 | bool Toggle::isReleased(uint8_t bit) { 68 | return out & (1 << bit); 69 | } 70 | 71 | bool Toggle::onPress() { 72 | if (csr & 0b10000000) { // onPress 73 | csr &= ~0b10000000; // clear onPress flag 74 | pOut = out; 75 | return true; 76 | } 77 | return false; 78 | } 79 | 80 | bool Toggle::onRelease() { 81 | if (csr & 0b01000000) { // onRelease 82 | csr &= ~0b01000000; // clear onRelease flag 83 | pOut = out; 84 | return true; 85 | } 86 | return false; 87 | } 88 | 89 | uint8_t Toggle::onChange() { 90 | if (csr & 0b01000000) return 2; // onRelease 91 | else if (csr & 0b10000000) return 1; // onPress 92 | return 0; 93 | } 94 | 95 | bool Toggle::toggle() { 96 | if (!getToggleTrigger()) { 97 | if (((!getInputInvert() && isPressed()) || (getInputInvert() && isReleased())) && !getLastToggleState()) { 98 | setLastToggleState(true); 99 | (getToggleState()) ? setToggleState(false) : setToggleState(true); // toggle 100 | } 101 | if (((!getInputInvert() && isReleased()) || (getInputInvert() && isPressed())) && getLastToggleState()) setLastToggleState(false); 102 | return getToggleState(); 103 | } else { 104 | if (((!getInputInvert() && isReleased()) || (getInputInvert() && isPressed())) && !getLastToggleState()) { 105 | setLastToggleState(true); 106 | (getToggleState()) ? setToggleState(false) : setToggleState(true); // toggle 107 | } 108 | if (((!getInputInvert() && isPressed()) || (getInputInvert() && isReleased())) && getLastToggleState()) setLastToggleState(false); 109 | return !getToggleState(); 110 | } 111 | } 112 | 113 | /********* button status register functions **********/ 114 | 115 | void Toggle::setInputInvert(bool invert) { 116 | (invert) ? csr |= 0b00000010 : csr &= ~0b00000010; 117 | } 118 | 119 | bool Toggle::getInputInvert() { // private 120 | return csr & 0b00000010; 121 | } 122 | 123 | void Toggle::setToggleTrigger(bool change) { 124 | (change) ? csr |= 0b00000100 : csr &= ~0b00000100; 125 | } 126 | 127 | bool Toggle::getToggleTrigger() { // private 128 | return csr & 0b00000100; 129 | } 130 | 131 | void Toggle::setToggleState(bool toggleState) { 132 | (toggleState) ? csr |= 0b00010000 : csr &= ~0b00010000; 133 | } 134 | 135 | bool Toggle::getToggleState() { // private 136 | return csr & 0b00010000; 137 | } 138 | 139 | void Toggle::setLastToggleState(bool lastToggleState) { 140 | (lastToggleState) ? csr |= 0b00100000 : csr &= ~0b00100000; 141 | } 142 | 143 | bool Toggle::getLastToggleState() { // private 144 | return csr & 0b00100000; 145 | } 146 | 147 | /************* button timer functions ****************/ 148 | 149 | void Toggle::clearTimer() { 150 | startUs = micros(); 151 | } 152 | 153 | uint32_t Toggle::getElapsedMs() { 154 | return (micros() - startUs) * 0.001; 155 | } 156 | 157 | bool Toggle::blink(uint16_t ms, uint8_t mode) { 158 | if (mode == 2 && onChange() == 2) clearTimer(); 159 | else if (mode == 1 && onChange() == 1) clearTimer(); 160 | else if (mode == 0 && onChange()) clearTimer(); 161 | onPress(); 162 | onRelease(); 163 | if ((startUs * 0.001) > ms) return (bool)(ms > (getElapsedMs())); 164 | else return 0; 165 | } 166 | 167 | bool Toggle::pressedFor(uint16_t ms) { 168 | if (onPress()) clearTimer(); 169 | if (isPressed() && getElapsedMs() > ms) { 170 | return true; 171 | } 172 | return false; 173 | } 174 | 175 | bool Toggle::releasedFor(uint16_t ms) { 176 | if (onRelease()) clearTimer(); 177 | if (isReleased() && getElapsedMs() > ms) { 178 | return true; 179 | } 180 | return false; 181 | } 182 | 183 | bool Toggle::retrigger(uint16_t ms) { 184 | if (onPress()) clearTimer(); 185 | if (isPressed() && getElapsedMs() > ms) { 186 | clearTimer(); 187 | return true; 188 | } 189 | return false; 190 | } 191 | 192 | uint8_t Toggle::pressCode(bool debug) { 193 | static uint8_t pCode = 0, code = 0; 194 | static uint32_t elapsedMs = 0; 195 | 196 | switch (_state) { 197 | case PB_DEFAULT: 198 | elapsedMs = getElapsedMs(); 199 | if (pCode && isReleased() && (elapsedMs > (CLICK::DONE))) _state = PB_DONE; 200 | if (onChange()) clearTimer(); 201 | if (onPress()) { 202 | _state = PB_ON_PRESS; 203 | } 204 | if (onRelease()) { 205 | if (debug) { 206 | Serial.print(F(" Pressed for:\t")); Serial.print(elapsedMs); Serial.println(" ms"); 207 | } 208 | _state = PB_ON_RELEASE; 209 | } 210 | break; 211 | 212 | case PB_ON_PRESS: 213 | _state = PB_DEFAULT; 214 | break; 215 | 216 | case PB_ON_RELEASE: 217 | if (elapsedMs > CLICK::FAST && (!pCode || pCode >= 0x10)) _state = PB_LONG_CLICKS; 218 | else if (elapsedMs < CLICK::FAST && pCode >= 0x10) _state = PB_SHORT_CLICKS; 219 | else if (elapsedMs < CLICK::FAST && !pCode) _state = PB_FAST_CLICKS; 220 | else _state = PB_SHORT_CLICKS; 221 | break; 222 | 223 | case PB_FAST_CLICKS: 224 | pCode |= 0xF0; 225 | if ((pCode & 0x0F) < 0x0F) pCode += 1; 226 | _state = PB_DEFAULT; 227 | break; 228 | 229 | case PB_SHORT_CLICKS: 230 | if ((pCode & 0x0F) < 0x0F) pCode += 1; 231 | _state = PB_DEFAULT; 232 | break; 233 | 234 | case PB_LONG_CLICKS: 235 | if ((pCode & 0xE0) < 0xE0) pCode += 0x10; 236 | _state = PB_DEFAULT; 237 | break; 238 | 239 | case PB_DONE: 240 | if (debug) { 241 | Serial.print(F(" Code:\t\t")); Serial.println(pCode, HEX); Serial.println(); 242 | } 243 | code = pCode; 244 | pCode = 0; 245 | _state = PB_DEFAULT; 246 | if (code) return code; 247 | break; 248 | 249 | default: 250 | _state = PB_DEFAULT; 251 | break; 252 | } 253 | return 0; 254 | } 255 | 256 | /************** debouncer ************************************************************************ 257 | The debouncer() function by default uses a robust algorithm that ignores up to 2 spurious 258 | signal transitions (glitches) and only adds up to 2 sample periods time lag to the output signal. 259 | **************************************************************************************************/ 260 | uint8_t Toggle::debouncer(uint8_t bit) { 261 | pOut = out; 262 | uint8_t bits = 2; 263 | if (_inputMode == inputMode::input_byte) bits = 8; 264 | 265 | for (int i = bit; i < bit + bits; i++) { 266 | if ((dat & (1 << i)) && (pDat & (1 << i)) && (ppDat & (1 << i))) out |= (1 << i); 267 | else if (!(dat & (1 << i)) && !(pDat & (1 << i)) && !(ppDat & (1 << i))) out &= ~(1 << i); 268 | } 269 | if ((pOut & (1 << bit)) && !isReleased(bit)) { 270 | csr |= 0b10000000; // set onPress 271 | csr &= ~0b01000000; // clear onRelease 272 | } else { 273 | if ((!(pOut & (1 << bit))) && isReleased(bit)) { 274 | csr |= 0b01000000; // set onRelease 275 | csr &= ~0b10000000; // clear onPress 276 | } 277 | } 278 | ppDat = pDat; 279 | pDat = dat; 280 | return out; 281 | } 282 | 283 | /*********** 3 position switch functions ****************/ 284 | 285 | bool Toggle::isUP() { 286 | return isPressed(); 287 | } 288 | 289 | bool Toggle::isDN() { 290 | return !(out & 0b00000010); 291 | } 292 | bool Toggle::isMID() { 293 | return isReleased() && !isDN(); 294 | } 295 | 296 | bool Toggle::UPtoMID() { 297 | return onRelease(); 298 | } 299 | 300 | bool Toggle::MIDtoUP() { 301 | return onPress(); 302 | } 303 | 304 | bool Toggle::MIDtoDN() { 305 | if ((pOut & 0b00000010) && isDN()) { 306 | pOut = out; 307 | return true; 308 | } 309 | return false; 310 | } 311 | 312 | bool Toggle::DNtoMID() { 313 | if (!(pOut & 0b00000010) && !isDN()) { 314 | pOut = out; 315 | return true; 316 | } 317 | return false; 318 | } 319 | -------------------------------------------------------------------------------- /src/Toggle.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef Toggle_h 3 | #define Toggle_h 4 | #include 5 | 6 | class Toggle { 7 | 8 | public: 9 | 10 | enum class inputMode : uint8_t {input = 0, input_pullup = 2, input_pulldown = 9, input_byte = 250}; 11 | 12 | Toggle(); 13 | Toggle(uint8_t *in); 14 | Toggle(uint8_t inA, uint8_t inB = 255) { 15 | _inA = inA; 16 | _inB = inB; 17 | }; 18 | 19 | void begin(uint8_t inA, uint8_t inB = 255); // required in setup 20 | void poll(uint8_t bit = 0); // required at top of loop 21 | void setInputMode(inputMode inputMode); // input, input_pullup, input_pulldown, input_byte 22 | void setInputInvert(bool invert); // normal(0), inverted(1) 23 | bool toggle(); // use momentary push button as a toggle switch 24 | void setToggleState(bool toggleState); // set toggle state 25 | void setToggleTrigger(bool change); // set toggle trigger mode: onPress(0), onRelease(1) 26 | void setSamplePeriodUs(uint16_t samplePeriodUs); // sample period in microseconds 27 | void clearTimer(); // clear ms timer 28 | uint32_t getElapsedMs(); // get elapsed ms since the last state change selected by timer mode 29 | bool isPressed(uint8_t bit = 0); // returns true if pressed 30 | bool isReleased(uint8_t bit = 0); // returns true if released 31 | bool onPress(); // returns true if just pressed 32 | bool onRelease(); // returns true if just released 33 | uint8_t onChange(); // returns: no change(0), onPress(1), onRelease(2) 34 | bool blink(uint16_t ms, uint8_t mode = 1); // returns true for given ms (blink) on mode: change(0), press(1), release(2), 35 | bool pressedFor(uint16_t ms); // returns true if pressed for at least the given ms 36 | bool releasedFor(uint16_t ms); // returns true if released for at least the given ms 37 | bool retrigger(uint16_t ms); // returns true each time the given ms expires while the button is pressed 38 | uint8_t pressCode(bool debug = 0); // returns byte code for number of fast, short and long clicks 39 | uint8_t debouncer(uint8_t bit = 0); // input debouncer 40 | bool isUP(); // functions for using 2 inputs with 3-position switches 41 | bool isMID(); 42 | bool isDN(); 43 | bool UPtoMID(); 44 | bool MIDtoDN(); 45 | bool DNtoMID(); 46 | bool MIDtoUP(); 47 | 48 | private: 49 | 50 | enum CLICK : uint16_t {FAST = 200, DONE = 500}; 51 | enum fsm_t : uint8_t { // finite state machine 52 | PB_DEFAULT = 0, 53 | PB_ON_PRESS = 1, 54 | PB_ON_RELEASE = 2, 55 | PB_FAST_CLICKS = 3, 56 | PB_SHORT_CLICKS = 4, 57 | PB_LONG_CLICKS = 5, 58 | PB_DONE = 6 59 | }; 60 | 61 | fsm_t _state = PB_DEFAULT; 62 | inputMode _inputMode = inputMode::input_pullup; 63 | 64 | uint8_t _inA, _inB; // input pin 65 | uint8_t *_in; // referenced to input variable 66 | uint8_t dat, pDat, ppDat; // current, previous and 2nd previos input data 67 | uint16_t us_period = 5000; // sample period μs 68 | uint32_t startUs = 0; // for timing transitions 69 | uint32_t us_timestamp; // most recent sample time μs 70 | uint8_t out = 0xFF, pOut = 0xFF; // debounced output and previous debounced output 71 | 72 | // B7:onPress, B6:onRelease, B5:lastToggleState, B4:toggleState, B3:x, B2:toggleTrigger, B1:invertInput, B0:firstRun 73 | uint8_t csr = 0b00000001; // status register 74 | 75 | bool getInputInvert(); 76 | bool getToggleState(); 77 | void setLastToggleState(bool lastToggleState); 78 | bool getLastToggleState(); 79 | bool getToggleTrigger(); 80 | }; 81 | #endif 82 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Test Runner and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/en/latest/advanced/unit-testing/index.html 12 | --------------------------------------------------------------------------------