[Complete] CH Products Trackball PRO - BUSMOUSE to USB

User avatar
GuilleAcoustic

09 Feb 2015, 15:00

Hello Deskthority,

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

Image

Image

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.

Image

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
  • +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.
Image

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
(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
(image courtesy of Dynapar)

Image

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.

Image

Image

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

Image

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.
Last edited by GuilleAcoustic on 09 Jul 2021, 12:30, edited 8 times in total.

User avatar
Muirium
µ

09 Feb 2015, 15:26

Nice first post! Welcome to land of Teensy mods.

User avatar
GuilleAcoustic

09 Feb 2015, 15:54

Muirium wrote: Nice first post! Welcome to land of Teensy mods.
Thanks a lot. It's been a very instructive project, with a few details still running. It was also my first time with both the Arduino and the Teensy. I'd love to find an affordable IBM 5150 keyboard as it would be a very nice match to the trackball design.

andrewjoy

09 Feb 2015, 16:19

That is so cool, how does scrolling work on it ? Does it have a scroll wheel of some sort ? I have a slim blade but need a second trackball for work, but i don't think i could live without the slim blades rotate to scroll.

Have you tried a slim blade ? How does this thing compare ?

User avatar
HzFaq

09 Feb 2015, 16:35

http://deskthority.net/for-sale-f55/ch- ... ml#p208869

How's that for a coincidence?

I was following this on bit tech, nice to see it's made it's way over here. I'm all for the PCI teensy card :D.

User avatar
GuilleAcoustic

09 Feb 2015, 16:44

andrewjoy wrote: That is so cool, how does scrolling work on it ? Does it have a scroll wheel of some sort ?
It does not have scroll capabilities yet. I'm using the window "elevators" or PgUP/PgDown at the moment. I'm thinking about using the "extra" pin on the PCB to use the top left button as a "toggle scroll" switch and use the ball to scroll.Or just mod a keyboard to have a scroll wheel on it, as I've never been a big fan of scroll wheels on mice.
andrewjoy wrote: Have you tried a slim blade ? How does this thing compare ?
I've been using a Logitech Cordless Optical Trackman for 2 years in the past and the CH's one is far superior despite being from the late 80's.

The ball is hand operated and has a diameter of 53mm (pool size in fact). It provides is great feel and is very accurate. The use of thick stainless shafts and sealed bearings is so much better than the poor three-tiny-ball suspension you often see on trackballs. Last but not least, the Omron switches are far superior from everything I've encountered on a pointing device so far.

It might lack features like scrool wheel/ring or laser tracking, but it's really a great trackball. Haven't tried the CST L-Trac, which could be a good opponent.
HzFaq wrote: http://deskthority.net/for-sale-f55/ch- ... ml#p208869

How's that for a coincidence?

I was following this on bit tech, nice to see it's made it's way over here. I'm all for the PCI teensy card :D.
Thanks mate, I've compiled my posts over BT to make a "nice" and better tought thread over here. About the trackball for sale, nice coincidence indeed :D.

I'm all for the PCI teensy too, just have to find enough time for this.

davkol

10 Feb 2015, 19:50

That's some nice piece of work there.
andrewjoy wrote: Have you tried a slim blade ? How does this thing compare ?
I've used both a modern DT225 and obviously the slimblade. The latter is *much* smoother, higher sensitivity (obviously) , somewhat more conveniently shaped, but feels cheap in comparison. Dealing with accumulated gunk is different as well, due to completely different bearings.

User avatar
GuilleAcoustic

11 Feb 2015, 09:43

davkol wrote: That's some nice piece of work there.
Thanks you for your interest in my humble project. I'll post the code here once the tweakings are finished.

User avatar
GuilleAcoustic

20 Feb 2015, 10:26

Below is a cleaned version of the code. This is not the final revision as I'm moving toward writing it in pure C and compile it with gcc-avr. I'll keep this updated.

Code: Select all

/* ============================================================================================
   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)
The code can be shrunk further, but is fully usable as is. Running flawlessly since a week now. Feel free to ask question if you need some explainations, but the code is pretty simple and straight forward.

Jonas

26 Apr 2015, 03:06

Hey GuilleAcoustic, that is awesome the movement is really smooth great job! I am new to USB interfacing and have been trying to use an old ps2 trackball and add my own board inside to interface it with USB. Your project seems perfect for me to understand the battle.

So far I have built the descriptor, It is recognized as hid mouse, but I am having difficulty processing the quadrature signals...

Have you managed to moving toward writing it in pure C as you wanted? If it is at all possible get take a look at it that would be awesome. It would help me a lot on my adaptation to my trackball.

I wasn't confident on being able to make it run smoothly but after watching your video I am really motivated. Great job! Thank you so much for sharing this.

Cheers!

User avatar
GuilleAcoustic

27 Apr 2015, 10:39

Jonas wrote: Hey GuilleAcoustic, that is awesome the movement is really smooth great job! I am new to USB interfacing and have been trying to use an old ps2 trackball and add my own board inside to interface it with USB. Your project seems perfect for me to understand the battle.
Thanks a lot for your interest in my humble project.
Jonas wrote: So far I have built the descriptor, It is recognized as hid mouse, but I am having difficulty processing the quadrature signals...
Having it detected as an HID mouse is one important step, nothing can work without that :D. I did proceed following those simple steps. It really helped me with keeping hope at a reasonably high level:
  • 1- Trackball detected as HID device (fairly easy when using the Teensy lib)
    2- Detecting buttons press/release (I logged actions to the serial output and debuged it with the Arduino IDE serial monitor)
    3- Buttons behaving as mouse button (at this step I used a mouse to move the cursor and used the trackball buttons to click)
    4- Detecting signal on the quadrature sensors (basically just output a message on falling/rising edge, serial monitor debug)
    5- Coding the quadrature decoding interrupt code (incrementing / decrementing a counter, serial monitor debug)
    6- Cursor moved using the quadrature code.
One important step is to initialise the current "position" returned by the quadrature sensors. It can only have 4 positions and only 1 channel can change at a time:
  • - Channel 1 / Channel 2
    - LOW / LOW
    - HIGH / LOW
    - HIGH / HIGH
    - LOW / HIGH
The idea is to attach an interrupt routine to each channel and when an interrupt occurs you compare the current "position" to the previous one. This will give you the way your encoding wheel turns.

This picture summarise it pretty well :

Image
Jonas wrote: Have you managed to moving toward writing it in pure C as you wanted? If it is at all possible get take a look at it that would be awesome. It would help me a lot on my adaptation to my trackball.
Sadly, I didn't work on the code lately. The teensy++ is too big to fit inside the trackball case and my local store doesn't stock teensy2.0 anymore. I just bought an Arduino Pro Micro and a 10 pins cable harness. I will continue with these ones.
I'll keep the code updated here, so keep an eye on it :)
Jonas wrote: I wasn't confident on being able to make it run smoothly but after watching your video I am really motivated. Great job! Thank you so much for sharing this.
The smoothness really surprised me too. The delay at the end of the loop as a huge effect on the smoothness though. Too small and the motion is really slow but smooth, too high and the motion is not smooth at all. Delay(10) looked like a good value from my experiment, but this might be affected by your serial connection speed and how quickly you process the data.

I did try several algorithms to process the quadrature encoders, some are easier to read or understand than others, but nothing that a drawing can't explain. I'll put them here too.

User avatar
GuilleAcoustic

06 May 2015, 00:01

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

Image

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

Image

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

Image

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

Image

Image

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 :lol:.

User avatar
GuilleAcoustic

16 May 2015, 02:52

Last update:

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

Image

Image

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

Image

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: Select all

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

User avatar
Redmaus
Gotta start somewhere

25 May 2015, 23:19

I am still looking for the black version of this trackball...

User avatar
HzFaq

26 May 2015, 10:06

Hmmm....I might drop you a message a bit later on about converting a CST trackball.

edit - Nice job by the way, how is it to use?

User avatar
GuilleAcoustic

26 May 2015, 11:17

Redmaus wrote: I am still looking for the black version of this trackball...
I'll use it with a Model F XT, so the light beige version is perfect.
HzFaq wrote: Hmmm....I might drop you a message a bit later on about converting a CST trackball.?
Please do, it'd be a pleasure if I can help you.
HzFaq wrote: edit - Nice job by the way, how is it to use?
It's great to use, less stressing than a mouse. The buttons have a nice tactile feel and clicky sound. The huge ball is a pleasure to navigate, and it has an inertia thanks to the steel shaft and bearings. Going back to a mouse is weird now.

Only drawback is the lack of scroll wheel, but I just use the keyboard keys (arrows, PgUp / PgDown) or just middle-click to activate the scroll mode in sofwares like Word, Chrome, etc.

User avatar
Redmaus
Gotta start somewhere

26 May 2015, 17:38

I thought you just scrolled with the ball when you pressed a certain button?

User avatar
GuilleAcoustic

26 May 2015, 18:10

Redmaus wrote: I thought you just scrolled with the ball when you pressed a certain button?
This is what the middle-click does with any mouse on several softwares, like Chrome for example.

I wanted to keep the trackball as vanilla as possible, so it acts as the original one but on USB.

I'm working on a DIY high DPI trackball, but I'm still not fixed on how to manage scrolls. I'll open a new thread as this is a new project :D

User avatar
Redmaus
Gotta start somewhere

26 May 2015, 18:37

Is the DPI on the CH trackball really that bad?

User avatar
GuilleAcoustic

26 May 2015, 19:02

Redmaus wrote: Is the DPI on the CH trackball really that bad?
No, it is nice. It has 400 CPR resolution and it works well, even on my dual 19" at work (2560x1024).

The idea behind the high DPI DIY trackball is not related to the CH product, just a project for fun. I'll try to create the thread tomorrow, it goes beyond standard devices.

User avatar
Redmaus
Gotta start somewhere

26 May 2015, 20:17

400 CPR? As in DPI? Sorry I don't know much

davkol

26 May 2015, 20:24

I guess it's "counts per rotation"…?

Anyway, I found the USB DT225 fine with a 2400×1600 desktop, but only if software acceleration was enabled and setup quite aggressively .

User avatar
GuilleAcoustic

26 May 2015, 20:32

Redmaus wrote: 400 CPR? As in DPI? Sorry I don't know much
CPR stands for Cycle Per Revolution. This is a term used for optical rotary encoders. It means that, for a full revolution of the encoding wheel, the signal generates 400 increment or decrement events.

Image

The ball to shaft ratio might be around 10, so a full revolution of the ball represent 4000 pixels. This is a rough guesstimation though.

User avatar
Redmaus
Gotta start somewhere

26 May 2015, 23:53


User avatar
GuilleAcoustic

27 May 2015, 08:19

So you where the one bying it. This is a great price :). I love mine.

I have the original manual at home. Drop me a Pm if you wand a scanned version of it, especially for the dip switches setup.

terrycherry

31 Aug 2015, 10:23

Awesome post for trackball with the strange port!
I hope to use the on board trackball like a mouse on my KeyTronic TRAK101 keyboard. Computer determine it for the arrows keys(↑↓←→) not like a mouse. It uses RS-232 port.
Do you have any idea?

User avatar
GuilleAcoustic

01 Sep 2015, 08:19

I don't know exactly. I've been searching informations on your keyboard but can't find much.

Are you using an adapter ?

terrycherry

01 Sep 2015, 20:32

Yes,I'm using the RS-232 to USB converter but not work like a mouse. Do I need do some settings on Win7?
I can post the PCB of trackball and keyboard if you need it.
Thank you=]

User avatar
Abstractions

08 Nov 2015, 06:06

If you guys are interested in more, here is op's original post: http://forums.bit-tech.net/showthread.php?t=278324

User avatar
GuilleAcoustic

30 Jan 2016, 22:50

Last modification:

[*]Added software switch debouncing
[*]Code cleanup

Code: Select all

#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;
  }
}

Post Reply

Return to “Mice & other input devices”