Button box for sim racing

Hello All.

I have a question about a button box I'd like to make for sim racing. I have studied a few different threads about the same topic, but I have not found an answer for my question.

So, the basis for my project is a tutorial from AMStudio. I purchased the code from them which works. I want to make a couple of changes though. I would like to change one (or more) of the momentary switches used in this box out for latching switches to make the box more realistic. As an example I will use the first switch, which I will use for ignition on/off. Ignition on -> switch latched high, ignition off -> switch latched low.

I got it all working the way it should. I found a response from @johnwasser, who mentioned not to use button 0, so I changed the numbering to start with button 1. My question is, by doing this, do I now have the change my button box from a 32 button box to a 31 button box? When I plug in my little test rig, it still show button 0 - 31, with button 0 not responding (which is correct).

Here is my code;

//BUTTON BOX 
//USE w ProMicro
//Tested in WIN10 + Assetto Corsa
//AMSTUDIO
//20.8.17

#include <Keypad.h>
#include <Joystick.h>
#include <DebugUtils.h>

#define DEBUG
#define ENABLE_PULLUPS
#define NUMROTARIES 4
#define NUMBUTTONS 24
#define NUMROWS 5
#define NUMCOLS 5


byte buttons[NUMROWS][NUMCOLS] = {
  {1,2,3,4,5},
  {6,7,8,9,10},
  {11,12,13,14,15},
  {16,17,18,19,20},
  {21,22,23,24},
};

struct rotariesdef {
  byte pin1;
  byte pin2;
  int ccwchar;
  int cwchar;
  volatile unsigned char state;
};

rotariesdef rotaries[NUMROTARIES] {
  {0,1,25,26,0},
  {2,3,27,28,0},
  {4,5,29,30,0},
  {6,7,31,32,0},
};

#define DIR_CCW 0x10
#define DIR_CW 0x20
#define R_START 0x0

#ifdef HALF_STEP
#define R_CCW_BEGIN 0x1
#define R_CW_BEGIN 0x2
#define R_START_M 0x3
#define R_CW_BEGIN_M 0x4
#define R_CCW_BEGIN_M 0x5
const unsigned char ttable[6][4] = {
  // R_START (00)
  {R_START_M,            R_CW_BEGIN,     R_CCW_BEGIN,  R_START},
  // R_CCW_BEGIN
  {R_START_M | DIR_CCW, R_START,        R_CCW_BEGIN,  R_START},
  // R_CW_BEGIN
  {R_START_M | DIR_CW,  R_CW_BEGIN,     R_START,      R_START},
  // R_START_M (11)
  {R_START_M,            R_CCW_BEGIN_M,  R_CW_BEGIN_M, R_START},
  // R_CW_BEGIN_M
  {R_START_M,            R_START_M,      R_CW_BEGIN_M, R_START | DIR_CW},
  // R_CCW_BEGIN_M
  {R_START_M,            R_CCW_BEGIN_M,  R_START_M,    R_START | DIR_CCW},
};
#else
#define R_CW_FINAL 0x1
#define R_CW_BEGIN 0x2
#define R_CW_NEXT 0x3
#define R_CCW_BEGIN 0x4
#define R_CCW_FINAL 0x5
#define R_CCW_NEXT 0x6

const unsigned char ttable[7][4] = {
  // R_START
  {R_START,    R_CW_BEGIN,  R_CCW_BEGIN, R_START},
  // R_CW_FINAL
  {R_CW_NEXT,  R_START,     R_CW_FINAL,  R_START | DIR_CW},
  // R_CW_BEGIN
  {R_CW_NEXT,  R_CW_BEGIN,  R_START,     R_START},
  // R_CW_NEXT
  {R_CW_NEXT,  R_CW_BEGIN,  R_CW_FINAL,  R_START},
  // R_CCW_BEGIN
  {R_CCW_NEXT, R_START,     R_CCW_BEGIN, R_START},
  // R_CCW_FINAL
  {R_CCW_NEXT, R_CCW_FINAL, R_START,     R_START | DIR_CCW},
  // R_CCW_NEXT
  {R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START},
};
#endif

byte rowPins[NUMROWS] = {21,20,19,18,15}; 
byte colPins[NUMCOLS] = {14,16,10,9,8}; 

Keypad buttbx = Keypad( makeKeymap(buttons), rowPins, colPins, NUMROWS, NUMCOLS); 

Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, 
  JOYSTICK_TYPE_JOYSTICK, 32, 0,
  false, false, false, false, false, false,
  false, false, false, false, false);

void setup() 
{
  Joystick.begin();
  rotary_init();
  Serial.begin(9600);                                                                       // Open Serial Port for debugging
  Serial.print("********** - System Initialized - **********");                             // Print message to user                  
}


void loop() 
{ 

  CheckAllEncoders();

  CheckAllButtons();

}

void CheckAllButtons(void) {
      if (buttbx.getKeys())
    {
       for (int i=0; i<LIST_MAX; i++)   
        {
           if ( buttbx.key[i].stateChanged )   
            {
            switch (buttbx.key[i].kstate) {  
                    case PRESSED:

                              Serial.println(buttbx.key[i].kcode);
                              if (buttbx.key[i].kcode == 0) 
                              {
                                Joystick.setButton(buttbx.key[i].kchar, 1);
                                delay(50);
                                Joystick.setButton(buttbx.key[i].kchar, 0);
                              }
                              else 
                              {
                                Joystick.setButton(buttbx.key[i].kchar, 1);
                              }
                              break;
                    
                    case HOLD:
                              Serial.println(buttbx.key[i].kcode);
                              if (buttbx.key[i].kcode != 0) 
                              {
                                Joystick.setButton(buttbx.key[i].kchar, 1);
                                break;
                              }
                              
                    case IDLE:
                    case RELEASED:

                              Joystick.setButton(buttbx.key[i].kchar, 0);
                              break;
                    
            }
           }   
         }
     }
}


void rotary_init() {
  for (int i=0;i<NUMROTARIES;i++) {
    pinMode(rotaries[i].pin1, INPUT);
    pinMode(rotaries[i].pin2, INPUT);
    #ifdef ENABLE_PULLUPS
      digitalWrite(rotaries[i].pin1, HIGH);
      digitalWrite(rotaries[i].pin2, HIGH);
    #endif
  }
}


unsigned char rotary_process(int _i) {
   unsigned char pinstate = (digitalRead(rotaries[_i].pin2) << 1) | digitalRead(rotaries[_i].pin1);
  rotaries[_i].state = ttable[rotaries[_i].state & 0xf][pinstate];
  return (rotaries[_i].state & 0x30);
}

void CheckAllEncoders(void) {
  for (int i=0;i<NUMROTARIES;i++) {
    unsigned char result = rotary_process(i);
    if (result == DIR_CCW) {
      Joystick.setButton(rotaries[i].ccwchar, 1); delay(50); Joystick.setButton(rotaries[i].ccwchar, 0);
    };
    if (result == DIR_CW) {
      Joystick.setButton(rotaries[i].cwchar, 1); delay(50); Joystick.setButton(rotaries[i].cwchar, 0);
    };
  }
}

Thanks!

Not to use button zero, or not to use pin zero?

To use button 0. he mentioned;

The Keypad library uses the value 0 to mean NO_KEY so you should not use that as a key code. Start your button numbering at 1.

It looks like you are defining 24 keys of a 25-key keyboard and 8 keys for the encoders (CW and CCW button numbers for each of 4 encoders). That should give you buttons numbered 0 to 23 and 25 to 32 for your encoders.

If you want your buttons to be numbered 0 to 23, instead of using the kchar (from your keymap):
Joystick.setButton(buttbx.key[i].kchar, 1);
use the kcode (index starting at 0):
Joystick.setButton(buttbx.key[i].kcode, 1);

That will move all the button codes down by one and let you use 24-31 for your encoders.

Thanks for your reply @johnwasser

I changed the code back to the original numbering for the buttons and rotaries. I also changed from kchar to kcode and everything seems to work the way I want it to. Sounds like using kcode is the way to go. Thanks again for your help.

//BUTTON BOX 
//USE w ProMicro
//Tested in WIN10 + Assetto Corsa
//AMSTUDIO
//20.8.17

#include <Keypad.h>
#include <Joystick.h>
#include <DebugUtils.h>

#define DEBUG
#define ENABLE_PULLUPS
#define NUMROTARIES 4
#define NUMBUTTONS 24
#define NUMROWS 5
#define NUMCOLS 5


byte buttons[NUMROWS][NUMCOLS] = {
  {0,1,2,3,4},
  {5,6,7,8,9},
  {10,11,12,13,14},
  {15,16,17,18,19},
  {20,21,22,23},
};

struct rotariesdef {
  byte pin1;
  byte pin2;
  int ccwchar;
  int cwchar;
  volatile unsigned char state;
};

rotariesdef rotaries[NUMROTARIES] {
  {0,1,24,25,0},
  {2,3,26,27,0},
  {4,5,28,29,0},
  {6,7,30,31,0},
};

#define DIR_CCW 0x10
#define DIR_CW 0x20
#define R_START 0x0

#ifdef HALF_STEP
#define R_CCW_BEGIN 0x1
#define R_CW_BEGIN 0x2
#define R_START_M 0x3
#define R_CW_BEGIN_M 0x4
#define R_CCW_BEGIN_M 0x5
const unsigned char ttable[6][4] = {
  // R_START (00)
  {R_START_M,            R_CW_BEGIN,     R_CCW_BEGIN,  R_START},
  // R_CCW_BEGIN
  {R_START_M | DIR_CCW, R_START,        R_CCW_BEGIN,  R_START},
  // R_CW_BEGIN
  {R_START_M | DIR_CW,  R_CW_BEGIN,     R_START,      R_START},
  // R_START_M (11)
  {R_START_M,            R_CCW_BEGIN_M,  R_CW_BEGIN_M, R_START},
  // R_CW_BEGIN_M
  {R_START_M,            R_START_M,      R_CW_BEGIN_M, R_START | DIR_CW},
  // R_CCW_BEGIN_M
  {R_START_M,            R_CCW_BEGIN_M,  R_START_M,    R_START | DIR_CCW},
};
#else
#define R_CW_FINAL 0x1
#define R_CW_BEGIN 0x2
#define R_CW_NEXT 0x3
#define R_CCW_BEGIN 0x4
#define R_CCW_FINAL 0x5
#define R_CCW_NEXT 0x6

const unsigned char ttable[7][4] = {
  // R_START
  {R_START,    R_CW_BEGIN,  R_CCW_BEGIN, R_START},
  // R_CW_FINAL
  {R_CW_NEXT,  R_START,     R_CW_FINAL,  R_START | DIR_CW},
  // R_CW_BEGIN
  {R_CW_NEXT,  R_CW_BEGIN,  R_START,     R_START},
  // R_CW_NEXT
  {R_CW_NEXT,  R_CW_BEGIN,  R_CW_FINAL,  R_START},
  // R_CCW_BEGIN
  {R_CCW_NEXT, R_START,     R_CCW_BEGIN, R_START},
  // R_CCW_FINAL
  {R_CCW_NEXT, R_CCW_FINAL, R_START,     R_START | DIR_CCW},
  // R_CCW_NEXT
  {R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START},
};
#endif

byte rowPins[NUMROWS] = {21,20,19,18,15}; 
byte colPins[NUMCOLS] = {14,16,10,9,8}; 

Keypad buttbx = Keypad( makeKeymap(buttons), rowPins, colPins, NUMROWS, NUMCOLS); 

Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, 
  JOYSTICK_TYPE_JOYSTICK, 32, 0,
  false, false, false, false, false, false,
  false, false, false, false, false);

void setup() 
{
  Joystick.begin();
  rotary_init();
  Serial.begin(9600);                                                                       // Open Serial Port for debugging
  Serial.print("********** - System Initialized - **********");                             // Print message to user                  
}


void loop() 
{ 

  CheckAllEncoders();

  CheckAllButtons();

}

void CheckAllButtons(void) {
      if (buttbx.getKeys())
    {
       for (int i=0; i<LIST_MAX; i++)   
        {
           if ( buttbx.key[i].stateChanged )   
            {
            switch (buttbx.key[i].kstate) {  
                    case PRESSED:

                              Serial.println(buttbx.key[i].kcode);
                              if (buttbx.key[i].kcode == 0) 
                              {
                                Joystick.setButton(buttbx.key[i].kcode, 1);
                                delay(50);
                                Joystick.setButton(buttbx.key[i].kcode, 0);
                              }
                              else 
                              {
                                Joystick.setButton(buttbx.key[i].kcode, 1);
                              }
                              break;
                    
                    case HOLD:
                              Serial.println(buttbx.key[i].kcode);
                              if (buttbx.key[i].kcode != 0) 
                              {
                                Joystick.setButton(buttbx.key[i].kcode, 1);
                                break;
                              }
                              
                    case IDLE:
                    case RELEASED:

                              Joystick.setButton(buttbx.key[i].kcode, 0);
                              break;
                    
            }
           }   
         }
     }
}


void rotary_init() {
  for (int i=0;i<NUMROTARIES;i++) {
    pinMode(rotaries[i].pin1, INPUT);
    pinMode(rotaries[i].pin2, INPUT);
    #ifdef ENABLE_PULLUPS
      digitalWrite(rotaries[i].pin1, HIGH);
      digitalWrite(rotaries[i].pin2, HIGH);
    #endif
  }
}


unsigned char rotary_process(int _i) {
   unsigned char pinstate = (digitalRead(rotaries[_i].pin2) << 1) | digitalRead(rotaries[_i].pin1);
  rotaries[_i].state = ttable[rotaries[_i].state & 0xf][pinstate];
  return (rotaries[_i].state & 0x30);
}

void CheckAllEncoders(void) {
  for (int i=0;i<NUMROTARIES;i++) {
    unsigned char result = rotary_process(i);
    if (result == DIR_CCW) {
      Joystick.setButton(rotaries[i].ccwchar, 1); delay(50); Joystick.setButton(rotaries[i].ccwchar, 0);
    };
    if (result == DIR_CW) {
      Joystick.setButton(rotaries[i].cwchar, 1); delay(50); Joystick.setButton(rotaries[i].cwchar, 0);
    };
  }
}

The values in your keymap must be unique and NON-ZERO. If you use 'kcode' and not 'kchar' then the values don't matter as long as they are unique and NON-ZERO.

byte buttons[NUMROWS][NUMCOLS] = {
  {'A','B','C','D','E'},
  {'F','G','H','I','J'},
  {'K','L','M','N','O'},
  {'P','Q','R','S','T'},
  {'U','V','W','X','Y'},
};

Don't worry about the 25th key being 'Y'. If you don't wire a button there it will never be selected.

If you WANT to use 'kchar', just subtract 'A' to get values from 0 to 23:
Joystick.setButton(buttbx.key[i].kchar-'A', 1);

Note: The makeKeymap() macro just flattens the array into a byte *. You don't need a two-dimensional array and can write it as one-dimensional, if you like:

byte buttons[] = {
  "ABCDE"
  "FGHIJ"
  "KLMNO"
  "PQRST"
  "UVWXY"
};

(note the lack of commas separating the strings. Adjacent strings are merged.)

or the equivalent:
byte buttons[] = "ABCDEFGHIJKLMNOPQRSTUVWXY";

That worked as well @johnwasser. Thanks!

Ok, I have a new issue. Hopefully @johnwasser can help.

I have wired up the button box they way I want it, and all momentary buttons work. I have 5 latching buttons though, and they do not work the way I want it. My code;


//BUTTON BOX 
//USE w ProMicro
//Tested in WIN10 + Assetto Corsa
//AMSTUDIO
//20.8.17

#include <Keypad.h>
#include <Joystick.h>
#include <DebugUtils.h>

#define DEBUG
#define ENABLE_PULLUPS
#define NUMROTARIES 4
#define NUMBUTTONS 24
#define NUMROWS 5
#define NUMCOLS 5

byte buttons[NUMROWS][NUMCOLS] = {
  {'A','B','C','D','E'},
  {'F','G','H','I','J'},
  {'K','L','M','N','O'},
  {'P','Q','R','S','T'},
  {'U','V','W','X','Y'},
};


struct rotariesdef {
  byte pin1;
  byte pin2;
  int ccwchar;
  int cwchar;
  volatile unsigned char state;
};


rotariesdef rotaries[NUMROTARIES] {
  {0,1,24,25,0},
  {2,3,26,27,0},
  {4,5,28,29,0},
  {6,7,30,31,0},
};


#define DIR_CCW 0x10
#define DIR_CW 0x20
#define R_START 0x0


#ifdef HALF_STEP
#define R_CCW_BEGIN 0x1
#define R_CW_BEGIN 0x2
#define R_START_M 0x3
#define R_CW_BEGIN_M 0x4
#define R_CCW_BEGIN_M 0x5


const unsigned char ttable[6][4] = {
  // R_START (00)
  {R_START_M,            R_CW_BEGIN,     R_CCW_BEGIN,  R_START},
  // R_CCW_BEGIN
  {R_START_M | DIR_CCW, R_START,        R_CCW_BEGIN,  R_START},
  // R_CW_BEGIN
  {R_START_M | DIR_CW,  R_CW_BEGIN,     R_START,      R_START},
  // R_START_M (11)
  {R_START_M,            R_CCW_BEGIN_M,  R_CW_BEGIN_M, R_START},
  // R_CW_BEGIN_M
  {R_START_M,            R_START_M,      R_CW_BEGIN_M, R_START | DIR_CW},
  // R_CCW_BEGIN_M
  {R_START_M,            R_CCW_BEGIN_M,  R_START_M,    R_START | DIR_CCW},
};
#else
#define R_CW_FINAL 0x1
#define R_CW_BEGIN 0x2
#define R_CW_NEXT 0x3
#define R_CCW_BEGIN 0x4
#define R_CCW_FINAL 0x5
#define R_CCW_NEXT 0x6


const unsigned char ttable[7][4] = {
  // R_START
  {R_START,    R_CW_BEGIN,  R_CCW_BEGIN, R_START},
  // R_CW_FINAL
  {R_CW_NEXT,  R_START,     R_CW_FINAL,  R_START | DIR_CW},
  // R_CW_BEGIN
  {R_CW_NEXT,  R_CW_BEGIN,  R_START,     R_START},
  // R_CW_NEXT
  {R_CW_NEXT,  R_CW_BEGIN,  R_CW_FINAL,  R_START},
  // R_CCW_BEGIN
  {R_CCW_NEXT, R_START,     R_CCW_BEGIN, R_START},
  // R_CCW_FINAL
  {R_CCW_NEXT, R_CCW_FINAL, R_START,     R_START | DIR_CCW},
  // R_CCW_NEXT
  {R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START},
};
#endif


byte rowPins[NUMROWS] = {21,20,19,18,15}; 
byte colPins[NUMCOLS] = {14,16,10,9,8}; 


Keypad buttbx = Keypad(makeKeymap(buttons), rowPins, colPins, NUMROWS, NUMCOLS); 


Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,
  JOYSTICK_TYPE_JOYSTICK, 32, 0,
  false, false, false, false, false, false,
  false, false, false, false, false);


void setup() 
{
  Joystick.begin();
  rotary_init();
  Serial.begin(9600);                                                                       // Open Serial Port for debugging               
}


void loop() 
{ 

  CheckAllEncoders();

  CheckAllButtons();

}


void CheckAllButtons(void) {
      if (buttbx.getKeys())
    {
       for (int i=0; i<LIST_MAX; i++)   
        {
           if ( buttbx.key[i].stateChanged )   
            { 
            switch (buttbx.key[i].kstate) {  
                    
                    case HOLD:

                              Serial.println("Hold Section");
                              
                              //Serial.println(buttbx.key[i].kcode);
                              //if (buttbx.key[i].kcode != 14 && buttbx.key[i].kcode != 5 && buttbx.key[i].kcode != 6 && buttbx.key[i].kcode != 7 && buttbx.key[i].kcode != 9) 
                              //{
                              //  Joystick.setButton(buttbx.key[i].kcode, 1);
                              //  break;
                              //}
                              
                    case IDLE:
                              
                              Serial.println("Idle Section");
                    
                    case RELEASED:

                              Serial.println("Release Section");

                              if (buttbx.key[i].kcode == 14 || buttbx.key[i].kcode == 5 || buttbx.key[i].kcode == 6 || buttbx.key[i].kcode == 7 || buttbx.key[i].kcode == 9)
                              {
                                Joystick.setButton(buttbx.key[i].kcode, 1);
                                delay(150);
                                Joystick.setButton(buttbx.key[i].kcode, 0);
                                break;
                              }
                              else 
                              {
                                Joystick.setButton(buttbx.key[i].kcode, 0);
                                break;
                              }
                    
                    case PRESSED:

                              Serial.println("Pressed Section");
                              
                              if (buttbx.key[i].kcode == 14 || buttbx.key[i].kcode == 5 || buttbx.key[i].kcode == 6 || buttbx.key[i].kcode == 7 || buttbx.key[i].kcode == 9)
                              {
                                Joystick.setButton(buttbx.key[i].kcode, 1);
                                Serial.println("Button Press");
                                delay(150);
                                Joystick.setButton(buttbx.key[i].kcode, 0);
                                Serial.println("Button Release");
                                break;
                              }
                              else 
                              {
                                Joystick.setButton(buttbx.key[i].kcode, 1);
                                break;
                              }      

                              break;
            }
           }   
         }
     }
}


void rotary_init() {
  for (int i=0;i<NUMROTARIES;i++) {
    pinMode(rotaries[i].pin1, INPUT);
    pinMode(rotaries[i].pin2, INPUT);
    #ifdef ENABLE_PULLUPS
      digitalWrite(rotaries[i].pin1, HIGH);
      digitalWrite(rotaries[i].pin2, HIGH);
    #endif
  }
}


unsigned char rotary_process(int _i) {
   unsigned char pinstate = (digitalRead(rotaries[_i].pin2) << 1) | digitalRead(rotaries[_i].pin1);
  rotaries[_i].state = ttable[rotaries[_i].state & 0xf][pinstate];
  return (rotaries[_i].state & 0x30);
}


void CheckAllEncoders(void) {
  for (int i=0;i<NUMROTARIES;i++) {
    unsigned char result = rotary_process(i);
    if (result == DIR_CCW) {
      Joystick.setButton(rotaries[i].ccwchar, 1); delay(50); Joystick.setButton(rotaries[i].ccwchar, 0);
    };
    if (result == DIR_CW) {
      Joystick.setButton(rotaries[i].cwchar, 1); delay(50); Joystick.setButton(rotaries[i].cwchar, 0);
    };
  }
}

When switching one of the latching switches high, the output of the serial monitor is;

Pressed Section
Button Press
Button Release
Hold Section
Idle Section
Release Section

When turning the switch off, the serial monitor outputs;

Release Section
Idle Section
Release Section

I don't understand why the code progresses through the other sections of the code after switching high or low. Is that due to debouncing? I thought the keypad library has built in debouncing?

What I want the behavior to be is;
Switch high -> code to execute a button press followed by a button release
switch low -> code to execute a button press followed by a button release

What am I missing?

Thanks in advance

You left out some "break;" statements;

      if ( buttbx.key[i].stateChanged )
      {
        switch (buttbx.key[i].kstate)
        {

          case HOLD:

            Serial.println("Hold Section");

          //Serial.println(buttbx.key[i].kcode);
          //if (buttbx.key[i].kcode != 14 && buttbx.key[i].kcode != 5 && buttbx.key[i].kcode != 6 && buttbx.key[i].kcode != 7 && buttbx.key[i].kcode != 9)
          //{
          //  Joystick.setButton(buttbx.key[i].kcode, 1);
          //  break;
          //}
// HERE

          case IDLE:
            Serial.println("Idle Section");
// AND HERE

          case RELEASED:

If you reach the end of a 'case' and there is no 'break;' the code just continues on to the next 'case'.

The order of states is PRESSED, HOLD, RELEASED and IDLE. With the missing breaks, the cases executed will be:
PRESSED
HOLD IDLE RELEASED
RELEASED
IDLE RELEASED

Thanks @johnwasser, that did the trick. I didn't understand the Switch States logic well enough. Now it is working as it is supposed to. Thanks again!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.