Author Topic: Digispark + NES Controller = MAME Gamepad (almost)  (Read 9208 times)

GoreGhoul

  • Newbie
  • *
  • Posts: 12
Digispark + NES Controller = MAME Gamepad (almost)
« on: March 20, 2013, 10:18:31 am »
Hello All,
My wife and I just purchased a Raspberry Pi, and we want to use it to rehab our coffee table into a MAME cocktail cabinet (something like this) using the GPIO pins. I got the MAME emulator running on the Pi using an Xbox1 controller over USB, then it was time to take on task of interfacing the MAME software with the hardware input from the GPIO pins...
...And now 2 days later that magical journey has led to me wiring my NES controller into my laptop using the Digispark to make it appear like a keyboard. I'm really excited that I got it to work (at least crudely) and I thought everyone would like to see it.  This was my first experience with interrupt timers and real time computing in general.  I borrowed heavily from the Arduino Timer Interrupts Instructable.  Most of the information about interfacing with the NES controller was found here.

Inside the controller is an 8 bit shift register that serializes the data from the 8 buttons on the controller. We control it with 2 output pins (latch, clock) and 1 input pin (data), plus Vcc and GND. My timer interrupt runs at 2kHz and is half a clock cycle, so I'm communicating with the controller at 1kHz clock speed, I think this is about twice the speed at which the NES communicated with it.  These frequencies are based on a 16Mhz processor speed, but I think the Digispark runs at 16.5MHz when connected to USB.

I connected the controller to IO pins 0,2 and 5 to leave pins 3 and 4 for USB communication.  Support for a second NES controller could be added by disconnecting the built in LED and using pin 1 as the data line for the second controller.

Once the pin states are read from the controller I use the DigiKeyboard library to translate them into key strokes.  It didn't work when I put the DigiKeyboard.SendKeyStroke() calls in the interrupt method, so I put them in the standard loop with a 25ms delay.  This won't work for entering passwords but will work great as a turbo controller, judging from controller output in text editors.

I pulled the controller connector out of my dead NES from my childhood and soldered the leads on to standard header for use in breadboards and digisparks.

So far it works out of the box on Win7, Win8 and Lubuntu 12.10 but the Raspberry Pi didn't recognize it yet.  I'm not worried considering this isn't really going to be part of my finished project, just a little side lesson in Human Interfacing.

I tested the timer interrupt on my Arduino Mega1280 before porting to Digispark and pretty much the only thing i had to do was change "TIMSK0" to "TIMSK"


Here's the code:

Code: [Select]
//NES Digispark
//by Matt Gorr
//March 2013
//borrowed from:
//timer interrupts
//by Amanda Ghassaei
//June 2012
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
*/


#include "DigiKeyboard.h"


boolean firstHalf = true; //toggle between first and second half of clock cycle
int buttonIndex = 0; //0-7 for 8 buttons
boolean pinState=false; //read button state while polling controller
boolean buttonStates[] = {false, false, false, false, false, false, false, false}; // Store button states to send via DigiKeyboard
char buttonNames[] = {'A','B','S','s','U','D','L','R'}; //Print to serial for debugging
int buttonVals[] = {KEY_A,KEY_B,KEY_S,KEY_G, KEY_U, KEY_D,KEY_L,KEY_R}; //Key values to send with DigiKeyboard, used G for select
int clockPin = 2;
int latchPin = 5;
int dataPin = 0;
int ledPin = 1; //Degbugging LED lights when a button press is detected


//timer0 will interrupt at 2kHz
void setup(){
  //set pins as outputs
  pinMode(ledPin, OUTPUT);
  pinMode(clockPin,OUTPUT);
  pinMode(latchPin,OUTPUT);
  pinMode(dataPin,INPUT);
 
  digitalWrite(ledPin,LOW);
  digitalWrite(clockPin,LOW);
  digitalWrite(latchPin,LOW);
  DigiKeyboard.update();
 
  cli();//stop interrupts


  //set timer0 interrupt at 2kHz
  TCCR0A = 0;// set entire TCCR2A register to 0
  TCCR0B = 0;// same for TCCR2B
  TCNT0 = 0;//initialize counter value to 0
  // set compare match register for 2khz increments
  OCR0A = 124;// = (16*10^6) / (2000*64) - 1 (must be <256)
  // turn on CTC mode
  TCCR0A |= (1 << WGM01);
  // Set CS11 bit for 64 prescaler
  TCCR0B |= (1 << CS11) | (1 << CS10);
  // enable timer compare interrupt
  TIMSK |= (1 << OCIE0A); //       <--The only thing
 
  sei();//allow interrupts
}//end setup


ISR(TIMER0_COMPA_vect){//timer0 interrupt 2kHz toggles pin 8
 pollController();
}


void pollController() {
  if (firstHalf) { //set latch high if its the first button, then clock high the rest of the time
    if (buttonIndex > 0) digitalWrite(clockPin,HIGH);
    else digitalWrite(latchPin,HIGH);
  }
  else { //set clock/latch low, then read pin state
    digitalWrite(latchPin,LOW);
    digitalWrite(clockPin,LOW);
   
    pinState=!digitalRead(dataPin);
    digitalWrite(ledPin,pinState);
    buttonStates[buttonIndex]=pinState;
 
    buttonIndex++;
    if (buttonIndex >7) buttonIndex = 0;
  }
 
  firstHalf = !firstHalf; //proceed to next half of clock waveform
  DigiKeyboard.update();
}


/*Diagnostic LED malfunctioned when I tried to send KeyStrokes inside the pollController() method.
I think the sendKeyStroke() function wasn't designed to send 120 strokes a second.
*/
void loop(){
  for (int i = 0; i < 8; i++) {
    if (buttonStates[i]) DigiKeyboard.sendKeyStroke(buttonVals[i]);
  }
  delay(25);
}

I should also say I haven't actually used it as a game controller yet.  I've just been checking output in text editors.
I'll snap a few pics of my hardware soon.

GoreGhoul

  • Newbie
  • *
  • Posts: 12
Re: Digispark + NES Controller = MAME Gamepad (almost)
« Reply #1 on: March 20, 2013, 10:40:47 am »
Here's a few pics of the hardware.  the standard controller only uses 5 of the 7 pins.  The other 2 were for extras like light gun, power glove, etc...

Bluebie

  • Sr. Member
  • ****
  • Posts: 486
Re: Digispark + NES Controller = MAME Gamepad (almost)
« Reply #2 on: March 20, 2013, 04:34:06 pm »
Thats cool! It would be pretty easy to use this as an iPad or Android gamepad by hooking it up through a USB adaptor and implementing the common iCade controller specification - it works by sending particular keys for various buttons down states, and other keys for their up states: https://docs.google.com/file/d/15dtxipfo0uGINz3BbYxAwUqStZRPQKJ-nR85OWi2kXoHW_cWAvc8l-QRwC8k/edit?usp=sharing


It's a rather neat protocol which can be implemented with the regular DigisparkKeyboard library quite easily. It could also be cool to try hooking it up to the DigisparkJoystick library, then you could straight up use it as a gamepad in whatever you like, should you decide not to bother messing about with GPIO on the Pi.


For anyone wanting to replicate this, you can get NES controller sockets from NES controller extension cables, available on ebay.

adammhaile

  • Newbie
  • *
  • Posts: 2
Re: Digispark + NES Controller = MAME Gamepad (almost)
« Reply #3 on: April 08, 2013, 01:10:31 pm »
How do you handle button hold on the gamepad? The sendKeyStroke method in DigiKeyboard does keydown and then up again after a few ms. But in most NES games you could actually hold down buttons? Did you have to modify anything with the DigiKeyboard library? I'm looking to do the same thing, but every gamepad press is registered on the computer as key down followed by key up.

GoreGhoul

  • Newbie
  • *
  • Posts: 12
Re: Digispark + NES Controller = MAME Gamepad (almost)
« Reply #4 on: April 09, 2013, 06:29:44 pm »
I didn't do anything to hold the buttons down. As I said in the post I only tested for output in text editors. I tried playing Joust in MAME right after I posted and realized how unusable the code is as it stands now.  I didn't read too much into the digikeyboard lib to see how to send key press and release events. 


If the sendKeystroke() method works as you describe it shouldn't be too hard to go into the lib and copy and past the code into separate functions to create sendKeyPress() and sendKeyRelease() functions, maybe even a sendReleaseALL() function to clear any keys being held down.


After modifying the lib, my code above would need to be modified to store the button states so it can sense press and release.


I haven't touched this code at all since I got the NES controllers successfully hooked up to the GPIO pins on my Raspberry Pi.  It's a pretty mean MAME box now, especially when its housed in the NES case.

Bluebie

  • Sr. Member
  • ****
  • Posts: 486
Re: Digispark + NES Controller = MAME Gamepad (almost)
« Reply #5 on: April 11, 2013, 05:43:37 pm »
I think the DigiKeyboard library would need to be modified more than that - the impression I have is the DigiKeyboard defines it's USB HID interface in a way that makes it impossible to hold two keys down at the same time.  Maybe I'm wrong on this though?


If I was building this project I would definitely use the joystick library, not the keyboard library. It has plenty of buttons and they can all be held concurrently and whatever else, plus you can add analog components like motion controls or little joysticks and things like that.

GoreGhoul

  • Newbie
  • *
  • Posts: 12
Re: Digispark + NES Controller = MAME Gamepad (almost)
« Reply #6 on: April 12, 2013, 05:25:24 am »
I looked at the keyboard library when I was starting the project and I went with the keyboard for 2 reasons:
1.  The Digispark wasn't recognized as a joystick by my computer when I uploaded the joystick example.
2. I didn't want to bother figuring out how to map the D-pad push buttons to analog joystick values.