Classic Computer Magazine Archive COMPUTE! ISSUE 36 / MAY 1983 / PAGE 244

Versatile Data Acquisition With VIC

Doug Homer and Stan Klein

This simple method of adjusting the VIC's internal jiffy dock can slow it down to match your timing needs making possible "variable speed" machine language subroutines. You can save a good amount of money by transforming a VIC into this special-purpose tool. You can even use this to speed up games.

Home computers are finding their "homes" in labs, more and more frequently. Their flexibility and low cost make them excellent substitutes for more expensive special equipment. One common use is as a data acquisition device. Data acquisition systems monitor and record information on experiments in progress. For example, a chemist may use a special electrode to measure the con­centration of a particular component in a chemical solution. As the concentration changes, the electrode sends a varying voltage to an analog-to-digital converter. The converter changes the voltage signal to binary data which can be recorded and stored for later analysis.

To log the data, the chemist could use a special-purpose data acquisition system perhaps costing thousands of dollars and useful only for a particular type of experiment. On the other hand, a microcomputer could be programmed to perform the same function. Moreover, to perform another type of experiment, the chemist need only modify the program instead of buying new equipment. When the data is stored, the computer might also be useful in analyzing it.

Surprisingly Simple

There is a surprisingly simple method for converting the VIC into a data acquisition system. A good acquisition system is based on a clock which uses interrupts to sample the user port at adjustable, fixed intervals. Data acquisition software is usually complicated because you must worry about interrupts generated from the jiffy clock.

A simpler scheme is to append the data acquisition routine to the front of the interrupt service routine which is already functioning in connection with the jiffy clock. Every 16.667 milliseconds, VIC interrupts whatever it is doing to look at the keyboard and update the jiffy timer. Here's how to attach your own program to the jiffy service routine and how to set the jiffy clock to any rate of data acquisition.

To change the number of interrupts per second, just POKE different numbers into the low timer latch (37158) and the high timer latch (37159). Under normal operating conditions, these bytes are loaded with 137 in the low latch and 66 in the high latch. An interrupt is generated and the latches are reloaded into the counters whenever the counters are decremented to zero. The number of cycles between interrupts is two cycles greater than the number in the latches.

You might expect the counter to be loaded with 16667 less two, since the normal interrupts are every 1/60 of a second; but 66*256 +137= 17033 rather than 16665. This means simply that the "1 MHz" counter decrements at 1.022*106 Hz, not at an even rate of 1.00*106 Hz. So, to make the jiffy clock interrupt at a rate different than the normal 1/60 per second, just multiply the desired number of microseconds per interrupt by 1.022 and subtract two from that number. Example: for a mil­lisecond interrupt (1000*1.022)-2 = 1020, so you would POKE 3 into the high byte at location 37159, and 252 into the low byte at location 37158 (3*256 + 252 = 1020) - and now you have an interrupt every millisecond.

There are limits to this method of changing the jiffy clock to produce varied interrupts. At the slow end, the largest number that could be loaded is $FFFF, or 65535. For the longest time interval between interrupts, the number of microseconds would be (65535 + 2)/l .022 = 64126. The fast end limit is set by the percent of time remaining for BASIC. This percent is derived by (L-IR)/(L + 2), where L is the number POKEd in the timer latch described above, and IR is the number of cycles taken up by the unmodified interrupt service routine.

Interruptions Can Make Your Games Run Faster

Ottis Cowper, Technical Editor

This is a very powerful programming technique, the interrupt driven subroutine, which has a much wider range of applications than merely gathering data from the user port. For example, how would you like your computer to handle two jobs at once? Actually, the 6502 microprocessor is a sequential device and can only do one operation at a time, but the VIC's hardware interrupts occur so frequently (60 times per second) that a machine language interrupt routine can appear to work concurrently with BASIC.

A Demonstration

As a demonstration, make the additions and changes shown in Program 1 to the program in the article. (This demonstration is for the unexpended VIC and requires a joystick. Remove or disable any expansion modules.) Since the DATA statements contain a machine language routine, they must be typed in exactly as shown. Be sure to save a copy of the program before you RUN since an error in an interrupt routine almost always causes your system to lock you out. For those interested in the operation of the routine, a disassembly of the code is provided in Program 2.

When you RUN the program, you should see a bar appear in the center of the screen. Try moving your joystick left and right and notice how smoothly the bar moves. Type in a new value for the high and low bytes of the timer. Higher timer values slow down the bar movement; lower values speed it up. Compare this to the slow and jerky movement you're used to in BASIC, and imagine how an interrupt joystick or character movement routine would improve your favorite game.

The main point is that the joystick reading and bar movement are totally inde­pendent of BASIC. To prove this to yourself, hit the STOP key. You'll see the message BREAK IN 35. The BASIC program has ended, but the interrupt routine is not affected. The bar movement continues as before. To disable the routine, hit the RUN/ STOP and RESTORE keys at the same time.

How To Add It To Your Programs

Here is the procedure for adding an interrupt driven routine to your BASIC program (example lines from the program given in the article are noted in parentheses):

  1. Reserve room for the new routine somewhere in memory (line 10).
  2. Load the machine language code into the protected area (line 15).
  3. Disable interrupts, load the address (known as the "interrupt vector") of the new routine into locations 788 and 789, and re-enable interrupts (line 20).
  4. If necessary, modify the speed of the interrupt routine by adjusting the rate of the jiffy clock (line 30).
  5. It is absolutely essential that the ap­pended interrupt routine end with a JuMP to the normal ROM interrupt handling routine (for the VIC, this would be JMP $EABF).
Program 1: Demonstration Program
12 FORI=38400TO38905 : POKEI, 0 :NEXT
13 POKE 1,8:POKE2,10
14 FORI=0TO2:POKE7909+I,160:NEXT
22 DATA 166,1,164,2,169,127,141,34,145,173
23 DATA 31,145,41,16,240,26,173,32,145,41
24 DATA 128,208,35,192,21,240,31,169,32,157
25 DATA 220,30,232,200,169,160,153,220,30,24
26 DATA 144,16,224,0,240,12,169,32,153,220
27 DATA 30,202,136,169,160,157,220,30,134,1
28 DATA 132,2,169,255,141,34,145,76,191,234
35 GOTO35
Program 2: Disassembly Of Machine Language; Routine In Program 1
1C00 A6 01      LDX $01
1C02 A4 02      LDY $02
1C04 A9 7F      LDA #$7F
1C06 8D 22 91   STA $9122
1C09 AD IF 91   LDA $911F
1C0C 29 10      AND #$10
1C0E F0 1A      BEQ $1C2A
1C10 AD 20 91   LDA $9120
1C13 29 80      AND #$80
1C15 D0 23      BNE $1C3A
1C17 C0 15      CPY #$15
1C19 F0 IF      BEQ $1C3A
1C1B A9 20      LDA #$20
1C20 E8         INX
1C21 C8         INY
1C22 A9 A0      LDA #$A0
1C24 99 DC IE   STA $1EDC,Y
1C27 18         CLC
1C28 90 10      BCC $1C3A
1C2A E0 00      CPX #$00
1C2C F0 0C      BEQ $1C3A
1C2E A9 20      LDA #$20
1C30 99 DC IE   STA $1EDC,Y
1C33 CA         DEX
1C34 88         DEY
1C35 A9 A0      LDA #$A0
1C37 9D DC IE   STA $1EDC,X
1C3A 86 01      STX $01
1C3C 84 02      STY $02
1C3E A9 FF      LDA #$FF
1C40 8D 22 91   STA $9122

There are approximately 220 cycles in the unmodified interrupt service routine; thus, if the number POKEd into the timer approaches 220, there will be no time available for anything other than attending to the interrupt service routine.

Here's how to add your own machine language routine to the jiffy clock service routine. Normally, when the decrementing counter hits zero, the operation is transferred to the interrupt service routine whose beginning address ($EABF) is stored in 788 and 789 ($0314 and $0315). By changing the address in 788 and 789, you can tell VIC to do additional instructions in machine language and then go to $EABF to run the normal service routine.

To change the address in 788 and 789, you must disable the interrupt enable register for the jiffy clock to allow the number in these locations to be changed. POKEing location 37166 with 128 will disable the interrupt; after the addresses in 788 and 789 have been changed, POKEing location 37166 with 192 will enable the interrupts again. Here's a sample program:

15 FOR Z=0 TO 9:READ Q : P0KE( 28*256+Z ), Q :N
20 POKE37166, 128 :POKE788,0 :POKE789, 28:POK
   W GO TO PAGE 28
25 DATA 173,16,145,157,0,29,232,76,191,23

The machine language program in line 25 disas­sembles to:

1C00 LDA $9110;   Get data from user port
1C03 STA $lD00,X; Store data in page 29 ring buffer
1C06INX;          Increment pointer for ring buffer
1C07 JMP $EABF;   Jump to normal jiffy service routine

This program can be used as a guide for setting up the jiffy clock for timed data acquisition. One additional consideration in terms of the percent of time left for BASIC: the above program has added an additional fourteen cycles which must be added to the IR variable. Exercise caution if data is to be gathered at faster than half-millisecond intervals.