USBASP, a cheap retro controller adapter?

Intro

Long ago, I stared at my trusty USBASP and asked myself if it was possible to convert it to a USB adapter for a Mega Drive(Genesis) controller. At the time I still did not even know about V-USB or that the USBASP used it. As I write this, I got it working recently, with a SNES controller tough.  In this article I will talk about how I made it work, as well a little about USB. Be warned that this is an article about how I hacked an USBASP ( with software USB) to work as an USB converter for retro controllers. Still, I know is 2017 and native USB is getting popular on many microcontrollers and there are other ways of creating USB adapters.

Because the USBASP has 6 GPIOS easily available, it can be used to connect many old devices, as retro controllers, mice, keyboards, or even custom joypads and controllers (for arcade games or simulators) with help of external chips.

If you are comfortable already with creating USB devices with V-USB, maybe this article can be a bit boring for you. Anyway, feel free to stick around.

USBASP

The usbasp is an open source AVR programmer by Thomas Fischl. I have the popular version showed in the picture below. The schematic for it is easily obtained online.

USBASP programmer
USBASP programmer
USBASP schematic
USBASP schematic

This picture above is from the manual of the programmer I have. It was colorized before, that’s why some wires look grey.

This programmer uses V-USB, by objective development,  a software-only implementation of a low speed USB device for AVR microcontrollers. If you don’t know V-USB, there are some really good tutorials for V-USB in Code and Life for getting started. The USBASP has six GPIOs available at the ICSP header, so there is no need to make any mod to the programmer itself. Also, it can be programmed by the ICSP header, just connect other programmer, short JP2 on the target one and you’re good to go.

I wanted to use a USBASP here because it can be bought for around 1,65USD on eBay. I have some  USB homemade devices here, but they were made with perfboard and lots of wires and solder and probably costed more than 1,65. Unfortunately, I still dont have easy access to a laser printer or CNC mill to make pretty boards.

HID and the SNES Joypad

You can’t compare USB to the simpler protocols used with microcontrollers in general. It is very complex, but thankfully there are people out there willing to make it easier to understand. There is a document called “USB in a Nutshell” by Beyond Logic that helps a lot in this matter.

You probably heard of HID already, it is used by a ton of input devices and it does not need specific drivers. HID uses report descriptors to send data that is interpreted by the computer. HID descriptors are not easy to understand either, but there is a fantastic article on the subject by Eleccelerator.

I chose a SNES controller as I have some fake ones here I could use for testing and spare if I made any mistake. However, this easily works with Genesis, Master System, Atari, NES, PlayStation I guess, and others. The Nintendo 64 and Nintendo Game cube controllers have a serial one wire protocol that is a PIA, They can be adapted to USB, but not easily. The SNES controller has a D-PAD and 8 buttons (X, Y, A, B, L, R, Select and Start).

The SNES joypad only needs three GPIO, and two of them can be shared. I mean n controllers use 2+n GPIOS (If you don’t use external chips, of course). That means the USBASP with 6 GPIOs on the ICSP header can easily have 4 SNES controllers attached to itself.

Poorly drawn front view of the joypad connector
Poorly drawn front view of the joypad connector

Joypad adapter

I made a super simple adapter to connect the SNES controller to the ICSP header. Just wired the SNES joypad buttons to the header on the USBASP. I used two standard male header pins for each controller pin and a 2×5 female header for connecting the board to the ICSP port.

 

That’s it. Some wires,perfboard and a bit of solder.

Code

First, I had to edit usbconfig.h, the configuration file for V-USB. These defines were there already in the HID example, I just edited them to match the schematic above. This is basically it for the configuration file, as I used the one in the HID example. In this file is also possible to change VID and PID or rename the device.

/* ---------------------------- Hardware Config ---------------------------- */

#define USB_CFG_IOPORTNAME      B
/* This is the port where the USB bus is connected. When you configure it to
 * "B", the registers PORTB, PINB and DDRB will be used.
 */
#define USB_CFG_DMINUS_BIT      0
/* This is the bit number in USB_CFG_IOPORT where the USB D- line is connected.
 * This may be any bit in the port.
 */
#define USB_CFG_DPLUS_BIT 1

As I said before. A HID device needs a report descriptor, the descriptor is an array of bytes that tell the computer how to read the data the device sends to the computer. I this case, I imagined the data,which must be defined as a report_t struct in main.c, as follows:

typedef struct{
    uchar   buttonMask;// 8 bits, a bit for each button
    char    X;//8 bits, a signed value -128 to 127
    char    Y;//8 bits, a signed value -128 to 127
}report_t;

You may be imagining why in the earth I need two axes on a SNES controller. Yeah, I thought this at first, as every tutorial I found online used axes for directions.  I read that devices implementing real D-pads are troublesome and sometimes not recognized. I even analyzed the descriptor for a USB controller I have here and it uses axes too. So I went with axis for now.

I used the HID toll from usb.org and built the following descriptor. Spoiler! It is wrong and the program did not let me fix it. It is missing a END_COLLECTION, that I added later by hand. If you have no idea how to read the descriptor below, you may need to have a read at the link from Eleccelerator I mentioned previously.

PROGMEM const char usbHidReportDescriptor[41] = {
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x05,                    // USAGE (Game Pad)
    0xa1, 0x01,                    // COLLECTION (Application)
    0xa1, 0x00,                    //   COLLECTION (Physical)
    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, 0x08,                    //     REPORT_COUNT (8) - Eight buttons, one bit in size
    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, 0x81,                    //     LOGICAL_MINIMUM (-127)
    0x25, 0x7f,                    //     LOGICAL_MAXIMUM (127)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x95, 0x02,                    //     REPORT_COUNT (2) - Two axis, 8 bit in size
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0                           // END_COLLECTION
};
https://dragaosemchama.com/texto-para-foto-namorada/
The HID tool from usb.org. Unfortunately, I haven’t tried to run it on Linux, but later I read it works fine with wine.

 

Anyway, I tried to plug the thing to the  USB port. Just to see what happened. The device was not recognized, but I ran dmesg and got the following:

[ 4352.161424] usb 2-1.1: USB disconnect, device number 7
[ 4361.829917] usb 2-1.1: new low-speed USB device number 8 using xhci_hcd
[ 4361.922914] usb 2-1.1: New USB device found, idVendor=16c0, idProduct=05dc
[ 4361.922917] usb 2-1.1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 4361.922919] usb 2-1.1: Product: USBasp
[ 4361.922920] usb 2-1.1: Manufacturer: www.fischl.de
[ 4379.553335] usb 2-1.1: USB disconnect, device number 8
[ 4450.150177] usb 2-1.1: new low-speed USB device number 9 using xhci_hcd
[ 4450.243649] usb 2-1.1: New USB device found, idVendor=16c0, idProduct=05dc
[ 4450.243653] usb 2-1.1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 4450.243654] usb 2-1.1: Product: USBasp
[ 4450.243656] usb 2-1.1: Manufacturer: www.fischl.de
[ 4476.321386] usb 2-1.1: USB disconnect, device number 9
[ 4480.870335] usb 2-1.1: new low-speed USB device number 10 using xhci_hcd
[ 4480.963588] usb 2-1.1: New USB device found, idVendor=16c0, idProduct=03e8
[ 4480.963590] usb 2-1.1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[ 4480.963592] usb 2-1.1: Product: Mouse
[ 4480.963593] usb 2-1.1: Manufacturer: obdev.at
[ 4480.963699] usb 2-1.1: ep 0x81 - rounding interval to 512 microframes, ep desc says 800 microframes
[ 4480.966537] hid-generic 0003:16C0:03E8.0006: unbalanced collection at end of report description
[ 4480.966549] hid-generic: probe of 0003:16C0:03E8.0006 failed with error -22

Brilliant. The driver even reported the problem. I fixed the report descriptor as follows. The USBASP is reported as “Mouse” because this is the default name for the HID example and I had not changed it at the time.

PROGMEM const char usbHidReportDescriptor[42] = {
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x05,                    // USAGE (Game Pad)
    0xa1, 0x01,                    // COLLECTION (Application)
    0xa1, 0x00,                    //   COLLECTION (Physical)
    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, 0x08,                    //     REPORT_COUNT (8)
    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, 0x81,                    //     LOGICAL_MINIMUM (-127)
    0x25, 0x7f,                    //     LOGICAL_MAXIMUM (127)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0                           //   END_COLLECTION
    0xc0                           //END_COLLECTION
};

The report descriptor size must be updated in the usbconfig.h file

#define USB_CFG_HID_REPORT_DESCRIPTOR_LENGTH 42

So, now I tested and the device was recognized, but I still needed to send the button presses correctly. For this, I just needed to fill a report_t struct with the data from the SNES controller.

The SNES controller has a very well known protocol, it can’t even be called a protocol. Is just a chip that works like a 74hc165 and sends the buttons pressed serially.

Accordingly to a file on Game FAQs

SNES JOYPAD SERIAL PROTOCOL
                   |<------------16.67ms------------>|

                   12us
                -->|   |<--

                    ---                               ---
                   |   |                             |   |
Data Latch      ---     -----------------/ /----------    


Data Clock      ----------   -   -   -  -/ /----------------   -   ...
                          | | | | | | | |                   | | | |
                           -   -   -   -                     -   -
                           1   2   3   4                     1   2

Serial Data         ----     ---     ----/ /           ---
                   |    |   |   |   |                 |
(Buttons B      ---      ---     ---        ----------
& Select        norm      B      SEL           norm
pressed).       low                            low
                        12us
                     -->|  |<--

And the button sequence:

        Clock Cycle     Button Reported
        ===========     ===============
        1               B
        2               Y
        3               Select
        4               Start
        5               Up on joypad
        6               Down on joypad
        7               Left on joypad
        8               Right on joypad
        9               A
        10              X
        11              L
        12              R
        13              none (always high)
        14              none (always high)
        15              none (always high)
        16              none (always high)

Then, it is simple to read the button states.

uint16_t readButtons(void){
  uint16_t buttonData=0;
  JOYPORT|=(1<<CLOCK)|(1<<LATCH);
  _delay_us(12);
  JOYPORT&=~(1<<LATCH);
  for(int i=0;i<16;i++){
    _delay_us(6);
    if((JOYPIN&(1<<DATA)))
      buttonData|=1<<i;
    JOYPORT&=~(1<<CLOCK);
    _delay_us(6);
    JOYPORT|=(1<<CLOCK);
  }
  return ~buttonData;//inverted because the buttons are pulled up, so i just dont confuse myself
}

CLOCK, LATCH and DATA are the pins of PORTB where the SNES controller is connected(these pins are available at the ISCP header).

#define JOYPORT PORTB
#define JOYPIN PINB
#define JOYDDR DDRB

#define CLOCK 5
#define LATCH 4
#define DATA 3

And, to fill the struct with the button states:

void packData(report_t * report_buff){
  uint16_t buttons=0x00;
  report_buff->buttonMask=0;
  report_buff->X=0;
  report_buff->Y=0;
  buttons=readButtons();
  if(buttons&(1<<0)){//B
    report_buff->buttonMask|=(1<<2);//Button 3
  }
  if(buttons&(1<<1)){//Y
    report_buff->buttonMask|=(1<<3);//Button 4
  }
  if(buttons&(1<<2)){//Select
    report_buff->buttonMask|=(1<<6);//Button 7
  }
  if(buttons&(1<<3)){//Start
    report_buff->buttonMask|=(1<<7);//Button 8
  }
  if(buttons&(1<<4)){//Up
    report_buff->Y=-126;//Button 4
  }else if(buttons&(1<<5)){//Down
    report_buff->Y|=126;//Button 4
  }
  if(buttons&(1<<6)){//Left
    report_buff->X=-126;//Button 4
  }else if(buttons&(1<<7)){//Right
    report_buff->X=126;//Button 4
  }
  if(buttons&(1<<8)){//A
    report_buff->buttonMask|=(1<<1);//Button 2
  }
  if(buttons&(1<<9)){//X
    report_buff->buttonMask|=(1<<0);//Button 1
  }
  if(buttons&(1<<10)){//L
    report_buff->buttonMask|=(1<<4);//Button 5
  }
  if(buttons&(1<<11)){//R
    report_buff->buttonMask|=(1<<5);//Button 6
  }
}

Then the struct is sent whenever the USB interrupt is ready.

if(usbInterruptIsReady()){
            packData(&reportBuffer);
            /* called after every poll of the interrupt endpoint */
            usbSetInterrupt((void *)&reportBuffer, sizeof(reportBuffer));
}

The complete code is available on my https://dragaosemchama.com/jogos-de-namorados-a-tirar-a-roupa/.

Conclusions

It works. I tested in both Linux and Windows. As I said before about HID, it does not need custom drivers.

https://dragaosemchama.com/1-e-7-meses-de-namoro/
Joypad test on Windows 10
Joypad test on Ubuntu with jstest-gtk

I am happy I could understand USB better, but I still have a lot to study and try. Hope that this post and the links I provide below will be helpful to you.

I may use this with a Raspberry Pi zero in the future to make a emulation machine with genuine SNES or NES controllers.The good thing is I only need a USB port to connect up to four controllers anyway.

How about Digispark? It should be not only cheaper, as smaller. I thought about this, but a Attiny85 only has 6 pins available, and this only if you disable the reset pin, two of these are used for USB, so it cuts down the number of devices that can be connected right to the micro. Also it has a bootloader for Arduino and does not have a ICSP header, so I went with the USBASP for this project.

That is it for now. Thanks for reading!

See ya next post.

Sources

The following articles were very helpful while writing this text:

Code and Life V-USB Tutorials

USB in a Nutshell

HID Report Descriptor Tutorial by Eleccelerator

Super Nintendo Pinouts and Protocol at Game FAQs

USBASP manual

 

 

Robson Couto

Recetnly graduated electrical engineer. I enjoy devoting my time to learning about computers, electronics, programming and reverse engineering. My projects are documented in this blog when possible.

18 thoughts to “USBASP, a cheap retro controller adapter?”

  1. Hi,

    Thanks to sharing it with us :)
    A question : you said “That means the USBASP with 6 GPIOs on the ICSP header can easily have 4 SNES controllers attached to itself.”. So I presume it is possible to create 4 HID too ?
    I would like to use my USBASP for 2 gamepad in a first time, do you have some advices ?

    Thank you :)

    1. Hello Shcmurtz.
      Sorry for the late reply, wordpress isn’t notifying my email correctly.
      To use 2 gamepads, you basically have to expand the code to two collections and report descriptors. Ufortunately I did not implement it, but it is explained on the Eleccelerator website.
      http://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/
      You have to program the next GPIO to read the other controller as well.
      Any trouble you can leave another comment here.

      Robson

      1. Thanks for your reply ! I will try but it’s not easy for me (I’m not really a developper ;) ).

        By the way, I take a look to this project below, it’s not based on usbasp but on a Atmega8. I’m pretty sure that we can adapt the code for USBasp. The code is harder but I will take a look too because this code has many interesting functionalities.

        http://www.raphnet.net/electronique/snes_nes_usb/index_en.php
        http://www.raphnet.net/electronique/4nes4snes/index_en.php

        Do you think it’s possible to adapt it to usbasp ? (basically it’s just necessary to change GPIO numbers, isn’t it ?)

        1. Hi Schmurtz
          Raphaels adaptors are great! I actually built the 4 joypads one in the link you sent me. You can see a picture of it here: https://dragaosemchama.com/2015/11/snes-controller-to-pc-adapter-with-arduino-micro/
          I believe it may be possible to adapt them to a usbasp, but I have to have a look at his code first.
          You see, I am not a developer as well haha

          Edit: I am getting it to work. The computer recognizes the usbasp as Rapahel’s 4snes, but I need to fix some things

          1. Thanks Robson !
            By my side I already made a kind of gamepad adapter before : the “USB RetroPad Adapter” :
            http://www.brunofreitas.com/node/41
            Not really hard to make (it’s basically an arduino too), the most harder part was to compile the firmware with some modifications (I use it on an old original xbox !).

            I was thinking that I will be able to modify Rapahel’s 4snes code to USBasp easily but his code is more complex than I was thinking :p
            I really like this idea because there’s almost nothing to solder on usbasp to get it works and it’s really cheap :)

            Awesome if you already has the usbasp recognized ! I tried few days ago without success (ahah it seems that you still more a developer than me :p ).
            I’ll be happy to help you or make tests if you need.
            Looking forward to your investigations :)

        2. Hi Schmurtz
          I left a long comment here yesterday but the website asked me to log again and I lost it.
          Anyway. Awesome adapter. This guy has some cool stuff on his website. I should check them out.
          So far I still have not gotten it to work. There seems to be some bug when reading the controller, I will try to hook up a logic analyser to the usbasp to check the signals this wekeend.
          I took this as a challenge to myself haha.
          Stick around!

  2. Hi Robson,
    Ahah great challenge, I totally understand this kind of obstinacy because I work in the same way ;)
    I would love to know how to use a logic analyser ! What kind do you use ?

    By the way, about your bad experience with “re-authentification” : I recommend you to try the extension “Lazarus”, I use it on firefox (but it seems to exist for chrome too), it allow to recover the content of a web form you were writing just before something bad happen. Not useful every day but you will love it when shit happens :p

    I keep in touch :)

    1. Hi Schmurtz

      I got it to work! I will tweak the code a little more and then post the source at the end of the post :)

      About the logic analyzer, I use a dirty cheap one from ebay. I guess it cost around five dolars. Also got some test clips from ebay to hook up to the pins or legs in the circuit.
      If you have seen a oscilloscope, it is about the some, but only for digital levels(0 to 5V) and usually eight or more inputs. You can see digital signals vary in time with one. With mine, I could see that the clock pin on the controller was not being driven, there should be a square wave in this pin and there was nothing. I used pin 1 on PORTD for clock, if you see the usbasp schematic online(http://www.fischl.de/usbasp/bilder/usbasp_circuit.png) there is a 270 ohm resistor between the microcontroller and the programming header.
      1- Anyway, the serial pins are not used by the official firmware.
      2- Mine had a 1K resistor instead, I guess that a 270 ohm resistor would not kill the signal. But 1K definitely is too much. China tools, you get what you paid for haha.
      I bypassed the 1K ohm resistor and the controller was working fine. I tested with one controller only, but switching between the 4 input pins and they all worked fine.
      I guess the resistor can be removed, bypassed with a wire or swapped for a lower value one.

      Thanks for the tip about the extension. I will check it out!
      I saw this video on hackaday early. It is about cheap logic probes and sigrok I guess. I have the blue one.
      https://www.youtube.com/watch?v=dobU-b0_L1I
      (It is an hour long, but hopefully you can cut through it to the interesting bits ;p or you can find another short one. These tools are very easy to understand )

      Robson

  3. Awesome Robson ! You did it !
    Good analyze, removing a resistor stay a very acceptable mod to make a 4 snes adapter !
    Looking forward your code to make a try and I’m very curious to compare your modifications with the original one. It will help me to understand the code and PIN affectation.

    Thanks for the video about the logic analyser, I’m about to watch this video and buy one :)

    1. Hello Schmurtz

      The code is available at:
      https://github.com/robsoncouto/usbasp4snes/
      As you guessed, it is mostly renaming GPIOs. But you can peek at every changes made in the commits tab (https://github.com/robsoncouto/usbasp4snes/commits/master). I warn you tough, there are some false positives as git saw some changes where there were none. This is possibly a bug with line breaks between different systems.
      GreatScott is a very talented youtuber. His videos are very packed with useful info :)
      I also find him very creative to come up with these many videos so quickly haha

      If you have trouble with this project or any other, just leave a comment here.
      Robson

  4. hi,
    i search for a wireless solution on original xbox to play snes/nes games with coinops.
    I want to use a controller like this one: http://www.8bitdo.com/sn30pro-sf30pro/
    Is there away to get a kind of bluetooth dongle that recoognizes bluetooth devices as usb devices?
    i can pluggin this thing (http://www.8bitdo.com/wireless-usb-adapter/ ) in an adpated usb port to my xbox but the xbox doesnt recognizes it as controller.
    I think its a driver related thing.
    Dou you think you can edit the firmware on this dongle or do you have a own/other solution?
    Thanks in advance!

    1. Hello Hei
      I never used an original xbox, but if I recall correctly, they use a proprietary USB protocol, like the xbox 360 controller. So I guess you are right, you would need your controller to talk with the xbox in this protocol.
      I have no idea how that works tough, sorry.

Leave a Reply

Your email address will not be published. Required fields are marked *