Measure Time Intervals With The Pet Parallel User Port
Robert Macnaughton
Rexdale, Canada
This article describes a machine language program that can be used to measure seven successive small time intervals, using the CBM Parallel User Port (PUP), and eight phototransistors, to the nearest 1/10000s.
Since no page zero locations are used, this program should run on any PET (except 4.0, since it would need to be moved above 864 decimal for 4.0 BASIC).
The PUP, located at the back of the CBM, consists of 24 contacts to the main logic board, labelled as follows:
Only the bottom row of contacts will be used. The top row of contacts are for use by CBM diagnostic routines during servicing.
On the bottom row of contacts, Pin M is the CB2 line, used in many programs for sound effects; contacts A and N are grounds, and contact B is the CA1 line.
We will use contacts C,D,E,F,H,J,K and L, known as PA0, PA1, PA2, PA3, PA4, PA5, PA6 and PA7, the programmable input/output lines, to receive information from eight phototransistors, the detectors of the position of some moving object.
The eight lines are treated by the PET as a single memory location, 59471 in decimal or $E84F in hexadecimal. It is known as the ORA, the output register for I/O Port A, without handshaking. At any time, a PEEK(59471) will indicate the condition of the ORA.
The DDR A, the data direction register for Port A, is used to designate which are the input and which are the output lines of the ORA. Its address is 59459 or $E843. A zero in bit three would make PA3 an input line and a one would make it an output line. If you POKE 59459,76 then PA2, PA3 and PA6 will be output lines and the rest input lines, since 76 in binary is 01001100.
In this timer, all eight lines are made inputs by POKE 59459,0. A PEEK(59459) when the CBM is first turned on will show that all the lines are initially inputs.
When running, the timer program looks at the contents of the ORA again and again. To understand the result, the contents of 59471 must be expressed as a binary number. Each of the eight I/O lines corresponds to one bit in this number. Any line grounded will be represented as a 0. If not grounded, it will be represented as a 1. More exactly, if a resistance of less than about 2000Ω is connected from a PA line to GND, the state of the line will be interpreted as a 0. If the resistance is more than 2000Ω, it will be interpreted as a 1.
If you PEEK(59471) with nothing connected to the PUP, you will get 255. If you short out all eight lines, you will get a 0. (First make sure that they are all input lines.)
PA7 | PA6 | PA5 | PA4 | PA3 | PA2 | PA1 | PA0 | |
bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
value | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
59471 | ||||||||
255 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
2 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
4 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
8 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
16 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
32 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
64 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
128 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
214 | 1 | 1 | 0 | 1 | 0 | 1 | 1 | 0 |
The collectors of eight FPT100 phototransistors are connected to the eight PA lines, and their emitters to ground at contact N. When enough light strikes a phototransistor such as the FPT100, its resistance falls to about 200Ω. This is interpreted as a 0 in the ORA. When the light is cut off, the resistance increases dramatically and is interpreted as a 1. As an object passes by a phototransistor, the state of that PA line will change from 0 to 1 and back to 0 as the light is temporarily interrupted.
I have placed the phototransistors in holes drilled in a meter stick 15 cm apart. The position of the first phototransistor must be adjustable to start the timer at the correct moment. Opposite each phototransistor is a small flashlight bulb attached to a second meter stick. The two meter sticks are placed on either side of a ramp. A large ball bearing rolling down the ramp will be timed as it interrupts each light beam in turn.
If the times you wished to measure were long, you could write a BASIC program to measure these time intervals, using the internal "jiffy" clock of the PET. The light to each phototransistor would have to be cut off long enough that it would still be cut off when the program got around to checking the state of 59471.
To fully utilize the 1 megacycle clock in the CBM, a machine language program must be used.
The program begins by setting the interrupt flag. This will ensure that the timmig will not be interrupted by the CBM as it performs its normal interrupt every 1/60 s, to update its clock, flash the cursor if needed, etc.
It then goes into a loop to load all the various memory locations used to store the times, with zeros. At the same time it prints a ? at the top left of the screen. It then goes into a second loop to wait for PA? to become 1 when the ball is rolled into place at the top of the ramp. An R for READY now appears on the screen.
The following table shows how the ORA changes as the ball rolls down the ramp.
SCREEN | BINARY | DECIMAL | |
? | 00000000 | 0 | ball not on ramp |
R | 00000001 | 1 | ball in place at top of ramp |
T | 00000000 | 0 | ball rolling |
1 | 00000010 | 2 | passes PA1 |
1 | 00000000 | 0 | ball rolling |
2 | 00000100 | 4 | passes PA2 |
2 | 00000000 | 0 | ball rolling |
3 | 00001000 | 8 | passes PA3 |
3 | 00000000 | 0 | ball rolling |
4 | 00010000 | 16 | passes PA4 |
4 | 00000000 | 0 | ball rolling |
5 | 00100000 | 32 | passes PA5 |
5 | 00000000 | 0 | ball rolling |
6 | 01000000 | 64 | passes PA6 |
6 | 00000000 | 0 | ball rolling |
7 | 10000000 | 128 | passes PA6 |
When 59471 becomes 0, the timer enters a timing loop. Each time through the loop it checks 59471 for a 0, then adds 1 to a counter. When 59471 has the next expected value, the contents of this counter are stored, and the timing resumes, continuing until all seven times have been measured. When the program returns to BASIC, the contents of the memory locations containing the count can be recalled and converted to seconds.
Since each timing loop takes 43 cycles of the CBM's internal 1 megacycle clock, each count represents 43 microseconds.
The count is contained in three locations. The first is incremented in each loop. The second is incremented only when the first passes 255 and becomes 0 again. The third is incremented only when the second passes 255 and becomes 0 again. The largest count possible is then (255×256×256) + (255×256) + 255 or 16777215. This is slightly more than 12 minutes.
I have included a second copy of the machine language program which shows the timing loop. Beside each step I have written the number of cycles of the PET's internal clock that are needed to complete each step. The total number of cycles is 43. Some extra time is used to store the count as each phototransistor is passed. If you wish, this could be calculated and added on to the total time as a correction.
I have also included a BASIC program to operate the clock in an organized fashion. It asks you how many runs you wish to make down the ramp, then stores the seven times for each run. Eventually, the average time for each part of the run is calculated. With a few minor changes, this program can be used in almost any situation where accurate timing is needed.
TIMER COMMENTS | |
1 | Disable the interrupt flag |
2 | Load the accum with the code for ? |
3 | Store at top left corner of screen |
4 | Load the x-register with a 2 |
5 | Store the 2 at 0336 |
6 | Load the y-register with decimal 25 |
7 | Load the accum with a 0 |
8 | Store 0 at all locations from 03DF to 03DF + 25 by looping until y = 0 |
10 | Compare y with zero |
11 | 12 If y isn't zero then loop to step 7 |
12 | Load accum with the contents of 59471 |
13 | Check if PA0 is a 1 or a 0 |
14 | If PA0 = 0 then loop and check again |
15 | Now PA0 is 1: R for Ready into accum |
16 | store R on the screen |
17 | This is a time delay while things |
18 | settle down. Load x and y with 255 |
19 | and decrement them both to zero |
20 | Each time x decrements from 255 to 0 |
21 | y decrements by one. Finally both are |
22 | zero |
23 | Load accum with 59471 once more |
24 | Test to see if PA0 is still a 1 |
25 | If so, loop back to 23 and try again |
26 | Now PA0 is a 0, the timing must start |
27 | Store a T on the screen |
28 | Begin timing loop by clearing the |
29 | carry flag, then load accum with 03E0 |
32 | Add 1 to the contents of this |
30 | location and then store it back there |
31 | Add zero to the contents of 03E1 |
32 | (and 1 if the carry flag was set by |
33 | the previous addition) and store it |
34 | Add zero(+ 1 if the carry flag is set) |
35 | to the contents of 03E2 |
36 | 03E0, 03E1, 03E2 contain the total time |
37 | Check 57471 to see if the next PA |
38 | line is a 1 or a 0 using 0336 |
39 | 0336 contains a 2: binary 00000010 |
40 | If a 0, loop: if a 1, then arithmetic |
41 | shift left the value in 0336: see text |
42 | Store the three values representing |
43 | the elapsed time using the current |
44 | value of y (It is 0 to start with) |
45 | |
46 | |
47 | |
48 | Clear the carry flag before addition |
49 | Transfer y, a counter, to the accum |
50 | Add 177 to it to make it the ASCII |
51 | code for y and store it on screen |
52 | Increment y (next time measurement) |
53 | Compare y with 7: If y is less than 7 |
54 | then go back to start of timing cycle |
55 | If y is 7, the program is over: clear |
56 | interrupt flag and return to BASIC |
Program 1.
TIMER $033A SYS 826 1 033A 78 SEI 2 033B A9 BF LDA #BF 3 033D 8D 00 80 STA 8000 4 0340 A2 02 LDX #02 5 0342 8E 36 03 STX 0336 6 0345 A0 19 LDY #19 7 0347 A9 00 LDA #00 8 0349 99 DF 03 STA 03DF, Y 9 034C 88 DEY 10 034D C0 00 CPY #00 11 034F D0 F6 BNE 0347 12 0351 AD 4F E8 LDA E84F 13 0354 29 01 AND #01 14 0356 F0 F9 BEQ 0351 15 0358 A9 92 LDA #92 16 035A 8D 00 80 STA 8000 17 035D A0 FF LDY #FF 18 035F A2 FF LDX #FF 19 0361 CA DEX 20 0362 D0 FD BNE 0361 21 0364 88 DEY 22 0365 D0 FA BNE 0361 23 0367 AD 4F E8 LDA E84F 24 036A 29 01 AND #01 25 036C D0 F9 BNE 0367 26 036E A9 94 LDA #94 27 0370 8D 00 80 STR 8000 28 0373 18 CLC 29 0374 AD E0 03 LDA 03E0 30 0377 69 01 ADC #01 31 0379 8D E0 03 STA 03E0 32 037C AD E1 03 LDA 03E1 33 037F 69 00 ADC #00 34 0381 8D E1 03 STA 03E1 35 0384 AD E2 03 LDA 03E2 36 0387 69 00 ADC #00. 37 0389 8D E2 03 STA 03E2 38 038C AD 4F E8 LDA E84F 39 038F 2D 36 03 AND 0336 40 0392 F0 DF BEQ 0373 41 0394 0E 36 03 ASL 0336 42 0397 AD E0 03 LDA 03E0 43 039A 99 E3 03 STA 03E3, Y 44 03A0 AD E1 03 LDA 03E1 45 03A0 99 EA 03 STA 03EA, Y 46 03A3 AD E2 03 LDA 03E2 47 03A6 99 F1 03 STA 03F1, Y 48 03A9 18 CLC 49 03AA 98 TYA 50 03AB 69 B1 ADC #B1 51 03AD 8D 00 80 STA 8000 52 03B0 C8 INY 53 03B1 C0 07 CPY #07 54 03B3 D0 BE BNE 0373 55 03B5 58 CLI 56 03B6 60 RTS
Program 2.
READY.
10 REM TIMER BASIC 20 REM ROBERT MACNAUGHTON OCT 5/80 25 REM 2124 GREENHURST AVE 30 REM MISSISSAUGA L4X 1J6 35 REM THE MACHINE LANGUAGE PROGRAM MEASURES 7 TIMES DURING A SINGLE TRIP 40 REM UP TO 8 PHOTOTRANSISTORS ARE CONNECTED TO PA0-7 45 REM SYS 826 ACTIVATES THE TIMER AND? APPEARS 50 REM WHEN PA0 IS BLOCKED OFF, RAPPEARS AND THE TIMER IS READY TO START 60 REM WHEN LIGHT AGAIN FALLS ON PA0, THE TIMER STARTS AND T APPEARS 70 REM AS EACH OF PA1-7 IS CUT OFF, THE TOTAL ELAPSED TIME IS STORED 75 REM AS EACH MEASUREMENT IS MADE, ITS NUMBER APPEARS (1–7) 80 REM UNUSED PA LINES SHOULD BE OPEN CIRCUITS 200 PRINT "ĥ"205 INPUT "↓NUMBER OF RUNS" ; NR 210 FORJ = 1TONR 215 SYS826 220 FOR I = 0TO6 225 REM THE NEXT STATEMENT CALCULATES THE TIMES 226 REM THE MEMORY LOCATIONS FOR THE TIMES ARE (995, 1002, 1009)(996, 1003, 1010), 227 REM CONTINUING UP TO (1001, 1008, 1015) 228 REM EACH TIMING CYCLE TAKES 43 MACHINE LANGUAGE STEPS OR 43 MICROSECONDS 230 T(I, J) = 43*(PEEK (995 + I) + PEEK(1002 + I) *256 + PEEK (1009 + I) *256*256)/1000000 240 REM NEXT STATEMENT ROUNDS OFF THE TIMES TO 1/10000 S 250 T(I, J) = INT (T(I, J)*10000)/10000 260 PINT T (I, J), 270 NEXT: PRINT: PRINT: NEXT 280 REM CALCULATE THE AVERAGE TIMES 290 PRINT "AVERAGE TIMES" 300 FOR I = 0TO6: TM (I) = 0: FOR J = 1 TONR 310 TM(I) = TM (I) + T (I, J) 320 AV(I) = TM (I)/NR 330 AV(I) = INT(AV(I)*10000)/10000 340 NEXT: NEXT 350 FOR I = 0 TO6: PRINTAV(I), : NEXT: PRINT 400 GOTO 205