Replace quadrature-output trackball with analog joystick using Arduino?

I'm going to apologize ahead of time because I'm in WAY over my head! I've never programmed, well, anything, but I know there's a solution to a simple problem that I have:

I am a home machinist with an older VMC, and the trackball recently died on me. The optical encoder wheels had pretty much disintegrated, and there aren't replacements. A new trackball is $500, which is just ridiculous.

My idea was to scrap the trackball altogether and replace it with a cheap, simple 4-way 5V joystick, as the trackball is only used to move a cursor around the drawing screen to select different views, so lacking simultaneous axis moves isn't the end of the world, and a microswitch joystick is dead-nuts reliable and won't fill with gunk like the trackball did.

This hit a dead end when I pulled out the multimeter and learned that the trackball doesn't output simple voltage signals. Then I learned that quadrature exists... There are a million solutions for converting quadrature to analog voltage online, but zero in the other direction. That being said, you can (for an exorbitant amount of money) buy a joystick module that is plug-and-play, so I know it's possible with the right chip and programming.

I've read about a dozen threads on this forum showing how quadrature signals can be simulated, but, again, I'm an idiot when it comes to coding, so I'm not sure any would work for me or if I'd be able to adapt any of that code to my exact needs. I haven't even bought an Arduino yet, as I'm not sure which specific one I would need.

If anybody could help with a path forward, that would be great. Specifically, I'm in need of hardware suggestion and the code to make it work to achieve the following goals:

1.) Read a 5V signal from either a simple switch (one for each of X-, X+, Y-, Y+) or two 0-5V analog outputs from a two-pot joystick and translate that into 2-axis quadrature output at a frequency I can specify (as I imagine this will take some guess and check with cursor speed). I don't care if the cursor moves in both axes at once or one at a time, and I don't care if the cursor moves at a fixed speed or variable speed as long as I can change the fixed speed if it is too fast or slow. Whatever's easiest and feasible.

2.) Whole assembly, including joystick base, fits in a 2.5-inch (63 mm) cube, although it could possibly be longer in one direction and tilted into the cabinet.

3.) Powered by the 5V pin from the 15-pin trackball cable.

What I'm envisioning is a setup where the four output terminals of a 4-way microswitch joystick are wired to four analog inputs on a Nano. When +5V is seen at one of these four pins, the corresponding quadrature square waves are seen at two of four digital output pins?

Anyway, that's the problem, and I appreciate any help I can get.

generating a fixed timing quadrature signal based on the position of a potentiometer is not very complicated

here is an example reading a 0-5V signal from A0 and converting into a positive, idle, or negative equivalent encoder signal

Off-topic but, how did you add text annotations to the simulator?

I edited manually the diagram.json tab and added parts of type wokwi-text

{
  "type": "wokwi-text",  "id": "text", "top": -100,  "left": -100,
  "attrs": { "text": "this is your text" }
}

just replace "this is your text" by what you want. use the mouse to move it where you want, it will update the top and left attributes. if you want more than one just copy paste in the GUI and then edit the text in the json or add more parts of this type in the json tab, changing the IDs so that you don't have two parts with the same ID

some parts also support a "label" attribute, but not all of them

Thanks!

I already had some Joystick sketches so it was fairly easy to adopt one to output quadrature.

Note: This uses 'delay()' for timing so the speed of motion will be slower if both X and Y are moving. If that is a problem the sketch can be expanded to use millis() for timing.

const byte XPotPin = A0;  // Left-Right control
const byte YPotPin = A1;  // Up-Down control
const byte QuadratureOutputXAPin = 4;
const byte QuadratureOutputXBPin = 5;
const byte QuadratureOutputYAPin = 6;
const byte QuadratureOutputYBPin = 7;

const int XCentered = 512;
const int YCentered = 512;
const int DEAD_ZONE = 10;

const int MinDelay = 1; // 1000 steps per second with joystick at the limits.
const int MaxDelay = 500; // 2 steps per second with the joystick near the center.

byte QuadratureStateX = 0;
byte QuadratureStateY = 0;

const boolean QuadratureStatesA[4] = {0, 0, 1, 1};
const boolean QuadratureStatesB[4] = {0, 1, 1, 0};

void XPlus()
{
  QuadratureStateX = (QuadratureStateX + 1) % 4;
  digitalWrite(QuadratureOutputXAPin, QuadratureStatesA[QuadratureStateX]);
  digitalWrite(QuadratureOutputXBPin, QuadratureStatesB[QuadratureStateX]);
}

void XMinus()
{
  QuadratureStateX = (QuadratureStateX - 1) % 4;
  digitalWrite(QuadratureOutputXAPin, QuadratureStatesA[QuadratureStateX]);
  digitalWrite(QuadratureOutputXBPin, QuadratureStatesB[QuadratureStateX]);
}

void YPlus()
{
  QuadratureStateY = (QuadratureStateY + 1) % 4;
  digitalWrite(QuadratureOutputYAPin, QuadratureStatesA[QuadratureStateY]);
  digitalWrite(QuadratureOutputYBPin, QuadratureStatesB[QuadratureStateY]);
}

void YMinus()
{
  QuadratureStateY = (QuadratureStateY - 1) % 4;
  digitalWrite(QuadratureOutputYAPin, QuadratureStatesA[QuadratureStateY]);
  digitalWrite(QuadratureOutputYBPin, QuadratureStatesB[QuadratureStateY]);
}

void setup()
{
  pinMode(QuadratureOutputXAPin , OUTPUT);
  pinMode(QuadratureOutputXBPin , OUTPUT);
  pinMode(QuadratureOutputYAPin , OUTPUT);
  pinMode(QuadratureOutputYBPin , OUTPUT);
}

void loop()
{
  int xInput = analogRead(XPotPin); // Left/Right
  int yInput = analogRead(YPotPin); // Up/Down

  // Check for X left of the dead zone
  if (xInput < XCentered - DEAD_ZONE)
  {
    XMinus();
    delay(map(xInput, 0, XCentered, MinDelay, MaxDelay));
  }

  // Check for X right of the dead zone
  if (xInput > XCentered + DEAD_ZONE)
  {
    XPlus();
    delay(map(xInput, XCentered, 1024, MaxDelay, MinDelay));
  }

  // Check for Y above the dead zone
  if (yInput > YCentered + DEAD_ZONE)
  {
    YPlus();
    delay(map(yInput, YCentered, 1024, MaxDelay, MinDelay));
  }
  
  // Check for Y below the dead zone
  if (yInput < YCentered - DEAD_ZONE)
  {
    YMinus();
    delay(map(yInput, 0, YCentered, MinDelay, MaxDelay));
  }
}

I took @J-M-L's wokwi and added speed control and removed delay()s.

There's a few things to fix up - it's basically without detents, but it shouldn't confuse any rotary encoder software.

Play with it here in the wokwi simulator.

THX @J-M-L for the wiring and basic quadrature logic. Fun!

/* used as a point of departure by alto777 who reserves nothing */

// https://wokwi.com/projects/353391956123907073
// https://forum.arduino.cc/t/replace-quadrature-output-trackball-with-analog-joystick-using-arduino/1075102

/* ============================================
  code is placed under the MIT license
  Copyright (c) 2023 J-M-L
  For the Arduino Forum : https://forum.arduino.cc/u/j-m-l

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  ===============================================
*/

const byte xAxisPin = A0;
const byte xCLKPin = 2;
const byte xDTPin = 3;

const int xCenter = 512;   // joystick is centered when reading 512
const int xDeadZone = 100; // joystick will be considered still with reading in -xDeadZone and +xDeadZone around the xCenter (so here reading in [312, 712] => no pulse)

// much faster values
//const unsigned long quadraturePeriod = 1;     // in ms
//const unsigned long tickPeriod = 100;         // in ms

// demo mode to see what's going on with the LEDs
const unsigned long quadraturePeriod = 200;   // in ms
// const unsigned long tickPeriod = 100;        // in ms

unsigned long xLastTick;

void setup() {
  Serial.begin(115200);
  Serial.println("rotary hack too\n");

  pinMode(xCLKPin, OUTPUT);
  pinMode(xDTPin, OUTPUT);
  digitalWrite(xCLKPin, HIGH);
  digitalWrite(xDTPin, HIGH);
}

void loop() {

  int potValue = analogRead(xAxisPin);

  byte direction = potValue < 512 ? -1 : 1;
  int distance = abs(512 - potValue);
  int tickPeriod = map(distance, 0, 512, 1000, 50);

  if (distance > xDeadZone) {

    if (millis() - xLastTick >= tickPeriod) {
      if (potValue > (xCenter + xDeadZone)) {
        quadFSM(1);
      }
      else if (potValue < (xCenter - xDeadZone)) {
        quadFSM(-1);
      }
      xLastTick += tickPeriod;
    }
  }
}

byte quadFSM(byte direction)
{
  static byte state;

  switch (state) {
  case 0 :
    digitalWrite(xDTPin, LOW);
    state += direction;

    break;

  case 1 :
    digitalWrite(xCLKPin, LOW);
    state += direction;

    break;
      
  case 2 :
    digitalWrite(xDTPin, HIGH);
    state += direction;

    break;

  case 3 :
    digitalWrite(xCLKPin, HIGH);
    state += direction;

    break;
  }

  state &= 0x3;
}

/*
  if (0) {
    Serial.print("go  ");
    Serial.print(direction);
    Serial.print("  at rate ");
    Serial.print(tickPeriod);
    Serial.print(" to cover ");
    Serial.println(distance);
  }
*/

a7

fun !

Oh man, THANK YOU all for the help! This is very similar to what I've seen in other threads, but the comments and simulator are very helpful for understanding what the code is actually doing.

I'm guessing all of this code is for the Uno? That's the model everyone would recommend for this project?

I'm going to look all of this over more closely when I get to work tonight, simulate it, and order parts. I'll check back in if I can't get it to work, and I'll definitely check back in with pictures of the finished product when it's done at the very least. Thanks again, everyone!

Aside from which I/O pins you decide to use, there is nothing about any of the code examples that wouldn't work on just about any Arduino board.

The non-blocking version of @J-M-L's demo in #7 abkve is very light on resources, it occurs to me that you could test its suitability and performance by writing a rotary encoder as a task that would run Arduino-concurrently on the same board.

That code has some redundant tests I overlooked, so can be simlpliifed a bit.

If anything, the synthetic quadrature signal is going to be better than a real encoder. So it may allow for some less than perfect decoder software to function.

a7

@J-M-L 's demo is for a single axis. One of the things I planned to test tonight is how I would go about adding a Y-axis to that as well. So I'm assuming this would double the amount of resources required?

@johnwasser 's code seems to do exactly what I need, so I was also planning on trying to simulate that tonight. It seems pretty lightweight as well.

Thanks again!

It's fine, but he points out a drawback to using delay().

As for dual axes, twice almost no resource use is still quite small. Any code could be generalized to handle an axis that was specified, or naively cut, pasted and edited.

Any of @johnwasser's functions like

void YMinus()
{
  QuadratureStateY = (QuadratureStateY - 1) % 4;
  digitalWrite(QuadratureOutputYAPin, QuadratureStatesA[QuadratureStateY]);
  digitalWrite(QuadratureOutputYBPin, QuadratureStatesB[QuadratureStateY]);
}

could be adjusted and take the place of the more literal quadFSM() function.

If you've got nothing else going on, the delay() version should work well, and it wouldn't be too hard to overcome any problems with moving both axes at once by tinkering with the delay amount when necessary.

a7

Sorry for the delay, but I got distracted by a machining project and only just got around to testing these programs this morning.

Because I don't know what I'm doing, I'm taking parallel paths of testing and tweaking @johnwasser's code and trying to tweak your code to add a second axis and swap the linear pot for an analog joystick in the simulator. Whichever works first wins!

I was able to use reverse engineer your Wokwi simulation (that you and @J-M-L SO much for that!) to figure out how that works and apply it to @johnwasser's code and make it run. Testing this code, though, it turns out weird results. When I move the joystick to Y+ or X-, it works exactly as it should, with the LEDs showing a quadrature pattern. When I move the joystick to Y- or X+, though, both LEDs come on at the same time then turn off in the order they should, staggered. So I'm not sure this would be interpreted properly by the machine's controller.

I had a lot more difficulty in attempting to add a second axis to your code and simulating. I was able to figure out that +5V, GND, and the LET GND wires are hidden, so that helped when it came to swapping a joystick for the linear pot. After that, though, I kept hitting dead ends. I was able to get the simulation to run without errors after a lot of trial and error, but the closest I got to getting the simulation to run properly was with the X-axis working as you left it but the Y-axis LEDs always lit no matter what.

I hope this will be good for a laugh, so here's the latest version of what I've tried:

*/

const byte xAxisPin = A0;
const byte yAxisPin = A1;
const byte xCLKPin = 2;
const byte xDTPin = 3;
const byte yCLKPin = 4;
const byte yDTPin = 5;

const int xCenter = 512;   // joystick is centered when reading 512
const int xDeadZone = 100;
const int yCenter = 512;
const int yDeadZone = 1000; // joystick will be considered still with reading in -xDeadZone and +xDeadZone around the xCenter (so here reading in [312, 712] => no pulse)

// much faster values
//const unsigned long quadraturePeriod = 1;     // in ms
//const unsigned long tickPeriod = 100;         // in ms

// demo mode to see what's going on with the LEDs
const unsigned long quadraturePeriod = 200;   // in ms
// const unsigned long tickPeriod = 100;        // in ms

unsigned long xLastTick;
unsigned long yLastTick;

void setup() {
  Serial.begin(115200);
  

  pinMode(xCLKPin, OUTPUT);
  pinMode(xDTPin, OUTPUT);
  pinMode(yCLKPin, OUTPUT);
  pinMode(yDTPin, OUTPUT);
  digitalWrite(xCLKPin, HIGH);
  digitalWrite(xDTPin, HIGH);
  digitalWrite(yCLKPin, HIGH);
  digitalWrite(yDTPin, HIGH);
}

void loop() {

  int potValue = analogRead(xAxisPin);
  int ypotValue = analogRead(yAxisPin);

  byte direction = potValue < 512 ? -1 : 1;
  int distance = abs(512 - potValue);
  int tickPeriod = map(distance, 0, 512, 1000, 50);

  if (distance > xDeadZone) {

    if (millis() - xLastTick >= tickPeriod) {
      if (potValue > (xCenter + xDeadZone)) {
        quadFSM(1);
      }
      else if (potValue < (xCenter - xDeadZone)) {
        quadFSM(-1);
      }
      xLastTick += tickPeriod;
    }
  }

  if (distance > yDeadZone) {

    if (millis() - yLastTick >= tickPeriod) {
      if (ypotValue > (yCenter + yDeadZone)) {
        quadFSM(1);
      }
      else if (ypotValue < (yCenter - yDeadZone)) {
        quadFSM(-1);
      }
      yLastTick += tickPeriod;
    }
  }
}

byte quadFSM(byte direction)
{
  static byte state;

  switch (state) {
  case 0 :
    digitalWrite(xDTPin, LOW);
    state += direction;

    break;

  case 1 :
    digitalWrite(xCLKPin, LOW);
    state += direction;

    break;
      
  case 2 :
    digitalWrite(xDTPin, HIGH);
    state += direction;

    break;

  case 3 :
    digitalWrite(xCLKPin, HIGH);
    state += direction;

    break;
 
  case 4 :
  digitalWrite(yDTPin, LOW);
  state += direction;

  break;

  case 5 :
    digitalWrite(yCLKPin, LOW);
    state += direction;

    break;
      
  case 6 :
    digitalWrite(yDTPin, HIGH);
    state += direction;

    break;

  case 7 :
    digitalWrite(yCLKPin, HIGH);
    state += direction;

    break;
  }

  state &= 0x3;
}

/*
  if (0) {
    Serial.print("go  ");
    Serial.print(direction);
    Serial.print("  at rate ");
    Serial.print(tickPeriod);
    Serial.print(" to cover ");
    Serial.println(distance);
  }
*/

I also tried a version in which I duplicated basically all of the code, adding a y before direction, distance, tickPeriod, and quadFSM, so...

byte ydirection = potValue < 512 ? -1 : 1;
  int ydistance = abs(512 - potValue);
  int ytickPeriod = map(distance, 0, 512, 1000, 50);

...but this exhibited pretty much the same behavior.

If you have any tips for making this work, I'd really appreciate it. I got a notification that the Uno is out for delivery today, but I'm not in any huge rush.

Thanks again!

OK, good. You could always post a link to your wokwi latest sim right here for us to see your code in action.

But right away I can see you need a course correction. You made a valiant and plausible effort to get a second axis going.

The easiest way to do it right is to cut, paste and edit some code. I am generally not in favor of that, but let us get this thing working, and working might be good enough.

Basically, you need to duplicate the entire function quadFSM(), as it is designed for, and can only handle one axis.

I suggest you start by backing up a bit and get the original quadFSM() working well with one axis. Say X.

Rename that quadFSM_X(), or whatver. Test that you didn't brake anything.

Now copy the entire function, and name that copy quadFSM_Y().

Armed with a function just for Y axis, see if you can just call it the same way the one for the X axis is called. Obvsly using the pot value from the other axis and so forth.

I've said this will be mildly offensive to some ppl. Greater programmers than I have done it, so. Jkust look forward to being able to play neat tricks with language features you may have yet to learn (or embrace) that would not require naive and crude solutions.

If you don't get it gping right away along those lines, put it in the wokwi and post the link and code here.

HTH

Added: the copy/pasted function for Y will need some what I hope are entirely obvious edits to its internals.

a7

I got it working!

Thanks again for all the help. Hopefully, I'll have a working joystick soon, and I'll post a little video or something.

IT WORKS!

I tested everything last night and it worked on the first try. The only issues are that the X-axis is backward, which is a two-second fix swapping pins, and the cursor moves reeeeeeeeally slowly--also an easy fix.

Looks like this forum doesn't have an image posting option, so I won't post the picture of my janky setup. I had issues with the fragile pins breaking while trying to make the wires bend as tightly as they'll need to, so I've ordered a screw terminal shield and the appropriate standoffs. Then I'll just need to machine a mount/cover for the joystick and Arduino, and this project will be living in the control cabinet full-time.

Thanks again to everyone for the help.

It actually does
You can just drop the image in the post area, or copy and paste it, or use the upload (up pointing arrow’) tool from the tool bar

Would be good to post the actual final code that is working for you

Oh, that's a fantastic point. In case anybody else is looking to do something similar to this, here is the code that is working for me to simulate a dual-encoder industrial trackball with a 2-axis analog joystick. Much thanks to @J-M-L and @alto777 for the heavy lifting on this one (and I appreciate that you made me struggle through adapting it to two-axis myself instead of spoonfeeding--I learned a ton!):

/* Thanks to alto777 and J-M-L for this code */
// https://wokwi.com/projects/353851175863937025
// https://forum.arduino.cc/t/replace-quadrature-output-trackball-with-analog-joystick-using-arduino/1075102

/* ============================================
  code is placed under the MIT license
  Copyright (c) 2023 GeddyT
  For the Arduino Forum : https://forum.arduino.cc/u/geddyt

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  ===============================================
*/

const byte xAxisPin = A0;
const byte yAxisPin = A1;
const byte xCLKPin = 2;
const byte xDTPin = 3;
const byte yCLKPin = 4;
const byte yDTPin = 5;

const int xCenter = 512;   // joystick is centered when reading 512
const int xDeadZone = 100;  // joystick will be considered still with reading in -xDeadZone and +xDeadZone around the xCenter (so here reading in [312, 712] => no pulse)
const int yCenter = 512;
const int yDeadZone = 100; 

// much faster values
//const unsigned long quadraturePeriod = 1;     // in ms
//const unsigned long tickPeriod = 100;         // in ms

// demo mode to see what's going on with the LEDs
const unsigned long quadraturePeriod = 200;   // in ms
// const unsigned long tickPeriod = 100;        // in ms

unsigned long xLastTick;
unsigned long yLastTick;

void setup() {
  Serial.begin(115200);
  pinMode(xCLKPin, OUTPUT);
  pinMode(xDTPin, OUTPUT);
  pinMode(yCLKPin, OUTPUT);
  pinMode(yDTPin, OUTPUT);
  digitalWrite(xCLKPin, HIGH);
  digitalWrite(xDTPin, HIGH);
  digitalWrite(yCLKPin, HIGH);
  digitalWrite(yDTPin, HIGH);
}

void loop() {

  int xpotValue = analogRead(xAxisPin);

  byte xdirection = xpotValue < 512 ? -1 : 1;
  int xdistance = abs(512 - xpotValue);
  int xtickPeriod = map(xdistance, 0, 512, 1000, 50);

  if (xdistance > xDeadZone) {

    if (millis() - xLastTick >= xtickPeriod) {
      if (xpotValue > (xCenter + xDeadZone)) {
        xquadFSM(1);
      }
      else if (xpotValue < (xCenter - xDeadZone)) {
        xquadFSM(-1);
      }
      xLastTick += xtickPeriod;
    }
  }

  int ypotValue = analogRead(yAxisPin);

  byte ydirection = ypotValue < 512 ? -1 : 1;
  int ydistance = abs(512 - ypotValue);
  int ytickPeriod = map(ydistance, 0, 512, 1000, 50);

  if (ydistance > yDeadZone) {

    if (millis() - yLastTick >= ytickPeriod) {
      if (ypotValue > (yCenter + yDeadZone)) {
        yquadFSM(1);
      }
      else if (ypotValue < (yCenter - yDeadZone)) {
        yquadFSM(-1);
      }
      yLastTick += ytickPeriod;
    }
  }
}

byte xquadFSM(byte xdirection)
{
  static byte state;

  switch (state) {
  case 0 :
    digitalWrite(xDTPin, LOW);
    state += xdirection;

    break;

  case 1 :
    digitalWrite(xCLKPin, LOW);
    state += xdirection;

    break;
      
  case 2 :
    digitalWrite(xDTPin, HIGH);
    state += xdirection;

    break;

  case 3 :
    digitalWrite(xCLKPin, HIGH);
    state += xdirection;

    break;
  }

  state &= 0x3;
}

byte yquadFSM(byte ydirection)
{
  static byte state;

  switch (state) {
  case 0 :
    digitalWrite(yDTPin, LOW);
    state += ydirection;

    break;

  case 1 :
    digitalWrite(yCLKPin, LOW);
    state += ydirection;

    break;
      
  case 2 :
    digitalWrite(yDTPin, HIGH);
    state += ydirection;

    break;

  case 3 :
    digitalWrite(yCLKPin, HIGH);
    state += ydirection;

    break;
  }

  state &= 0x3;
}

/*
  if (0) {
    Serial.print("go  ");
    Serial.print(direction);
    Serial.print("  at rate ");
    Serial.print(tickPeriod);
    Serial.print(" to cover ");
    Serial.println(distance);
  }
*/

The cursor needs sped up (a lot...), so I'm going to change the "const unsigned long quadraturePeriod" value from 200 to first 10 and then 1 if that's still not fast enough (I see the latter is commented out in the code for just this reason). Interestingly, the simulator looks exactly the same no matter what that value is, but I'm guessing that's not how the actual cursor on the machine will behave.

Just a mess of wires, an Uno, a $13 joystick, and a really dirty VMC console, but here's the test setup from a couple of nights ago:

There will be one more picture left in this thread, and that will be a week or so from now, when I have a mounting plate machined up and everything all nice and tidy in the cabinet.

If anyone is interested, the specific machine this is going in is a 2006 Hurco VMX30. The trackball unit was, I believe, a BEI Sensors TBS225. The trackball has a 15-pin gameport connector. Pins 1-4 are the quadrature signals (Xa, Xb, Ya, Yb) and are wired to the Uno's 3-6 digital output pins. Trackball connector pin 15 is +5V and is wired to Uno's Vin. Trackball connector pin 13 is 5V return and is wired to Uno's GND pin.

This will effectively replace the trackballs that are probably failing in every single old Hurco that has an Ultimax control and be a much more robust solution. It's possible that different control generations (Ultimax I, II, III, IV) have different pinouts, but I'm not sure.

1 Like

Did Chuck Norris help you with that? :wink:

That code looks like it only handles one axis…

a7

A 5V supply should be connected to the +5V pin. The Vin pin is for unregulated 7V-12V.