geekhack Community > Input Devices

[Modding] CH Products Trackball PRO - BUSMOUSE to USB (Completed)

(1/5) > >>

Good evening GeekHack,

this is my first post here, even though I've been a long time lurker. A few month ago, I bough a vintage PS/2 Trackball from the CH Products brand. I must say that I'm very impressed by the quality of this device. It well derserves its title of "Model M of the trackballs".

The trackball was advertised as being the PS/2 variant, but unfortunatly it is not. In fact, it uses a pretty old connector and protocol called : BUS mouse.

The bus mouse connector is very similar to the PS/2, with the same 5/16" diameter, but it as 9 pins instead of 6. I can't blame the seller, as PS/2 and BUS can easily been mixed up.

Unlike PS/2, serial or USB devices, absolutly NO LOGIC is done on the device side. BUS mouse devices send the raw data from the optomechanical encoders and buttons to a decoding card on the computer side. The connector has the following pins:

[*]XA (X axis encoder channel A)
[*]XB (X axis encoder channel B)
[*]YA (Y axis encoder channel A)
[*]YB (Y axis encoder channel B)
[*]Switch 1
[*]Switch 2
[*]Switch 3
You've probably spotted that, despite having 4 buttons, the connector only carries 3 switches informations. In fact, the top buttons acts as middle-click (both of them) or as click-lock (left lock and right lock). Everything is configurable thanks to 8 DIP switches below the device.

Fortunatly, all is not so bad:

[*]The cable is connected to a header so that changing it will be easier.
[*]The pinout is provided on both manual and PCB
[*]The controller is an 8bit PIC16C55 with a DIP28 package.[/list]

How do the encoders work:

They are based on quadrature encoders and consists on a shaft wheel and 2 optical sensors per axis. When the shaft spins, it generates 2 electrical signals phased out by 90 degrees

(image courtesy of Dynapar)

The picture below, shows that there're 3 achievable count speed:
[*]x1 if you count on channel A rising edge only
[*]x2 if you count on channel A rising edge and falling edge
[*]x4 if you count on both channel A and B rising and falling edges[/list]

(image courtesy of Dynapar)

Retro-mod operation:

After several tests using an Arduino Mega 2560 rev2 board, I bought a Teensy++ 2.0 as it supports the HID protocol.
At the moment, it is connected it to the BUS Mouse cable header.

The quadrature encoders are handled by interrupt, on both FALLING and RISING transitions. Buttons states are handled by the background loop and updated every 10 microseconds, along with relative coordinates updates if anything happend on the encoders side.

The video below shows a quick demonstration of the trackball in use.

(the embedded video doesn't seem to work: )

What's left to do:

Next step will be to either put the Teensy inside the trackball case or use it as as inline adapter. The later option would require a 3D printed case.

Another cool option, that would match the retro-tech theme of this project, would be a controller board. Bus mouse pointing devices were used with specific controller board (like the one pictured below), where all the logic were done.

The basic idea would be to connect the Teensy to an internal USB header of the motherboard and run the required pins the a connector attached to a PCI bracket. A PCB, with the Teensy socketed into it, could be used for a genuine retro look :D.

Kind of link the pictured Microsoft InPort card, but with the Teensy in place of the PIC and a blank / dummy PCIe connector to secure the board in place.

Nice work, and very interesting. After reading this I must say that I'm glad mine is a PS/2 type, so I can just plug it in.  ;)


--- Quote from: Tactile on Mon, 09 February 2015, 16:12:01 ---Nice work, and very interesting. After reading this I must say that I'm glad mine is a PS/2 type, so I can just plug it in.  ;)

--- End quote ---

Thanks a lot; this is very much appreciated. At first I was a bit angry that it's been advertised as PS/2 while not being, but in the end that just gave me a excuse to hack it and play with a Teensy. It's been a very interesting and enjoyable project. I'm even considering scratch building a trackball with high CPR encoder now  ;D


I recently purchased an Arduino PRO micro, as the Teensy2.0++ doesn't fit the trackball case. This was probably a lucky day, but the store also had the exact same 10 pins cable harness than the one originally used by CH Prodcuts.

As you can see, the PRO micro (in red) is almost half the size of the Teensy2.0++ (in green).

CH Products used some plastic "jumpers" to tie the wires.

The new wires are thicked than the original ones and I can't use them. What could I use then ...

... assume that the cable is a button and the PCB is some piece of fabric ...

I finally took the time to solder the Arduino PRO Micro to the new cable harness.

I made a temporary cable from a micro USB cable. I just got the casing off of the micro USB connector.

I'll do some proper sleeving during the weekend and a strain releaser from epoxy in other to have a clean finish. At least, no more code nor soldering and the controller is now inside the case.

Thanks a lot for everyone who followed this and for the support I got from all of you.

The code is much more compact and optimzed:

--- Code: ---/* =================================================================================
   Author  : GuilleAcoustic
   Date    : 2015-05-16
   Revision: V1.0
   Purpose : Opto-mechanical trackball firmware
   Wiring informations: Sparkfun Pro micro (Atmega32u4)
     - Red             : Gnd                         |  Pin: Gnd
     - Orange          : Vcc (+5V)                   |  Pin: Vcc
     - Yellow          : X axis encoder / channel A  |  Pin: INT0 - SCL
     - Green           : X axis encoder / channel B  |  Pin: INT1 - SDA
     - Blue            : Y axis encoder / channel A  |  Pin: INT2 - Rx
     - Violet          : Y axis encoder / channel B  |  Pin: INT3 - Tx
     - Grey            : Switch 1                    |  Pin: PB3  - MISO
     - White           : Switch 2                    |  Pin: PB2  - MOSI
     - Black           : Switch 3                    |  Pin: PB1  - SCK
   ================================================================================= */

// =================================================================================
// Type definition
// =================================================================================
typedef struct
  int8_t  coordinate = 0;
  uint8_t index      = 0;

// =================================================================================
// Constant for binary mask
// =================================================================================
#define  _SWITCH_1    B1000
#define  _SWITCH_2    B0100
#define  _SWITCH_3    B0010

// =================================================================================
// Constants
// =================================================================================
const int8_t lookupTable[] = {0, 1, -1, 0, -1, 0, 0, 1, 1, 0, 0, -1, 0, -1, 1, 0};

// =================================================================================
// Volatile variables
// =================================================================================
volatile ENCODER_ xAxis;
volatile ENCODER_ yAxis;

// =================================================================================
// the setup function runs once when you press reset or power the board
// =================================================================================
void setup()
  // Attach interruption to encoders channels
  attachInterrupt(0, ISR_HANDLER_X, CHANGE);
  attachInterrupt(1, ISR_HANDLER_X, CHANGE);
  attachInterrupt(2, ISR_HANDLER_Y, CHANGE);
  attachInterrupt(3, ISR_HANDLER_Y, CHANGE);
  // Start the mouse function

// =================================================================================
// The loop function runs over and over again forever
// =================================================================================
void loop()
  // Update mouse coordinates
  if (xAxis.coordinate != 0 || yAxis.coordinate != 0)
    Mouse.move(xAxis.coordinate, yAxis.coordinate);
    xAxis.coordinate = 0;
    yAxis.coordinate = 0;

  // Update buttons state
  !(PINB & _SWITCH_1) ?   : Mouse.release(MOUSE_LEFT);
  !(PINB & _SWITCH_2) ?  : Mouse.release(MOUSE_RIGHT);
  !(PINB & _SWITCH_3) ? : Mouse.release(MOUSE_MIDDLE);

  // Wait a little before next update

// =================================================================================
// Interrupt handlers
// =================================================================================
  // Build the LUT index from previous and new data
  xAxis.index = ((xAxis.index << 2) | ((PIND & 0b0011) >> 0)) & 0b1111;

  // Compute the new coordinates 
  xAxis.coordinate += lookupTable[xAxis.index];

  // Build the LUT index from previous and new data
  yAxis.index = ((yAxis.index << 2) | ((PIND & 0b1100) >> 2)) & 0b1111;

  // Compute the new coordinates 
  yAxis.coordinate += lookupTable[yAxis.index];
--- End code ---

meow a cat:
Nice project! I'm jealous, I love CH gear. I use their stuff exclusively for flight simming. Really well made stuff!

I'd love to own a CH trackball, but they're hard to find, and when they do come up they're expensive. The programming software is amazing though!


[0] Message Index

[#] Next page

Go to full version