Here is a program that demonstrates a simple yet effective way to get analog information (data) into your ZX81/TS1000/TS1500 computer, with a minimum of additional hardware. Also included are various plot and display routines so you can get visual and printed output of your acquired data. Along the way it will show you various “tricks” and other tidbits that might help out in your programming.
A little background is in order. My original intent was to write a “universal” data acquisition program for the popular “VOTEM” analog interface. “Universal” is a pretty broad term, though; it didn’t take me long to realize that a truly general program would be difficult to implement in 16K. A better approach is to write a number of customized programs, each tailored for a certain range of applications. If nothing else, possibilities for display and printer formats are almost endless, not to mention ways in which the data may be processed once acquired. So what I’ll try to do in this series of articles is to present some “basics” from which you can expand to suit your application.
In this issue I present a complete program which will sample data at a rate from about 1 sample per sec. to about 350 samples per second, with resolution between 8 bits (fastest sample rate) and 14 bits (slowest sample rate.) It can store 2048 discrete samples, each one consisting of two bytes. Data is organized into “screens” of 64 samples. Each screen can be acquired/ displayed individually, or several screens can be chained for longer time-bases. As listed, the capacity is 32 screens, but you can easily make this more or less with simple changes. TS2040 printer is supported, and you can even do “side-winder” plots with a vertical resolution of 60 points and length (width?) depending only on the number of screens plotted. Plot limits are easily changed, and screen plots are done in machine-code for speed. Since this program is optimized for speed, no data display occurs during acquisition, and there are no additional pause routines for longer time-bases. This will be the topic for the next issue. But for now, let’s backtrack a little and look at how the hardware works, and then at what you’ll need, hardware-wise, to make the software work.
Theory
There are several ways to get analog data into a computer. The usual approach is to use an analog-to-digital converter; such devices have at least one analog input channel, and a digital data bus for output. The binary number appearing at the data bus is proportional to the voltage at the analog input. Converters are available to give a resolution anywhere between 8 and 14 bits (15 bit with a minus sign is present “state of the art”; that’s 4 significant figures. ed.), depending on the accuracy required. Eight-bit converters are relatively easy to implement on an eight-bit CPU like the Z80, since the data can be impressed directly onto the CPU’s data bus. For higher-resolution A-D’s the acquisition has to be done in two “chunks,” using some decoding scheme to first look at the first 8 bits of the output bus, then at the remaining bits. In either case, the page to start a conversion can be either I/O or memory-mapped. The resulting data is then loaded into memory for subsequent display and other processing.
With all its advantages (mainly speed) this approach does have a few drawbacks. First among these is that it requires connection to the computer’s data bus (8 lines) as well as to at least part of the address and control busses (lines like MREQ, IORQ, RAMCS, etc). Another drawback is that matters start getting quite complicated if greater than 8-bit resolution is required. Because of all the circuitry needed, most folks would prefer simply buying a commercial module rather than mess with all those wires, decoding, timing circuits, etc. See the Ener-Z Report Generator review elsewhere in this issue for an overview of one such package. Another is the Computer Continuum “(8+8)*8” A-D, D-A board (reviewed in a future issue.)
There is another route we can take if high speed (faster than about 300 per second on the ZX-TS) is not of the essence. It is an old method, but still very useful. In this method, the voltage is first converted into a stream of on-off pulses whose frequency is proportional to the input voltage. Such a device is called, aptly enough, a “voltage-to-frequency converter,” or more simply a “V-F.” Now all that is required is a single inport bit to get the stream of pulses into the computer. It is a serial rather than parallel approach.
Your ZX/TS has a dedicated input port already – the EAR jack. A single Z80 IN A,(FE) command gets the state of the EAR jack input in bit 7 (the most significant bit;) from there it is a simple matter to write software to count how many pulses (state changes) appear during a fixed time span – the resulting count will be proportional to the input voltage. By appropriately modifying the IN command, this approach will work with any computer with an IN port such as a cassette input. Calibration can be done entirely in software, making the hardware aspect simple indeed.
V-F converters almost always use a circuit called an “integrator,” which provides an output voltage proportional to the integral of the input voltage. This means that the higher the voltage to the input, the faster the output from the integrator will ramp up (increase). This signal is applied to a comparator, and when it exceeds the comparators upper trip point the comparator toggles (changes state), at the same time resetting the integrator. Result – the higher the input voltage, the higher the frequency of the pulses at the output of the comparator. See Figure 1. This is called “single-slope V-F conversion.” Also possible is what is termed “dual-slope conversion,” in which the conversion is done in two steps; a known reference voltage is used to ramp the integrator up, and the unknown voltage is used to ramp the signal back down. The ratio of the ramp-down time to the ramp-up time equals the ratio of the reference voltage to the unknown voltage, from which the unknown voltage can be easily derived. The dual-slope approach is more immune to drift and other inaccuracies, and allows “auto-calibration” in dedicated devices like the ubiquitous Digital Volt Meter (DVM). (Virtually all inexpensive DVM’s use some variant on this approach – as I mentioned earlier, we’re not dealing with anything new and esoteric here.)

When using V-F data acquisition, accuracy depends on the “acquisition interval.” This is how long we are counting, and therefore how many counts we read for a given input voltage. If “full-scale” (the highest voltage we wish to measure) only returns 256 counts, we get 8-bit accuracy; but if it returns 16384 counts at full scale we have 14-bit accuracy. So you see that the more accurate you want it, the longer you’ll have to sample the input pulses. How fast you can go depends on computer speed; on the ZX-TS it takes about 1 second to get maximum resolution (better than 14 bit, or about .005%.) At the other extreme, we can get 8-bit resolution (about .5%) with acquisition times down to a few milliseconds.
In this program we’ll experiment with the relationship between speed and accuracy by making the acquisition interval equal to the sampling interval; as soon as one sample is acquired, the program goes right on to the next, with no additional timing pauses. Timing is varied by changing the acquisition interval; as a result, the full-scale count and the number of counts per volt changes with sample-rate, and has to be compensated for when converting the counts into voltage or other units.
Next time we’ll add programmable pauses so the sampling interval is greater than the acquisition interval for longer time-bases (minutes, hours, days). This will also allow time to update a continuous display during acquisition. You are encouraged to customize and improve these programs from there, to come up with exactly what you need for your application.
What applications can it be put to? Well, just briefly mentioning some of the possible input devices should help suggest plenty of ideas. You could use a potentiometer for position sensing (simple robotics, wind or antenna rotor direction indication, etc.), a photocell (light meter, etc.) or a strain transducer (for forces, stresses.) Use a microphone as the heart of a decibel (sound level) meter. Let’s not forget about temperature probes, for clinical/ meteorological “thermographs,” solar heating control, or heat-transfer studies. For some uses you won’t even need the V-F. The obvious example is as a frequency counter (this program will handle frequencies from about 2 Hz to about 20 kHz.) Similarly, you won’t need the V-F for such things as radiation monitors (just run a Geiger counter output to the EAR jack and have the computer count the pulses. Use Tom’s moving-trend routine to smooth the data as required.)
A final point before we get on to brass tacks is that the V-F approach lends itself very well to remote sensing; you can locate the V-F with the sensing device, and bring the data to the computer along a single digital line (use a comparator for cleanup at the receiving end if needed.) This is much more accurate and free of glitches than running a long analog line, and is cheaper and easier than bringing the data in on parallel data + control lines. But now that I’ve firmly convinced you that “you NEED this program,” let’s take care of the hardware aspect, and then briefly go over the system software (the fun part).
The Hardware
If you have a ZX/TS with 16K RAM and a VOTEM analog interface, you already have all the hardware you need. If you don’t have a VOTEM, you should consider purchasing either a complete or partial kit (any combination of bare board, documentation and V-F chip). (they are still available. ed.) Down East Computers‘ prices are not out of line, and their unit is the best approach if you’re not willing to build a V-F board from scratch.
For those of you who don’t mind a little hardware hacking, I’m printing a “rip-off” circuit for the LM331 V-F chip, derived from the National Semiconductor application notes for this device. It is accurate, inexpensive, and quite readily obtainable (if you can’t find it a your local jobbers’, check with the nearest location of Avnet/Hamilton.) Values shown are for a 1 volt full-scale sensitivity, you can easily sense higher ranges by preceding the V-F with a resistive voltage divider. The absolute precision of the other parts is non-critical, since we’ll calibrate the final circuit anyway (using a software scale constant).

What IS important is that the timing parts be stable with temperature; the timing capacitor should be polystyrene or polycarbonate, and the resistors should be metal-film. Avoid extreme temperature changes around the V-F board if possible. Wiring is non-critical, and you can use wire-wrap on a perf board to do the actual construction. You can power the V-F from the computer supply, or use a small battery (especially recommended if you’ll be locating the interface remotely.)
You can use the Radio Shack 276-1790 V-F, F-V IC. Applications notes are provided with the part. (Though we won’t be using it, this unit also allows you to convert from a frequency back to a voltage – you could conceivably work up a circuit which will translate a frequency -such as you can make available at the MIC jack- into an analog voltage, which can be used to control such things as proportional heaters, motors, etc. More about this in a future installment.)
If you’re using a VOTEM, you should disconnect capacitor C8 at the input to the V-F, or replace it with a much smaller unit (e.g. .01 uF), otherwise you’ll get considerable waveform distortion due to its filtering effect. (A 20 Hz. square wave won’t look the least bit “square.”)
Whatever V-F circuit you use, the maximum output frequency should be limited to about 25 kHz. At higher frequencies you’ll rapidly get inaccuracy due to “strobing” effects, since the rate at which the sampling loop runs is about 65 kHz. The amplitude requirement is that the “on” voltage should be 2-5 volts, and the “off” voltage should be a few tenths of a volt maximum. Duty cycle (ratio of On-time to total cycle time) is non-critical, but shouldn’t deviate too far from 50% for best results. Run the output to the EAR jack, and the hardware and interfacing is complete.
The Software
In order to be able to present the entire program in one installment, I’ll have to gloss over some “fine points” regarding the program this time. This is perhaps just as well, since what you learn on your own is much more likely to stick than what you read. Instead, I’ll concentrate on how to get the program entered into your computer, and on how to use it once it’s debugged and safely on tape. Thus you’ll have something to play with while we’re working up the additions and improvements for the next episode. You shouldn’t have much difficulty entering and debugging it, and you can use any of several different ways to enter the machine-code portion.
The Machine Code
First you have to enter the machine-code. Start with the usual line 1 REM statement, 407 bytes long. Use a toolkit or enter it the “hard way,” one character at a time. Another way of doing it is shown in the listing for line 1 in the BASIC listing on page XX. Enter 1 PRINT 0 followed by 50 repetitions of +0. The computer interprets each zero as a floating point number – so it uses up six additional bytes for the binary representation of each zero. The first 0 takes 7 bytes, each +0 takes 8 bytes. We’re using the ZX’s extravagant number storage to our advantage, requiring only 101 keystrokes to enter 407 bytes. Note that this won’t work using 1 REM 0+0+… because after a REM, the zeros are interpreted as characters (1 byte each). So use PRINT, then change the PRINT to a REM using POKE 16513,234.
However you get the REM statement in there, you can check that you have the right length by entering PRINT PEEK 16921, which should return 118 (end of line marker). If you want to make line 1 more resistant to accidental deletion, POKE 16510,0 to assign it a line number of zero. Now enter a decimal loader as on page XX, setting the counter limits (in line 10) FOR A=16514 TO 16843, and use the values shown in the decimal table. Or use a hex loader or assembler and enter the program from the Hot-Z listings. The locations from 41CCh (16844d) to 41D7h (16855) are machine-code variables, and need not be entered. Similarly, the 64 bytes at 41D8-4217h (16856-16920d) represent a machine-code “array”, which holds the “timing value” for each screen. If you want more than 32 screens, make the original REM longer – e.g. for 40 screens, add two more “+0″‘s. (16 bytes.) This timing value is the number that is loaded into 409C-409Dh (16551/2d) in the acquisition routine, which determines how many times the program loops to take another look at the state of the inport bit 7, and sets our acquisiton interval or “window.”
The first three routines are nested loops; MULTISCREEN calls ACQUIRE A SCREEN, which calls ACQUIRE A POINT. You could save some time between samples to improve accuracy as low timing values by writing it as one routine, but I left it as shown to make it a little easier to follow. The acquisition routine is a slight variation on the routine supplied with VOTEM. The next routine gets the start address of the specified screen being written to/read from. The data is stored in the first BASIC variable, D$(4096), which has to be twice as long as the total number of points. Although the array is actually organized as 32 screens of 64 2-byte points, DIM D$(32,64,2) or DIM D$(32,128) will not work with this storage system, D$ must be single-dimensioned.
The next routine draws the axes MUCH faster than any BASIC loop could, especially for the vertical axis. The rest of the code is the data graphing program, consisting of an arithmetic package (so we can do 16-bit by 16-bit division) which is called by the plotting routine to evaluate each Y-position. The arithmetic package is a portion of the run-time package of Bob Berch‘s ZX COMPILER, and is actually more than we need here, as it also contains multiplication, ABS, SGN and other functions. It was used here to demonstrate that a compiler (especially Bob Berch’s, of Cinagro Software, 19 Jacques St., Rochester, NY 14620) is a very worthwhile thing to buy even if you’re more interested in hand-assembly; you can find many useful routines in the run-time package that will save you time in assembly by giving you function utilities to call when needed. Stay tuned to SWN for a significant article by Bob which shows how you can drive a serial printer from your MIC jack – meanwhile, make it worth his while to submit this article (and otherwise keep supporting your machine) by ordering his compiler or other programs.
If you wish to get graphs dumped to your Centronics printer via Memotech CIF, you have to enter the print buffer and conversion table as per SWN 1:3, then enter the machine-code of this program into a line 2 REM statement. It will be displaced by 348d bytes, starting at 16862. You will have to change (offset by 348) all CALLs and references to machine-code variables or use Hot-Z II to relocate it for you. You’ll also have to change routine addresses in the BASIC, and add the screen-dump given in 1:4 to replace the COPY command.
The BASIC
Once the machine-code is in, the BASIC should present no special problems. You might want to rearrange the menu options – e.g. put “Initialization” as option 1 and start it at line 1000, put “Acquire a Screen” at option 2 and start at 2000, or however you want it. Though the BASIC listing is only 2 pages long, there’s quite a lot going on here, including some “frills” you could do without (like the pseudo-INPUT routine at 8000, which inputs 2-digit numbers without pressing Enter, or 1 digit numbers using Enter).
When using RUN for the first time (after SAVEing in case you have bugs in the machine-code), the program goes to the initialization/ calibration part of the program. You have to calibrate first voltage (calibrating the V-F) and then the time-base (calibrating the computer). Use a voltage close to full-scale, and monitor it with an accurate DVM while pressing Enter. The reference to “switches 3,6 and 7” refer to VOTEM, in which this connection measures the 1V reference built into the V-F. The display will disappear for about 1 second, then input the measured reference voltage. The program prints the observed count and counts per volt (at the longest timing value possible). Then you have to time how long it takes to acquire a screen of 64 points; it will be between 60 and 66 seconds, depending on the speed of your particular machine. Enter the actual time, and the computer is calibrated for time-base.
When acquiring data, you have three options for starting the acquisition; by pressing a key (option “K”), when the input frequency becomes non-zero (press “N”) or when the input frequency goes to zero (press “Z”). You may have other requirements, which you should be able to program if you study how these options work.
Some Important Variables
Here are some of the crucial variables in the program. Some (like R, CL, C0) are also machine code variables: the address is shown where applicable.
VREF full scale calibration value
C counts at VREF. C0=65535 (later a tem. var.)
C1 counts per volt at C0=65535
C0 (16540-41) Timing value (number of acquisition
loops). C0=65535*TB/TREF. Stored in C0 array
for each screen.
TREF Time required to acquire 1 screen at C0=65535
TB Time-base for 64 acquisitions (1 screen)
SR Sample Rate (mSec.)=1000*TB/64
CM counts per volt (or kHz.)=C1*C0/65535
CL (16852/53) Count at lower plot limit (=PL*CM)
CH Count at upper plot limit =PH*CM
R (16850/51) Plot ratio =(CH-CL)/(max. y val.)
Y-position given by (count-CL)/R
PL value at bottom of graph
PH value at top of graph
DP order of magnitude of accuracy (for # of
decimal places)
Operation of the rest of the features is pretty straight-forward, and the prompts should help. When doing a side-winder plot, you might save paper if you first do a “dry run” (prints to screen only) to make sure your data doesn’t go off scale.
Multi-screen acquisition and display asks first which screen to start at, then how many screens the acquisition is to run. Obviously you can’t acquire more screens than you have available after the starting screen. The program then loads the timing value into the timing value machine-code array for each screen involved. (Assures correct scaling for each screen when plotting and printing later.)
Line 9999 shows an interesting option to the STOP command, first reported in SYNAPSE (Central PA Users’ Group.) Look ma, no report code! Verify that the program indeed has stopped by pressing Enter or LISTing.
Another tidbit is demonstrated in lines 2090-2210 and a couple other places. S1 and S2 are the two bytes of the SEED variable which is set by RAND. This is a great way to convert a decimal number (e.g. R) into two-byte form; just RAND R, and then PEEK 16434 and 16435 to get the high and low bytes for that number. One warning – it doesn’t work with zero, since RAND 0 means the same as RAND – i.e. get the value currently in the FRAMES variable. So for the low-count variable CL (count corresponding to the bottom of the screen) we have to use another approach (lines 2120-2130), as this variable may be zero. I first saw this programming device in Ralph Coletti‘s newsletter (no longer being published,) though it has also appeared in a couple other publications. Lines 4-7 restore the program listing cursor and the top line in automatic listings to line 8, in case you list line 1 and the automatic listing gets stuck. These POKEs can be done in the immediate mode also.
When printing the data values, the program takes the acquisition interval into account and shows you only as many decimal places as you can expect for that acquisition rate.
You may wish to change the display format; this routine prints a two-column listing specifically with the TS2040 in mind, but is a little awkward for “screen-only” data display. Likewise, you might come up with a faster side-plot approach, perhaps using a m/c routine similar to the single-screen plot.
Other challenges: – write additional lines in the printing routine to compensate for the non-zero time between acquisitions. Modify the program to work with Nissim Elmaleh‘s Software-only Hi-Res or Russell’s HRP hi-res printer package, or with Memotech HRG or other hi-res hardware package. Translate the program for use with the TS2068 – shouldn’t give much trouble, though upgrading the display routines to hi-res might give you a challenge. Work out the machine-code to plot quantities with negative slope factors (such as most temperature probes.) Above all, have fun with this! More later.

More Important Variables
FL (16844/45) Which file? (screen)
N (16849) Number of screens
D (16846/47) Start address of data
VL Voltage to print in left column
VH Voltage to print in right column
N0, N1, N2, O0, H5, TFS, BL, S1, S2 - common numbers.
A, Y, L, F - FOR/NEXT variables.
Routines
FI line 7900. Input which file (into FL)
SS line 7950. Set "number of screens" into N.
FS line 8200. Set C0 values for files being acquired.
FR line 8300. Read C0 for that file and evaluate CM,
TB, SR, CL, and CH.
CO line 8400. COpy or Continue
D$ Data (2048 points total
Y$,Z$ Keyboard input.
The Machine Code
Enter the listing below, then RUN it. When it stops, delete lines 10 and 11, replacing them with the “Simple Loader Program” given. RUN this program and input the numbers shown in the “Decimal Listing” table. Start with the top left column and work horizontally. Ex: 169, ENTER, 166, ENTER, 185, ENTER, 166, ENTER and so forth. When the screen fills, press CONT and ENTER to continue. When finished, delete the loader program and enter the Basic listing.
1 PRINT 0+0+0+0+0+0+0+0+0+0+0
+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0
+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0+0
+0+0+0+0+0+0+0+0
4 POKE 16394,8
5 POKE 16395,0
6 POKE 16419,8
7 POKE 16420,0
8 REM [illegible]
9 REM BY F.NACHBAUR 1984
10 POKE 16513,234
11 REM RUN, THEN DELETE 10,11.
START M/C LOADER HERE,
DELETE WHEN M/C ENTERED
SIMPLE LOADER PROGRAM
10 FOR X=16514 TO 16843
20 PRINT X;" ";
30 INPUT Y
40 POKE X,Y
50 PRINT Y
60 NEXT X
THE DECIMAL LISTING
169:166:185:166:128:166:182:155: 171:179:157:165:164:160:000:059: 000:029:027:031:000:000:001:000: 000:017:255:255:027:122:179:040: 018:219:254:007:048:246:003:027: 122:179:040:007:219:254:007:056: 246:024:233:201:062:064:042:206: 065:034:206:065:050:208:065:205: 152:064:042:206:065:113:035:112: 035:058:208:065:061:200:024:233: 058:209:065:061:200:050:209:065: 205:182:064:034:206:065:024:240: 000:062:128:042:016:064:017:006: 000:025:237:075:204:065:011:009: 061:040:002:024:250:034:206:065: 201:000:017:033:000:042:012:064: 035:062:020:054:027:025:061:040: 002:024:248:062:032:054:027:035: 061:200:024:249:000:014:001:124: 170:230:128:071:213:205:106:065: 227:205:106:065:235:225:121:167: 040:054:120:177:229:237:098:237: 082:229:193:209:033:000:000:055: 063:245:122:083:030:008:041:048: 005:143:237:074:024:009:135:237: 074:056:003:237:066:061:060:029: 032:236:095:241:048:226:203:079: 032:001:235:203:127:200:024:011: 205:116:065:203:016:208:024:003: 124:007:208:235:167:237:098:237: 082:201:197:006:016:124:077:005: 014:000:041:203:017:023:048:001: 025:016:247:193:201:000:062:000: 050:208:065:042:206:065:022:000: 095:025:025:078:035:070:197:225: 237:091:212:065:237:082:056:031: 237:091:210:065:205:023:065:000: 000:035:035:124:254:000:056:015: 125:254:044:048:010:069:058:208: 065:079:062:128:205:178:011:058: 208:065:060:254:064:200:024:191:
The BASIC
8 REM [illegible]
9 REM BY F.NACHBAUR 1984
10 GOTO 5000
12 REM
15 REM [illegible]
500 CLS
510 POKE BL,N0
520 SLOW
530 PRINT TAB 6;"[illegible]",,,TAB 6;"PROGRAM 1, V 1.3";TAB 6;"SAMPLE RATE 3-1000 MSEC.",,,L$;TAB 3;"(C)1984","BY F.NACHBAUR",L$
540 PRINT ,,"[illegible]",,,"1. ACQUIRE DATA (SINGLE SCREEN)","2. PLOT DATA",,"3. PRINT DATA",,L$;"4. RE-SET PLOT LIMITS","5. INITIALIZE",,L$;"6. MULTI-SCREEN ACQUISITION","7. SIDE-WINDER PLOT (TS2040)",L$,,"A. STOP ","S. SAVE "
550 IF INKEY$="" THEN GOTO 550
560 LET Z$=INKEY$
570 POKE BL,N2
580 IF Z$="S" THEN GOTO 9970
590 IF Z$="A" THEN GOTO 9990
600 IF Z$>"7" OR Z$<"0" THEN GOTO H5
610 CLS
620 GOTO N2*H5*VAL Z$
1000 REM [illegible]
1010 LET N=N1
1020 GOSUB FI
1030 POKE 16849,N+N1
1040 PRINT "ENTER TIME BASE (.1-";TREF;" SEC): ";
1050 INPUT TB
1060 IF TB<.1 OR TB>TREF THEN GOTO 1050
1070 LET C0=(65535*TB/TREF)
1080 RAND C0
1090 POKE 16540,PEEK S1
1100 POKE 16541,PEEK S2
1110 GOSUB FS
1120 PRINT TB
1130 PRINT ,,"START BY [illegible]EY OR [illegible]ON-[illegible]ERO INPUT ?"
1140 LET Z$=INKEY$
1150 GOTO 1140+(80 AND Z$="K")+(120 AND (Z$="N" OR Z$="Z"))
1160 FAST
1170 RAND USR AQ
1180 SLOW
1190 PRINT AT 21,N0;"==[illegible]=="
1200 GOSUB CO
1210 GOTO H5
1220 PRINT AT 21,N0;"[illegible]"
1230 IF INKEY$<>"" THEN GOTO 1230
1240 IF INKEY$="" THEN GOTO 1240
1250 GOTO 1160
1260 PRINT AT 21,N0;"CHECKING..."
1270 LET C=USR 16536
1280 IF (C AND Z$="N") OR (NOT C AND Z$="Z") THEN GOTO 1160
1290 GOTO 1270
2000 REM [illegible]
2010 CLS
2020 GOSUB FI
2030 GOSUB FR
2040 LET R=INT ((CH-CL)/40)
2050 CLS
2060 PRINT (" "+STR$ PH+" V --------------------------") ( TO 32)
2070 RAND USR 16636
2075 POKE BL,N0
2080 PRINT AT O0,N0;"*";AT 21,N0;PL;TAB O0;"<---TIME--->";TAB 27;TB;TAB 14;"[illegible]:";FL
2085 POKE BL,N2
2090 RAND R
2100 POKE 16850,PEEK S1
2110 POKE 16851,PEEK S2
2120 POKE 16853,INT (CL/TFS)
2130 POKE 16852,CL-TFS*PEEK 16853
2140 RAND USR 16776
2150 GOSUB CO
2160 GOTO H5
3000 REM [illegible]
3010 GOSUB FI
3020 FAST
3030 GOSUB FR
3040 CLS
3050 PRINT ,,"FILE NO:",FL,"TIME BASE =",TB;" SEC.","SAMPLE RATE = ";SR;" MSEC",L$,,"[illegible]",,
3060 LET DP=O0**INT (.2+LN CM/LN O0)
3070 FOR A=N1 TO 32
3080 FAST
3090 LET D=N2*(A-N1)+PEEK 16846+TFS*PEEK 16847
3100 LET VL=(PEEK D+TFS*PEEK (D+1))/CM
3110 LET VL=(INT (VL*DP))/DP
3120 LET VH=(PEEK (D+64)+TFS*PEEK (D+65))/CM
3130 LET VH=(INT (VH*DP))/DP
3140 PRINT A;TAB 3;INT (A*SR+N1/N2);TAB 9;VL;TAB 16;"▒";A+32;TAB 20;INT ((A+32)*SR+N1/N2);TAB 26;VH
3150 IF A=14 THEN GOSUB CO
3160 IF A=14 THEN CLS
3170 NEXT A
3180 GOSUB CO
3190 GOTO H5
4000 PRINT "PRESENT LIMITS:",,"MIN. VOLTAGE=";PL,"MAX. VOLTAGE=";PH
4010 PRINT ,,"CHANGE TO:",,"MIN.=";
4020 INPUT PL
4030 PRINT PL,,"MAX.=";
4040 INPUT PH
4050 PRINT PH,,"OK?"
4060 IF INKEY$="" THEN GOTO 4060
4070 IF INKEY$="Y" THEN GOTO H5
4080 CLS
4090 GOTO 4000
5000 PRINT "[illegible]"
5010 DIM D$(4096)
5020 LET L$="[illegible]"
5030 LET N0=0
5040 LET N1=1
5050 LET N2=2
5060 LET O0=10
5070 LET TFS=256
5080 LET S1=16434
5090 LET S2=16435
5100 LET H5=500
5110 LET BL=16418
5120 LET FI=7900
5130 LET SS=7950
5140 LET FS=8200
5150 LET FR=8300
5160 LET CO=8400
5170 LET AQ=16594
5180 LET PL=N0
5190 LET PH=N1
5200 LET N=N1
5210 PRINT "SET SW. 3,6,7; MEASURE VREF",,,"VREF=";
5220 INPUT VREF
5230 PRINT VREF
5240 POKE 16540,255
5250 POKE 16541,255
5260 FAST
5270 LET C=USR 16536
5280 SLOW
5290 PRINT ,," MAX COUNT=";C
5300 LET C1=C/VREF
5310 PRINT "1V COUNT AT MAX TIME =";C1;" "
5320 PRINT ,,"SET TIME-BASE; PRESS ENTER WHEN READY, NOTE TIME WHEN DISPLAY RE-APPEARS."
5330 IF INKEY$<>CHR$ 118 THEN GOTO 5330
5340 FAST
5350 LET FL=N1
5360 GOSUB 7915
5370 LET C0=65535
5380 GOSUB FS
5390 PRINT "LAST COUNT=";USR 16566
5400 SLOW
5410 PRINT ,,"TIME=?";
5420 INPUT TREF
5430 PRINT TREF
5440 GOSUB CO
5450 GOTO H5
6000 REM [illegible]
6010 CLS
6020 PRINT "START AT "
6030 GOSUB FI
6040 PRINT
6050 GOSUB SS
6055 IF FL+N>33 THEN GOTO 6000
6060 PRINT ,,,,"(EACH SCREEN)="
6070 GOTO 1030
7000 REM [illegible]
7010 GOSUB FI
7020 GOSUB SS
7025 IF FL+N>33 THEN GOTO 7000
7030 GOSUB FR
7040 LET R=((CH-CL)/60)
7050 LET DR=N0
7060 PRINT ,,"DRY RUN?"
7070 IF INKEY$<>"" THEN GOTO 7070
7080 IF INKEY$="" THEN GOTO 7080
7090 IF INKEY$="Y" THEN LET DR=N1
7100 FAST
7110 IF NOT DR THEN LPRINT ,,"TB=";TB;" SEC PER SCREEN","SAMPLE RATE=";SR;" MSEC.","TOTAL TIME=";N*TB;" SEC"
7120 IF NOT DR THEN LPRINT ,,STR$ PL;"=MIN. VOLTS";TAB 21;"MAX.=";PH
7130 LET D=PEEK 16846+TFS*PEEK 16847
7140 LET F=FL
7150 LET C=N0
7160 LET L=N0
7170 LET TM=N0
7180 FOR L=L TO L+21
7190 SCROLL
7200 IF L>=32*N THEN GOTO 7340
7210 PRINT AT 21,30;".";CHR$ (O0*NOT TM);AT 21,N0;"T";AT 21,N0;(CHR$ (F+156)+"----.----+----.----+----.----I") AND NOT C
7220 FOR Y=N1 TO N0 STEP -N1
7230 LET B=PEEK D+TFS*PEEK (D+N1)
7240 LET X=INT ((B-CL)/R+N1/N2)
7250 IF X<64 AND X>=N0 THEN PLOT X,Y
7260 LET D=D+N2
7270 NEXT Y
7280 LET TM=TM+N1
7290 LET C=C+N1
7300 IF TM=5 THEN LET TM=N0
7310 IF C<>32 THEN GOTO 7340
7320 LET C=N0
7330 LET F=F+N1
7340 NEXT L
7350 IF NOT DR THEN COPY
7360 IF DR THEN PAUSE 4E4
7370 IF L<32*N THEN GOTO 7180
7380 GOTO H5
7900 LET N$="WHICH SCREEN? (01-32)= "
7905 GOSUB 8000
7910 LET FL=A
7915 POKE 16844,FL
7920 POKE 16845,N0
7925 RAND USR 16611
7930 RETURN
7950 LET N$="HOW MANY SCREENS? ( TO "+STR$ (33-FL)+"): "
7960 GOSUB 8000
7970 LET N=A
7980 RETURN
8000 PRINT ,,N$;
8010 LET Z$=INKEY$
8020 IF Z$<"0" OR Z$>"9" THEN GOTO 8010
8030 PRINT Z$;
8040 IF INKEY$<>"" THEN GOTO 8040
8050 LET Y$=INKEY$
8060 IF Y$="" THEN GOTO 8050
8070 IF CODE Y$=118 THEN GOTO 8110
8080 PRINT Y$;
8090 IF Y$<"0" OR Z$>"3" OR (Z$="3" AND Y$>"2") OR Y$>"9" THEN GOTO 8160
8100 GOTO 8130
8110 LET Y$=Z$
8120 LET Z$="0"
8130 PRINT " *"
8140 LET A=O0*VAL Z$+VAL Y$
8150 RETURN
8160 CLS
8170 GOTO 8000
8200 RAND C0
8210 FOR F=FL TO FL+N-1
8220 POKE 16854+2*F,PEEK S1
8230 POKE 16855+2*F,PEEK S2
8240 NEXT F
8250 RETURN
8300 LET C0=PEEK (16854+N2*FL)+TFS*PEEK (16855+N2*FL)
8310 LET CM=C1*C0/65535
8320 LET TB=INT (TREF*C0/655.35+.5)/O0/O0
8330 LET SR=N2*H5*TB/64
8340 LET CL=PL*CM
8350 LET CH=PH*CM
8360 RETURN
8400 POKE 16418,N0
8410 SLOW
8420 PRINT AT 23,N0;"[illegible]- COPY ";TAB 23;"[illegible]- CONT "
8430 POKE 16418,N2
8440 IF INKEY$="Z" THEN COPY
8450 IF INKEY$="C" THEN RETURN
8460 GOTO 8440
9970 CLS
9974 PRINT "[illegible]"
9976 IF INKEY$<>"" THEN GOTO 9976
9978 IF INKEY$="" THEN GOTO 9978
9980 CLS
9982 SAVE "VDAQ[illegible]"
9984 GOTO H5
9990 CLS
9991 POKE BL,0
9992 PRINT AT 21,0;" GOTO 500 TO RE-START","""ENTER"" TO LIST "
9993 FAST
9999 POKE 16384,89
HEX-ASSEMBLY LISTING
(Annotated as best I can)
ADDR HEX NAME DEC ADDR CHR$ --------------------------------- 4082 A9 169 16514 [illegible] 4083 A6 166 16515 [illegible] 4084 B9 185 16516 [illegible] 4085 A6 166 16517 [illegible] 4086 80 128 16518 [illegible] 4087 A6 166 16519 [illegible] 4088 B6 182 16520 [illegible] 4089 9B 155 16521 [illegible] 408A AB 171 16522 [illegible] 408B B3 179 16523 [illegible] 408C 9D 157 16524 [illegible] 408D A5 165 16525 [illegible] 408E A4 164 16526 [illegible] 408F A0 160 16527 [illegible] 4090 00 0 16528 4091 3B 59 16529 V 4092 00 0 16530 4093 1D 29 16531 1 4094 1B 27 16532 . 4095 1F 31 16533 3 4096 00 0 16534 4097 00 0 16535
ADDR HEXCODE MNEMONIC ACQ. A POINT
=============================
4098 010000 LD BC,0000 :RESET COUNT
409B 11FFFF LD DE,FFFF :SET TIMING LOOP
409E 1B DEC DE :LOOP 1
409F 7A LD A,D DEC. TIMING VALUE
40A0 B3 OR E IS IT ZERO?
40A1 2812 JR Z,40B5 EXIT IF YES
40A3 DBF3 IN A,(FE) :GET INPORT VALUE
40A5 07 RLCA BIT 7 INTO CARRY
40A6 30F6 JR NC 409E :LOOP 1 IF "OFF"
40A8 03 INC BC :ELSE INCR. COUNT
40A9 1B DEC DE :LOOP 2
40AA 7A LD A,D DEC. TIMING VALUE
40AB B3 OR E IS IT ZERO?
40AC 2807 JR Z 40B5 EXIT IF YES
40AE DBFE IN A,(FE) :GET INPORT VALUE
40B0 07 RLCA BIT 7 INTO CARRY
40B1 38F6 JR C 40A9 :LOOP 2 IF "ON"
40B3 18E9 JR 409E :ELSE LOOP 1
40B5 C9 RET
ACQ. A SCREEN
40B6 3E40 LD A,40 :INIT. COUNTER
40B8 2ACE41 LD HL,(41CE) :GET START ADDR.
40BB 22CE41 LD (41CE),HL :STORE ADDR.
40BE 32D041 LD (41D0),A :STORE COUNTER
40C1 CD9840 CALL 4098 :GET DATA IN BC
40C4 2ACE41 LD HL,(41CE) :PUT DATA
40C7 71 LD (HL),C INTO ADDRESS
40C8 23 INC HL AND INCREASE ADDR.
40C9 70 LD (HL),B BY 2 FO NEXT POINT
40CA 23 INC HL
40CB 3AD041 LD A,(41D0) :GET COUNTER
40CE 3D DEC A :DECR. COUNTER
40CF C8 RET Z :ALL DONE?
40D0 18E9 JR 40BB :NO? DO NEXT POINT
MULTI-SCREEN
40D2 3AD141 LD A,(41D1) :GET # OF SCREENS-N
40D5 3D DEC A :DECREMENT
40D6 C8 RET Z :ALL DONE?
40D7 32D141 LD (41D1),A :NO. STORE N
40DA CDB640 CALL 40B6 :ACQUIRE SCREEN
40DD 22CE41 LD (41CE),HL :STORE ADDRESS
40E0 18F0 JR 40D2 :LOOP BACK
GET START ADD.
40E2 00 NOP
40E3 3E80 LD A,80 :PUT 128d INTO A
40E5 2A1040 LD HL,(VARS) :GET VARS START
40E8 110600 LD DE,0006
40EB 19 ADD HL,DE :AND ADD 6
40EC ED4BCC41 LD BC,(41CC) :GET WHICH SCREEN
40F0 0B DEC BC :AND SUBTRACT 1
40F1 09 ADD HL,BC :ADD FILE NUMBER
40F2 3D DEC A TO ADDRESS
40F3 2802 JR Z 40F7 128 TIMES
40F5 18FA JR 40F1 :LOOP BACK
40F7 22CE41 LD (41CE),HL :STORE FINAL ADDR.
40FA C9 RET :AND RETURN
AXES
40FB 00 NOP
40FC 112100 LD DE,0021 :DE=33 CHAR/LINE
40FF 2A0C40 LD HL,(DFIL) :GET DISP.FILE ADDR
4102 23 INC HL AND ADD 1
4103 3E14 LD A,14 :NO. OF LINES=22
4105 361B LD (HL),1B :FILL VERTICAL DOTS
4107 19 ADD HL,DE :NEXT LINE ADDR
4108 3D DEC A :NEXT LINE COUNTER
4109 2802 JR Z 410D :CONT IF DONE
410B 18F8 JR 4105 :ELSE LOOP BACK
410D 3E20 LD A,20 :32 CHAR. ON LINE
410F 361B LD (HL),1B :FILL HORIZ. DOTS
4111 23 INC HL :NEXT COLUMN
4112 3D DEC A :NEXT COL. COUNTER
4113 C8 RET Z :RETURN IF DONE
4114 18F9 JR 410F :ELSE LOOP BACK
4116 00 NOP
THE 16-BIT DIVISION ROUTINE
BELOW IS PRINTED COURTESY OF
BOB BERCH. CONSULT THE TEXT FOR
HIS ZX COMPILER AND FOR OTHER
ROUTINES AND ENTRY POINTS.
4117 0E01 LD C,01
4119 7C LD A,H
411A AA XOR D
411B E680 AND 80
411D 47 LD B,A
411E D5 PUSH DE
411F CD6A41 CALL 416A
4122 E3 EX (SP),HL
4123 CD6A41 CALL 416A
4126 EB EX DE,HL
4127 E1 POP HL
4128 79 LD A,C
4129 A7 AND A
412A 2836 JR Z 4162
412C 78 LD A,B
412D B1 OR C
412E E5 PUSH HL
412F ED62 SBC HL,HL
4131 ED52 SBC HL,DE
4133 E5 PUSH HL
4134 C1 POP BC
4135 D1 POP DE
4136 210000 LD HL,0000
4139 37 SCF
413A 3F CCF
413B F5 PUSH AF
413C 7A LD A,D
413D 53 LD D,E
413E 1E08 LD E,08
4140 29 ADD HL,HL
4141 3005 JR NC 4148
4143 8F ADC A,A
4144 ED4A ADC HL,BC
4146 1809 JR 4151
4148 87 ADD A,A
4149 ED4A ADC HL,BC
414B 3803 JR C 4150
414D ED42 SBC HL,BC
414F 3D DEC A
4150 3C INC A
4151 1D DEC E
4152 20EC JR NZ 4140
4154 5F LD E,A
4155 F1 POP AF
4156 30E2 JR NC 413A
4158 CB4F BIT 1,A
415A 2001 JR NZ 415D
415C EB EX DE,HL
415D CB7F BIT 7,A
415F C8 RET Z
4160 180B JR 416D
4162 CD7441 CALL 4174
4165 CB10 RL B
4167 D0 RET NC
4168 1803 JR 416D
416A 7C LD A,H
416B 07 RLCA
416C D0 RET NC
416D EB EX DE,HL
416E A7 AND A
416F ED62 SBC HL,HL
4171 ED52 SBC HL,DE
4173 C9 RET
4174 C5 PUSH BC
4175 0610 LD B,10
4177 7C LD A,H
4178 4D LD C,L
4179 05 DEC B
417A 0E00 LD C,00
417C 29 ADD HL,HL
417D CB11 RL C
417F 17 RLA
4180 3001 JR NC 4183
4182 19 ADD HL,DE
4183 10F7 DJNZ 417C
4185 C1 POP BC
4186 C9 RET
4187 00 NOP
PLOT A SCREEN
4188 3E00 LD A,00 :INIT. X COUNTER
418A 32D041 LD (41D0),A :STORE COUNTER
418D 2ACE41 LD HL,(41CE) :GET START ADDRESS
4190 1600 LD D,00 :PUT COUNTER IN D
4192 5F LD E,A AND E
4193 19 ADD HL,DE AND ADD TO ADDRESS
4194 19 ADD HL,DE TWICE
4195 4E LD C,(HL) :GET DATA INTO BC
4196 23 INC HL
4197 46 LD B,(HL)
4198 C5 PUSH BC
4199 E1 POP HL :NOW IT'S IN HL
419A ED5BD441 LD DE,(41D4) :GET CL INTO DE
419E ED52 SBC HL,DE :HL=COUNT-CL
41A0 381F JR C 41C1 :NEGATIVE? SKIP IF SO
41A2 ED5BD242 LD DE,(41D2) :GET R INTO DE
41A6 CD1741 CALL 4117 :DIVIDE HL/DE
41A9 00 NOP RESULT IN HL
41AA 00 NOP :NOPS AND INC HL'S
41AB 23 INC HL USED TO TRIM
41AC 23 INC HL VERTICAL OFFSET
41AD 7C LD A,H :CHECK HI-BYTE
41AE FE00 CP 00 FOR OVERRANGE
41B0 380F JR C 41C1 :SKIP IF TOO HIGH
41B2 7D LD A,L :CHECK LO-BYTE
41B3 FE2C CP 2C FOR OVERRANGE
41B5 300A JR NC 41C1 :SKIP IF TOO HIGH
41B7 45 LD B,L :Y COORD. IN B
41B8 3AD041 LD A,(41D0) :GET X COORD.
41BB 4F LD C,A AND PUT INTO C
41BC 3E80 LD A,80 :SET UP FOR -PLOT-
41BE CDB20B CALL 0BB2 :CALL -PLOT-
41C1 3AD041 LD A,(41D0) :GET COUNTER
41C4 3C INC A AND INCR. IT
41C5 FE40 CP 40 :ALL DONE?
41C7 C8 RET Z :YES, GO HOME
41C8 18BF JR 4189 NO, LOOP BACK
41CA 00 NOP
Products
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.