Jump to content
Sign in to follow this  
GuilleAcoustic

[Completed] CH Products Trackball PRO - BUSMOUSE to USB

Recommended Posts

Hello modZoo,
 
I thought it would be nicer to open a dedicated thread, instead of posting the progress in my generic "Drawing board".
 
Below is the compiled version of my previous posts:
 
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".
 
iD6EkAcl.jpg
 
YI5bXlNl.jpg
 
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.
 
dHrKSixl.jpg
 
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 1)
  • XB (X axis encoder channel 2)
  • YA (Y axis encoder channel 1)
  • YB (Y axis encoder channel 2)
  • Switch 1
  • Switch 2
  • Switch 3
  • +5V
  • Ground
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.
9okrCj8l.jpg
 
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. Note that encoders with a single channel can only give a speed information, 2 channels are mandatory in order to computer CW or CCW rotation.
 
UazlpB1l.png
 
The picture above, shows that there're 3 achievable count speeds:
  • 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
zZ2QGRil.jpg
 
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.
 
61qIXXTl.jpg
 
AsE7PCrl.jpg
 
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 happened on the encoders side.
 
The video below shows a quick demonstration of the trackball in use.
 
 
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.
 
oiMcfUpl.jpg
 
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.
 

 
Below is the current code for this project: I will upgrade it through the revisions.
/* ============================================================================================   Author  : GuilleAcoustic   Date    : 2015-02-18   Revision: V1.0   Purpose : Opto-mechanical trackball firmware   --------------------------------------------------------------------------------------------   Wiring informations:   --------------------------------------------------------------------------------------------     - GND    / Black  : Gnd      - VCC    / White  : Vcc (+5V)     - Pin_D0 / Green  : X axis encoder / channel A     - Pin_D1 / Blue   : X axis encoder / channel B     - Pin_D2 / Violet : Y axis encoder / channel A     - Pin_D3 / Gray   : Y axis encoder / channel B     - Pin_D4 / Orange : Switch 1     - Pin_D5 / Red    : Switch 2     - Pin_D6 /        : not connected     - Pin_D7 / Brown  : Switch 3   --------------------------------------------------------------------------------------------   Note: The Pin_D6 must not be used on Teensy++ 2.0 as it controls the embedded LED   ============================================================================================ */// --------------------------------------------// Constant for binary mask// --------------------------------------------#define  _SWITCH_1    B00010000#define  _SWITCH_2    B00100000#define  _SWITCH_3    B10000000// --------------------------------------------// Type definition// --------------------------------------------typedef struct{  byte coordinate;  byte state;  byte stateMachine [4];  byte shift;  byte bitMask;} ENCODER_;// --------------------------------------------// Global variables// --------------------------------------------volatile ENCODER_ X_Axis;volatile ENCODER_ Y_Axis;// =====================================================================// the setup function runs once when you press reset or power the board// =====================================================================void setup(){  // --------------------------------------------  // Set the whole port D as input  // --------------------------------------------  DDRD = B00000000;  delay(100);    // --------------------------------------------  // Initialize encoders informations  // --------------------------------------------  static byte initTable [4] = {0, 1, 3, 2};    X_Axis.coordinate      = 0;  X_Axis.shift           = 0;  X_Axis.stateMachine[0] = B00000000 << X_Axis.shift;  X_Axis.stateMachine[1] = B00000001 << X_Axis.shift;  X_Axis.stateMachine[2] = B00000011 << X_Axis.shift;  X_Axis.stateMachine[3] = B00000010 << X_Axis.shift;  X_Axis.bitMask         = B00000011 << X_Axis.shift;  X_Axis.state           = initTable [(PIND & X_Axis.bitMask) >> X_Axis.shift];    Y_Axis.coordinate      = 0;  Y_Axis.shift           = 2;  Y_Axis.stateMachine[0] = B00000000 << Y_Axis.shift;  Y_Axis.stateMachine[1] = B00000001 << Y_Axis.shift;  Y_Axis.stateMachine[2] = B00000011 << Y_Axis.shift;  Y_Axis.stateMachine[3] = B00000010 << Y_Axis.shift;  Y_Axis.bitMask         = B00000011 << Y_Axis.shift;  Y_Axis.state           = initTable [(PIND & Y_Axis.bitMask) >> Y_Axis.shift];  // --------------------------------------------  // Attach interruption to Axis sensors  // --------------------------------------------  attachInterrupt(PIN_D0, ISR_HANDLER_X, CHANGE);  attachInterrupt(PIN_D1, ISR_HANDLER_X, CHANGE);  attachInterrupt(PIN_D2, ISR_HANDLER_Y, CHANGE);  attachInterrupt(PIN_D3, ISR_HANDLER_Y, CHANGE);    // --------------------------------------------  // Communication bus  // --------------------------------------------  Serial.begin(115200);  Mouse.begin();}// =====================================================================// the loop function runs over and over again forever// =====================================================================void loop(){  // --------------------------------------------  // Update mouse coordinates  // --------------------------------------------  if (X_Axis.coordinate != 0 || Y_Axis.coordinate != 0)  {    Mouse.move(X_Axis.coordinate, Y_Axis.coordinate);    X_Axis.coordinate = 0;    Y_Axis.coordinate = 0;  }  // --------------------------------------------  // update buttons state  // --------------------------------------------  byte buttons = PIND;  Mouse.set_buttons(!(buttons & _SWITCH_1), !(buttons & _SWITCH_2), !(buttons & _SWITCH_3));    // --------------------------------------------  // Wait a little before next update  // --------------------------------------------  delay(10);}// =====================================================================// Interrupt handlers// =====================================================================// -----------------------------------------------// Horizontal axis sensor// -----------------------------------------------void ISR_HANDLER_X(){  // X axis encoder handling  if ((PIND & X_Axis.bitMask) == X_Axis.stateMachine[(X_Axis.state + 1) % 4])  {    X_Axis.state = (X_Axis.state + 1) % 4 ;    X_Axis.coordinate++ ;  }  else  {    X_Axis.state = (X_Axis.state + 3) % 4 ;    X_Axis.coordinate-- ;  }}// -----------------------------------------------// Vertical axis sensor// -----------------------------------------------void ISR_HANDLER_Y(){  // Y axis encoder handling  if ((PIND & Y_Axis.bitMask) == Y_Axis.stateMachine[(Y_Axis.state + 1) % 4])  {    Y_Axis.state = (Y_Axis.state + 1) % 4 ;    Y_Axis.coordinate++ ;  }  else  {    Y_Axis.state = (Y_Axis.state + 3) % 4 ;    Y_Axis.coordinate-- ;  }}

Compilation report:

  • Binary sketch size: 5,650 bytes (of a 130,048 byte maximum)
  • Estimated memory use: 116 bytes (of a 8,192 byte maximum)

Share this post


Link to post
Share on other sites

Although not technically case moding (it might belong in the off-topic section) this is really cool,

 

there is definitely room for the teensy inside the case (even if you have to desolder the header pins)... so I would remove the original cable from the header and put in a safe place, then put the teensy inside (hot glue to not damage case) and run a light grey/beige usb cable out.

 

anyway I look forward to seeing more  :D

Share this post


Link to post
Share on other sites

Although not technically case moding (it might belong in the off-topic section) this is really cool,

 

Thanks a lot for your interest in my project. I haven't found any other project log thread, please let me know if this log is not on the right forum place.

 

there is definitely room for the teensy inside the case (even if you have to desolder the header pins)... so I would remove the original cable from the header and put in a safe place, then put the teensy inside (hot glue to not damage case) and run a light grey/beige usb cable out.

 

anyway I look forward to seeing more   :D

 

 

I guess I'll have to get a smaller Teensy. Something like the Teensy 2.0, which is almost half the size of the Teensy++, will have enough I/O and ROM/RAM for this project.

 

features.gif

 

The case of this devise uses very thick plastic and the button actuators assembly eat up quite a lot of space.

 

AgDADVal.jpg

 

KPyhFEbl.jpg

 

enObXUll.jpg

 

I see two spots where a Teensy 2.0 (not the ++ version) coul sit:

  • Where the cable header is.
  • At the rear of the case, behind the ball.

The second option is the best in my option, as it would keep the trackball as untouched as possible.

Share this post


Link to post
Share on other sites

I'm gonna have to throw my vote at this is EXACTLY what the heart of Modding is - Taking something that doesn't quite match your situation and changing it to fit what you want.

Case modding isn't all aesthetics - It was birthed in functionality mods and hacks. I also like to think that the Mod Zoo celebrates modding of all kinds, not just computer cases.

In either case, I love what you're doing here and you've earned yourself the first fanpage feature of the day!

https://www.facebook.com/TheModZoo/posts/706986769417798

Share this post


Link to post
Share on other sites

I'm gonna have to throw my vote at this is EXACTLY what the heart of Modding is - Taking something that doesn't quite match your situation and changing it to fit what you want.

Case modding isn't all aesthetics - It was birthed in functionality mods and hacks. I also like to think that the Mod Zoo celebrates modding of all kinds, not just computer cases.

In either case, I love what you're doing here and you've earned yourself the first fanpage feature of the day!

https://www.facebook.com/TheModZoo/posts/706986769417798

 

That's an honour, Sir ! Thanks a lot for the interest in this project.

 

Nice post GA!!!

 

Thank you Bill. I'll post the code for this once I'm done with the tweakings. It's very stable at the moment, but there is have something I want to try.

Share this post


Link to post
Share on other sites

Thanks a lot for your interest in my project. I haven't found any other project log thread, please let me know if this log is not on the right forum place.

 sorry for the confusion, you may have misunderstood my intent, I was meaning that as it is pc modding in general, not pc a pc case mod, I felt it may fall into a grey area in that regard and some forums are more strict than others on what is allowed in the worklogs section, meaning that you might want to double check

 

I see two spots where a Teensy 2.0 (not the ++ version) coul sit:

  • Where the cable header is.
  • At the rear of the case, behind the ball.

The second option is the best in my option, as it would keep the trackball as untouched as possible.

 

I would have to agree with you on the second option as it looks like it would make a neater run for the usb cable going out of the case

 

and this is looking even better, cant wait to see it finished!

Share this post


Link to post
Share on other sites

 sorry for the confusion, you may have misunderstood my intent, I was meaning that as it is pc modding in general, not pc a pc case mod, I felt it may fall into a grey area in that regard and some forums are more strict than others on what is allowed in the worklogs section, meaning that you might want to double check

 

That makes sense.

 

I would have to agree with you on the second option as it looks like it would make a neater run for the usb cable going out of the case

 

and this is looking even better, cant wait to see it finished!

 

 

Thanks a lot !

 

I posted my current code on this thread first post. The actual version could be better and I'll take my time to tweak it to death. I'll keep it up to date with the latest revision.

Share this post


Link to post
Share on other sites

Small progress on this project:

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).

zAktOITl.jpg

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

zHP1pnel.jpg

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

WehYPSgl.jpg

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

PcboItPl.jpg?1

yXwJSLsl.jpg

I spent most of my time working on the code. I greatly improved the way I handle the quadrature data. I'll write a proper post about it to explain everything in details.

Last thing to do it to solder the wires to the PRO micro and fit it inside the case. I'm working on a small holder that will be 3D printed ...

Disclaimer: No PCB has been harmed during the process. I just reused the "plastic jumper" fitting holes  :D .

Share this post


Link to post
Share on other sites

wow!, that's looking absolutely awesome  :D

 

Thanks a lot. I'll solder the wires next week, as I'll really need an helping hand. I'm considering lacing the whole cable, that will look better and I'll get a very tidy result.

Share this post


Link to post
Share on other sites
Last update:

 

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

 

ftG95val.jpg

 

ZipDFOLl.jpg

 

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

 

DWk1mKSl.jpg

 

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:

 



/* =================================================================================
   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];
}


Share this post


Link to post
Share on other sites

Last modification:

  • Added sowftware switch debouncing
  • Code cleanup
#include <Mouse.h>/* ================================================================================   Author  : GuilleAcoustic   Date    : 2015-05-22   Revision: V1.1   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: PD3 - (INT0)     - Green  : X axis encoder / channel B   |   Pin: PD2 - (INT1)     - Blue   : Y axis encoder / channel A   |   Pin: PD0 - (INT2)     - Violet : Y axis encoder / channel B   |   Pin: PD1 - (INT3)     - Grey   : Switch 1                     |   Pin: PB3     - White  : Switch 2                     |   Pin: PB2     - Black  : Switch 3                     |   Pin: PB1   --------------------------------------------------------------------------------   Latest additions:     - 2016-01-28: Software switch debouncing   ================================================================================ */// =================================================================================// Type definition// =================================================================================#ifndef DEBOUNCE_THREASHOLD#define DEBOUNCE_THREASHOLD 50#endif// =================================================================================// Type definition// =================================================================================typedef struct ENCODER_{  int8_t  coordinate;  uint8_t index;} ENCODER_;typedef struct BUTTON_{  boolean state;  boolean needUpdate;  char    button;  byte    bitmask;  long    lastDebounceTime;} BUTTON_;// =================================================================================// Constant definition// =================================================================================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 = {0, 0};volatile ENCODER_ yAxis = {0, 0};// =================================================================================// Global variables// =================================================================================BUTTON_ leftButton   = {false, false, MOUSE_LEFT,   0b1000, 0};BUTTON_ middleButton = {false, false, MOUSE_MIDDLE, 0b0010, 0};BUTTON_ rightButton  = {false, false, MOUSE_RIGHT,  0b0100, 0};// =================================================================================// Setup function// =================================================================================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();}// =================================================================================// Main program loop// =================================================================================void loop(){  // Update mouse coordinates  if (xAxis.coordinate != 0 || yAxis.coordinate != 0)  {    Mouse.move(xAxis.coordinate, yAxis.coordinate);    xAxis.coordinate = 0;    yAxis.coordinate = 0;  }  // ---------------------------------  // Left mouse button state update  // ---------------------------------  ReadButton(leftButton);  UpdateButton(leftButton);  // ---------------------------------  // Right mouse button state update  // ---------------------------------    ReadButton(rightButton);  UpdateButton(rightButton);    // ---------------------------------  // Middle mouse button state update  // ---------------------------------  ReadButton(middleButton);  UpdateButton(middleButton);  // 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 & 0b00000011) >> 0);  xAxis.coordinate += lookupTable[xAxis.index & 0b00001111];}void ISR_HANDLER_Y(){  // Build the LUT index from previous and new data  yAxis.index       = (yAxis.index << 2) | ((PIND & 0b00001100) >> 2);  yAxis.coordinate += lookupTable[yAxis.index & 0b00001111];}// =================================================================================// Functions// =================================================================================void ReadButton(BUTTON_& button){  // Variables  long    currentime;  boolean switchState;  boolean debounced;    // Get current time  currentime = millis();  debounced  = (currentime - button.lastDebounceTime > DEBOUNCE_THREASHOLD);  // Get current switch state  switchState = !(PINB & button.bitmask);  // Button state acquisition  if ((switchState != button.state) && debounced)  {    button.lastDebounceTime = currentime;    button.state            = switchState;    button.needUpdate       = true;  }}void UpdateButton(BUTTON_& button){  if (button.needUpdate)  {    (button.state) ? Mouse.press(button.button) : Mouse.release(button.button);    button.needUpdate = false;  }}

Share this post


Link to post
Share on other sites
Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

    Chatbox
    There's a thing here. Somehow it moved to the top of the page, but I didn't do it. No, really! But still, Cheaps is likely to blame...
    You don't have permission to chat.
×
×
  • Create New...