Best practice for many-button controller

Status
Not open for further replies.

jonfitt

Member
Hello,

I have a project which has a mixture of 10 buttons and toggles, and 2 rotary encoders. The idea is that all of them will send joystick button presses.
Some toggles will be on/off like a button, but some will send a momentary joybutton push when toggled. The rotary encoders will send a momentary joybutton push when they increment/decrement.

In isolation everything is good. The rotary encoders are interrupt driven and the loop() queries their value for changes, and the other buttons/toggles are debounced and updated.

But putting it all together it looks a little ugly. Every loop() I check the values of the rotary encoders for changes, then "if rising/falling edge" all 10 buttons and joybutton them if active. It's a massive long list of if's.

1) Is that an ok way to operate? I don't have a good measure for the time it takes to go through that loop. Should I check all of the control positions then do the joystick update in one update?

2) To do the momentary joystick button presses when the rotary encoder ticks, or a toggle toggles I do:
Code:
 Joystick.button(13, 1);
delay(50);
Joystick.button(13, 0);
But that adds a 50ms block every time this activates. Is that a problem? I'm not making a fighting game controller, this is a flight sim control panel, so super speed is not an issue.

Basically, is this a perfectly fine way to do a controller or am I unaware of a much preferable paradigm?
 

Attachments

  • elite_controller.ino
    10.4 KB · Views: 88
It is generally a bad idea to use the delay(). When using delay(), your code can not respond to user input while the delay is happening.

Use elapsedMillis & elapsedMicros more info here:https://www.pjrc.com/teensy/td_timing_elaspedMillis.html


Just a quick example.
Code:
elapsedMillis since_reset_request_flag_B9;
elapsedMillis since_reset_request_flag_B10;

void setup() { }

void loop()
{
  if (since_reset_request_flag_B9 >= 50) {
    Joystick.button(9, 0);
  }
  if (since_reset_request_flag_B10 >= 50) {
    Joystick.button(10, 0);
  }

  // Check each button for "falling" edge.
  // Update the Joystick buttons only upon changes.
  // falling = high (not pressed - voltage from pullup resistor)
  //           to low (pressed - button connects pin to ground)
  if (button7.fallingEdge()) {
    Joystick.button(8, 1);
  }
  if (button8.fallingEdge()) {
    Joystick.button(9, 1);
    since_reset_request_flag_B9 = 0; // set the variable back to zero
    // delay(buttonPushLengthMs);
    // Joystick.button(9, 0);
  }
  if (button9.fallingEdge()) {
    Joystick.button(10, 1);
    since_reset_request_flag_B10 = 0; // set the variable back to zero
    // delay(buttonPushLengthMs);
    // Joystick.button(10, 0);
  }


  // Check each button for "rising" edge
  // Update the Joystick buttons only upon changes.
  // rising = low (pressed - button connects pin to ground)
  //          to high (not pressed - voltage from pullup resistor)
  if (button8.risingEdge()) {
    Joystick.button(9, 1);
    since_reset_request_flag_B9 = 0; // set the variable back to zero
    //delay(buttonPushLengthMs);
    //Joystick.button(9, 0);
  }
  if (button9.risingEdge()) {
    Joystick.button(10, 1);
    since_reset_request_flag_B10 = 0; // set the variable back to zero
    //delay(buttonPushLengthMs);
    //Joystick.button(10, 0);
  }

}
Also you have to fix the other delay() in your rotary encoders.
 
Last edited:
elapsed millis is the way to go ...

Also maybe you could think about restructuring to use callbacks ...

I guess button10.fallingEdge function returns true if there is a falling edge.

Why not rewrite it so that instead of returning true, it calls a function pointer that you have passed to it? No more if's ..

So you write a function for Joystick.button (9,1) etc void JS9 () {Joystick.button(9,1);}, and pass its signature to a function pointer variable in Button class function (maybe on construction) Button button10 (JS9 )...the constructor would initialise a function pointer variable 'func' and within Button class member function fallingEdge would be something like bool/void fallingEdge () {func();}

You just call button10.fallingEdge() in loop() as quickly as you can, without ifs! instead of returning true, the function calls "JS9".
 
Last edited:
Every loop() I check the values of the rotary encoders for changes, then "if rising/falling edge" all 10 buttons and joybutton them if active. It's a massive long list of if's.

1) Is that an ok way to operate?

Yes, that's perfectly fine. It may look like a lot of code, but Teensy does this sort of thing very fast.

As others mentioned, avoid delay(). Use an elapsedMillis variable and more if checks. Yes, it's more code on your screen, but this way performs much better because it never waits and spends time not checking the many if conditions.
 
Thanks.
I've updated it to use elapsedMillis instead of short delays. I'll see how it goes once I get all of the switches soldered. I've tried them all in isolation.

If I need to switch to callbacks I can do that but the Button class is in the Bounce library I believe, and I'd rather stick with using it as-is unless really necessary. This is a flight sim controller, and not a fight stick so I have some delay tolerance ;P
 
Status
Not open for further replies.
Back
Top