Among the things I want to do with the Raspberry Pi is to build a miniature arcade thing. I had planned on making one with one of my mini-itx systems, but the Pi just seems so much better. And honestly, it's more stable than any of the mini-itx systems I've used. I had already ordered an Ultimarc Ultrastik 360 and a bunch of arcade buttons. The Ultrastik is awesome, I seriously wouldn't build any type of arcade without using one of these. One of the things it can do is flash 9x9 control maps so it can be a 4-way, 8-way, 2-way, and more. It also hooks up via usb, supports direct connection of buttons, and the 2.5 firmware can use one of those as a shift (so it basically can support about 16 buttons). Awesome. I got the medium-firm spring and the circular restrictor plate to give it a shorter throw.
I started with the Adafruit Occidentalis build for my RPi, which came with the stuff I needed "out of the box" to get the WiFi dongle working.
Earlier experiments trying to get MAME and a frontend gui working didn't go so well, but I ran across a few posts that helped. I followed Chris's guide and parts of Ben's post to get AdvanceMAME and AdvanceMenu compiled and installed. I'll skip the hassle I had getting all the appropriate files & configurations loaded into the right directories.
Then I snagged Al Crate's configuration utility for linux (link goes to Ultimarc page, code is linked at second line from top). To get that to compile, I needed to grab a few things and compile libhid (because it's not available to RPi via apt-get install yet for some reason). But to get *that* to compile without a warning throwing the whole thing out of the make command, I had to edit the libhid-0.2.16/test/lshid.c
There's a line that says:
len = *((unsigned long*)custom);
Right below it, I threw this:
if(len==0){;}
So that got libhid compiled, and after an
ldconfig
I got ultrastikcmd to compile. The next set of problems I ran into were:- When I ran ultrastikcmd to load a map into the Ultrastik, it dropped off the machine.
jstest /dev/input/js0
no longer found it (although anls /dev/input/
shows ajs0
node). - When I unplugged and replugged the joystick, the jstest would work again but the input either didn't work at all or was scrambled. I did a LOT of using my Windows machine to load new maps into joystick.
The section of code I fixed is toward the bottom -- I just commented out the if and closing brace. Since it sets the defaults & overrides them if you load them from a map or command line, it should be ok to write them to the device no matter what. I might not have needed to do this if the Ultrastik configuration maps I used had map values in them.
//if(arguments.border_set){
// Byte[3]-Byte[10] = Map Border Location
memcpy(&data[3],arguments.border,sizeof(arguments.border));
//}
The remaining big issue was the fact that the thing kept falling offline. After hours and hours, and a significant amount of searching, I figured out that when ultrastikcmd takes control of the joystick to flash a map to it, the system unbinds the kernel driver. I tried a couple dozen things to get it rebound programmatically (and learned a lot about devices and usb on linux in the process), but the reattach code isn't in the libusb or other stuff built into the RPi image I was using, and to heck if I'm going to re-compile the whole OS.
I ended up creating a whole lot less elegant solution, but it works.
Here's what we started with. You can see that jstest works - the joystick gives appropriate readings when I move it.
$ jstest /dev/input/js0
Driver version is 2.1.0.
Joystick (Ultimarc Ultra-Stik Ultimarc Ultra-Stik Player 1) has 2 axes (X, Y)
and 16 buttons (Trigger, ThumbBtn, ThumbBtn2, TopBtn, TopBtn2, PinkieBtn, BaseBtn, BaseBtn2, BaseBtn3, BaseBtn4, BaseBtn5, BaseBtn6, BtnDead, BtnA, BtnB, BtnC).
Testing ... (interrupt to exit)
Axes: 0: 0 1: 0 Buttons: 0:off 1:off 2:off 3:off 4:off 5:off 6:off 7:off 8:off 9:off 10:off 11:off 12:off 13:off 14:off 15:off
Now switching to 4-way map
$ ultrastikcmd -vfou ~/ustik_maps/4-way.um
And trying jstest again -- no love$ jstest /dev/input/js0
jstest: No such device
One of the things I learned about along the way is sysfs. That and a bit of poking around yielded something interesting. This presumes you have usbip installed, and gives you a list of usb devices by USB BUS ID. d209:0501 is the Ultrastik.
$ sudo usbip list -l
- busid 1-1.3.3 (d209:0501)
1-1.3.3:1.0 -> unknown
1-1.3.3:1.1 -> usbhid
See that little "unknown" there? That's the not-bound kernel driver. Here are the input devices -- notice there aren't any js0 entries, but there is for event1 (1-1.3.3:1.1), which comes from the Ultrastik. Note: for this and other directory listings, I'm going to edit them down to save space & improve readability.
$ ls -la /sys/class/input/
event1 -> ../../devices/platform/bcm2708_usb/usb1/1-1/1-1.3/1-1.3.3/1-1.3.3:1.1/input/input10/event1
mice -> ../../devices/virtual/input/mice
mouse0 -> ../../devices/platform/bcm2708_usb/usb1/1-1/1-1.3/1-1.3.3/1-1.3.3:1.1/input/input10/mouse0
It doesn't show above as bound to the driver, but we can double check. No driver listed!
$ ls -la /sys/bus/usb/devices/1-1.3.3:1.0/
bAlternateSetting
bInterfaceClass
bInterfaceNumber
bInterfaceProtocol
bInterfaceSubClass
bNumEndpoints
ep_82
modalias
power
subsystem -> ../../../../../../../../bus/usb
supports_autosuspend
uevent
This one is (the one for event1, which is the other BUS ID listed by the usbid command).
$ ls -la /sys/bus/usb/devices/1-1.3.3:1.1/
0003:D209:0501.000B
bAlternateSetting
bInterfaceClass
bInterfaceNumber
bInterfaceProtocol
bInterfaceSubClass
bNumEndpoints
driver -> ../../../../../../../../bus/usb/drivers/usbhid
ep_81
input
modalias
power
subsystem -> ../../../../../../../../bus/usb
supports_autosuspend
uevent
Yup, a driver!Now for the fun part. Evidently, if you write the appropriate usb bus ID to the kernel driver bind file, it rebinds. Hmmm...
echo -n "1-1.3.3:1.0" | sudo tee -a /sys/bus/usb/drivers/usbhid/bind
You need to put the sudo in the right spot and use tee because the user doesn't have write permissions to bind. I'll fix that later.Now lets check to see if it appears rebound:
$ sudo usbip list -l
- busid 1-1.3.3 (d209:0501)
1-1.3.3:1.0 -> usbhid
1-1.3.3:1.1 -> usbhid
Yes! And importantly, jstest worked again!
Now how do I keep from having to manually do this every time I load a map into the Ultrastik. Enter udev (and two more links) and some shell scripting.
First step, since I want to try and programmatically be able to rebind in user (vs. root) space, I'm changing permissions/group for the usbhid kernel bind. I'm sure someone will point out all kinds of evil wrong things this will do, but this isn't a server/production machine... it's an arcade joystick.
$ sudo chmod g+w /sys/bus/usb/drivers/usbhid/bind
$ sudo chown root:pi /sys/bus/usb/drivers/usbhid/bind
$ ls -la /sys/bus/usb/drivers/usbhid/
--w--w---- 1 root pi 4096 Aug 31 23:45 bind
To make things easy, I also want to create a symlink to the bind in a convenient location.
$ ln -s /sys/bus/usb/drivers/usbhid/bind ~/ultrastik/usbhid_bind
Now I need a udev rule. I've added a rule to write the kernel to a file under the home folder, for ease of access in my shell script.
$ cat /etc/udev/rules.d/81-ultrastik.rules
SUBSYSTEM=="usb", ATTRS{idVendor}=="d209", ATTRS{idProduct}=="0501", GROUP="input", RUN+="/home/pi/receive_udev.sh '%s{manufacturer}' '%k'"
That matches the Ultimarc Ultrastik, ensures when it's added that a group the user is in can get to it, and then runs a custom shell script passing in a couple variables. I'm not using the first one (manufacturer), it was just for testing. Here's the script that receives that rule and writes the usb bus id out. I'm hard-coding the ":1.0" part in hopes that doesn't screw it up later.
$ cat receive_udev.sh
#!/bin/bash
echo "$2:1.0" > /home/pi/ultrastik/ultrastik_sysfs_id
So now all I need to do to fix/rebind the joystick is:
$ cat ~/ultrastik/ultrastik_sysfs_id > ~/ultrastik/usbhid_bind
I've tweaked the AdvanceMenu configuration (
~/.advance/advmenu.rc
) emulator line to call a generic emulator (my script):
emulator "AdvanceMAMECustom" generic "/home/pi/ultrastik/advshim.sh" "%s"
That script is as follows -- where you see rom1|rom2, etc. you would replace those with the names of the roms that need a specific Ultrastik map.
#!/bin/bash
# if no command line arg given
# set ROM to Unknown
if [ -z $1 ]
then
ROM="*** Unknown ROM ***"
elif [ -n $1 ]
then
# otherwise make first arg as a ROM
ROM="$1"
fi
case $ROM in
# 8-way
rom1|rom2|rom3)
ultrastikcmd -ou ~/ultrastik/ustik_maps/8-way.um
cat ~/ultrastik/ultrastik_sysfs_id > ~/ultrastik/usbhid_bind
;;
# 4-way
rom4|rom5)
ultrastikcmd -ou ~/ultrastik/ustik_maps/4-way.um
cat ~/ultrastik/ultrastik_sysfs_id > ~/ultrastik/usbhid_bind
;;
*)
echo "Sorry, no ROM defined!"
;;
esac
/usr/local/bin/advmame $ROM
So...
- On boot or Ultrastik insert, udev catches it and writes something like 1-1.3.3:1.0 to
~/ultrastik/ultrastik_sysfs_id
- When you launch a game from advmenu, it calls the
advshim.sh
script, passing in the rom name. - The script checks the rom name to find the appropriate ultrastik configuration map,
- ...runs the ultrastikcmd configurator (losing the joystick in the process),
- ...writes the contents of the previously-written usb bus ID to the bind to pick back up the joystick,
- ...then launches advmame with the rom.
Now all I need to do is wire up the buttons, get them all configured properly, and build a case for the whole assembly.
20 comments:
Can this be followed as is for a linux noob? I had a self-contained 2 player controller with a mini-itx, minipac and Ultrastik and SpinTrak. But I want to redo it all with RPi.
Honestly, probably not for a complete beginner for a couple reasons: I gloss over the whole system set up, getting it up to date, configured with g++, etc. and I make some assumptions about a reader knowing how to do things like navigate around the OS, get in and out of configuration files.
The couple posts/blogs I link to at the beginning of my post are pretty well done, so if you are able to follow those you could likely get through mine without too much trouble either.
And there are few more fun ways to learn linux than to muck around with the RPi, so I'd highly recommend getting one and even if you decide not to dismantle your mini-itx system, you'll still find some useful or fun things to do with the RPi.
Thanks for posting this experience with the RPi and the Ultrastick. I am a Linux noob, and I am using this to learn about some of the functionality of Linux (along with some other tutorials). One question - where you mention the "chmown" command above, do you mean "chown"?
Like I said, I'm a noob.
Thanks for publishing this experience you had with the RPi and the ultrastikcmd software and Ultrastick hardware. As a Linux noob, I'm finding it a cool practical example to help me learn about more advanced Linux functionality (it's a nice break from general tutorials).
One question . . . where it says "chmown" above, should it actually be "chown"? Like I said, I'm a noob.
@Chris - yes, sorry. It should probably be 'chown' - I edit it.
Noob alert. I got all of this to work great, with just one exception. I've been trying to solve the problem on my own, but I'm now willing to ask for advice.
For some reason, when I change the access for the bind file with
sudo chmod g+w /sys/bus/usb/drivers/usbhid/bind
sudo chown root:pi /sys/bus/usb/drivers/usbhid/bind
it works great, but each time I reboot my RPi, it "forgets" the access settings for the bind file, and I have to enter these commands again. Is there any reason why the bind file would "heal" itself and prevent access on every reboot?
Hmm. I looked through a few pages, some of which I may have already linked to, all of which I tagged on delicious: https://delicious.com/benjaminellison/linux
However, this last page (http://zurlinux.com/?cat=291) had an interesting sentence at the bottom, "Note that this change does not persist across a reboot unless you modify the appropriate modprobe configuration file." - I'd recommend starting with that clue. If you figure it out, let me know :)
@Ben - thanks for a hint instead of a recipe - much more satisfying.
I'm not sure if I went down the road you were hinting at, but here's my solution using init.d scripts. Let me know if this is inelegant:
Go to the init.d directory, where all scripts to be run at bootup are located:
cd /etc/init.d
Create a shell script called bind-write.sh (with superuser access)
sudo nano bind-write.sh
Paste the following into the editor, and exit and save:
#!/bin/bash
# allow the group associated with bind to have write access
# change the owner of bind to root, and the group of bind to pi
sudo chmod g+w /sys/bus/usb/drivers/usbhid/bind
sudo chown root:pi /sys/bus/usb/drivers/usbhid/bind
echo "Executed bind-write.sh . . . bind file can now be modified by user"
Set the ownership and permissions on this script so root can execute the shell script:
sudo chmod 755 /etc/init.d/bind-write.sh
sudo chown root:root /etc/init.d/bind-write.sh
Now set this script to run at startup by creating symbolic links to bind-write.sh under each multi-user runlevel directory (don’t forget the . at the end):
update-rc.d -f bind-write.sh start 99 2 3 4 5 .
This creates symbolic links (S-scripts with NN numbers) in each run level directory per the following relationships:
/etc/rc2.d/S99bind-write.sh --> /etc/init.d/bind-write.sh
/etc/rc3.d/S99bind-write.sh --> /etc/init.d/bind-write.sh
/etc/rc4.d/S99bind-write.sh --> /etc/init.d/bind-write.sh
/etc/rc5.d/S99bind-write.sh --> /etc/init.d/bind-write.sh
This means that bind-write.sh will be executed upon any bootup (runlevels 2, 3, 4, or 5). When it is executed, it will grant write access to /sys/bus/usb/drivers/usbhid/bind.
This actually works for me. And I got to learn about init.d and runlevels and S-scripts.
Thank you very much for this excellent info.
Have you tried the new ultrastik firmware 2.5 with linux?. I am experiencing problems with the map 8 ways. Only down and rigth are working in linux but everything is ok under windows.
Any idea would be very apreciated.
@A. Morales - I'm not sure which firmware I have loaded at the moment (I may check later assuming I can dig out all the pieces). I ran into similar problems though and had to plug it into Windows and use the Ultrastik configurator to tell it to keep the device analog. That seemed to work when I moved it back onto linux.
Thank you. Keep it analog works but no mapping. Linux recognized an analog joystick with good "analog" behavior but no mapping.
When I load a map with ultrastickcmd 0.1.3 ... bad behaviour. I have to load the mouse map and then the analog map to restore the good behaviour under linux. When load directly the analog map did not work.
Thank you very much.
+Ben Thanks for posting these instructions...it looks from Google like these are still the most authoritative and informative on this topic!
I have exactly the same problem that A. Morales experiences. Doing more investigation I am confident that the map loading is working fine on both windows and linux, but with any map other than analog, the results in jstest show that the joystick is only reporting positive values, rather than negative. Hence this appears like right and down only work.
I'm guessing this must be a problem with the linux kernel driver for usbhid? Any ideas on how to debug further?
+Ben Thanks for posting these instructions...it looks from Google like these are still the most authoritative and informative on this topic!
I have exactly the same problem that A. Morales experiences. Doing more investigation I am confident that the map loading is working fine on both windows and linux, but with any map other than analog, the results in jstest show that the joystick is only reporting positive values, rather than negative. Hence this appears like right and down only work.
I'm guessing this must be a problem with the linux kernel driver for usbhid? Any ideas on how to debug further?
@Chris - sorry it took so long for me to get to your comment, although I don't have an answer. I dug out my joystick and when I clear the queue of things on my workbench will take a peek to see what I can see. I'm not much of a kernel driver person, however... more of a fiddler. I'm not sure if the issue is in the driver or in the firmware. With the popularity of the Raspberry Pi and the quality of the Ultrastik, you'd think there would be more work in this area...
I was having the same issue in Linux. I set the kernel boot flag hid.debug=1 in grub. Once I did this and tested the joystick there were a bunch on "Ignoring out-of-range value x" messages scrolling in the event log as I moved the joystick around. So I went to the kernel source and edited drivers/hid/hid-input.c. And commented out the if statement testing for out of range values. Re-compiled kernel and boom it is working. I still have an issue with applying the maps in linux though. I have to go to my windows box to do that.
Thank you for the tip! What problem are you having applying maps?
I just figured the map problem out. I am using firmware 2.5 on my joysticks and ultrastikcmd-0.1.3.
The only map that would apply and work was the Analog.um map. The others would seem to load correctly but in joystick testing would not be the correct x and y coordinates. Even loading the map on linux and moving the joystick to windows would give me bad coordinates. So it had to be something with the linux map writer. So I took a look in the analog.um file to see if anything was different and it is. It has a line for
MapBorderLocations=30,58,86,114,142,170,198,226
where the other maps do not. So I added this to my other maps and I am off and rolling. Hope this helps others out there.
I am SO glad I found this post. There is no way I would have gotten the Ultrastik working without following ALL of the directions here, including the need to patch the HID kernel driver to get the non-analog maps to work correctly.
I found that if I modified the source to ultrastikcmd and added the following code just before the hid_close, it seems to correctly reset the driver.
int reset_ret = usb_reset(hid->dev_handle);
if (reset_ret != 0) {
fprintf(stderr, "usb_reset failed with return code %d\n", reset_ret);
return 1;
}
There's a version 0.1.3 of the ultrastikcmd tool that includes the kernel rebinding automatically. I think I found the link on the Ultimarc arcadecontrols forum. It's a direct download from al3ph.com.
Post a Comment