Author: William Adams (wadams@be.com) Name: Gamestick Date: 10/08/96 Release: DR8 ================================================================ A Pleasurable Stick? I've been programming since I was a wee little lad. My first machine was a Commodore PET with the chicklettes keyboard, which was fine because I had small fingers and hands. Back in those days I programmed in machine code (not even assembly) because it was the best way to get at the hardware and fiddle about. One of the more fascinating things for me to do was to play with the I/O controller chip (which I fried several times). The edge connector allowed me to input digital values directly to the machine. This was power too awesome to let go unused. I took the game controller off of our Fairchild video game machine and wired it up to this port. Boy what a game controller!! It could be pulled up, pushed down, or left in its neutral position. At each level you had four directions you push it in, as well as the ability to twist left and right. I haven't seen any game controllers as capable as that since then. Programming for this game stick was a pleasure. I made arcade amusements and sound controllers. Oh boy, what fun! WHAT's A JOYSTICK? The BeBox has the ability to interface with 4 game controllers. I'll call them game controllers because the name 'joystick' is a specific type of game controller, and it's not the only one that can be used on this interface. What is a Joystick? From the PC world there are these mindlessly simple interface devices which have nothing more than two variable resistors tied to a stick, and a couple of switches for 'firing' buttons. This is an inherently analog device in that you hook it up to some port, and there is some piece of hardware or software that turns the raw resistance values reported by these things into some digital form that can be used as coherent input to some application. In the PC world, the game port doesn't do any of this conversion for you. Your options are to write some assembly that will do some funky timing to convert decay rates into resistance values, or use the BIOS of the system. to do it for you. The BeBox has two game controller ports. There is the lower port and the upper port. This refers to their orientation on the back of the BeBox. Each port has a 15 pin female connector into which two types of things can be plugged in. If you plug in a game controller directly, then you can only use two game controllers. Alternatively, you can plug in a Y splitter and plug in 2 game controllers per port. These game controller ports are hooked up to a Analog to Digital controller, wich is a piece of hardware on the I/O board. The driver for the ports simply reads digital values from this hardware. It doesn't spend much time trying to convert from the analog to the digital world, that's done by the hardware. So the values reported by the BJoystick object are the direct conversion of the data that comes in from the port. If you are a speed freak, then spending less time in any area is a good thing. The fact that the game controller code spends a minimal amount of time doing A/D conversion is a big plus. The BeOS has a BJoystick object which is used to access these game controller ports. It's nothing special. It simply allows you to get some raw data that is being reported by the A/D controller. It connects to a device driver (/dev/joystick[1-4]) and reports back what it finds there whenever you call the Update() method. GENERAL STRUCTURE The BeOS is generally a event driven system. Events; mousdown, keydown, etc., are generated by various peripherals and recorded in event queues. These devices can generate their events by using interrupts. A interrupt handler is triggered whenever there is data to be consumed. The handler will place the events into the stream headed for the event queue. Separate processes will go to the queue and consume the events. Unlike a keyboard and some other peripheral devices, the game controller ports do not generate interrupts. That is, the system does not have an interrupt handler that automatically processes the information and places events into the queue. The game controller port must be polled. That is, you must query the device periodically to figure out when there is new data waiting to be consumed. This polling can happen within the main processing loop of an application, but this is really inappropriate since it breaks the clean event driven structure of the program. Since the BeOS is a multi-threaded OS, it makes sense to assign a single thread to the task of reading the joystick port. This task can poll the port periodically at whatever rate is appropriate for the application, and when it finds that there is data to be consumed, it can create a event and dispatch it to the application. This way, game controller messages will show up at the application's MessageReceived() interface just like every other event in the system. In the included code, the BTSGameStickThread performs this task. It has an instance of a BTSGameStick which it polls periodically. Whenever a button is pressed, a BTSGameStickMessage is generated and the BTSJoystick data is stuffed into it. This message is then posted to the application, which then processes it. This keeps the applications event processing paradigm clean, and makes the joystick just another peripheral that generates messages for the application. This is simple, straight forward, and very useful. TOPICS Joysticks are funny devices. They have the two potentiometers for the x and y axis movement, and the two switches for the firing buttons. What could be more simple? From the outside looking in, I would expect the x values to go from 0 when the stick is all the way left, and some higher number like 255 when it's all the way right. Similarly I would expect the y values to go from 0 when the stick is pulled toward me, and 255 when it is pressed all the way forward. I would expect the 'at rest' or zero position to be at about 127, 127. I would expect that the range of values would be distributed smoothly and evenly along this range. Well... It just isn't so! When I played with my joystick, I found the following: Minimum X Value: 1593 Maximum X Value: 3856 Minimum Y Value: 1585 Maximum Y Value: 3858 It gets worse. The center values were at: X: 2411 Y: 2302 Hmmm. That doesn't look like the middle of the range. In addition, the X values were in the opposite order than I thought they would be. The maximum X value occured when the stick was all the way to the left, and the minimum was on the right. The Y values were in the order I expected. The maximum value occured when I pressed the stick all the way forward, and the minimum value was reported when I pulled it all the way toward me. With the center value where it is, the range of values is not exactly smooth. The range of values from center to the minimum values is different than from center to the maximum values! And when I press the fire buttons, I get logical 0 instead of 1! What a mess. What I want for my programs is very simple: 1) I don't want to have to tell the user to 'calibrate' the joystick 2) I want to work with all joysticks including game pads. 3) I want values reported as -1 to 1 to represent minimum and maximum 4) I want to fire buttons to be logical 1s when pressed SELF CENTERED JOYSTICKS First of all, you can find the center value if you just read enough values from the joystick at rest and keep the mean. This is what the FindZero() method does in the BTSGameStick class. It keeps a weighted average of the values it reads from the port. You can train longer if you think your stick is a bit wobbly. It is called in the constructor, but can be called at any time. Now I'm set to turn this into the -1 to 1 range that I really want. HOW FLEXIBLE IS YOUR JOYSTICK Knowing the center value is a good start. Next you need to know the range of values that your game controller is capable of. Without asking the user to swizzle the stick around a little bit, you could build this information over time. The initial range values are clamped to 50. That is, the range is assumed to be at least 50, which is good enough to recognize small movement in the controller right out of the shoot without any usage history. As the stick is used, every time it reports values through the GetPosition() methods, it retrains itself. In the training process, it will take the current values, and re-calculate the range values. Since the range of values is not evenly distributed, you must maintain both the range from center to max and the center to min, in both the x and y directions. With this information, we're finally set to give us those -1 to 1 values. Now that we know everything there is to know about the game controller, converting the current values into the desired -1 to 1 range is simple. Get the current position if the position falls within the center to minimum value divide the value by the minimum range if the position falls within the center to maximum value divide the value by the maximum range if this is the x postion, change the sign And there you have it. A self centered, learning game controller. This method will work for all joysticks no matter how crappy or where their calibration thumbwheels are set. It will work with game pads which don't report a full range of values. It will work with wires connected to your fingertips for bio feedback (try it). These classes are fairly straightforward. You can probably just drop them into your own code if you need joystick support. And now there's one less black hole in your BeOS knowledge. INSTRUCTIONS FOR CODE SAMPLE 1) Put the code into a directory and compile it. 2) Plug a joystick into the lower joystick port. 3) Run the programe 'Gamestick' Whenever you press the 'A' button, the display will update with values (x on left, y on right). If you press the 'B' button, the stick will re-find its zero position based on where it currently sits.