Using the Joystick Driver

Overview

The joystick driver allows you to:

  • Send X, Y, Z, RX, RY, RZ, Slider, Dial and Wheel axis positions.
  • Press and/or release the hat button.
  • Press and/or release up to 128 buttons.

The Joystick Reader Utility

While developing code that uses the joystick driver, use the reader utility to consume data from the joystick driver. This is easier than using the Windows Game Controller control panel app, or an actual game that consumes joystick data. It will be way faster and will help you understand the data that we need to send to the driver, especially the hat and button bit arrays.

The Windows Game Controller control panel app. It kind of works, but doesn’t show all 128 buttons or help us understand the data sent to the driver.
This is the Sender app sending data to the joystick driver which is being read by the Reader app. Using the Reader for development makes things easier to understand.

The Joystick Driver Sender Utility

Use the SDK Joystick Driver Sender to send axis, hat and button states to the joystick driver. The joystick driver will appear to Windows as if it is an actual physical joystick. Be sure to press the ‘Connect to Joystick Driver’ before sending data to the driver.

Axis Values

The joystick driver supports nine analog axis. Nine! Not many joysticks have that. Cheap joysticks may have Rx, Ry and a Slider for throttle. Cheap joysticks are full of surprises though, where they may take something like a Rx, which means rotate about the X axis, which would be a pitching motion if it were an aircraft sim, and map it to something strange like Ty. There doesn’t seem to be much logic behind this, but this is what manufacturers, and their driver makers do. That’s why game devs don’t assume that the axis will be used correctly, and allow you to map each joystick driver axis output to a game axis input.

Good joysticks have Rx, Ry, Rz (Rudder) and slider for throttle, and maybe a dial. Throttle quadrants will usually take over the slider for throttle and maybe use the wheel axis as well. Brakes and clutch may use joystick axis. There are many variations out there.

Joysticks also can have a lot of jitter. You can see this if you tweak the Reader code to open a real physical joystick you have laying around. Leaving the stick in the neutral position should only send an unchanged value if you aren’t touching the stick. But a lot of sticks, expensive ones included, will have a ‘noise’ while in neutral. That’s why games have deadzones.

Joysticks also report stick deflection in as a linear value, and this is why games have axis curves. Linear values can be really frustrating to use by end-users.

And finally, joysticks axis can also drift over time and go out of calibration.

A virtual joystick driver has none of these problems. The value you send to the driver is the value the driver consumer (a game?) will use. You can deadzone, smooth and curve the data all you want and then send it to the joystick driver. It is in your control, and that’s a good thing.

Each of the X, Y, Z, Rx, Ry, Rz, Slider, Dial and Wheel axis accept values from 0 to 32767. Using Ry as an example (roll in an airplane), 0 would be deflected fully left, 16384 is neutral, 32767 is full right deflection.

Sending Data to the Driver

First, iterate through all drivers until you find ‘Tetherscript Virtual Joystick’ and connect to that driver. Then send a packed record as a ‘Feature Report’ to the driver. More info on feature reports below:

https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/

Joystick driver reports are based sdk/delphi/common/hut1_12v2.pdf

Delphi

Here’s the joystick driver report Delphi data format. In our Delphi source we call this a Feature, but really it is a standard HID Input Report. This is just the terminology left over from Delphi’s JCL and JVCL libraries.:

type
PTSetFeatureJoy = ^TSetFeatureJoy;
TSetFeatureJoy = packed record
  ReportID: Byte;
  CommandCode: Byte;
  X: Word;
  Y: Word;
  Z: Word;
  rX: Word;
  rY: Word;
  rZ: Word;
  slider: Word;
  dial: Word;
  wheel: Word;
  hat: Byte;  //array of 8 bits, one per hat position
  buttons: array[0..15] of Byte;  //array of 128 bits, one per button
end;

C#

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SetFeatureJoy
{
  public Byte ReportID;
  public Byte CommandCode;
  public UInt16 X;
  public UInt16 Y;
  public UInt16 Z;
  public UInt16 rX;
  public UInt16 rY;
  public UInt16 rZ;
  public UInt16 slider;
  public UInt16 dial;
  public UInt16 wheel;
  public Byte hat;
  public Byte btn0; //you really could use a byte[15] array here instead, but it's a bit more complex to implement, so we didn't do that here
  public Byte btn1;
  public Byte btn2;
  public Byte btn3;
  public Byte btn4;
  public Byte btn5;
  public Byte btn6;
  public Byte btn7;
  public Byte btn8;
  public Byte btn9;
  public Byte btn10;
  public Byte btn11;
  public Byte btn12;
  public Byte btn13;
  public Byte btn14;
  public Byte btn15;
}

c

This corresponds to the following c struct (this is the actual receiving input report struct from the driver source):

typedef struct _HIDMINI_CONTROL_INFO {
 UCHAR ReportId;
  UCHAR ControlCode;
   unsigned short X;
  unsigned short Y;
  unsigned short Z;
  unsigned short rX;
  unsigned short rY;
  unsigned short rZ;
  unsigned short slider;
  unsigned short dial;
  unsigned short wheel;
  BYTE hat; //array of 8 bits, one per hat position
  BYTE buttons[16]; //array of 128 bits, one per button
} HIDMINI_CONTROL_INFO, * PHIDMINI_CONTROL_INFO;

  • ReportID: is always 1. Just leave it as 1.
  • CommandCode: This is 1 or 2. Also known as a ControlCode. Set it to 1 to reset axis, hat and buttons. Set it to 2 to send axis, hat and button info.
  • X, Y, Z: These are translational axis positions. These are more appropriate for a 6DOF device where you can slide along an axis, as well as rotating around the axis. Values are (0-32737). The original spec designers where really thinking ahead to 6DOF, but I have never seen a physical joystick slide along an X, Y or Z axis.
  • RX, RY, RZ: These are rotational axis positions. These are the standard joystick axis where you have pitch=RY, Roll=RY and Yaw=RZ. Values are (0-32737).
  • Slider: This is usually used as a throttle. Values are (0-32737).
  • Dial and Wheel: Rarely found on joysticks, except maybe the really expensive ones. Values are (0-32737).
  • hat: This is a single byte that represents the position of the hat switch. A hat switch indicates direction, such as in a flight sim, head-look to the right. Values are 255, (0-7).
    • 255= hat switch is not pressed
    • 0= hat switch pressed UP
    • 1= hat switch pressed UPPER_RIGHT
    • 2= hat switch pressed RIGHT
    • 3= hat switch pressed DOWN_RIGHT
    • 4= hat switch pressed DOWN
    • 5= hat switch pressed DOWN_LEFT
    • 6= hat switch pressed LEFT
    • 7= hat switch pressed UPPER_LEFT
  • buttons: This is an array of 16 bytes, which is 128 bits, which corresponds to 128 buttons. If a bit is set to 0, the button is up. Set it to a 1 and the button is pressed. So to press a virtual joystick button and release it, you set a bit to 1, wait x ms (try 50ms), then set the bit to zero. Each byte has represents 8 buttons, with the right-most bit representing the first button and the left-most bit representing the last buttons, as shown below (this can be confusing).
    • 00000000 00000000 00000000 00000000…00000000: 16 bytes, all bits are zero, so no buttons are pressed.
    • 00000001 00000000 00000000 00000000…00000000: Button 1 is pressed.
    • 00000010 00000000 00000000 00000000…00000000: Button 2 is pressed.
    • 00000100 00000000 00000000 00000000…00000000: Button 3 is pressed.
    • 00000101 00000000 00000000 00000000…00000000: Button 1 and 3 are pressed.
    • 00000000 00000001 00000000 00000000…00000000: Button 9 is pressed.
    • 00000010 00000001 00000000 00000000…00000000: Button 2 and 9 are pressed.
    • 00000000 00000000 00000000 00000000…00000001: Button 128 is pressed.
    • Simple, right?

Handling Stuck Joystick Buttons

As an example, you may press joystick button 1 (usually the trigger button) and then it is possible that you may have forgotten, or due to your app crashing, that the button remains pressed. There are three ways to unstuck a button.

  1. Clear the button by setting its byte array bit to zero.
  2. Send a reset command to the driver. This sets all axis to mid-range (center) and releases the hat and all buttons.
  3. Reboot.

Resetting the Driver

To reset the driver, send the report with a CommandCode = 1 and any values for axis, hat and buttons. The driver will center all axis and release the hat and all buttons.

You can also just send the axis centering values and release the hat and buttons yourself with a CommandCode = 2. It’s up to you. Both methods are equivalent.

Your First Hello-Driver App – a Suggestion

  1. Start the Joystick Reader utility and connect to the driver.
  2. In your app, iterate through the drivers and find and connect to the joystick driver.
  3. In your app, send data with a command code of 2, X = 10000, hat = 0, button array all zeros.
  4. in the reader utility, you should see the X axis deflected a bit left of center.