X-Y Plotter Table For The TI 99/4A Computer - Updated 6/17/18
So I've been mulling the idea of creating an X-Y table for my TI computer, which could be used to draw a bitmap image or perhaps do some laser engraving. I searched the web for inspiration and found this ingenious video by HomoFaciens where he uses the stepper platform that moves the laser head in optical drives to create a very effective but small X-Y plotter.
So I went ahead an found a couple of used DVD-ROM drives on Ebay and ripped them apart, only to find that only one of them had a stepper motor assembly. According to HomoFaciens, only about 50% of the optical drives actually use stepper motors. Nonetheless, I figured I could start by experimenting in controlling the stepper platform with the TI's parallel (PIO) port. Below is the actual stepper motor assembly from one of the drives:
The stepper motor in this assembly is a 4 wire stepper motor, which means it has 2 coils with each pair of wires going to one coil. It's easy to identify each pair by simply doing a continuity test on the wires using a multimeter. To move the motor in one direction, one of the coil has to cycle it's polarity repeatedly between positive and negative while the other coil is idle. To reverse the motion, simply repeat the process using the second coil.
In order to be able to control the polarity on the coil, one could use relay circuits like the one on my robotic arm controller, or more commonly use an H-bridge circuit as below:
Based on the input to the 4 input points on the circuit marked A, B, C and D, the polarity can be switched at will. The table below shows how to do this.
Given that we have 2 coils in the stepper motor, we obviously will need 2 H-bridges to control it, one for each coil. While you can purchase ready made stepper motor controllers on most hobbyist sites, it's really pretty simple to build for just pennies, so I went ahead and breadboarded a couple of H-bridges for test purposes.
As for the connection to the TI, I decided to allocate one data pin on the PIO port to each input port on the H-bridges, which means that all the data pins will be used (Eight). This is problematic because the X-Y table will need 2 stepper motors at a minimum, one for the X axis and one for the Y axis, and I obviously will not have enough data pins for that. The solution is going to involve some kind of multiplexing, but we'll come back to that. For now let's make sure this thing actually works!
Here's the connection layout:
A --> D7
B --> D6
C --> D5
D --> D4
A --> D3
B --> D2
C --> D1
D --> D0
So in order to control the coils, I just need to send the appropriate byte pattern to the PIO port. For example, in order to run the stepper motor forward, coil 1 needs to have a bit pattern of 1100 corresponding to the DCAB H-bridge inputs, and coil 2 needs to be inactive, which translates to a bit pattern of 1010 (refer to the table above). Putting it all together we get:
Coil 2 Coil 1 D C B A D C B A D0 D1 D2 D3 D4 D5 D6 D7 1 0 1 0 1 1 0 0
Converting this 8-bit binary number to decimal we get 172, which we can send to the PIO port via the CALL LOAD command in Rich Extended Basic. Then we reverse the polarity of coil 1 while keeping coil 2 inactive to keep the stepper motor moving forward and so on and so forth.
Here's the control program on the TI. I opted to use Rich Extended Basic for its ease of use and unique ability to allow low-level access to hardware. Below is the test program listing:
10 CALL CLEAR 20 CRU=2432 ! RS232 CARD CRU ADDRESS IS >1300 (4864). RXB USES CRU/2 30 CALL IO(3,1,CRU,1) ! ACTIVATE THE RS232 CARD 40 CALL IO(3,1,CRU+7,1) ! TURN ON THE LED ON THE CARD 50 CALL IO(3,1,CRU+1,0) ! SET THE PIO PORT TO OUTPUT 60 FOR I=1 TO 30 ! 30 FORWARD STEPS 70 CALL LOAD(20480,172) ! COIL 1 POSITIVE POLARITY - COIL 2 INACTIVE -- PIO ADDRESS IS 20480 80 GOSUB 500 ! DELAY TO SLOW DOWN THE MOVEMENT 90 CALL LOAD(20480,163) ! COIL 1 NEGATIVE POLARITY - COIL 2 INACTIVE 100 GOSUB 500 110 NEXT I 120 FOR I=1 TO 30 ! 30 REVERSE STEPS 130 CALL LOAD(20480,202) ! COIL 1 INACTIVE - COIL 2 POSITIVE POLARITY 140 GOSUB 500 150 CALL LOAD(20480,58) ! COIL 1 INACTIVE - COIL 2 NEGATIVE POLARITY 160 GOSUB 500 170 NEXT I 180 GOTO 60 ! REPEAT THE ENTIRE PROCESS 500 FOR D=1 TO 200::NEXT D::RETURNAnd here's what it looks like in action:
So while it works in principle, as I mentioned in the video there are several issues to contend with if we are to use the DVD-ROM assemblies:
- The quality of the worm gear assembly is highly variable. Mine was pretty bad...
- The weight that could be supported by the carrier is tiny on my drive assembly, so it will not be able to hold a second assembly on top for the Y axis.
- The usable draw area is very small, 40x40 steps on my drive assembly, so not very practical.
Pack007 suggested using a 74HC595 8-bit serial shift register chip for multiplexing to PIO data lines, and it worked extremely well. That chip basically takes in an 8 bit number serially one bit at a time starting with the LSB and outputs that number in parallel using 8 output lines. In addition it has an Output Enable pin which places the parallel output pins in a high impedance state when high, effectively inactivating the chip. The control sequence will go like this:
- Make the LATCH pin (12) low to isolate the serial input from the output
- Activate the chip by making the OE pin (13) low
- Present 1 bit to the DATA pin (14) and cycle the CLOCK pin (11)
- Go back to 3 until all 8 bits are in
- Cycle the LATCH pin (12) to present the number to the output pins
- Go back to 1 for the next number
PIO Connections: D7 (2) --> DATA D6 (3) --> OE for motor 1 D5 (4) --> OE for motor 2 D4 (5) --> OE for motor 3 HSKOUT (1) --> CLOCK SPROUT (14) --> LATCH
Now we will need 3 stepper motors for the plotter: X, Y and Pen. The way this will work is that each motor will have 2 H-bridges connected to a separate 74HC595 chip. These 3 chips will share the LATCH, CLOCK and DATA pins connections but will each have their own OE pin connections. That way, the computer will be able to select the desired motor by simply making the corresponding OE line low and the other 2 OE lines high. All in all, only 6 output lines from the PIO port will be needed instead of the 24 lines required without multiplexing. Problem solved! This leaves me with several lines that could be used to detect axis end of travel through the use of micro-switches.
And here's the RXB test code. It's definitely a slower process than the previous direct parallel method, but it's the price to pay for multiplexing. This is fine for testing, but I might have to switch to assembly for the final control program in order to speed things up. We'll see...
10 CALL CLEAR 11 OE1=12 ! BIT PATTERN TO ISOLATE MOTOR 1 14 DIM FWD(16),REV(16),OFF(8) 15 CRU=2432 ! CRU BASE OF RS232 CARD DIVIDED BY 2 20 CALL IO(3,1,CRU,1) ! ACTIVATE RS232 CARD 30 CALL IO(3,1 CRU+7,1) ! TURN LED ON 40 CALL IO(3,1,CRU+1,0) ! SET PIO TO OUTPUT 50 FOR I=0 TO 15::READ FWD(I)::NEXT I ! READ FORWARD CONTROL SEQUENCE 60 FOR I=0 TO 15::READ REV(I)::NEXT I ! READ REVERSE CONTROL SEQUENCE 70 FOR I=0 TO 8::READ OFF(I)::NEXT I ! READ IDLE CONTROL SEQUENCE 80 REM MOTOR 1 MOVE FORWARD 90 CALL IO(3,1,CRU+3,0) ! MAKE LATCH PIN LOW 100 CALL LOAD(20480,OE1) ! ISOLATE MOTOR 1 110 FOR I=1 TO 30 ! 30 FORWARD STEPS 120 FOR N=0 TO 15 ! FORWARD BIT SEQUENCE IS 2 8-BIT NUMBERS LONG (16 BITS) 121 REM THE LINE BELOW SENDS A SINGLE BIT OUT THE PIO LSB (D7) PIN 122 REM IF THE BIT IS 1, IT IS ADDED TO THE OE1 BIT PATTERN (D6-D4) 123 REM OTHERWISE ONLY OE1 IS SENT AND D7 REMAINS 0 130 IF FWD(N)=1 THEN CALL LOAD(20480,OE1+1) ELSE CALL LOAD(20480,OE1) 140 CALL IO(3,1,CRU+2,1)::CALL IO(3,1,CRU+2,0) ! CYCLE THE CLOCK PIN 141 REM THE LINE BELOW PRESENTS THE 8-BIT NUMBER TO OUTPUT 150 IF N=7 OR N=15 THEN CALL IO(3,1,CRU+3,1)::CALL IO(3,1,CRU+3,0) ! CYCLE THE LATCH PIN 160 NEXT N 170 NEXT I 180 REM MOTOR 1 REVERSE MOVE 190 FOR I=1 TO 30 ! 30 REVERSE STEPS 200 FOR N=0 TO 15 ! REVERSE BIT SEQUENCE IS 2 8-BIT NUMBERS LONG (16 BITS) 201 REM THE LINE BELOW SENDS A SINGLE BIT OUT THE PIO LSB (D7) PIN 202 REM IF THE BIT IS 1, IT IS ADDED TO THE OE1 BIT PATTERN (D6-D4) 203 REM OTHERWISE ONLY OE1 IS SENT AND D7 REMAINS 0 210 IF REV(N)=1 THEN CALL LOAD(20480,OE1+1) ELSE CALL LOAD(20480,OE1) 220 CALL IO(3,1,CRU+2,1)::CALL IO(3,1,CRU+2,0) ! CYCLE THE CLOCK PIN 221 REM THE LINE BELOW PRESENTS THE 8-BIT NUMBER TO OUTPUT 230 IF N=7 OR N=15 THEN CALL IO(3,1,CRU+3,1)::CALL IO(3,1,CRU+3,0) ! CYCLE THE LATCH PIN 240 NEXT N 250 NEXT I 260 GOTO 110 ! REPEAT THE PROCESS 500 REM MOTOR ACTIVATION SEQUENCES 510 REM FORWARD 520 DATA 0,0,1,1,0,1,0,1,1,1,0,0,0,1,0,1 530 REM REVERSE 540 DATA 0,1,0,1,0,0,1,1,0,1,0,1,1,1,0,0 550 REM IDLE 560 DATA 0,1,0,1,0,1,0,1It is likely I will need to use more beefy transistors for the H-bridges because the current needed by the stepper motors in the final design will be hefty. However, the basic circuit design will remain the same.
Next I'm going to focus on the mechanical assembly.
Soooo, to be perfectly honest I have not been too happy with the performance of the stepper motor extracted from the optical drive. The torque was way too low and the steps were far too large and I had the nagging suspicion I was doing something wrong, and indeed I was!
After further research, it turned out that my step control sequence was incomplete, and I was essentially skipping every other step. The correct sequence for a bipolar 4-wire stepper motor like the one I have turned to be as below:
A and B represent the 2 coils in the motor. To reverse the rotation, simply swap the sequences of A and B. And sure enough when I applied that sequence the torque increased dramatically because both coils were always energized at any one time and the steps were much smaller and smoother. Live and learn
Furthermore, after taking a closer look at the H-bridge circuit I had used and comparing it to other circuits out there, I realized that there was no need to use a mix of NPN and PNP transistors, and that just NPN ones will do the trick, as well as only require 2 input pins for control instead of 4 per H-bridge! And that meant that now I could control 2 stepper motors using only one 74HC595 chip instead of 2 chips! Here's the updated circuit for a single motor:
The new PIO connections become:
PIO Connections: D7 (2) --> DATA D6 (3) --> OE for motor 1 & 2 D5 (4) --> OE for motor 3 HSKOUT (1) --> CLOCK SPROUT (14) --> LATCHAnd the control sequence for the steps will be as below, with A and B this time representing each H-bridge input:
COIL 2 COIL 1 A B A B ---------------------- 1 0 1 0 FORWARD 0 1 1 0 0 1 0 1 1 0 0 1 1 0 1 0 REVERSE 1 0 0 1 0 1 0 1 0 1 1 0Amended RXB control test program. Notice that the trailing 4 zero bits of each byte in the control sequences are reserved for the future control of the seconds stepper motor.
10 CALL CLEAR 20 OE1=4 ! ISOLATE MOTORS 1 & 2 30 OE2=2 ! ISOLATE MOTOR 3 40 DIM FWD(32),REV(32) 50 CRU=2432 ! BASE RS232 / 2 60 CALL IO(3,1,CRU,1) ! ACTIVATE RS232 CARD 70 CALL IO(3,1,CRU+7,1) ! TURN LED ON 80 CALL IO(3,1,CRU+1,0) ! SET PIO TO OUTPUT 90 FOR I=1 TO 31::READ FWD(I)::NEXT I 100 FOR I=1 TO 31::READ REV(I)::NEXT I 110 REM FORWARD STEPS 120 CALL IO(3,1,CRU+3,1) ! MAKE LATCH LOW 130 CALL LOAD(20480,OE1) ! ISOLATE MOTORS 1 & 2 140 FOR I=1 TO 5 150 FOR N=0 TO 31 155 REM LINE BELOW SENDS DATA BIT BY BIT VIA D7 PIN ON PIO PORT 160 IF FWD(I)=1 THEN CALL LOAD(20480,OE1+1) ELSE CALL LOAD(20480,OE1) 170 CALL IO(3,1,CRU+2,1)::CALL IO(3,1,CRU+2,0) ! PULSE CLOCK 180 IF N=7 OR N=15 OR N=23 OR N=31 THEN CALL IO(3,1,CRU+3,1)::CALL IO(3,1,CRU+3,0) ! CYCLE LATCH 190 NEXT N 200 NEXT I 210 REM REVERSE STEPS 220 FOR I=1 TO 5 230 FOR N=0 TO 31 240 IF REV(N)=1 THEN CALL LOAD(20480,OE1+1) ELSE CALL LOAD(20480,OE1) 250 CALL IO(3,1,CRU+2,1)::CALL IO(3,1,CRU+2,0) ! PULSE CLOCK 260 IF N=7 OR N=15 OR N=23 OR N=31 THEN CALL IO(3,1,CRU+3,1)::CALL IO(3,1,CRU+3,0) ! CYCLE LATCH 270 NEXT N 280 NEXT I 290 GOTO 140 500 REM MOTOR ACTIVATION SEQUENCES 510 REM FORWARD 520 DATA 1,0,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,0,1,0,0,0,0,1,0,0,1,0,0,0,0 530 REM BACKWARD 540 DATA 1,0,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1,0,1,0,0,0,0,0,1,1,0,0,0,0,0And here's the whole thing in action:
OK now I can focus on the mechanical assembly
Quick update here: I re-wrote the stepper motor control program in assembly (quick and dirty draft) and the speed difference was remarkable as seen in the video below. I actually could drive the motor much faster than this, but it would likely damage it... And here's where the usefulness of RXB really comes through: it's super efficient to use for rapid prototyping and validation of design ideas because of the ease of editing and running, and once everything is working properly then porting to assembly, if needed for speed, can be done in a fairly straightforward manner.