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 |
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 |
Quadrature mouse, vertical move down |
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 |
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 |
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 |
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...