Friday, July 3, 2015

Alto mouse to IBM PC adapter

 The Alto Hacker


The Xerox Alto was one of the most historically significant computers ever made.  There's been plenty written about it on the net so there's no need to go over that ground.  This blog will be about bringing the Alto into the 21st century and giving it new life.  One of my hopes is to get the Alto connected to the Internet but for now we're going to warm up with something a lot easier.

Project 1: Alto mouse to IBM PC adapter

7/3/15  For our first hack, we're going to make a converter that allows the Alto mouse to be used with a PC (or any computer that uses a USB mouse).  I currently have an Alto mouse connected to an Amiga 2000 but I can't say that I use it very often.  I'd use it a lot more if I could connect it to my PC.  After all these years I still think the Alto mouse has the best ergonomics of any that I've ever useed.

Unfortunately the Alto, like the Amiga uses a quadrature protocol which is completely incompatible with the PC.  In addition, most PC's these days only offer USB ports for mice, and writing USB drivers is a complete pain.  What to do?

Well we start with an Arduino ProMicro microcontroller, available from: Sparkfun here.  This, it is claimed, has a built-in USB HID API and that should make life a lot easier for us.  This version also runs on 5 v. which makes it easier to interface with the mouse.  Basically, the Arduino is going to read the Alto mouse output, convert it into PC mouse protocol, and then send it out to the PC.  Another plus is that I've been wanting to learn about Arudino for a while now and this seemed like a good excuse.  Disclaimer: I have no association with Sparkfun other than as a satisfied customer.  There are no doubt other ways to get this same job done.

The first thing I discovered about the ProMicro is that it really is micro.  At first I thought Sparkfun had sent me an empty box.  The whole thing is barely an inch and a half long.  It is dwarfed by the mouse itself.  In fact, it might just be possible to build the whole thing inside the mouse, something I hadn't considered originally.

Alto mouse meets Arduino ProMicro

I'm following the installation directions in https://learn.sparkfun.com/tutorials/pro-micro--fio-v3-hookup-guide

They say to download the Arduino IDE (v. 1.6.5) from their web site.  I installed it on my PC (running Windows XP SP3).  Installation was simple enough and worked fine.

Next we need to download the drivers for the board here: https://cdn.sparkfun.com/assets/learn_tutorials/1/2/1/SH32U4_driver.zip

This needs to be done before plugging in the board.  That also worked fine - no reboot required.  Next we plug it in.  We get a bright red LED on the board and a pop-up from Windows asking for a driver.  We tell it to install the drivers from the location where they were saved.  It then tells me it installed an HID driver which is cool and that the device is ready to use.  Yay.  Note the other nice thing here - no external power is required.  The proMicro gets all its power directly from the USB cable.

Then we download the Arduino addons from https://cdn.sparkfun.com/assets/learn_tutorials/1/2/1/sparkfun_32u4_boards_02.zip .  I saved this file in C:\<top level>\Arduino\hardware\.sparkfun_32u4_boards_02.  (This diverges a bit from the directions on the Sparkfun web page but it ended up working anyway).  I copied their "blinky LED" sketch into the IDE and uploaded it.  Window then wanted to install more drivers.  I selected "automatic" for this and in short order it said the drivers were installed.  But the program seemed to be hung on "loading".  So I ran it again and this time it worked.  The RX/TX LED's are blinking once per second just like they're supposed to.  Yay!

So that's already progress.  The ProMicro is installed and talking to the PC.

7/5/15
The Amiga mouse
Now that we have the Arudino working, I want to take a look at the quadrature signals the mouse sends, just to have some idea of what I'm dealing with.  Of course I've seen pictures in books, but there's nothing like seeing it for yourself for real on your own scope.  But I'm not going to use the Alto mouse to do it.  I'm afraid I might accidentally hook up something wrong or drop it on the floor.  Instead I will use the Amiga mouse which is 100% functionally identical to the Alto mouse, just with a different connector (and very ugly).

Here's the pinout of the Amiga mouse (courtesy of http://www.allpinouts.org/index.php/Mouse/Joystick_Amiga_9_pin).

Pin Mouse/Trackball Lightpen Digital Joystick Paddle Dir Comment
1 V-pulse n/c /FORWARD BUTTON 3 IN
2 H-pulse n/c /BACK n/c IN
3 VQ-pulse n/c /LEFT BUTTON 1 IN
4 HQ-pulse n/c /RIGHT BUTTON 2 IN
5 BUTTON 3(M) Penpress n/c PotX IN/OUT
6 BUTTON 1(L) /Beamtrigger /BUTTON 1 n/c IN/OUT
7 +5V +5V +5V +5V OUT 50 mA max
8 GND GND GND GND
9 BUTTON 2(R) BUTTON 2 BUTTON 2 PotY IN/OUT

I already checked  the two buttons (pins 6 and 9) with an ohmmeter.  Now I want to look at pins 1 and 3, the vertical motion pulses.

Quadrature mouse, vertical move up
So here's what we see, thanks to Messrs. Hewlitt and Packard.  This is moving the mouse slowly up (ie. from the bottom of the screen to the top).  The upper trace is pin 1 (V-pulse) and the lower trace is pin 3 (VQ pulse).  You can easily see the offset between the two pulse trains.

Quadrature mouse, vertical move down
I marked off the distance between the rising edges of the two pulses and found it to be just 600 us. - easily within the capability of the Arduino to resolve.  You can read all the theory you want, but there's nothing like doing the experiment yourself and getting your own results.  That just never gets old.

The next shot is the same pins but this time moving the mouse vertically down (from the top of the screen to the bottom).  The pulse widths aren't exactly the same because I didn't move this mouse at exactly the same speed.  But you can clearly see how the lower trace leads the upper trace compared to the first figure where it lags the upper trace.

As one last experiment, I tried moving the mouse vertically as fast as I could to get some idea of the maximum frequency we'd be dealing with.

Vertical up, really fast
Note that the time base has gone from  2 ms. to 200 us.  And the delta t between the rising edges of the two pulse trains has gone from 600 us. to 136 us.  So it's definitely faster but it should still be well within the ability of the ProMicro running at 16 MHz.

It's also helps to know that the pulses are 5 v. high and they look nice and clean.  No debouncing should be needed here.  Oh yes, and the Amiga mouse draws less than 20 mA.  My bench supply only reads out in 0.01 A increments and it flips between 0.01 and 0.02 when powering the mouse

Now we can get on with it.



7/8/15

The ProMicro in place
OK, the next step is to get this board so I can actually work with it.  I can't just leave it dangling from the end of the USB cable.  Unfortunately those cheapskates at Sparkfun neglected to put any pins on this Arduino.  There's just two rows of holes there.  That may be good for something, but not for me.  At least they are standard 0.1" spacing.

I needed two 12 pin headers to be able to connect this thing to my breadboard.  But Ye Olde Junquebox only yielded two pairs of 8's and 6's, so I have two pins left over hanging out on both sides.  O h well, they'll probably come in handy for, uh, for, well they're there anyway.  So with the headers soldered on and the board seated securely on the breadboard, we can move on.  (And this isn't an ad for Radio Shack.  They're bankrupt anyway.  The most interesting thing about this breadboard is that it's stamped "Made in USA" - now there's a rarity).

Now all we need to do is finish soldering the rest of the wires to the DB-9 for the mouse and we can finally get started on the programming.

7/11/15

We finally have some time to work on this project again.  Today I got the rest of the pins soldered to the mouse connector.  This table below shows how I assigned the wires.  Since all of the Arduino pins are apparently available for digital I/O, I arbitrarily used pins A1-A9.  The DB-9 connector pin 5 is for the middle button, so that won't do anything with the two-button Amiga mouse.  There are two columns for Aruino pin numbers.  The first ("Ard. pin. no.") refers to the number silk-screened on the board and the second ("phys. pin")  refers to that pin's actual physical position using standard DIP pin numbering.  It's all a bit confusing so the table helps keep everything organized.  The "color" column refers to the color of the wires I used, not the colors in the ProMicro diagram (from Sparkfun) below.

Amiga
pin
func.
Ard.
name
Ard. pin
no.
Ard.
phys. pin
color
1
V-pulse
A1
19
18
YLW
2
H-pulse
A2
20
19
GRN
3
VQ-pulse
A3
21
20
YLW
4
HQ-pulse
A6
4
7
GRN
5
M button
A7
6
9
BRN
6
L button
A8
8
11
VIO
7
+5 v.
VCC
VCC
21
RED
8
GND
GND
GND
23
BLK
9
R button
A9
9
12
VIO

Arduino ProMicro pin assignments
That completes the physical part of the job.  We are now ready to move on to the programming.

7/12/15

I decided to start with the mouse buttons since that's clearly easier than doing the quadrature decoding.  I've connected only the mouse power, ground, left, and right wires to the ProMicro.  I then copied the sample HID Joystick Mouse program from https://learn.sparkfun.com/tutorials/pro-micro--fio-v3-hookup-guide and made a few changes.  I had to comment out the x and y motion commands since there's nothing connected to those pins.  If you run the program with those commands enabled, the cursor scoots to the upper left corner of the PC screen and jitters around, refusing to go anywhere else.  I had to unplug the ProMicro to get control back.

The example also only had one "select" button (being written for a joystick), so I duplicated that part to give me code for left and right buttons.  This is the modified code:

/* HID Joystick Mouse Example
   by: Jim Lindblom
   date: 1/12/2012
   license: MIT License - Feel free to use this code for any purpose.
   No restrictions. Just keep this license if you go on to use this
   code in your future endeavors! Reuse and share.

   This is very simplistic code that allows you to turn the
   SparkFun Thumb Joystick (http://www.sparkfun.com/products/9032)
   into an HID Mouse. The select button on the joystick is set up
   as the mouse left click.
 */
int horzPin = A0;  // Analog output of horizontal joystick pin
int vertPin = A1;  // Analog output of vertical joystick pin
int LbuttonPin = 8;  // Left button pin of mouse
int RbuttonPin = 9;  // Right button pin of mouse
int RXLED = 17;  // The RX LED has a defined Arduino pin

int vertZero, horzZero;  // Stores the initial value of each axis, usually around 512
int vertValue, horzValue;  // Stores current analog output of each axis
const int sensitivity = 200;  // Higher sensitivity value = slower mouse, should be <= about 500
int mouseClickFlag = 0;

void setup()
{
  pinMode(horzPin, INPUT);  // Set both analog pins as inputs
  pinMode(vertPin, INPUT);
  pinMode(LbuttonPin, INPUT);  // set L button select pin as input
  pinMode(RbuttonPin, INPUT);  // set R button select pin as input
  pinMode(RXLED, OUTPUT);  // Set RX LED as an output
 
  digitalWrite(LbuttonPin, HIGH);  // Pull L button pin high 
  digitalWrite(RbuttonPin, HIGH);  // Pull R button pin high
  delay(1000);  // short delay to let outputs settle
  vertZero = analogRead(vertPin);  // get the initial values
  horzZero = analogRead(horzPin);  // Joystick should be in neutral position when reading these

}

void loop()
{
  vertValue = analogRead(vertPin) - vertZero;  // read vertical offset
  horzValue = analogRead(horzPin) - horzZero;  // read horizontal offset

/*  if (vertValue != 0)
    Mouse.move(0, vertValue/sensitivity, 0);  // move mouse on y axis
  if (horzValue != 0)
    Mouse.move(horzValue/sensitivity, 0, 0);  // move mouse on x axis
*/

  if ((digitalRead(LbuttonPin) == 0) && (!mouseClickFlag))  // if the left button is pressed
  {
    mouseClickFlag = 1;
    digitalWrite(RXLED, LOW);   // set the LED on
    Mouse.press(MOUSE_LEFT);  // click the left button down
  }
  else if ((digitalRead(LbuttonPin))&&(mouseClickFlag)) // if the left button is not pressed
  {
    mouseClickFlag = 0 ;
     digitalWrite(RXLED, HIGH);   // set the LED off
    Mouse.release(MOUSE_LEFT);  // release the left button
  }

  if ((digitalRead(RbuttonPin) == 0) && (!mouseClickFlag))  // if the right button is pressed
  {
    mouseClickFlag = 1;
    digitalWrite(RXLED, LOW);   // set the LED on
    Mouse.press(MOUSE_RIGHT);  // click the left button down
  }
  else if ((digitalRead(RbuttonPin))&&(mouseClickFlag)) // if the right button is not pressed
  {
    mouseClickFlag = 0 ;
     digitalWrite(RXLED, HIGH);   // set the LED off
    Mouse.release(MOUSE_RIGHT);  // release the right button
  }
}


I plugged in the board, uploaded this code and much to my surprise, it actually works!  Pressing the left and right buttons on the Amiga mouse yields the expected actions on the PC screen.  There's no lag, nothing hangs, it's perfect.  This also proves to me that the ProMicro is in fact capable of powering the mouse from its Vcc pin.

The only problem is that if I remove power from the ProMicro and then plug it in again, it sometimes wants to connect to COM11 on the PC.  Then when I upload something it claims the board is not found.  I have to go into the Tools -> Port menu in the IDE and change it to COM12.  Then it's happy.

So that's some nice progress.  W're finally ready to move on to the main attraction: quadrature decoding.

Quadrature decoding

Turns out there's already a ton of stuff on quadrature decoding using Arduino floating around the web.  The most comprehensive source of information seems to be this page: http://playground.arduino.cc/Main/RotaryEncoders.  I decided to start off using the example code they had, just to get started and see if this would work at all.  I like it because it's short and doesn't need any fancy interrupts or libraries.


 /* Read Quadrature Encoder
  * Connect Encoder to Pins encoder0PinA, encoder0PinB, and +5V.
  *
  * Sketch by max wolf / www.meso.net
  * v. 0.1 - very basic functions - mw 20061220
  *
  */  


 int val; 
 int encoder0PinA = 3;
 int encoder0PinB = 4;
 int encoder0Pos = 0;
 int encoder0PinALast = LOW;
 int n = LOW;

 void setup() { 
   pinMode (encoder0PinA,INPUT);
   pinMode (encoder0PinB,INPUT);
   Serial.begin (9600);
 } 

 void loop() { 
   n = digitalRead(encoder0PinA);
   if ((encoder0PinALast == LOW) && (n == HIGH)) {
     if (digitalRead(encoder0PinB) == LOW) {
       encoder0Pos--;
     } else {
       encoder0Pos++;
     }
     Serial.print (encoder0Pos);
     Serial.print ("/");
   } 
   encoder0PinALast = n;
 } 

With the two vertical pins connected, this program produced output like this as you move the mouse back and forth:

.../-10/-9/-8/-7/-6/-5/-4/-3/-2/-1/0/1/2/3/4/5/6/7/8/9/10/11/12/13/12/13/14/15/16/15/16/15/16/15/14/13/12/11/10/9/8/7/8/9/10/11/12/13...

New numbers appear whenever the mouse is moved vertically.  (Don't forget to open the Serial Monitor window!  Tools -> Serial Monitor).  So the vertical is doing something.  Yay!  I tried it again, substituting in the horizontal pins (20 and 4) and got comparable results, although moving left to right gave numbers getting smaller and vice-versa  A simple sign change can fix that.

7/13/15

Today I added some code to control the y-axis to the above program and tried it out.  It works - sort of.  The first problem is that just doing a  Mouse.move (1, 0, 0) command every time we get one x-quadrature count just won't do.  That makes the mouse extremely slow.  Also, oddly enough, moving the mouse up in the y direction sometimes results in the cursor going the opposite direction.

I changed the increment from 1 to 10 but that just made the motion very jittery in the off-axis.  Clearly this isn't going to work and I'm not sure why.  The code is about as simple as it can be and it doesn't appear to have any errors in it.  All I can think is tht it runs much slower than I assumed and we're randomly missing rising edges.  I'm going to try removing all that serial print stuff to see if that helps.  If not, it's on to Plan B and driving it with interrupts.

7/14/15

I stripped out all the non-essentials and reran the code - no change.  The mouse jitters like crazy and vertical up-motions are completely erratic.   Here's what's running now:

 /* Read Quadrature Encoder
  * Connect Encoder to Pins encoder0PinA, encoder0PinB, and +5V.
  *
  * Sketch by max wolf / www.meso.net
  * v. 0.1 - very basic functions - mw 20061220
  *
  */ 

 int val;
 int Xencoder0PinA = 20;
 int Xencoder0PinB = 4;
 int Xencoder0PinALast = LOW;
 int xn = LOW;

 int Yencoder0PinA = 19;
 int Yencoder0PinB = 21;
 int Yencoder0PinALast = LOW;
 int yn = LOW;

 void setup() {
   pinMode (Xencoder0PinA,INPUT);
   pinMode (Xencoder0PinB,INPUT);
   pinMode (Yencoder0PinA,INPUT);
   pinMode (Yencoder0PinB,INPUT);
   Mouse.begin () ;
 }

 void loop()
 {
   xn = digitalRead(Xencoder0PinA);
   if ((Xencoder0PinALast == LOW) && (xn == HIGH))
     {
      if (digitalRead(Xencoder0PinB) == LOW)
         Mouse.move (10, 0, 0) ;
      else
         Mouse.move (-10, 0, 0) ;
     }  // end if Xencoder

   yn = digitalRead(Yencoder0PinA);
   if ((Yencoder0PinALast == LOW) && (yn == HIGH))
     {
      if (digitalRead(Yencoder0PinB) == LOW)
         Mouse.move (0, 10, 0) ;
      else
         Mouse.move (0, -10, 0) ;
     }  // end if Yencoder

   Xencoder0PinALast = xn;
   Yencoder0PinALast = yn;
 } 


So I guess it's on to Plan B - use interrupts.  I finally settled on this page: http://www.pjrc.com/teensy/td_libs_Encoder.html.  I downloaded the Arudiino encoder library from there, but haven't done anything with it yet.

7/15/15

It occured to me to check whether the Mouse.move command was really working properly so I changed the loop to just call Mouse.move programmarically in a for loop that just called Mouse.move (1,1,0) 200 times.  As expected, it sent the cursor diagonally down the screen (very fast).  So that's not the problem.

Next I installed the Encoder library.  This proved to be trivial,  In the IDE, just click on Sketch -> Include Library -> Manage Libraries... and then scroll down to Encoder.  Select that, click Install, done.

I tried out the Basic example program (just print out coordinate) and it worked fine.

Now apparently, to use interrupts, the mouse has to be connected to interrupt capable pins.  Fortunately, the ProMicro has five such pins: 3, 2, 0, 1, and 7.  So if I'm going to do that, I'll have to rewire the board.  Maybe I'll just try to get it to work as is first.

7/17/15

OK, so we're going to try some code using interrupts.  Unfortunately this means changing the pin assignments of the Amiga quadrature signals.  The following example encodes just one axis.  We put V on pin2 and Vq on pin 3.  That lines up with the example code.

#define encoder0PinA 2
#define encoder0PinB 3

volatile unsigned int encoder0Pos = 0;
unsigned int tmp_Pos = 1;
unsigned int valx;
unsigned int valy;
unsigned int valz;

boolean A_set;
boolean B_set;


void setup() {

  pinMode(encoder0PinA, INPUT);
  pinMode(encoder0PinB, INPUT);

// encoder pin on interrupt 0 (pin 2)
  attachInterrupt(0, doEncoderA, CHANGE);

// encoder pin on interrupt 1 (pin 3)
  attachInterrupt(1, doEncoderB, CHANGE);

  Serial.begin (9600);
}


void loop(){
//Check each second for change in position
  if (tmp_Pos != encoder0Pos) {
    Serial.print("Index:"); Serial.print(encoder0Pos, DEC); Serial.print(", Values: ");
    Serial.print(valx, DEC); Serial.print(", ");
    Serial.print(valy, DEC); Serial.print(", ");
    Serial.print(valz, DEC); Serial.println();
    tmp_Pos = encoder0Pos;
  }
  delay(1000);
}


// Interrupt on A changing state
void doEncoderA(){

  // Low to High transition?
  if (digitalRead(encoder0PinA) == HIGH) {
    A_set = true;
    if (!B_set) {
      encoder0Pos = encoder0Pos + 1;
      valx=analogRead(0);
      valy=analogRead(1);
      valz=analogRead(2);
    }       
  }

  // High-to-low transition?
  if (digitalRead(encoder0PinA) == LOW) {
    A_set = false;
  }

}

// Interrupt on B changing state
void doEncoderB(){

  // Low-to-high transition?
  if (digitalRead(encoder0PinB) == HIGH) {  
    B_set = true;
    if (!A_set) {
      encoder0Pos = encoder0Pos - 1;
    }
  }

  // High-to-low transition?
  if (digitalRead(encoder0PinB) == LOW) {
    B_set = false;
  }
}


 7/18/15

That code compiled OK and actually produced appropriate-looking numbers so I added the Mouse.move calls.  The result is a lot better than my first attempt and the mouse motion is much smoother.  On to the x-axis.

According to Sparkfun, "pin 3 maps to interrupt 0, pin 2 is interrupt 1, pin 0 is interrupt 2, pin 1 is interrupt 3, and pin 7 is interrupt 4.".

We've already used pins 3 and 2 for the y-axis so we'll assign x to pins 0 and 1, for no particular reason.


More to come, stay tuned...