acid-drop- Hacking the planet from a LilyGo T-Deck using custom firmware |
git clone git://git.acid.vegas/acid-drop.git |
Log | Files | Refs | Archive | README | LICENSE |
AceButton.cpp (12555B)
1 /* 2 MIT License 3 4 Copyright (c) 2018 Brian T. Park 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy 7 of this software and associated documentation files (the "Software"), to deal 8 in the Software without restriction, including without limitation the rights 9 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 copies of the Software, and to permit persons to whom the Software is 11 furnished to do so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 #include "TimingStats.h" 26 #include "AceButton.h" 27 28 namespace ace_button { 29 30 // Check that the Arduino constants HIGH and LOW are defined to be 1 and 0, 31 // respectively. Otherwise, this library won't work. 32 #if HIGH != 1 33 #error HIGH must be defined to be 1 34 #endif 35 #if LOW != 0 36 #error LOW must be defined to be 0 37 #endif 38 39 AceButton::AceButton(uint8_t pin, uint8_t defaultReleasedState, uint8_t id): 40 mButtonConfig(ButtonConfig::getSystemButtonConfig()) { 41 init(pin, defaultReleasedState, id); 42 } 43 44 AceButton::AceButton(ButtonConfig* buttonConfig): 45 mButtonConfig(buttonConfig) { 46 init(0, HIGH, 0); 47 } 48 49 void AceButton::init(uint8_t pin, uint8_t defaultReleasedState, uint8_t id) { 50 mPin = pin; 51 mId = id; 52 mFlags = 0; 53 mLastButtonState = kButtonStateUnknown; 54 mLastDebounceTime = 0; 55 mLastClickTime = 0; 56 setDefaultReleasedState(defaultReleasedState); 57 } 58 59 void AceButton::setDefaultReleasedState(uint8_t state) { 60 if (state == HIGH) { 61 mFlags |= kFlagDefaultReleasedState; 62 } else { 63 mFlags &= ~kFlagDefaultReleasedState; 64 } 65 } 66 67 uint8_t AceButton::getDefaultReleasedState() { 68 return (mFlags & kFlagDefaultReleasedState) ? HIGH : LOW; 69 } 70 71 // NOTE: It would be interesting to rewrite the check() method using a Finite 72 // State Machine. 73 void AceButton::check() { 74 // Get the micros. 75 uint16_t nowMicros = mButtonConfig->getClockMicros(); 76 77 // Retrieve the current time just once and use that in the various checkXxx() 78 // functions below. This provides some robustness of the various timing 79 // algorithms even if any of the event handlers takes more time than the 80 // threshold time limits such as 'debounceDelay' or longPressDelay'. 81 uint16_t now = mButtonConfig->getClock(); 82 83 uint8_t buttonState = mButtonConfig->readButton(mPin); 84 85 // debounce the button 86 if (checkDebounced(now, buttonState)) { 87 // check if the button was initialized (i.e. UNKNOWN state) 88 if (checkInitialized(buttonState)) { 89 checkEvent(now, buttonState); 90 } 91 } 92 93 TimingStats* stats = mButtonConfig->getTimingStats(); 94 if (stats != nullptr) { 95 uint16_t elapsedMicros = mButtonConfig->getClockMicros() - nowMicros; 96 stats->update(elapsedMicros); 97 } 98 } 99 100 void AceButton::checkEvent(uint16_t now, uint8_t buttonState) { 101 // We need to remove orphaned clicks even if just Click is enabled. It is not 102 // sufficient to do this for just DoubleClick. That's because it's possible 103 // for a Clicked event to be generated, then 65.536 seconds later, the 104 // ButtonConfig could be changed to enable DoubleClick. (Such real-time change 105 // of ButtonConfig is not recommended, but is sometimes convenient.) If the 106 // orphaned click is not cleared, then the next Click would be errorneously 107 // considered to be a DoubleClick. Therefore, we must clear the orphaned click 108 // even if just the Clicked event is enabled. 109 // 110 // We also need to check of any postponed clicks that got generated when 111 // kFeatureSuppressClickBeforeDoubleClick was enabled. 112 if (mButtonConfig->isFeature(ButtonConfig::kFeatureClick) || 113 mButtonConfig->isFeature(ButtonConfig::kFeatureDoubleClick)) { 114 checkPostponedClick(now); 115 checkOrphanedClick(now); 116 } 117 118 if (mButtonConfig->isFeature(ButtonConfig::kFeatureLongPress)) { 119 checkLongPress(now, buttonState); 120 } 121 if (mButtonConfig->isFeature(ButtonConfig::kFeatureRepeatPress)) { 122 checkRepeatPress(now, buttonState); 123 } 124 if (buttonState != getLastButtonState()) { 125 checkChanged(now, buttonState); 126 } 127 } 128 129 bool AceButton::checkDebounced(uint16_t now, uint8_t buttonState) { 130 if (isDebouncing()) { 131 132 // NOTE: This is a bit tricky. The elapsedTime will be valid even if the 133 // uint16_t representation of 'now' rolls over so that (now < 134 // mLastDebounceTime). This is true as long as the 'unsigned long' 135 // representation of 'now' is < (65536 + mLastDebounceTime). We need to cast 136 // this expression into an uint16_t before doing the '>=' comparison below 137 // for compatability with processors whose sizeof(int) == 4 instead of 2. 138 // For those processors, the expression (now - mLastDebounceTime >= 139 // getDebounceDelay()) won't work because the terms in the expression get 140 // promoted to an (int). 141 uint16_t elapsedTime = now - mLastDebounceTime; 142 143 bool isDebouncingTimeOver = 144 (elapsedTime >= mButtonConfig->getDebounceDelay()); 145 146 if (isDebouncingTimeOver) { 147 clearDebouncing(); 148 return true; 149 } else { 150 return false; 151 } 152 } else { 153 // Currently not in debouncing phase. Check for a button state change. This 154 // will also detect a transition from kButtonStateUnknown to HIGH or LOW. 155 if (buttonState == getLastButtonState()) { 156 // no change, return immediately 157 return true; 158 } 159 160 // button has changed so, enter debouncing phase 161 setDebouncing(); 162 mLastDebounceTime = now; 163 return false; 164 } 165 } 166 167 bool AceButton::checkInitialized(uint16_t buttonState) { 168 if (mLastButtonState != kButtonStateUnknown) { 169 return true; 170 } 171 172 // If transitioning from the initial "unknown" button state, just set the last 173 // valid button state, but don't fire off the event handler. This handles the 174 // case where a momentary switch is pressed down, then the board is rebooted. 175 // When the board comes up, it should not fire off the event handler. This 176 // also handles the case of a 2-position switch set to the "pressed" 177 // position, and the board is rebooted. 178 mLastButtonState = buttonState; 179 return false; 180 } 181 182 void AceButton::checkLongPress(uint16_t now, uint8_t buttonState) { 183 if (buttonState == getDefaultReleasedState()) { 184 return; 185 } 186 187 if (isPressed() && !isLongPressed()) { 188 uint16_t elapsedTime = now - mLastPressTime; 189 if (elapsedTime >= mButtonConfig->getLongPressDelay()) { 190 setLongPressed(); 191 handleEvent(kEventLongPressed); 192 } 193 } 194 } 195 196 void AceButton::checkRepeatPress(uint16_t now, uint8_t buttonState) { 197 if (buttonState == getDefaultReleasedState()) { 198 return; 199 } 200 201 if (isPressed()) { 202 if (isRepeatPressed()) { 203 uint16_t elapsedTime = now - mLastRepeatPressTime; 204 if (elapsedTime >= mButtonConfig->getRepeatPressInterval()) { 205 handleEvent(kEventRepeatPressed); 206 mLastRepeatPressTime = now; 207 } 208 } else { 209 uint16_t elapsedTime = now - mLastPressTime; 210 if (elapsedTime >= mButtonConfig->getRepeatPressDelay()) { 211 setRepeatPressed(); 212 // Trigger the RepeatPressed immedidately, instead of waiting until the 213 // first getRepeatPressInterval() has passed. 214 handleEvent(kEventRepeatPressed); 215 mLastRepeatPressTime = now; 216 } 217 } 218 } 219 } 220 221 void AceButton::checkChanged(uint16_t now, uint8_t buttonState) { 222 mLastButtonState = buttonState; 223 checkPressed(now, buttonState); 224 checkReleased(now, buttonState); 225 } 226 227 void AceButton::checkPressed(uint16_t now, uint8_t buttonState) { 228 if (buttonState == getDefaultReleasedState()) { 229 return; 230 } 231 232 // button was pressed 233 mLastPressTime = now; 234 setPressed(); 235 handleEvent(kEventPressed); 236 } 237 238 void AceButton::checkReleased(uint16_t now, uint8_t buttonState) { 239 if (buttonState != getDefaultReleasedState()) { 240 return; 241 } 242 243 // Check for click (before sending off the Released event). 244 // Make sure that we don't clearPressed() before calling this. 245 if (mButtonConfig->isFeature(ButtonConfig::kFeatureClick) 246 || mButtonConfig->isFeature(ButtonConfig::kFeatureDoubleClick)) { 247 checkClicked(now); 248 } 249 250 // check if Released events are suppressed 251 bool suppress = 252 ((isLongPressed() && 253 mButtonConfig-> 254 isFeature(ButtonConfig::kFeatureSuppressAfterLongPress)) || 255 (isRepeatPressed() && 256 mButtonConfig-> 257 isFeature(ButtonConfig::kFeatureSuppressAfterRepeatPress)) || 258 (isClicked() && 259 mButtonConfig->isFeature(ButtonConfig::kFeatureSuppressAfterClick)) || 260 (isDoubleClicked() && 261 mButtonConfig-> 262 isFeature(ButtonConfig::kFeatureSuppressAfterDoubleClick))); 263 264 // button was released 265 clearPressed(); 266 clearDoubleClicked(); 267 clearLongPressed(); 268 clearRepeatPressed(); 269 270 if (!suppress) { 271 handleEvent(kEventReleased); 272 } 273 } 274 275 void AceButton::checkClicked(uint16_t now) { 276 if (!isPressed()) { 277 // Not a Click unless the previous state was a Pressed state. 278 // This can happen if the chip was rebooted with the button Pressed. Upon 279 // Release, it shouldn't generated a click, even accidentally due to a 280 // spurious value in mLastPressTime. 281 clearClicked(); 282 return; 283 } 284 uint16_t elapsedTime = now - mLastPressTime; 285 if (elapsedTime >= mButtonConfig->getClickDelay()) { 286 clearClicked(); 287 return; 288 } 289 290 // check for double click 291 if (mButtonConfig->isFeature(ButtonConfig::kFeatureDoubleClick)) { 292 checkDoubleClicked(now); 293 } 294 295 // Suppress a second click (both buttonState change and event message) if 296 // double-click detected, which has the side-effect of preventing 3 clicks 297 // from generating another double-click at the third click. 298 if (isDoubleClicked()) { 299 clearClicked(); 300 return; 301 } 302 303 // we got a single click 304 mLastClickTime = now; 305 setClicked(); 306 if (mButtonConfig->isFeature( 307 ButtonConfig::kFeatureSuppressClickBeforeDoubleClick)) { 308 setClickPostponed(); 309 } else { 310 handleEvent(kEventClicked); 311 } 312 } 313 314 void AceButton::checkDoubleClicked(uint16_t now) { 315 if (!isClicked()) { 316 clearDoubleClicked(); 317 return; 318 } 319 320 uint16_t elapsedTime = now - mLastClickTime; 321 if (elapsedTime >= mButtonConfig->getDoubleClickDelay()) { 322 clearDoubleClicked(); 323 // There should be no postponed Click at this point because 324 // checkPostponedClick() should have taken care of it. 325 return; 326 } 327 328 // If there was a postponed click, suppress it because it could only have been 329 // postponed if kFeatureSuppressClickBeforeDoubleClick was enabled. If we got 330 // to this point, there was a DoubleClick, so we must suppress the first 331 // Click as requested. 332 if (isClickPostponed()) { 333 clearClickPostponed(); 334 } 335 setDoubleClicked(); 336 handleEvent(kEventDoubleClicked); 337 } 338 339 void AceButton::checkOrphanedClick(uint16_t now) { 340 // The amount of time which must pass before a click is determined to be 341 // orphaned and reclaimed. If only DoubleClicked is supported, then I think 342 // just getDoubleClickDelay() is correct. No other higher level event uses the 343 // first Clicked event. If TripleClicked becomes supported, I think 344 // orphanedClickDelay will be either (2 * getDoubleClickDelay()) or 345 // (getDoubleClickDelay() + getTripleClickDelay()), depending on whether the 346 // TripleClick has an independent delay time, or reuses the DoubleClick delay 347 // time. But I'm not sure that I've thought through all the details. 348 uint16_t orphanedClickDelay = mButtonConfig->getDoubleClickDelay(); 349 350 uint16_t elapsedTime = now - mLastClickTime; 351 if (isClicked() && (elapsedTime >= orphanedClickDelay)) { 352 clearClicked(); 353 } 354 } 355 356 void AceButton::checkPostponedClick(uint16_t now) { 357 uint16_t postponedClickDelay = mButtonConfig->getDoubleClickDelay(); 358 uint16_t elapsedTime = now - mLastClickTime; 359 if (isClickPostponed() && elapsedTime >= postponedClickDelay) { 360 handleEvent(kEventClicked); 361 clearClickPostponed(); 362 } 363 } 364 365 void AceButton::handleEvent(uint8_t eventType) { 366 ButtonConfig::EventHandler eventHandler = mButtonConfig->getEventHandler(); 367 if (eventHandler) { 368 eventHandler(this, eventType, getLastButtonState()); 369 } 370 } 371 372 }