Jump to content
cheesestraws

Joystick mouse replacement for Lisa/128/512/Plus

Recommended Posts

I'm about to send my STL file to a friend so he can start the 3d printing for me. 

 

Check it out: assembly with 4 screws. I'm hoping the holes are big enough for the button and stick.

 

in-browser model viewer:

https://a360.co/2X7DvxZ

 

Side analysis:

 

Share this post


Link to post
Share on other sites

First iteration's problems:

 

-stick is a smidge too high and will cause friction with the opening's edges

-the cylinder riser pins broke very early and just can't be used, and the risers themselves broke off after minimal lateral force

-the cable hole was too small

-I think the back and front cable holes could just be improved if the middle separation goes to their centers

-the arduino's placement is just bad in the dead center, it creates too many problems during assembly with the cables I have.

-I need to find a way to create one 4-way node and one 5-way node. I don't have those simple through-hole breadboards, so....solder wires "in the air"?

 

Link towards revised version:

 

-fat cylinders for screws 

-"cradle" + glue approach instead of risers to hold the pcbs

-arduino on the side instead of center

 

https://a360.co/2X7DvxZ

 

image.thumb.png.0444f4ba244f3825de30a118aaeb9f38.png

 

image.thumb.png.17a427c202109db4818f7c8324b55cea.png

Share this post


Link to post
Share on other sites

Moving on to the next gamepad that would use the 4C4P RJ11 cable, replacing a keyboard. I've quoted the appropriate section of Inside Macintosh but I'm at a loss of how to make it work in Arduino.

 

I admit I don't know that much about the arduino architecture.

 

-I imagine I need to use digital pins and not analog pins, since I only need to create 5V pulses to create the bits.

-at 9600 bps, we get 104 microseconds at the smallest unit of change, but we need control over creating data pulses that happen 40 microseconds before the clock pulse ticks, so I must use a faster speed?

-so far, all the arduino code I've seen configures GPIO pins as either WRITE or READ. But Inside Macintosh clearly says the data wire needs to be able to send and receive both according to its unique specific protocol. How do I set this up in Arduino?

 

I found this website that sheds some light on setting up communications with another board (outside of the arduino ecosystem), but it looks pretty similar:

 

http://www.synack.net/~bbraun/mackbd/index.html 

 

Share this post


Link to post
Share on other sites
14 hours ago, Mu0n said:

I imagine I need to use digital pins and not analog pins, since I only need to create 5V pulses to create the bits.

Yup.

 

14 hours ago, Mu0n said:

at 9600 bps, we get 104 microseconds at the smallest unit of change, but we need control over creating data pulses that happen 40 microseconds before the clock pulse ticks, so I must use a faster speed? 

No idea.  I'd be inclined to suggest thinking of this as a timing problem rather than as a "bits per second" problem, though.  Timers are probably your friend here.

 

14 hours ago, Mu0n said:

so far, all the arduino code I've seen configures GPIO pins as either WRITE or READ. But Inside Macintosh clearly says the data wire needs to be able to send and receive both according to its unique specific protocol

You can switch a pin between read and write as you need, it's not a one-time initialisation.  If you look at bbraun's code (which you linked), the data line is switched between IN and OUT (main.c line 201, for example) depending on whether the board is reading from it or writing to it.

Share this post


Link to post
Share on other sites

 I also revisited your mouse code yesterday.

 

What I'm experiencing:

 

-cursor movement is somewhat a little slow during normal operation, but it's ok

-when in combat in Archon, mouse scrolling speed has no impact - the game only detects the direction of movement and the characters move to some pre-planned speed according to the game rules

-It's very hard to go into purely horizontal or vertical directions with a deadzone of 30. Most of the time, diagonal movement is done and it forces me to fight with these lines of approach and throws away pure horizontals and verticals. Kind of like in real life with a physical mouse, but even worse

 

So, I played around with the code a bit, and did:

 

-push up the minimal movement threshold to 40 instead of 30

-first check that a direction is dominating (dominating threshold of 200) WHILE the other IS NOT dominating to only push for a movement in that solo dominating direction; then check the other with an else if

-when no direction is dominating, do what was done initially, which is allow both directions to act at the same time

 

What I get is noticeably better in Archon combat (I'm able to hit those x-only and y-only shots more often), but noticeably worse during normal cursor operation: I get feeble movement whenever both directions are involved. I get slightly faster movement when only one direction is involved. The strangest thing is that I can still get a small movement in the non-dominating direction.

 

// The quadrature phases to output
const bool waveform1[] = {HIGH, LOW, LOW, HIGH};
const bool waveform2[] = {HIGH, HIGH, LOW, LOW};
const int doNothingTolerance = 40; //no movement if you don't reach this miminal threshold
const int pureDirectionTolerance = 200;  //if a direction has that, but the other doesn't, lock it in the direction that has it

int xGPIOs[] = {6, 5};
int yGPIOs[] = {4, 3};
const int xAnalogue = 3;
const int yAnalogue = 2;

const int clickOutGPIO = 10;
const int clickInGPIO = 11;

// Two digital outputs set permanently to high and low, respectively, to reference
// the variable resistors with 
const int hiRefGPIO = 13;
const int loRefGPIO = 12;

// the timestamps (in milliseconds) when the axis last was updated
unsigned long xTimestamp = 0;
unsigned long yTimestamp = 0;

// the indexes into the waveforms
int xIdx = 0; 
int yIdx = 0;

int xIsStill() {
  return analogueValueIsStill(analogRead(xAnalogue));
}

int yIsStill() {
  return analogueValueIsStill(analogRead(yAnalogue));
}

long xDelay() {
  return analogueValueToDelay(analogRead(xAnalogue));
}

long yDelay() {
  return analogueValueToDelay(analogRead(yAnalogue));
}

int xDirection() {
  return analogueValueToDirection(analogRead(xAnalogue));
}

int yDirection() {
  return analogueValueToDirection(analogRead(yAnalogue));
}

int xShouldAdvance() {
  unsigned long now = millis();
  // if the joystick is in the "still" region, don't update
  if (xIsStill()) {
    return 0;
  }
  
  if ((xTimestamp + xDelay()) < now) {
    xTimestamp = now;
    return 1;
  } else {
    return 0;
  }
}

int yShouldAdvance() {
  unsigned long now = millis();
  // if the joystick is in the "still" region, don't update
  if (yIsStill()) {
    return 0;
  }
  
  if ((yTimestamp + yDelay()) < now) {
    yTimestamp = now;
    return 1;
  } else {
    return 0;
  }
}

int xIsWellEstablished() {
     unsigned long now = millis();
  // if the joystick is in the "dominating" region
  if (analogueValueIsDominating(analogRead(xAnalogue))) {
    return 1;
  }
}


int yIsWellEstablished() {
     unsigned long now = millis();
  // if the joystick is in the "dominating" region
  if (analogueValueIsDominating(analogRead(yAnalogue))) {
    return 1;
  }
}

void dealWithButton() {
  int btn = digitalRead(clickInGPIO);
  digitalWrite(clickOutGPIO, btn);
}

int analogueValueIsStill(int value) {

  if (value > (512 + doNothingTolerance)) {
    return 0;
  }
  if (value < (512 - doNothingTolerance)) {
    return 0;
  }
  
  return 1;
}

int analogueValueIsDominating(int value) {

  if (value > (512 + pureDirectionTolerance)) {
    return 1;
  }
  if (value < (512 - pureDirectionTolerance)) {
    return 1;
  }
  
  return 0;
}

int analogueValueToMagnitude(int value) {
  return abs(value - 512);
}

int analogueValueToDirection(int value) {
  if (value < 512) {
    return -1;
  }
  return 1;
}

long analogueValueToDelay(int value) {
  const long minDelay = 4;
  
  long mag = analogueValueToMagnitude(value);

  return ((512 - mag) / 32) + minDelay;
}

void writeQuadrature(int gpios[], int idx) {
  digitalWrite(gpios[0], waveform1[idx % 4]);
  digitalWrite(gpios[1], waveform2[idx % 4]);
}


void setup() {
  Serial.begin(9600);
  
  // set up quadrature outputs
  pinMode(xGPIOs[0], OUTPUT);
  pinMode(xGPIOs[1], OUTPUT);
  pinMode(yGPIOs[0], OUTPUT);
  pinMode(yGPIOs[1], OUTPUT);
  pinMode(clickInGPIO, INPUT_PULLUP);
  pinMode(clickOutGPIO, OUTPUT);

  pinMode(hiRefGPIO, OUTPUT);
  digitalWrite(hiRefGPIO, HIGH);
  pinMode(loRefGPIO, OUTPUT);
  digitalWrite(loRefGPIO, LOW);

  digitalWrite(xGPIOs[0], HIGH);
  digitalWrite(xGPIOs[1], HIGH);
  digitalWrite(yGPIOs[0], HIGH);
  digitalWrite(yGPIOs[1], HIGH);
  digitalWrite(clickOutGPIO, HIGH);

  Serial.print("initialised...\n");
}

void loop() {
  dealWithButton();
  
  //clear cut x movement only
  if (xShouldAdvance() && xIsWellEstablished() && !yIsWellEstablished()) {
    xIdx = (xIdx + xDirection()) % 4;
    if (xIdx < 0) { xIdx += 4; }
    writeQuadrature(xGPIOs, xIdx);
    return;
  }
  //clear cut y movement only
  else if (yShouldAdvance() && yIsWellEstablished() && !xIsWellEstablished()) {
    yIdx = (yIdx + yDirection()) % 4;
    if (yIdx < 0) { yIdx += 4; }
    writeQuadrature(yGPIOs, yIdx);
    return;
  }
  
  //weak x
  if(xShouldAdvance()) {
   xIdx = (xIdx + xDirection()) % 4;
    if (xIdx < 0) { xIdx += 4; }
    writeQuadrature(xGPIOs, xIdx);
  }
  //weak y
  if(yShouldAdvance()) {
   yIdx = (yIdx + yDirection()) % 4;
    if (yIdx < 0) { yIdx += 4; }
    writeQuadrature(yGPIOs, yIdx);
  }


  // DEBUG:
  //Serial.print("x: ");
  //Serial.print(analogRead(xAnalogue));
  //Serial.print("y: ");
  //Serial.print(analogRead(yAnalogue));
  //Serial.print("button: ");
  //Serial.print(digitalRead(clickInGPIO));
  //Serial.print("\n"); 
}

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×