Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable GPIO buttons #274

Merged
merged 20 commits into from
Jul 15, 2022
Merged

Conversation

probonopd
Copy link
Owner

Thanks @diyelectromusic and @stevelittlefish

(This is a continuation of #271 and includes that PR, so only one of the two should be merged eventually.)

@probonopd
Copy link
Owner Author

A build is available for testing:
MiniDexed_2022-06-05-a9e4268

@stevelittlefish
Copy link

@probonopd I have pushed a working version with all of the suggested features. It would be good to get any feedback soon as I need to get back to my much more boring (but pays the bills) actual job!

@probonopd
Copy link
Owner Author

Thank you very much @diyelectromusic and @stevelittlefish. I think many MiniDexed users will be very happy!

There is one issue I found: It breaks shortcuts

While you are editing a parameter that applies to one of the Tone Generators (TG1...8) or one of the Operators (OP1...6), you can go from TG1...8 or from OP1...6 quickly by keeping the knob pressed and rotating it at the same time. This allows you to quickly edit the same parameter for multiple tone generators or operators.

To reproduce:

  • Go to TG1 -> Voice
  • Select a voice
  • While staying in the voice selection submenu, press the knob and rotate it to the right while it is pressed. You should be able to select TG2, 2,... TG8

My configuration:

#  Any buttons set to 0 will be ignored
ButtonPinPrev=0
ButtonActionPrev=
ButtonPinNext=0
ButtonActionNext=
ButtonPinBack=11
ButtonActionBack=longpress
ButtonPinSelect=11
ButtonActionSelect=click
ButtonPinHome=0
ButtonActionHome=

# KY-040 Rotary Encoder
EncoderEnabled=1
EncoderPinClock=10
EncoderPinData=9

@stevelittlefish
Copy link

@probonopd This is not really fixable with the current implementation (I'm not saying it shouldn't be fixed!).

How would you define which button is the shortcut button in the config? Bear in mind either select or back could be bound to double click so they can't really be used.

It sounds like we need to bring the encoder button back in some capacity but this particular feature gets messy.

How do you want this to work?

@stevelittlefish
Copy link

stevelittlefish commented Jun 7, 2022

The easiest fix would be to completely remove the select button, and reinstate the encoder switch pin. Then the encoder button would do the shortcut and also act as the select button.

EDIT: Actually I think we should keep the buttons exactly as they are, and put the encoder back exactly how it was. The buttons should just not replace the encoder.

We could add further configuration options to change what single, double and triple click do but that is separate to the features in this branch. Optionally you could unbind all of the encoder events and it would only function as a shift key for the shortcuts.

The technical advantage of this is that the encoder and buttons are isolated from each other. If we had to implement the shortcut functionality from the buttons, it would mean the encoder and buttons would have to interact, as the encoder would need to detect that a button is held down, and then cancel the click and long press for that button when it moves.

Alternatively, we would need to ditch the encoder library and handle the buttons and encoder together but this would be more effort than it is worth at this point.

@probonopd
Copy link
Owner Author

If we could use the old code for the encoder's button but the new configuration file esactly as it is now in this PR (which probably implies adding some code to make the action that happens when the encoder button is clicked/double-clicked/long-pressed configurable), I think that'd be the best of both worlds. Doable?

@stevelittlefish
Copy link

I don't think it's really possible.

First of all you have to specify which pin is the encoder button (i.e. pin 11). But the ky040 library supports triple click instead of long press, so the encoder can't do the same as the buttons. Personally I think long press is way better.

If the ky040 library handles the encoder button at the same time as the ui buttons, then there is going to be a conflict where a long press is triggered while you are using the shortcut. It would be impossible to have, for example, click->select; double click->back; long press->home while also having it do the shortcut.

If we really needed to get the encoder working with click, double click and long press the same as the buttons I think we would need to look for another encoder library or roll our own into the code base (it's not that complicated but I don't have time to do it).

I'd prefer not to add triple click to the buttons because I don't think triple click is as good as long press, it would increase code complexity and also it adds more latency on detecting a click event as you have to wait longer to account for the triple click.

Although it's a little messy, the best way to support all of the current functionality plus the configurable buttons is to add EncoderClickAction, EncoderDoubleClickAction and EncoderTripleClickAction as config variables.

For the default config, I'd suggest having most of the options commented out at the bottom of the file. You could have all of the button config and the encoder actions commented out, so by default the config would be identical as it currently is on the master branch. This means out of the box you would get the current implementation with a single encoder.

We could add a page on the wiki to show how to add the extra buttons. A common use case would be to unbind double click and triple click from the encoder button, and add a second button to do back with a click and home with a long press.

@diyelectromusic
Copy link
Contributor

I must admit that I wasn't quite sure why we took out the encoder switch... it seems much simpler to have an encoder mode and a switch mode for most "trivial" use cases but then allowing people to mix and match as they require if they are a little more willing to experiment.

Another minor point is that removing the "enabled" flag also means that the code has to run and check all buttons even when disabled, but at the clock speed of a Pi, that isn't likely to be a problem really I guess.

Just out of interest, did you measure the update period? I didn't get a chance to. I see you've added code to only scan if 100uS have passed... this will make the call to update quite "lumpy" but that might be fine (as I said elsewhere, I've not seen any documenting of the scheduler algorithms in play). If we're worried about updating every time, then different option (one I use in Arduino code quite a lot) would be to spread out the button scanning so it only checks one per call or something... but without measurements, I have no idea what would make a difference or not. Either way, if you're slowing it down, then I guess that answers the previous polling vs interrupt question! :)

I do worry slightly that the whole single-double-long press thing has made things quite a bit more complicated for users in what was originally quite a simple thing to grasp... but I don't have a strong view either way :) I do think the default configuration ought to err on the side of simple though.

Aside: why did you rename all the member variables? I thought they were following the naming convention used elsewhere? Again I don't really have a view, as long as it is all consistent, but I thought I was following this: https://github.com/probonopd/MiniDexed/wiki/Development#naming-conventions - so if I got that wrong, do let me know so I know for next time.

Thanks,
Kevin

@sou7611
Copy link

sou7611 commented Jun 7, 2022 via email

@stevelittlefish
Copy link

stevelittlefish commented Jun 7, 2022

I must admit that I wasn't quite sure why we took out the encoder switch... it seems much simpler to have an encoder mode and a switch mode for most "trivial" use cases but then allowing people to mix and match as they require if they are a little more willing to experiment.

It wasn't my suggestion, but it seems nice to only have 1 way of specifying buttons. I think we should probably add it back in though to support shortcuts, and also because the encoder library has no way to disable it - you have to bind it to a pin. (I bound it to GPIO0 as this pin is reserved.)

Another minor point is that removing the "enabled" flag also means that the code has to run and check all buttons even when disabled, but at the clock speed of a Pi, that isn't likely to be a problem really I guess.

If all of the button pins are set to 0 it basically does nothing. The time to loop through all buttons and check that they are 0 is not going to be an issue, but if it was we could just internally disable the buttons when they are all 0 without having to have a separate config option for it.

Just out of interest, did you measure the update period? I didn't get a chance to. I see you've added code to only scan if 100uS have passed... this will make the call to update quite "lumpy" but that might be fine (as I said elsewhere, I've not seen any documenting of the scheduler algorithms in play). If we're worried about updating every time, then different option (one I use in Arduino code quite a lot) would be to spread out the button scanning so it only checks one per call or something... but without measurements, I have no idea what would make a difference or not. Either way, if you're slowing it down, then I guess that answers the previous polling vs interrupt question! :)

I haven't measured it. If performance becomes an issue we could spread out the button scanning as you describe, but I'm not convinced it will make a noticeable difference.

The reason I put in the limit was because I had to set the timers to extremely high values to handle long press. Also, as more stuff gets added to the code base and the update cycle takes longer the numbers may need to be adjusted. I'm assuming the timer value will not change so will remain consistent.

I do worry slightly that the whole single-double-long press thing has made things quite a bit more complicated for users in what was originally quite a simple thing to grasp... but I don't have a strong view either way :) I do think the default configuration ought to err on the side of simple though.

I think most of the button options should be commented out so as not to make the initial config more confusing. Having long press on the back button to return to home is nice though!

Aside: why did you rename all the member variables? I thought they were following the naming convention used elsewhere? Again I don't really have a view, as long as it is all consistent, but I thought I was following this: https://github.com/probonopd/MiniDexed/wiki/Development#naming-conventions - so if I got that wrong, do let me know so I know for next time.

I actually never saw that page. I added quite a few variables and then I started getting confused and just changed all of the ones that I kept forgetting how they were spelt! It was a long day!

Like nPin and pPin - I changed them to pinNumber and pin so I could remember which one was which!

@stevelittlefish
Copy link

Another alternative solution would be to do this:

  • Add the encoder switch pin back to the config, for example we could set it back to pin 11
  • Continue to map the buttons using the new scheme, so maybe we would have pin 11 to select and another pin like 5 to go back
  • Inside the encoder handling code, single, double and triple click don't fire any events but it will keep track of if the button is held down
  • If the encoder is turned while the encoder button is held down, it does the current shortcut behaviour and also sends a signal to the button handler to ignore any in-progress events

This means if you click pin 11 and then turn the encoder, it would adjust the parameter on all voices as it previously did, and then call a function in CUIButtons to reset the state of any buttons bound to pin 11.

With the above solution we could rename the config variable as it doesn't actually have to be a button on the encoder, and it basically acts as a shift key for the encoder.

I'd go with either this behaviour, or just re-instating the encoder alongside the buttons and you can choose whether to use the encoder button or separate buttons (or both).

I feel like this has gone a little out of the original scope of just adding a separate back button!

@probonopd
Copy link
Owner Author

@stevelittlefish that approach you described in the post above sounds good to me as it would combine the best of both worlds if I understand it correctly. Do you think you could implement it? That would be trememdous. I know from other discussions that many users will appreciate this kind of configuration flexibility.

@stevelittlefish
Copy link

@probonopd I will try to get it done, but my wife literally just gave birth so I'm pretty busy all of a sudden!

@probonopd
Copy link
Owner Author

Congratulations!

@stevelittlefish
Copy link

@probonopd I'm going to try to get this done over the next few days. I know exactly what to do so it shouldn't take me too long.

I'll try and push something that is testable - then next week I will do one last push to clean up variable names and tidy everything up.

@stevelittlefish
Copy link

@probonopd I've pushed all of these changes now. Once you've tested and are happy with everything, let me know and I will remove all of the debug code and tidy some stuff up ready for merging.

@probonopd
Copy link
Owner Author

probonopd commented Jun 27, 2022

Thank you very much @stevelittlefish. Will test MiniDexed_2022-06-27-de1c9af now.

@probonopd
Copy link
Owner Author

probonopd commented Jun 27, 2022

In my quick test the following configuration worked well for me:

# GPIO Button Navigation
#  Any buttons set to 0 will be ignored
ButtonPinPrev=0
ButtonActionPrev=
ButtonPinNext=0
ButtonActionNext=
ButtonPinBack=11
ButtonActionBack=longpress
ButtonPinSelect=11
ButtonActionSelect=click
ButtonPinHome=11
ButtonActionHome=doubleclick
ButtonPinShortcut=11
# (Shortcut doesn't have an action)

# Timeouts in milliseconds for double click and long press
DoubleClickTimeout=400
LongPressTimeout=200

# KY-040 Rotary Encoder
EncoderEnabled=1
EncoderPinClock=10
EncoderPinData=9

I like it a lot! 👍 Definitely a great improvement.

Note that I have set LongPressTimeout=200 and I can do long presses and shortcuts; but I do realize that for some users doing shortcuts might be difficult to do given this setting.

Did you consider making the longpress event fire only on button up? In that case, LongPressTimeout=... could be set to an even shorter time because regardless of that time, if the encoder was rotated since the button down, then we'd know it is a shortcut event...
(This would need to be tried out; I can't say from theoretically thinking about it whether it would actually be better.)

@stevelittlefish
Copy link

Did you consider making the longpress event fire only on button up? In that case, LongPressTimeout=... could be set to an even shorter time because regardless of that time, if the encoder was rotated since the button down, then we'd know it is a shortcut event... (This would need to be tried out; I can't say from theoretically thinking about it whether it would actually be better.)

Honestly I hate it when longpresses are triggered on release. It's very difficult to tell if you've held the button for long enough as there is no feedback, so you always end up holding it for longer than you need to, just to be sure. I think it's much more useable with a longer timeout, triggering as soon as the timeout is reached.

Obviously that's just my opinion!

@stevelittlefish
Copy link

# Timeouts in milliseconds for double click and long press
DoubleClickTimeout=400
LongPressTimeout=200

# KY-040 Rotary Encoder
EncoderEnabled=1
EncoderPinClock=10
EncoderPinData=9

One thing to note - LongPressTimeout must be longer than DoubleClickTimeout. If you set it shorter than DoubleClickTimeout, it will print an error to the HDMI and increase LongPressTimeout to match DoubleClickTimeout.

So in your above example, it will have used 400ms for the Long Press.

@probonopd
Copy link
Owner Author

Makes sense...
I think functionality-wise this PR is definitely a candidate for merging now.
@fp64lib @craigyjp @diyelectromusic what do you think?

Would anyone volunteer to do a code review? Should be someone more versed in C++ than myself ;-)

@stevelittlefish
Copy link

Can we get this merged? It's been sitting here for a while.

@probonopd
Copy link
Owner Author

Indeed. Looks like in the process of experimenting, I fried the input pins on my Raspberry Pi due to electrical issues on my side, so can't test right now. But it was looking good in my last tests, so if no one objects in the last second, I'll go ahead and merge it.

Would you agree to making this the default?

# GPIO Button Navigation
#  Any buttons set to 0 will be ignored
ButtonPinPrev=0
ButtonActionPrev=
ButtonPinNext=0
ButtonActionNext=
ButtonPinBack=11
ButtonActionBack=longpress
ButtonPinSelect=11
ButtonActionSelect=click
ButtonPinHome=11
ButtonActionHome=doubleclick
ButtonPinShortcut=11
# (Shortcut doesn't have an action)

# Timeouts in milliseconds for double click and long press
DoubleClickTimeout=400
LongPressTimeout=400

# KY-040 Rotary Encoder
EncoderEnabled=1
EncoderPinClock=10
EncoderPinData=9

@stevelittlefish
Copy link

Would you agree to making this the default?

# GPIO Button Navigation
#  Any buttons set to 0 will be ignored
ButtonPinPrev=0
ButtonActionPrev=
ButtonPinNext=0
ButtonActionNext=
ButtonPinBack=11
ButtonActionBack=longpress
ButtonPinSelect=11
ButtonActionSelect=click
ButtonPinHome=11
ButtonActionHome=doubleclick
ButtonPinShortcut=11
# (Shortcut doesn't have an action)

# Timeouts in milliseconds for double click and long press
DoubleClickTimeout=400
LongPressTimeout=400

# KY-040 Rotary Encoder
EncoderEnabled=1
EncoderPinClock=10
EncoderPinData=9

I think this is fine - but I haven't really played with it with this configuration.

The way I was using it was to have LongPressTimeout really long (around 800ms), and to use double click for back, and long press for home.

I don't have a strong opinion though - the whole point of this set of changes is that I wanted to have a separate back button! :-)

Double click is now home and long press is back
@stevelittlefish
Copy link

@probonopd I've pushed a commit with the changes you suggested.

@probonopd
Copy link
Owner Author

Tested MiniDexed_2022-07-13-b2caa36, works very well for me. A big "Thank You" to everyone involved, especially @diyelectromusic and @stevelittlefish.

@probonopd probonopd merged commit 50e9b7b into probonopd:main Jul 15, 2022
@probonopd
Copy link
Owner Author

probonopd commented Jul 15, 2022

@probonopd
Copy link
Owner Author

probonopd commented Jul 15, 2022

Note to everyone updating to the new version:

PLEASE NOTE: minidexed.ini has changed. Please update yours accordingly when updating MiniDexed. Please see the sections "Buttons", "Timeouts", and "Rotary Encoder" at https://github.com/probonopd/MiniDexed/wiki/Files for details.

  • Default behavior changed: Click to enter menus, long-press to leave menus, double-click to go to the home screen (configurable)
  • Buttons can have click, double click and long press events
  • Changed default config, added new options and renamed EncoderPinSwitch to ButtonPinShortcut

@stevelittlefish
Copy link

Thanks all for your help getting this tested / ready for merge.

Kevin @diyelectromusic - your first batch of changes saved me a lot of time and head scratching and the discussion around polling vs interrupts was very helpful - definitely changed the way I will think about this going forwards!

@probonopd
Copy link
Owner Author

Look Ma! No rotary encoder!

@diyelectromusic
Copy link
Contributor

Oh, very nice :) Is that using a ssd1306 display too?

@probonopd
Copy link
Owner Author

Yes.

@probonopd
Copy link
Owner Author

probonopd commented Jul 23, 2022

What I think would be nice is if the left/right = prev/next buttons would continue to decrement/increment values while being hold down. E.g., when you change a numeric value, you would not have to press up to 127 times. (No long-press for these.)

@stevelittlefish
Copy link

What I think would be nice is if the left/right = prev/next buttons would continue to decrement/increment values while being hold down. E.g., when you change a numeric value, you would not have to press up to 127 times. (No long-press for these.)

Technically, this is very easy to achieve.

The thing to think about, however, is that it means those buttons behave differently to the other buttons. This causes 2 issues - first of all there is going to be a different handler for these buttons which means there are now 3 different places in the code where the buttons are handled (encoder, current buttons solution, and a special handler for these buttons). But perhaps more importantly, the ini file could get a little more complicated as there will be inconsistencies between the different buttons.

Another thing to consider is would you want the (press and hold) shortcut to work with these buttons too, so they can switch between the different channels?

I think it might be worth thinking about where you want to go with the user interface a bit longer term, considering the following:

  • This change that you've suggested - holding the buttons to increment the values
  • MIDI controller buttons triggering the UI
  • The current encoder library is handling one of the buttons and is not ideal (see below)

I personally am not very impressed with the ky040 encoder library - even with hardware debounce on the encoder pins it still doesn't work 100% accurately for me, even though with the exact same circuit with an arduino nano works flawlessly. The other issue I have with it is that there is no way to unmap the encoder button. If you set it to 0 in the config it actually binds it to GPIO0. It's not a huge deal as this pin is not likely to ever be used for anything, but I'd like to be able to turn off the encoder button!

I also find calling the encoder pins "clock" and "data" pretty confusing.

Those are just a few points to think about. If you want to go ahead with your suggested change I'm happy to do the development work as I'm familiar with the button handling - let me know!

@probonopd
Copy link
Owner Author

Thanks @stevelittlefish. By now you know this code much, much better than me, so you are in the best position to make technical choices. I will add my 2 cents about the intended behavior:

it means those buttons behave differently to the other buttons

That would be acceptable for me, as we use only the left/right = prev/next buttons for decrementing/incrementing values, for which we need the press-and-hold behavior.

would you want the (press and hold) shortcut to work with these buttons too

I would not need the prese-and-hold behavior for the shortcut (while selecting the different TGs), because it only goes through 8 values (whereas where we need the press-and-hold behavior there are 127 values most of the time). But of course the shortcut should still be usable with the left/right = prev/next buttons (for selecting the different TGs). I hope my description is clear, if not please ask.

I think it might be worth thinking about where you want to go with the user interface a bit longer term

I fear I cannot add much usable information to this discussion, but:

I personally am not very impressed with the ky040 encoder library - even with hardware debounce on the encoder pins it still doesn't work 100% accurately for me

For me neither - but I don't know how to improve the situation.

I also find calling the encoder pins "clock" and "data" pretty confusing.

That's actually how the rotary encoder PCBs are labeled, hence the library is using those terms, even though they are technically incorrect.

Sorry I can't be more helpful with the technical choices here, but I believe who implements this is best positioned to make technical decisions. I'd be very happy if you could give it a go @stevelittlefish.

@stevelittlefish
Copy link

@probonopd I never implemented the shortcut for the buttons at all - it only works on the encoder. Do we need to add it for the buttons too?

@probonopd
Copy link
Owner Author

The button in the encoder is also just a button... nothing prevented me from using the same pin for just a plain button - which I did, and it worked for me iirc. Will resume my tests as soon as I have soldered my v2 prototype modules :)

@stevelittlefish
Copy link

@probonopd I've been super busy with some work / life stuff and not got onto this. Can we open a new discussion for this set of features? Do you need me to create my branch first?

@diyelectromusic
Copy link
Contributor

For what it's worth I'm having a look at the feasibility of adding MIDI buttons, so in the interests of not having the code diverge too much at the same time, maybe there is no rush for an update right now? :)

@probonopd
Copy link
Owner Author

Thanks @stevelittlefish, no worries. We are not in a hurry. If you have some time, it'd be super cool if you could send a pull request for "shortcut for buttons" and/or "decrement/increment values while left/right = prev/next are being hold down".

If you would like to discuss more regarding these features first, feel free to open new issues for them first.

Thank you for your highly valuable contributions.

@sou7611
Copy link

sou7611 commented Oct 11, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants