Saturday, February 26, 2011

dreams of a mini MAME cabinet bring out my c++ chops

so I wanna build me a mini MAME cabinet. something along the lines of this. but i'm cheap, and i can't justify dropping $350 just to get my old-school street fighter turbo on. so i dug out an old linux laptop and got xmame running on it. surprisingly it seems to hold up pretty well. so i gotz the screen and brains part of the cabinet accounted for.

a'course, you can't have a MAME cabinet without a nice set of arcade controls (or two sets ). i'm not _too_ picky, so the x-arcade joystick and button parts will do just nicely. $20 per player is hard to beat.

and to interface all those nice buttons to the laptop there's the teensy++ usb microcontroller board. this little guy (among other cooler, more complex microcontrollery things) can take physical button presses and translate them into usb keyboard presses my little linux box can understand. and with the teensyduino add-on, i can program this thing with easy-to-use arduino environment. w00t!

there's only one problem: the Keyboard class that's provided can only do up to 6 simultaneous virtual keypresses, not enough if i'm gonna wire up two sets of controllers on this thing.

never to fear, my c++ skills are here. i dug in to the directory where teensyduino got installed and added a generic send method to the Keyboard class. here's the deets (i copied most of send_now() and made a few modifications) (unfortunatly, i don't have a teensy board yet, so i don't even know if this poo is gonna work. who want's to by ol' dick a birthday present? =]):

usb_api.h
class usb_keyboard_class : public Print
{
 public:
 virtual void write(uint8_t);
 void write_unicode(uint16_t unicode_code_point);
 void set_modifier(uint8_t);
 void set_key1(uint8_t);
 void set_key2(uint8_t);
 void set_key3(uint8_t);
 void set_key4(uint8_t);
 void set_key5(uint8_t);
 void set_key6(uint8_t);
 void send_now(void);
 // here's the method i added
 void send(uint8_t, uint8_t*, uint8_t);
 private:
 void write_keycode(KEYCODE_TYPE key);
 void write_key(uint8_t code);
 uint8_t utf8_state;
 uint16_t unicode_wchar;
};

usb_api.cpp
void usb_keyboard_class::send(uint8_t modifier_keys, uint8_t* keys, uint8_t n_keys)
{
        uint8_t i, intr_state, timeout;

        if (!usb_configuration) return;
        intr_state = SREG;
        cli();
        UENUM = KEYBOARD_ENDPOINT;
        timeout = UDFNUML + 50;
        while (1) {
                // are we ready to transmit?
                if (UEINTX & (1<<RWAL)) break;
                SREG = intr_state;
                // has the USB gone offline?
                if (!usb_configuration) return;
                // have we waited too long?
                if (UDFNUML == timeout) return;
                // get ready to try checking again
                intr_state = SREG;
                cli();
                UENUM = KEYBOARD_ENDPOINT;
        }
        UEDATX = modifier_keys;
        UEDATX = 0;
        for(i = 0; i < n_keys; i++){
            UEDATX = keys[i];
        }
        
        UEINTX = 0x3A;
        keyboard_idle_count = 0;
        SREG = intr_state;
}

here's what i got on how to use this shiny new method:

arcadestick.pde
/**
  * Compile with Teensduino
  * Board: Teensy++ 2.0
  * USB Type: Keyboard + Mouse
  */

#ifdef STR_PRODUCT
#  undef STR_PRODUCT
#endif
#define STR_PRODUCT L"DeathByAutoScroll Arcade Stick (X-Arcade Dual Compatible)"

#define MAX_SIMULTANEOUS_KEYS 25

uint8_t keys[MAX_SIMULTANEOUS_KEYS];
uint8_t modifier_keys;
uint8_t n_keys;

void setupPins(){
  // no need, all pins are defaulted to INPUT
}

void reset(){
  modifier_keys = 0;
  n_keys = 0;
}

void addKey(uint8_t key){
  keys[n_keys++] = key;
}

void addModifierKey(uint8_t key){
  modifier_keys |= key;
}

void setKeys(){
  // convert physical button presses to keyboard keys presses
  // follows x-arcdae dual-joystick keyboard mapping scheme
  // see http://www.xgaming.com/service/ServiceFiles/X-Arcade%20Tankstick%20Manual%20USA.pdf
  
  // player1 buttons on PORTD
  // player2 buttons on PORTC
  // player1 direction on lower nibble of PORTA
  // player2 direction on upper nibble of PORTA
  
  // player 1
  // direction
  //p1 up
  if(PIN_A0){
    addKey(KEYPAD_8);
  }
  //p1 down
  if(PIN_A1){
    addKey(KEYPAD_2);
  }
  //p1 left
  if(PIN_A2){
    addKey(KEYPAD_4);
  }
  //p1 right
  if(PIN_A3){
    addKey(KEYPAD_6);
  }
  // buttons
  //p1 1
  if(PIN_D0){
    addModifierKey(MODIFIERKEY_LEFT_CTRL);
  }
  //p1 2
  if(PIN_D1){
    addModifierKey(MODIFIERKEY_LEFT_ALT);
  }
  //p1 3
  if(PIN_D2){
    addKey(KEY_SPACE);
  }
  //p1 4
  if(PIN_D3){
    addModifierKey(MODIFIERKEY_LEFT_SHIFT);
  }
  //p1 5
  if(PIN_D4){
    addKey(KEY_Z);
  }
  //p1 6
  if(PIN_D5){
    addKey(KEY_X);
  }
  //p1 start
  if(PIN_D6){
    addKey(KEY_1);
  }
  //p1 coin
  if(PIN_D7){
    addKey(KEY_3);
  }
  
  // player 2
  // direction
  //p2 up
  if(PIN_A4){
    addKey(KEY_R);
  }
  //p2 down
  if(PIN_A5){
    addKey(KEY_F);
  }
  //p2 left
  if(PIN_A6){
    addKey(KEY_D);
  }
  //p2 right
  if(PIN_A7){
    addKey(KEY_G);
  }
  // buttons
  //p2 1
  if(PIN_C0){
    addKey(KEY_A);
  }
  //p2 2
  if(PIN_C1){
    addKey(KEY_S);
  }
  //p2 3
  if(PIN_C2){
    addKey(KEY_Q);
  }
  //p2 4
  if(PIN_C3){
    addKey(KEY_W);
  }
  //p2 5
  if(PIN_C4){
    addKey(KEY_E);
  }
  //p2 6
  if(PIN_C5){
    addKey(KEY_LEFT_BRACE);
  }
  //p2 start
  if(PIN_C6){
    addKey(KEY_2);
  }
  //p2 coin
  if(PIN_C7){
    addKey(KEY_4);
  }
}

void setup(){
  setupPins();
}

void loop(){
  reset();
  setKeys();
  Keyboard.send(modifier_keys, keys, n_keys);
}

now all i need is a sheet or two of MDF, a little cash for the parts, and a lot of free time to make it all happen.