Hello SFF,
this is my first post here, but some of you already know me. 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 deserves its title of "Model M of the trackballs".
The trackball was advertised as being the PS/2 variant, but unfortunately 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, absolutely NO LOGIC is done on the device side. BUS mouse devices send the raw data from the opto-mechanical encoders and buttons to a decoding card on the computer side. The connector has the following pins:
You've probably spotted that, despite having 4 buttons, the connector only carries 3 switches information. 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.
Fortunately, all is not so bad:
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:
(image courtesy of Dynapar)
Retro-mod operation:
I 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 Products.
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 thicker 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 spent most of my time working on the code. I greatly improved the way I handle the quadrature data.
I made a temporary cable from a micro USB cable. I just got the casing off of the micro USB connector.
I still to do some proper sleeving and a strain release, in order to have a clean finish. I hope you do like this humble project of mine. Please do not hesitate to leave a comment or ask any question.
Below is a compact and optimized code:
this is my first post here, but some of you already know me. 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 deserves its title of "Model M of the trackballs".
The trackball was advertised as being the PS/2 variant, but unfortunately 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, absolutely NO LOGIC is done on the device side. BUS mouse devices send the raw data from the opto-mechanical 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
- +5V
- Ground
You've probably spotted that, despite having 4 buttons, the connector only carries 3 switches information. 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.
Fortunately, 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.
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
(image courtesy of Dynapar)
Retro-mod operation:
I 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 Products.
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 thicker 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 spent most of my time working on the code. I greatly improved the way I handle the quadrature data.
Disclaimer: No PCB has been harmed during the process. I just reused the "plastic jumper" fitting holes.
I made a temporary cable from a micro USB cable. I just got the casing off of the micro USB connector.
I still to do some proper sleeving and a strain release, in order to have a clean finish. I hope you do like this humble project of mine. Please do not hesitate to leave a comment or ask any question.
Below is a compact and optimized code:
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;
} ENCODER_;
// =================================================================================
// 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
Mouse.begin();
}
// =================================================================================
// 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.press(MOUSE_LEFT) : Mouse.release(MOUSE_LEFT);
!(PINB & _SWITCH_2) ? Mouse.press(MOUSE_RIGHT) : Mouse.release(MOUSE_RIGHT);
!(PINB & _SWITCH_3) ? Mouse.press(MOUSE_MIDDLE) : Mouse.release(MOUSE_MIDDLE);
// Wait a little before next update
delay(10);
}
// =================================================================================
// Interrupt handlers
// =================================================================================
void ISR_HANDLER_X()
{
// 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];
}
void ISR_HANDLER_Y()
{
// 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];
}
Last edited: