tauzero Posted April 28, 2023 Share Posted April 28, 2023 3 hours ago, 3below said: Not that long ago I wrote a page turner Blue Tooth app for ESP32 to drive my tablet with music tabs etc. This involved short and long button detection, debounce etc. I tried several libraries and eventually reached a simple approach adapted from somewhere that I can not now remember / find. I have pmd you some code that is straightforward and seemed effective. HTH. In Arduinoland, ezButton is pretty handy. https://arduinogetstarted.com/tutorials/arduino-button-library - the button array is good for multiple buttons. Setting the pressed time when isPressed is true and then comparing it when isReleased is true and seeing what the delay was. I did a similar ESP32 page turner using bluetooth - made it to send page up/down, line up/down, or next/prev window with a display to say which it was doing. That was before I encountered ezButton so it debounced by spotting the button press, hanging on briefly, then checking if the button was still pressed. Subsequently I saw another method which I thought was very clever - there's a variant on it at https://www.e-tinkers.com/2021/05/the-simplest-button-debounce-solution/ (go down to "The simplest debounce function"). 3 Quote Link to comment Share on other sites More sharing options...
Woodinblack Posted April 28, 2023 Share Posted April 28, 2023 9 hours ago, Smanth said: The wise wise words of one who has been there and done that! I can spot a rabbit hole and dive into it from a long way away! 1 Quote Link to comment Share on other sites More sharing options...
SamIAm Posted April 29, 2023 Author Share Posted April 29, 2023 (edited) Mostly been working on some houskeeping. I "think" I've got the Cmake configuration sorted (I'm new to writing CMakeLists so it's been ... fiddly!) It has been a couple of years or so since I last used VSCode in anger (And that was for TypeScript work) ... I've been adding in all the nice extensions/settings that I like (eg Rainbow Indent, Git Lens, Error Lens, Commitizen, Better Comments) I'm sure I've missed some but my dev environment is now humming along nicely. At some point I'll need to eat the frog of getting the debugger working ... but not just yet! I've done the first checkin of the codebase to git. (My intention is to share this once I've reached a MVP). The PICO SDK can automagically hook up printf etc to the serial output to make debug printg easy ... however ... when this approach is used, it it rather selfish and will not let other play with the USB and this is incompatabile with my USB for Serial and MIDI approach. So I've written a crude but effective wrapper around sprintf and serial send to allow me to achieve a serial printf. It's been my first forray into variadic functions. Following on from the link that @tauzeroshared about easy button debouncing I stumbled across http://www.ganssle.com/ which has a wealth of information on writing code on embedded systems. After a bunch of reading I am going to try to incorporate FreeRTOS into Trampa to keep the architecture simple. It provides a widely used, robust and very tiny kernel that supports multitasking (including multicore!) with the stuff that makes it easy like queues, semaphores, mutexes etc. It also has a set of libraries that bring in stuff like Http (when and if I reach the Trampa web based config functionality). So my next goal is to convert my current "MIDI Note Player with Serial Debug Print" example so that it runs under FreeRTOS. Fingers crossed ... S'manth x Edited April 29, 2023 by Smanth 1 Quote Link to comment Share on other sites More sharing options...
Woodinblack Posted April 30, 2023 Share Posted April 30, 2023 Certainly sounds like you are diving straight in! I hate cmake with a passion (and not a huge fan of VSCode either), but they are good for that sort of thing if you can deal with their stupid ways and mostly unavoidable if you are using a bunch of different other peoples libraries. I haven't done anything about footpedals yet - I am a few months behind you and I am still not sure how I want to use the device, it could well be I can use the mvave as it is. 1 Quote Link to comment Share on other sites More sharing options...
SamIAm Posted April 30, 2023 Author Share Posted April 30, 2023 Progress ... Wired up my buttons with a long cable so I can connect it to the prototyping breadboard my PICO lives on. Managed to integrate FreeRTOS so that now all activities are multi threading, it was not as fiendishly difficult as I feared ... the code was very straightforward, the CMake configuration took much longer. I've now got a system that will print when it detects button presses, a hop skip and a jump away from being able to fire off simple MIDI control messages. Then the logic to properly debounce the buttons as well as detect single/double/long taps. S'manth x 3 Quote Link to comment Share on other sites More sharing options...
fleabag Posted May 1, 2023 Share Posted May 1, 2023 Jeeeeez .. I got as far as Here She Goes Again in post 1, and the rest is Egyptian hieroglyphics. You clever peep ! Sounds ultra cool and ultra complicated to a buffoon 1 Quote Link to comment Share on other sites More sharing options...
SamIAm Posted May 6, 2023 Author Share Posted May 6, 2023 (edited) Some progress made on detecting button presses. I found a nifty algorithm to debounce presses (it uses an integration approach). After a lot of reading it seems that detecting presses using interrupts is generally not considered a good idea, so I am now having a background timer fire a "button tick" every 5ms that will check button state, seems to be working. I think I've got a state machine sorted for the buttons, this will be run (for each button) when the "button tick" occurs. A button can be configured in one of two ways: clickDetect OFF A button will simply react to a press and release. There should be minimal (a few ms) delay in doing so. This will be suitable for tap tempo/looper type situations where the latency needs to be as low as possible. A button can be configured to be latching- alternate press/release activity will alternately send a message A or message B when pressed (And optionally message A1 & B1 upon release). non-latching - the same message will be sent every time it is pressed (optionally a differnet message when released) clickDetect ON A button will be able to detect single/double (even triple if desired) clicks and send a suitable singleClick/doubleClick/etc message when it has decided which situation applies. The button needs to wait a short time (configurable) after a release before it decides that no more clicks are going to happen. I'm going to try about 0.3 seconds initially and see how it goes. Whilst not a huge delay (Hopefully responsive enough for changing snapshots or perhaps even turning on/off 'pedals') I suspect it will not be great for time sensitive uses (Hence the clickDetect OFF mode). A button in theis mode can also detect if it is held down for a longer time (say 0.6s) and send a hold message and if it continues to be held down it will periodically send a repeat message, I see this as useful for scrollowing through patches/pedalboards between songs. I'd appreciate those of you with a technical bent letting me know of any issues/gaffs you can see. S'manth x Edited May 6, 2023 by Smanth Quote Link to comment Share on other sites More sharing options...
Woodinblack Posted May 6, 2023 Share Posted May 6, 2023 Certainly looks like you have looked into it a lot (a lot more than I would ever get round to doing!) - do you always want to wait for the button up? I know if I was only using a switch as a toggle I would want it to toggle on the leading edge (also tap tempo). Another thought having done my early programming on keyboards that really had a debounce problem (Acorn atom keyboards were terrible), do modern footswtiches have much of a bounce? I know old ones didn't as they had a lever that flicked over - one smallish rc input on the board on it and you had no bounce at all from a digital point of view. Good for not using an interrupt for a button (I wouldn't use a timer either, I would just be constantly monitoring, what more is a footpedal going to be doing?), that would make it a bit more complicated. 1 Quote Link to comment Share on other sites More sharing options...
SamIAm Posted May 6, 2023 Author Share Posted May 6, 2023 (edited) 30 minutes ago, Woodinblack said: do you always want to wait for the button up? Only in the clickDetect mode. In the non clickDetect I will fire the 'pressed' event as soon as the the debounce code reports the button is pressed, ditto for the 'released' event ... I would hope that the debounce settle time will not add more than about 10ms or so (Of course I'll need to see how quickly the switches I've got do actually take to settle). (I've just noticed that my state diagram did not reflect this correctly, I've fixed it) The system will be doing a number of decoupled things including. Detecting button state changes Sampling the expression pedal changes (via ADC) Sending out MIDI messages Checking for MIDI messages. I don't intend on doing any MIDI hub stuff at present, but I intend to use the Dwarf buttons to select/load snapshots/pedalboards. It is possible to configure things (using one of the MIDI plugins) so that a different MIDI message is sent from the Dwarf each time and I intend on using this to reconfigure the Trampa. Reading/sending HTTP messages for the web configuration interface. Periodic updating the OLED displays to allow for scrolling of long text content. I'm using the FreeRTOS kernel to provide threading, I'm not sure yet if I'll run one per category (one button thread, one display thread) or one thread per individual input/output device ... the latter has the appeal that once I've created a deviceobject and kicked it off running I can pretty much forget about it and having to do my own interleaving/scheduling of checking this, that and the other. S'manth x Edited May 6, 2023 by Smanth Quote Link to comment Share on other sites More sharing options...
Woodinblack Posted May 6, 2023 Share Posted May 6, 2023 29 minutes ago, Smanth said: The system will be doing a number of decoupled things including. Detecting button state changes Sampling the expression pedal changes (via ADC) Sending out MIDI messages Checking for MIDI messages. I don't intend on doing any MIDI hub stuff at present, but I intend to use the Dwarf buttons to select/load snapshots/pedalboards. It is possible to configure things (using one of the MIDI plugins) so that a different MIDI message is sent from the Dwarf each time and I intend on using this to reconfigure the Trampa. Reading/sending HTTP messages for the web configuration interface. Periodic updating the OLED displays to allow for scrolling of long text content. Huge reply snipped as this isn't a design meeting, its your project and I have finished work for the rest of the month!! For me, i wouldn't consider most of those things decoupled, just 'operating system tasks' (the button state, screen state, midi monitoring and expression pedal) which I would all be doing just on a schedule with a few queues as a low level thing. The HTTPD stuff, obviously whole different ball game and that certainly needs its own world - as far as i see that is where 90% of the complication will be. But as you say you have a whole operating system now so you can just do them as a series of different objects and abstract it away and that is probably the easiest way to do it. It is however checking how much worry you have to put into buttons and switches (and also expression pedals) to debounce as sometimes it is far less of an issue than you think, especially these days. Going to be fun though, and also good to have some milestones along the way. 1 Quote Link to comment Share on other sites More sharing options...
3below Posted May 6, 2023 Share Posted May 6, 2023 As always an incredible amount of progress and skill The debounce integrator code is very elegant to say the least :). @Woodinblack's keyboard comments and mention of rc leads me to ask have you considered hardware debouncing? https://www.digikey.co.uk/en/articles/how-to-implement-hardware-debounce-for-switches-and-relays. If you debounce using hardware you would then be able to poll the input buttons concurrently as a single port byte. This would reduce the latency caused by software debouncing each input after serial reads. Equally well I may have misunderstood the situation and this suggestion is irrelevant. 1 Quote Link to comment Share on other sites More sharing options...
SamIAm Posted May 6, 2023 Author Share Posted May 6, 2023 (edited) 34 minutes ago, 3below said: As always an incredible amount of progress and skill The debounce integrator code is very elegant to say the least :). @Woodinblack's keyboard comments and mention of rc leads me to ask have you considered hardware debouncing? https://www.digikey.co.uk/en/articles/how-to-implement-hardware-debounce-for-switches-and-relays. If you debounce using hardware you would then be able to poll the input buttons concurrently as a single port byte. This would reduce the latency caused by software debouncing each input after serial reads. Equally well I may have misunderstood the situation and this suggestion is irrelevant. I did come across a s/w debounce that handles parallel inputs and the PICO does have a read all GPIO pins at once function, but I steered away from that approach simply because it seems cleaner (tho admittedly not as effecient) to have a single button object per GPIO pin. I had considered h/w debouncing, but I'm using SPST switches and I got the impression that the approaches to debounce these in h/w still require about 20ms to reach a steady output signal (This sorta makes sense at a gut level too, regardless of how you debounce something, you need it to stop bouncing to do so), so it didn't seem worth bothering. S'manth x Edited May 6, 2023 by Smanth 1 Quote Link to comment Share on other sites More sharing options...
Woodinblack Posted May 6, 2023 Share Posted May 6, 2023 Indeed - there is no time advantage doing a hardware debounce, you still need to wait for a stable state however it is done. 2 Quote Link to comment Share on other sites More sharing options...
3below Posted May 6, 2023 Share Posted May 6, 2023 Thinking some more about this, consider four buttons (B1 -> B4) being serially polled. On each each poll a detection event must happen for say 20ms. This means that an event on B4 could be missed while B1 -> B3 are being polled serially and detected (say 60ms total). Using a dedicated hardware debounce with 4 channels continuously detecting and reading the buttons as a port byte every 20ms would reduce the worst case detection latency to 40ms. The advantage of hardware debounce is not in the single case, it is when you have parallel concurrent switch operation. On the plus side I suspect in real operation foot switch timing changes are not this critical with only 4 switches. 1 Quote Link to comment Share on other sites More sharing options...
SamIAm Posted May 6, 2023 Author Share Posted May 6, 2023 1 minute ago, 3below said: Thinking some more about this, consider four buttons (B1 -> B4) being serially polled. On each each poll a detection event must happen for say 20ms. This means that an event on B4 could be missed while B1 -> B3 are being polled serially and detected (say 60ms total). Using a dedicated hardware debounce with 4 channels continuously detecting and reading the buttons as a port byte every 20ms would reduce the worst case detection latency to 40ms. The advantage of hardware debounce is not in the single case, it is when you have parallel concurrent switch operation. On the plus side I suspect in real operation foot switch timing changes are not this critical with only 4 switches. Sound thinking. I am using FreeRTOS to prempetively multithread. Each button object has its own thread with a wait 5ms then check the button kinda logic, so I would not anticipate any stacking up of latency as you describe. I've not (and I'm not totally sure how I could without falling foul of Heisenberg) timed how long it takes the code to read the button state and run the simple FSM, but my gut says it is likely to be really fast (and I've some performance tuning ideas I could implement if needed.) Having said all that, I've not a lot of experience with microcontrollers so ... hic sunt dracones! 🤣 S'manth x 1 Quote Link to comment Share on other sites More sharing options...
3below Posted May 6, 2023 Share Posted May 6, 2023 1 minute ago, Smanth said: Sound thinking. I am using FreeRTOS to prempetively multithread. Each button object has its own thread with a wait 5ms then check the button kinda logic, so I would not anticipate any stacking up of latency as you describe. I've not (and I'm not totally sure how I could without falling foul of Heisenberg) timed how long it takes the code to read the button state and run the simple FSM, but my gut says it is likely to be really fast (and I've some performance tuning ideas I could implement if needed.) Having said all that, I've not a lot of experience with microcontrollers so ... hic sunt dracones! 🤣 S'manth x ✴️ Multithreading ✴️ I have been working in 'old world' single thread linear microcontroller program mode. Carry on, ignore me lol, and yes we have dragons in Wales. 1 Quote Link to comment Share on other sites More sharing options...
Woodinblack Posted May 6, 2023 Share Posted May 6, 2023 Not sure why you would be taking 20ms for a detection event on a button, I would be very worried about my code if it was taking more than 100us to scan all the buttons, although I haven't done any timings on this. 1 Quote Link to comment Share on other sites More sharing options...
3below Posted May 6, 2023 Share Posted May 6, 2023 (edited) 34 minutes ago, Woodinblack said: Not sure why you would be taking 20ms for a detection event on a button, I would be very worried about my code if it was taking more than 100us to scan all the buttons, although I haven't done any timings on this. I was working on the 20ms 'bounce/settling' time, sample the button and keep sampling until a change of state (or not) is certain. However, from your question I now realise we could sample through B1 -> B4 store results and then every 20ms or so do a state check. Slowly getting brain into gear will get electronics kit out, think and play tomorrow. Edited May 6, 2023 by 3below Quote Link to comment Share on other sites More sharing options...
Woodinblack Posted May 6, 2023 Share Posted May 6, 2023 Indeed - sample all the buttons, adc etc every milisecond, and then when you have decided that the switch is not bouncing (and it doesn't need to be anything like 20ms) report any state changes. The worst latency you would have would be the time you have given for the button stabilisation + a max of just less than 1ms. 1 Quote Link to comment Share on other sites More sharing options...
SamIAm Posted May 7, 2023 Author Share Posted May 7, 2023 (edited) 13 hours ago, Woodinblack said: Not sure why you would be taking 20ms for a detection event on a button, I would be very worried about my code if it was taking more than 100us to scan all the buttons, although I haven't done any timings on this. I've just got some crude timings and it appears that a scan of my 4 buttons comes in at under 10us when they are quiescent and no higher than about 75us when I'm pressing them. edit: I've just realised most of this increase is probably due to me serial printing when the button is pressed! S'manth x Edited May 7, 2023 by Smanth 2 Quote Link to comment Share on other sites More sharing options...
Woodinblack Posted May 7, 2023 Share Posted May 7, 2023 Indeed - that is the sort of timings that seems reasonable and in the ballpark of 'that isn't going to cause me any problems'. 2 Quote Link to comment Share on other sites More sharing options...
tauzero Posted May 7, 2023 Share Posted May 7, 2023 16 hours ago, Woodinblack said: Indeed - sample all the buttons, adc etc every milisecond, and then when you have decided that the switch is not bouncing (and it doesn't need to be anything like 20ms) report any state changes. The worst latency you would have would be the time you have given for the button stabilisation + a max of just less than 1ms. Or maybe it has to be up to 157mS. http://www.ganssle.com/debouncing.htm 1 Quote Link to comment Share on other sites More sharing options...
SamIAm Posted May 7, 2023 Author Share Posted May 7, 2023 So button logic code written and checked. Flashed the PICO and nothing worked in any logical way 😖 Checked the code again ... and then again. Removed the debounce logic ... same thing. But then I realised that I had the state wrong for my button. My code was looking for active HIGH and the button is configured as active LOW ... D'oh! After correcting this, it now seems to work as intended ... well pleased Welcome to minicom 2.8 OPTIONS: Compiled on Oct 24 2022, 11:16:41. Port /dev/tty.usbmodemTrampa1, 16:36:52 Press Meta-Z for help on special keys Button[1]- Press Button[1]- Release Button[2]- Press Button[2]- Release Button[3]- Click Button[3]- Click Button[3]- DoubleClick Button[3]- TripleClick Button[3]- Hold Button[3]- Repeat Button[3]- Repeat Button[3]- Repeat Button[3]- Repeat Button[3]- Hold Release Button[3]- Hold(Shifted) Button[3]- Repeat(Shifted) Button[3]- Repeat(Shifted) Button[3]- Repeat(Shifted) Button[3]- Repeat(Shifted) Button[3]- Hold(Shifted) Release Button[4]- Click Button[4]- Hold(Shifted) Button[4]- Repeat(Shifted) Button[4]- Repeat(Shifted) Button[4]- Repeat(Shifted) Button[4]- Hold(Shifted) Release Press and Release are instant! Click (Single/Double/Triple) are detected about 0.2s after the last release. The shifted Hold (etc) are triggered by a short click followed by a hold ... so in all I am able to get 5 different actions from one button (if it is configured as clickDetect ON). I want to add some logic to detect simulataneous presses of adjacent buttons, but first I'm going to add in the "Let's send a MIDI CC message" based on the events raised. S'manth x 1 Quote Link to comment Share on other sites More sharing options...
3below Posted May 7, 2023 Share Posted May 7, 2023 Great stuff, did you use the Kenneth A. Kuhn debounce method? Quote Link to comment Share on other sites More sharing options...
SamIAm Posted May 7, 2023 Author Share Posted May 7, 2023 (edited) 26 minutes ago, 3below said: Great stuff, did you use the Kenneth A. Kuhn debounce method? Yes, it's part of my button class /* --.-- | ,---.,---.,-.-.,---.,---. | | ,---|| | || |,---| ` ` `---^` ' '|---'`---^ | (C) 2023 Samantha-uk https://github.com/samantha-uk/trampa Adapted from debounce.c https://www.kennethkuhn.com/electronics/debounce.c /****************************************************************************** debounce.c written by Kenneth A. Kuhn version 1.00 This is an algorithm that debounces or removes random or spurious transistions of a digital signal read as an input by a computer. This is particularly applicable when the input is from a mechanical contact. An integrator is used to perform a time hysterisis so that the signal must persistantly be in a logical state (0 or 1) in order for the output to change to that state. Random transitions of the input will not affect the output except in the rare case where statistical clustering is longer than the specified integration time. The following example illustrates how this algorithm works. The sequence labeled, real signal, represents the real intended signal with no noise. The sequence labeled, corrupted, has significant random transitions added to the real signal. The sequence labled, integrator, represents the algorithm integrator which is constrained to be between 0 and 3. The sequence labeled, output, only makes a transition when the integrator reaches either 0 or 3. Note that the output signal lags the input signal by the integration time but is free of spurious transitions. real signal 0000111111110000000111111100000000011111111110000000000111111100000 corrupted 0100111011011001000011011010001001011100101111000100010111011100010 integrator 0100123233233212100012123232101001012321212333210100010123233321010 output 0000001111111111100000001111100000000111111111110000000001111111000 I have been using this algorithm for years and I show it here as a code fragment in C. The algorithm has been around for many years but does not seem to be widely known. Once in a rare while it is published in a tech note. It is notable that the algorithm uses integration as opposed to edge logic (differentiation). It is the integration that makes this algorithm so robust in the presence of noise. ******************************************************************************/ #include "button.h" Button::Button(int id, ButtonConfig* buttonConfig, uint pin) { _id = id; _buttonConfig = buttonConfig; _pin = pin; } void Button::init(void) { gpio_init(_pin); gpio_set_dir(_pin, GPIO_IN); gpio_pull_up(_pin); } bool Button::pressed() { /* Step 1: Update the integrator based on the input signal. Note that the integrator follows the input, decreasing or increasing towards the limits as determined by the input state (0 or 1). */ if (gpio_get(_pin) == 1) { if (_integrator > 0) _integrator--; } else if (_integrator < INTEGRATOR_MAX) _integrator++; /* Step 2: Update the output state based on the integrator. Note that the output will only change states if the integrator has reached a limit, either 0 or MAXIMUM. */ if (_integrator == 0) _pressed = false; else if (_integrator >= INTEGRATOR_MAX) { _integrator = INTEGRATOR_MAX; /* defensive code if integrator got corrupted */ _pressed = true; } return _pressed; } void Button::setState(ButtonState state) { // write_serial("Button[%d] from [%d]->[%d]\r\n", _id, _state, state); _state = state; } void Button::check(void) { // Get the debounced current debounced pressed state of the button bool isPressed = pressed(); // bool isPressed = gpio_get(_pin); // Process the state machine for the button switch (_state) { case ButtonState::IDLE: if (isPressed) { // Capture the time _buttonPressedTime = get_absolute_time(); // Reset _clickCount _clickCount = 0; // should we send a PRESS event if (!_buttonConfig->_clickDetect) fireEvent(ButtonEvent::PRESS); // Change to PRESSED state setState(ButtonState::PRESSED); } break; case ButtonState::PRESSED: if (!isPressed) { // The button is now released if (_buttonConfig->_clickDetect) { // We ARE detecting clicks // Capture the time _buttonReleasedTime = get_absolute_time(); // Change to RELEASED state setState(ButtonState::RELEASED); } else { // We ARE NOT detecting clicks fireEvent(ButtonEvent::RELEASE); // Reset _clickCount _clickCount = 0; // Change to IDLE state setState(ButtonState::IDLE); } } else { // The button is still pressed // If we are detecting clicks and the time since _buttonDownTime is // longer than _holdDelay if (_buttonConfig->_clickDetect && absolute_time_diff_us(_buttonPressedTime, get_absolute_time()) >= _buttonConfig->_holdDelay) { fireEvent(ButtonEvent::HOLD); // Capture the time _buttonRepeatTime = get_absolute_time(); // Change to HOLD state setState(ButtonState::HOLD); } } break; case ButtonState::RELEASED: if (isPressed) { // The button is pressed // Capture the time _buttonPressedTime = get_absolute_time(); // Increment _clickCount _clickCount++; // Change to PRESSED state setState(ButtonState::PRESSED); } else { // The button is still releasaed // check to see if _clickDelay has passed since we entered RELEASED // state if (absolute_time_diff_us(_buttonReleasedTime, get_absolute_time()) >= _buttonConfig->_clickDelay) { // _clickDelay time has passed so we report the number of clicks fireEvent(ButtonEvent::CLICK); // Reset _clickCount _clickCount = 0; // Change to IDLE state setState(ButtonState::IDLE); } } break; case ButtonState::HOLD: // If the button is now released // _clickCount is used to indicate if any single clicks preceeded the hold // These are used as "shifts" to potentially alter the behaviour of // hold/repeat events. if (!isPressed) { // The button is now released fireEvent(ButtonEvent::HOLD_RELEASE); // Reset _clickCount _clickCount = 0; // Change to IDLE state setState(ButtonState::IDLE); } else { // The button is still pressed // If time down is longer than _repeatDelay if (absolute_time_diff_us(_buttonRepeatTime, get_absolute_time()) >= _buttonConfig->_repeatDelay) { fireEvent(ButtonEvent::REPEAT); // Reset _buttonHoldTime _buttonRepeatTime = get_absolute_time(); } } break; } } void Button::fireEvent(ButtonEvent event) { // Check to make sure that we have a cbFunction if (_buttonConfig->cbButtonEvent) { _buttonConfig->cbButtonEvent(_id, event, _clickCount, _latched); } if (_buttonConfig->_latching) _latched = !_latched; } ButtonConfig::ButtonConfig() {} S'manth x Edited May 7, 2023 by Smanth 1 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.