Wednesday, November 7, 2012

Arduino HID Gamepad, Part 2

Well, I'm finally getting around to moving forward with my Arduino HID Gamepad project (part 1 here).  I finished the wiring on the gamepad shield, and started trying to get the gamepad device report descriptor and event handlers added to the Arduino libraries.  First of all, you need to add the device report descriptor to HID.cpp, located in hardware\arduino\cores\arduino.  Sorry, I can't explain this very well, I barely understand it myself, but this page helped me stumble through.  Add the following to const u8 _hidReportDescriptor[] (near the top of the .cpp file), above the #if RAWHID_ENABLED directive:


    // Gamepad
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x05,                    // USAGE (Game Pad)
    0xa1, 0x01,                    // COLLECTION (Application)
    0xa1, 0x00,                    //   COLLECTION (Physical)
    0x85, 0x03,                    //     REPORT_ID (3)
    0x05, 0x09,                    //     USAGE_PAGE (Button)
    0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
    0x29, 0x08,                    //     USAGE_MAXIMUM (Button 8)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x95, 0x04,                    //     REPORT_COUNT (4)
    0x75, 0x01,                    //     REPORT_SIZE (1)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0x05, 0x01,                    //     USAGE_PAGE (Generic Desktop)
    0x09, 0x30,                    //     USAGE (X)
    0x09, 0x31,                    //     USAGE (Y)
    0x15, 0xff,                    //     LOGICAL_MINIMUM (-1)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x75, 0x02,                    //     REPORT_SIZE (2)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //   END_COLLECTION
    0xc0                           // END_COLLECTION


This defines a 2-axis, 4-button gamepad.  Next, you need to add the Gamepad class and event handlers to USBAPI.h:


class Gamepad_
{
private:
uint8_t _buttons;
public:
Gamepad_(void);
void begin(void);
void end(void);
void press(uint8_t b);
void release(uint8_t b);
bool isPressed(uint8_t b);
};
extern Gamepad_ Gamepad;


Now you need to put the class definitions in HID.cpp:


//================================================================================
//================================================================================
// Gamepad

Gamepad_::Gamepad_(void) : _buttons(0)
{
}

void Gamepad_::begin(void)
{
}

void Gamepad_::end(void)
{
}

void Gamepad_::press(uint8_t b)
{
_buttons |= b;
HID_SendReport(3,&_buttons,1);
}

void Gamepad_::release(uint8_t b)
{
_buttons &= ~b;
HID_SendReport(3,&_buttons,1);
}

bool Gamepad_::isPressed(uint8_t b)
{
if ((b & _buttons) > 0) 
return true;
return false;
}


Finally, you need to instantiate the Gamepad singleton.  At the top of HID.cpp, add the following line under the Mouse and Keyboard instantiations:

Gamepad_ Gamepad;

Now, you should be able to call Gamepad.begin() in your sketch setup() function, and then call Gamepad.press() and Gamepad.release() to send button press and release reports.  As you can see, Windows now recognizes my new 2-axis, 4-button gamepad :)





Tuesday, October 23, 2012

I have a domain :)

I just finished registering my very own domain :)  This blog can now be found at blog.qwertymodo.com, and my Zelda: Parallel Worlds mapping site can be found at zelda.qwertymodo.com.

Tuesday, October 16, 2012

Non-volatile N64 Controller Pak

Ever since I read this post on the BenHeck forums about modding an N64 Controller Pak to eliminate the need for a battery to maintain the save data.  I decided to take it one step further and build one from scratch.  The first step was to draw up the schematic for the existing Controller Pak, and modify it from there.  For that, I needed a pinout for the card edge.  I found one with a few errors, used it to fill out the schematic, and determined the function of the remaining contacts from there.  Here's the working pinout:

  Pin   Name
--------------
  1     GND
  2     A14
  3     A12
  4     A7
  5     A6
  6     A5
  7     A4
  8     A3
  9     A2
  10    A1
  11    A0
  12    D0
  13    D1
  14    Detect*
  15    3V3
  16    D2

  17    GND
  18    CE1
  19    /CE2
  20    /WE
  21    A13
  22    A8
  23    A9
  24    A11
  25    /OE
  26    A10
  27    D7
  28    D6
  29    D5
  30    D4
  31    3V3
  32    D3

The Detect line is pulled low inside the controller through a pull-down resistor.  The Controller Pak connects Detect to 3V3 to indicate the presence of a cart in the slot (this is true for both the Controller Pak and the Rumble Pak;  the controller determines which type of cart it is by strobing a specific address and reading the response).

From this, it's fairly trivial to connect an FRAM chip in place of the original SRAM.  I chose to use the Ramtron FM28V020, though the any of the FM18*08 chips should work as well.  If we don't really care about the dual CE lines, we can ignore CE1 and connect /CE2 directly to the RAM.  After that, all that is needed is a pull-up resistor on the /CE line (10Kohm is a good value) and a smoothing cap between 3V3 and GND (100nF is good).

I'll be working on a 4x version soon, using a 1Mbit FRAM chip (Ramtron FM28V100).  The basic wiring for this is the same as above, but add pull-up resistors on the A15 and A16 lines, then connect those lines to a 4-position switch that connects to GND.  Use diodes to isolate the connections between the address lines and the switch, since one position on the switch will pull both lines to GND, but you don't want *every* position to do that.

If you'd like to buy a pre-assembled, nonvolatile N64 Controller Pak (1x or 4x), shoot me an email.  I have a lot of extras sitting around...

Wednesday, August 29, 2012

Progress on Parallel Worlds

Well, in case you haven't seen it (or haven't seen it recently), head over to http://parallelworldsmaps.appspot.com to check out the new visual overhaul I've finally given to my mapping project for the game Legend of Zelda: Parallel Worlds.  For those of you who don't know, Parallel Worlds is a ROM hack of Legend of Zelda: A Link to the Past, resulting in a completely new game, with all an all-new story, fully redone dungeons and maps, and some minor graphical changes to go with all that.  The game is HARD.  Like, Contra hard.  And what's worse is, because the map and dungeons are all new, there aren't any maps to show you where everything is when you inevitably get lost.  I have found a single walkthrough floating around on the internet that has maps, but other than that, you're s.o.l.  So, I took it upon myself to map the entire game, in full, lossless resolution.  After completing the Light World Overworld map (well, I still have some touch-up work to do...), I discovered an awesome piece of software called Worldkit which would allow me to turn my shiny new really-big-picture into a fully interactive map with zoom and pan, as well as geotag annotations for tagging items and important landmarks.  So I got it all loaded up and decided to host it on Google's App Engine framework (which is kind of a weird use of the App Engine framework, but there are plenty of other people hosting static websites on GAE, so whatever...).  As of yet, I can't seem to get annotations working on the web (they work great on the local dev server that comes with the GAE SDK... frustrating), but I have finally gotten around to giving the page itself a facelift from the previous blank-page-with-embedded-flash-object-slapped-into-the-corner.  It is now a presentable web page, with a nice template that will allow me to easily deploy new pages as I finish the maps.  Also, speaking of finishing maps, I managed to map all of Din's Catacombs, which is a pretty nice achievement (if I do say so myself) in that the entire dungeon is normally pitch-black, making mapping via screenshot extremely tedious at best and nearly impossible at worst.  The walkthrough I mentioned earlier with screenshots of all of the dungeons actually doesn't have one for Din's Catacombs.  So I may be the first to have mapped it.  Go me :)  Anyway, the Din's Catacombs page is a bit of a mess right now, I was trying out a different template which really isn't working so well, but I'll fix it up soon.  I've also started work on the Icy World Overworld map, which I currently have about 20% complete, so hopefully that will be online soon as well.

Friday, August 17, 2012

New Zelda: Parallel Worlds Interactive Map

I have discovered a means to make my Zelda Parallel Worlds mapping project much more interesting and useful by creating a fully interactive map with navigation and annotation features using an awesome piece of software called WorldKit.  I am hosting the project using Google's App Engine framework, and have uploaded my current Light World overworld map to test the software and deployment functionality.  I have not yet added any annotations (and I probably won't until I have the Icy World map done), but you can test the zoom and pan features.  Unfortunately, the zoomify software I'm using to generate the zoom tiles only works with JPEG images, so it does get lossy at full zoom, but I suppose that's really the best route to go anyway, then I can offer the full-resolution, lossless PNG for download from a separate link.  I'm still brushing up on my HTML so I can actually have a nice page rather than the current blank page with the embedded map viewer.  It's a work in progress, but check it out at http://parallelworldsmaps.appspot.com/

Tuesday, July 24, 2012

The Legend of Zelda: Parallel Worlds Mapping Project

I've decided to undertake the mapping of the Zelda 3 ROM hack called Parallel Worlds.  Being a hack, not a lot of documentation exists out there other than a few walkthroughs, almost all of which are exclusively text-based, and many of which are incomplete.  I've decided to create a full-resolution map of all areas of the game, starting with the overworld(s) and then moving on to the dungeons.  So far, I have a significant portion of the Light World Overworld completed, and so I've decided to start a page to publish my progress.  You can check it out here.  You can check out the hack's homepage at https://sites.google.com/site/zeldaparallelworlds/

Friday, July 13, 2012

Arduino HID Gamepad, part 1

Note: This post mostly outlines my shield design and a quick test of the hardware using the Mouse example included with the Arduino library.  If you're looking for the actual HID gamepad implementation, have a look at part 2

I have been thinking for quite some time about a project which I hope to shape into a Senior Project for my Embedded Systems and Software Engineering degrees, but I've gotten a bit impatient and have kind of jumped the gun to start working on it.  The first step in this project requires implementing a generic HID gamepad.  The Arduino Leonardo recently launched with USB HID profile support, thanks to the on-chip USB support of the on-board Atmega32U4 microcontroller.  This makes prototyping much simpler, and it actually reduces the cost of the Leonardo compared to the Uno--which is otherwise the most comparable Arduino model out there--due to not needing a separate microcontroller to handle the USB-serial connection.  Now, I know there are a lot of Arduino haters out there, but the fact is, it is fantastic for prototyping.  I get as annoyed as anybody when I see somebody enclose an entire Arduino into a finished product rather than redesigning for the bare microcontroller (which I fully intend to do), but it's a nice prototyping platform with great community support.

Anyway, I got my Leonardo today and immediately loaded up the button mouse example sketch to try it out and lo and behold, it just worked.  Well, it would have just worked if I had built the circuit the way I was supposed to.  As it was, I didn't feel like breadboarding it with pull-down resistors, so I changed the code around to enable the internal pull-up resistors and then changed the logic to be low-triggered rather than high-triggered:


const int upButton = 2;     
const int downButton = 3;        
const int leftButton = 4;
const int rightButton = 5;
const int mouseButton = 6;

int range = 5;     // output range of X or Y movement; affects movement speed
int responseDelay = 10;     // response delay of the mouse, in ms

void setup() {
  // initialize the buttons' inputs:
  pinMode(upButton, INPUT);       
  pinMode(downButton, INPUT);       
  pinMode(leftButton, INPUT);       
  pinMode(rightButton, INPUT);       
  pinMode(mouseButton, INPUT);
  
  digitalWrite(upButton, HIGH);
  digitalWrite(downButton, HIGH);
  digitalWrite(leftButton, HIGH);
  digitalWrite(rightButton, HIGH);
  digitalWrite(mouseButton, HIGH);
  
  // initialize mouse control:
  Mouse.begin();
}

void loop() {
  // read the buttons:
  int upState = digitalRead(upButton);
  int downState = digitalRead(downButton);
  int rightState = digitalRead(rightButton);
  int leftState = digitalRead(leftButton);
  int clickState = digitalRead(mouseButton);

  // calculate the movement distance based on the button states:
  int  xDistance = (leftState - rightState)*range;
  int  yDistance = (upState - downState)*range;

  // if X or Y is zero, move:
  if ((xDistance == 0) || (yDistance == 0)) {
    Mouse.move(xDistance, yDistance, 0);
  }

  // if the mouse button is pressed:
  if (clickState == LOW) {
    // if the mouse is not pressed, press it:
    if (!Mouse.isPressed(MOUSE_LEFT)) {
      Mouse.press(MOUSE_LEFT); 
    }
  } 
  // else the mouse button is not pressed:
  else {
    // if the mouse is pressed, release it:
    if (Mouse.isPressed(MOUSE_LEFT)) {
      Mouse.release(MOUSE_LEFT); 
    }
  }

  // a delay so the mouse doesn't move too fast:
  delay(responseDelay);
}


Anyway, the next thing to do was to build myself a gamepad that I could plug in to the Leonardo for testing.  I plan to use the SNES controller in my final project, but for prototyping, I decided to go with NES instead.  The NES uses the exact same control protocol as the SNES, just with fewer buttons, so that meant fewer parts, as well as fewer IO pins for my debug setup.  I had a few empty protoshields lying around, so I figured that would be perfect for this build.  I also had a CD4021 shift register in my parts drawer from an NES controller I cannibalized awhile back.  If you're looking to build a DIY NES controller, please, for heaven's sake, just go out and buy a 4021 and a few resistors and do it from scratch.  Don't destroy an NES controller just for its IC.  I've seen so many awesome mods that just piss me off because after all that hard work, they have this wiring on the inside.  Seriously, this is all you need (plus a couple of resistors).  Or, if you want to get really fancy, hit me up for one of these (scroll down to my last post for a picture with a size comparison to an SD card.  Anyway, despite that rant, I used the IC from an original controller, because I had already used the rest of the controller for various other stuff, and happened to have the IC left over.  I have a stash of the SMD 4021's, but alas, the protoshield's SOIC footprint is only 14 pins, so I needed the DIP version, and this one was the only one I had.

Ok, now that I'm done with that rant, time for pictures :)

I ran out of solder, so nothing is connected yet.  However, all of the parts are mounted, so I just need to get more solder (and perhaps some finer gauge wire than what I have on hand at the moment would be good too).  Apparently, RadioShack no longer carries through-hole mounted tact switches, but thankfully, the spacing on the SMD feet lined up well enough with the board holes.  The plan is to hook each button up directly to a digital I/O, as well as hooking them up to the 4021 and connecting its latch, clock, and data lines to the remaining digital IO's in order to implement the NES protocol as well.  This way, I can read the buttons either directly or via the NES protocol all from the same shield configuration.


Also, in case you're looking for the correct stacking headers for the Arduino shields, I've managed to track down a manufacturer that makes them.  Unfortunately, the stackable header kits on SparkFun and elsewhere don't actually have the right pin counts.  They sell kits with 2x6-pin headers and 2x8-pin headers.  Arduino shields actually use 1x6, 2x8, and 1x10.  You don't, strictly speaking, need the other four pins, but all of the official shields have them.  Digikey carries these headers, but they don't seem to carry them with tails quite as long as the ones on official shields.  There's not a lot of clearance with the ones I have.  So anyway, here are part numbers for the headers that you'll want, they aren't quite the same as the ones Digikey has (DK carries the -03 variety rather than the -04, which I explain below).


Samtec
1x10 pins: SSQ-110-04-T-S
1x8 pins: SSQ-108-04-T-S
1x6 pins: SSQ-106-04-T-S


Some variations on these part numbers:
-04 is the length of the tail.  -03 or shorter will make for very little clearance above the barrel jack.  On the older boards with full-sized USB-B plugs, you won't be able to clear the USB jack.  However, Digikey doesn't seem to carry anything longer than -03.  If you can find another distributor that stocks the longer tails, let me know in the comments


-T is tin plated pins, replace it with -G for gold plated.
The -110, -108, and -106 are the pin count.  If you want dual row, the numbering is -2XX (i.e. -205 would be 2x5 pins, etc.).

Anyway, that's it for now.  I'm super busy working two jobs this summer, so hopefully I'll get some free time now and then to work on some of this stuff.  Once I get USB HID down, I plan to move on to bluetooth HID (which is just a simple wrapper around the USB HID protocol, so it should be simple enough).  Then I'll be working on building the host module.  Fun times.

Friday, June 22, 2012

Today is a good day.  On Monday, I randomly made an image and posted it on Twitter, @wilw (Wil Wheaton).  He called it "awesome" and reposted it to the world.  "Awesome" is exactly how I feel right now.