This is only a preview of the May 1990 issue of Silicon Chip. You can view 44 of the 104 pages in the full issue, including the advertisments. For full access, purchase the issue for $10.00 or subscribe for access to the latest issues. Articles in this series:
Articles in this series:
Articles in this series:
Articles in this series:
Articles in this series:
|
Digital waveform generation
using a computer, Pt.3
This month we continue our mini series on
digital signal generation with a couple of
interesting software listings. By putting in a
little work at the keyboard, you can turn
your PC into an assortment of audio test
gear.
By STEVE PAYOR
First up, we have an audio tone
burst generator capable of generating signals to the IHF standard, or any other you might care to
program. Listing 1 and the CRO
photographs of Fig.2 provide all the
details.
The program is written in
Borland's TURBO BASIC and
achieves a data rate of 20k
samples/sec. If you have a copy of
TURBO BASIC, the only change you
may need to make to the program is
the address of the parallel printer
port.
If you have a different BASIC
compiler (eg, Microsoft's QUICK
BASIC), try it. Once you have sorted
out any minor syntax clifferences,
and provided the compiler is not
pathetically slow, the only alteration you will have to make is to the
SAMPLE.TIME! variable (here set to
50E-6 or 50µs).
By the way, the"!" on the end of
a variable name makes it a real
variable instead of an integer. If
you want to save some typing by
shortening the variable names, just
be careful to keep them of the same
type, real or integer, as the original.
You can also save a lot of typing
by leaving out all the comments,
although this may be a false
economy in the long run. For the
uninitiated, anything to the right of
I
Data bits
D?
~----DURATION----~
BURST .DURATION
to 1kHz
+OdB
-.1dB
0
D-A
PC
RESISTOR
PARALLEL
NETWORJ<
PRINTER
PORT
a single quote mark is a comment,
or REMark statement. Although
these statements are completely ignored by the compiler and contribute nothing to the running of the
program, they are worth their
weight in gold to the programmer
because they describe each step.
The tone burst program functions
in much the same way as the frequency sweep generator described
last month. Once the data array has
been filled with bytes, they are output repeatedly until a key is
pressed.
The time taken for the program to
read the keyboard status leaves a
tiny "gap" in the waveform. This
small gap is not likely to be of any
real consequence over the normal
tone burst interval of half a second.
It becomes an embarrassment,
however, if you alter the tone burst
parameters in the hope of generating continuous sine waves with
this program. Generating a continuous waveform without any gaps
is a much more difficult undertaking and this is where our second
program excels.
IIUIIIIIIIUIUIIIIIIIIUIIIIIIIIIIIIIIIIUIIIIIIII-
3RD ORDER
LOW-PASS
FILTER
DO
-STROBE
i------------►
CRO sync pulse
~
BURST.START
Fig.1: how to set up the hardware for the tone burst program of Listing 1. The D-A converter and filter were described
in the March 1990 issue. Note that all the parameters of the tone burst (eg, frequency, burst duration, etc) are fully
programmable - just change the numbers in the program.
92
SILICON CHIP
Listing 1: Tone Burst Generator
• SILICON CHIP PROGRAMMABLE AUDIO TONE BURST GENERATOR <TURBO BASIC 1.1>
• Copyright CC> Slllcon Chip Publications Pty. Ltd., 1990
• Data rate: 20k saaples/sec <running on a 4.77MHz PC-XT>
DEFINT A-Z
• All variables are integers unless otherwise indicated
DIM TONE.BURST(lOOOO) • At 50vsec/saaple, this array can store up to 500asec
• of wavefor•
• (Note: The variables listed below are suitable for testing power a•plifiers
• to specification IHF-A-202 1978, The Institute of High Fidelity, Inc, USA.>
• Variables defining tone burst:
FREQUENCYalOOO
DURATION=500
BURST.DURATIONa20
BURST.START=20
MIN.AMPLITUDEl=.l
•
•
•
•
•
Frequency <Hz>
Total duration (in cycles of sine wave)
Burst duration <cycles>
Tiae of burst start after CRO sync pulse (cycles>
Aaplitude during •quiet• part of burst (0.1 = -20d8)
• Prograa constants:
PORT.A=&H378
PORT.C=PORT.A+2
Pil=3.141593
SAMPLE.TIMEl=50E-6
• Parallel port
• addresses
<Note: Other possible base addresses for
the printer port are Hex 3BC or Hex 278>
• Deterained by experiaent. Change this to suit faster
• CPU's, or faster or slower BASIC coapilers
• No. of saaples in one cycle
SAMPLES=l/FREQUENCY/SAHPLE.TIMEI
• Total no. of saaples in entire wavefor•
N•SAHPLES*DURATION
BURST.END=BURST.START+BURST.DURATION
FOR X=O TO SAMPLES-1
• This section of code fills the
SINE.WAVEl=127.5*SINCX/SAMPLES*6.2832)
• wavefora array with the required
FOR CYCLE=O TO DURATION-1
• signal
IF CYCLE<BURST.START OR CYCLE>=BURST.END THEN AMPl=HIN.AHPLITUDEI ELSE AMPl=l
TONE.BURST(CYCLE*SAMPLES+X>=AHPl*SINE.WAVEl+127.5
NEXT CYCLE
• <Be patient - it takes a whole
NEXT X
• 4 seconds of coaputing tiae>
TONE.BURST<N>=128
OUT &H21,INP<&H21) OR 1
• Disable DOS real tiae clock interrupt
WHILE NOT INSTAT
OUT PORT.C,O
• Positive edge of CRO sync pulse
FOR X•O TO N:OUT PORT.A,TONE.BURST<X>:NEXT X • Tone burst wavefora
OUT PORT.C,1
• Negative edge of CRO sync pulse
WEND
OUT &H21,INPC&H21) AND &HFE • Restore DOS real tiae clock interrupt
END
Full range
function generator
The program of Listing 2 can fit
waveform cycles together without
any visible seams, thanks to some
clever programming. First, it uses
a high-speed machine code subroutine to do the actual outputting
- we were able to tune this to a
speed of exactly 100k samples/sec
on an ordinary 4.77MHz XT PC.
Second, the code is written in
such a way that the time between
OUT instructions stays the same,
even while the loop counters are being reset.
The problem of breaking out of
the loop is solved by re-directing the
standard keyboard interrupt,
which accounts for the complexity
of an otherwise short program.
Because the interrupt is done by the
hardware, no instructions are
needed within the loop to check for
a keypress .
If you are worried about the size
of this listing, note that the machine
code can be typed in as a simple list
of decimal numbers, as shown at
the end of the listing. This conMAY 1990
93
Listing 2: Sine, Triangle & Square Wave Generator
10 • SILICON CHIP DIGITAL AUDIO SIGNAL GENERATOR (GWBASIC 3.22)
20 • Copyright CC) Silicon Chip Publications Pty. Ltd., 1990
30 • Data rate: 100k saaples/sec <running on a 4.77MHz PC-XT>
40'
50 DEFINT A-Z
• All variables are 16-bit integers unless specified
60 I=O:BYTE•O
• otherwise. The variables are being initialised here
70 NO.OF.BYTES=O
• for the sole purpose of establishing their locations
80 START.ADDRESS=O
• at the start of the GWBASIC data segaent. If an
90 PORT.ADDRESS=&H378
• unused variable happens to appear after the arrays
100 CODE.ADDRESS=O
• have been diaensioned, the arrays will be physically
110 FREQ!=O:V=O:Kt=••
• aoved to aake rooa for it, requiring the address
120
• pointer for each array to be re-evaluated
130 •
140 DIM NACHINE.CODE(55) • Sufficient space for 112 bytes of aachlne code
150 DIM BYTE.ARRAY(25000)' Wavefora storage space - 50k bytes - sufficient for
160
• Ii second at a data r ,a te of 100k bytes per second
170 •
180 DEF SEG
• Set Data Segaent register to GWBASIC•s data segaent
190 •
200 • MACHINE CODE SUBROUTINE TO OUTPUT A STREAM OF BYTES QUICKLY TO I/O PORT
210 •
220 • Usage: CALL CODE.ADDRESSCPORT.ADDRESS,START.ADDRESS.NO.OF.BYTES)
230 •
240 • The following section sets up the aachine code subroutine in an integer
250 • array MACHINE.CODE, within the GWBASIC data segaent:
260 •
270 START.ADDRESS=VARPTR<MACHINE.CODE<O>>
280 NO.OF.BYTES=t12
290 FOR I=O TO NO.OF.BYTES-1 • This section of code sets up the
300 READ BYTE
• aachine code subroutine within
310 POKE START.ADDRESS+I.BYTE • the GWBASIC data segaent
320 NEXT I
•
330 •
340 DATA &H8B,&HEC
:• MOV BP,SP
Load current stack pointer into BP
350 DATA &H8B,&H5E,&H08 :• NOV BX, CBP 1+8 Get address of 1st paraaeter
360 DATA &H8B,&H17
:• NOV DX.CBXJ
Load 1/0 port address into DX
370
:•
380 DATA &H8B,&H5E,&H06 :• MOV BX,tBPJ+6 Get address of 2nd paraaeter
390 DATA &H8B,&H37
:• HOV Sl,CBXJ
Load SI with starting address of bytes
400
:•
410 DATA &H8B,&H5E,&H04 :• HOV BX,tBP1+4 Get address of 3rd paraaeter
420 DATA &H8B,&HOF
:• HOV CX,CBXJ
Load no. of bytes into CX
430
:•
Clear direction flag
440 DATA &HFC
:' CLD
450
:•
460 DATA &HE4,&H21
:• IN AL,21H
Get interrupt aask register
470 DATA &HOC,&HOl
:' OR AL, 1
Disable tiaer interrupts
480 DATA &HE6,&H21
:• OUT 21H,AL
Write interrupt aask register
490
:•
500
: ' The next section of code alters the keyboard
510
: ' interrupt pointers so that a keyboard interrupt will
520
: ' get us out of the continuous signal generation loop
530
:•
540 DATA &HE8,&HOO,&HOO :• CALL 0
Puts IP for next instruction on stack
550 DATA &H5D
:' POP BP
Gets address of this instruction in BP
560 DATA &H83,&HC5,&H2F :• ADD BP.47
BP now points to the instruction after
the •infinite• loop below
570
:'
580 DATA &HB8,&HOO,&HOO :• HOV AX,O
Clear AX
590 DATA &H8C.&HDB
:• HOV BX,DS
Save DS in BX
600 DATA &H8E,&HD8
:• HOV DS,AX
Clear OS (to access low aeaory)
610 DATA &HFF,&H36,&H26,&HOO:' PUSH 26H
Save CS of interrupt pointer on stack
620 DATA &HFF,&H36,&H24,&HOO:' PUSH 24H
Save IP of interrupt pointer on stack
94
SILICON CHIP
630 DATA &HBC,&HOE,&H26,&HOO:' NOV 26H,CS Change CS of interrupt pointer
640 DATA &H89,&H2E,&H24,&HOO:' MOY 24H,BP Change IP of interrupt pointer
:• NOV DS,BX
Restore OS
650 DATA &HSE,&HDB
:,
660
670 DATA &H8B,&HD9
:• MOV BX,CX
Make a copy of CX in BX, and SI in DI
680 DATA &HBB,&HFE
:' MOV DI,SI
so that these registers can be reset
:
690
quickly
:,
700
710
:' Allowing for aeaory refresh interrupts, the loop
720
:' below executes in alaost exactly 10 psec, on a
730
:• 4.77MHz PC-XT
:,
740
750 DATA &H90
:' NOP
Adjusts loop tiaing
760 DATA &HAC
Load byte into accuaulator
: ' LOOS
Output byte
(40 clocks>
770 DATA &HEE
: ' OUT DX,AL
780 DATA &HE2,&HFB
Loop CX tiaes
: ' LOOP -5
790 DATA &H90
:' NOP
:• NOV CX,BX
800 DATA &H8a.&HCB
Restore loop counter
810 DATA &H88,&HF7
:• HOV SI,DI
Restore string pointer (32 Locks)
820 DATA &HAC
Load byte
: ' LOOS
830 DATA &HEE
: ' OUT DX,AL
Output byte
840 DATA &HE2,&HF3
Continue looping ----\40 clocks)
:' LOOP -13
:,
850
:• We get here on a keyboard interrupt. Before juaping
860
870
:' to the keyboard interrupt service routine, we need to
880
:' adjust the IRET address on the stack, to force the
890
:' interrupt service routine to return below
:,
900
910 DATA &H83,&HC5,&HOC :• ADD BP,12
Adjust BP to point to the interrupt
:,
920
address restoration routine below
930 DATA &H83,&HC4,&H02 :' ADD SP,2
Reaove IP for IRET from stack
940 DATA &H55
:• PUSH BP
Substitute the desired address
:
950
960 DATA &HBB,&HEC
:• MOV BP,SP
Get stack pointer into BP
970 DATA &HFF,&H6E,&H06 :' JMP CBPl+6
Juap off to service keyboard interrupt
:,
980
990
:• Now restore the original keyboard interrupt pointers
:,
1000
1010 DATA &HB8,&HOO,&HOO :' HOV AX,O
Clear AX
:• MOV BX,DS
1020 DATA &HSC,&HDB
Save DS in BX
1030 DATA &H8E,&HD8
:' HOV DS,AX
Clear OS
1040 DATA &H8F,&H06,&H24,&HOO:' POP 24H
Restore IP of keyboard interrupt
1050 DATA &HBF,&H06,&H26,&HOO:' POP 26H
Restore CS of keyboard interrupt
1060 DATA &HSE,&HDB
:' MOY DS,BX
Restore OS
:,
1070
1080 DATA &HE4,&H21
:' IN AL,21H
Get interrupt aask register
1090 DATA &H24,&HFE
Re-enable tiaer interrupts
: ' AND AL, FEH
1100 DATA &HE6,&H21
Write interrupt aask register
:' OUT 21H,AL
:,
1110
1120 DATA &HCA,&H06,&HOO :• RET 6
Return and pop GWBASIC's CALL
1130
:•
paraaeters fro• stack
.
<----,, <
<_J
.
1140'
1150 • Progra• proper starts here
1160 '
1170 CLS
1180 INPUT•Frequency <Hz) ••• •,FREQt:IF FREQ1(2 THEN END
1190 NO.OF.BYTES=lOOOOOI/FREQt 'Calculate no. of saaples in one cycle
1200'
1210' The following section of code sets up an array of data bytes for one
1220' cycle of the wavefora. Choose the appropriate calculation for sine,
1230' triangle or square waves by turning the unwanted lines into REM
1240' stateaents with a single quote aark
1250'
continued next page
MAY 1990
95
Listing 2: continued from previous page
1260
1270
1280
1290
1300
1310
1320
1330
1340
1350
1360
1370
1380
1390
340
350
360
370
380
390
400
410
420
430
440
450
START.ADDRESS=VARPTR<BYTE.ARRAY<O>>
FOR I=O TO NO.OF.BYTES-1
• Sine
V=l27.5*<l+COS(I/NO.OF.BYTES*6.2832))
• Triangle
• V=ABS<IINO.OF.BYTES*2~1)*255
• IF I<NO.OF.BYTES/2 THEN V=O ELSE V=255 • Square
• White noise?
• V=RND*255
POKE START.ADDRESS+l,V
NEXT I
•
CODE.ADDRESS=VARPTR<MACHINE.CODE(O)) • Get address of subroutine
•
CALL CODE.ADDRESS(PORT.ADDRESS,START.ADDRESS,NO.OF.BYTES)
•
K$=INKEY$:GOTO 1180 • Absorb unwanted keypress and request another freq.
DATA 139,236,139,94,8,139,23,139,94,6
DATA 139,55,139,94,4,139,15,252,228,33
DATA 12,l,230,33,232,0,0,93,131,197
DATA 47,184,0,0,140,219,142,216,255,54
DATA 38,0,255,54,36,0,140,14,38,0
DATA 137,46, 36,0,142,219,139,217,139,254
DATA 144,172,238,226,251,144,139,203,139,247
DATA 172,238,2 26,243,131,197,12,131,196,2
DATA 85,139,236,255,ll0,6,184,0,0,140
DATA 219,142,216,143,6,36,0,143,6,38
DATA 0,142,219,228,33,36,254,230,33,202
DATA 6,0
(Lines 460 to 1130 omitted)
If you don't feel like typing in the detailed machine code in the above
listing, substitute these condensed DATA statements instead.
denses the whole subroutine into 12
short lines. However, we have included the full listing for those who
wish to experiment with the
machine code.
The main body of the program is
written in GW BASIC, since speed is
no longer a problem. Some changes
to the machine code will be needed
to accommodate the differing calling protocol of other languages,
such as TURBO BASIC or "C".
Included in the listing are lines
for generating sine, triangle or
square waves. Fig.3 shows each
waveform at lkHz.
Is Your Product Getting
The Exposure It Deserves?
Consumers need to
see your product if you
want them to buy it
Contact Paul To Reserve
This Space - (02) 982 9553
96
SILICON CHIP
There is no reason why other
waveforms can't be added; eg, a
two-tone audio test signal for SSB
transmitters. You will notice that
waveform selection is a little
primitive - you have to disable the
ones that you don't want by inserting a single quote mark - but we
have done this to keep the listing
short. A proper program would let
you choose the waveform at run
time.
As shown, the program is set for
generating sinewaves. To generate
triangle waves, all you have to do is
insert a single quote mark at the
start of line 1280 and delete the
quote mark at the start of line 1290.
Square waves and white noise can
be generated by changing the program in similar fashion.
While we're on the subject of
limitations, we have removed the
need for an anti-aliasing filter by
rounding off the number of samples
per cycle to the nearest integer.
However, this means that the
highest frequencies are rounded to
the following values: 50kHz,
33.3kHz, 25kHz, 20kHz, 16.7kHz
and so on. In order to generate any
in-between frequencies, the program needs to step through more
than one cycle of the waveform.
This will generate a "double sideband" modulated signal, as
described last month.
The lower frequency limit is
determined by how long you are
prepare to wait for the array to be
filled.
A suitable filter will fix this. For
a start you can try the simple third
order filter described in March,
with the L and C values scaled
(a)
(b)
(c)
Fig.2: these waveforms were produced by the TURBO BASIC program
of Listing 1 . The CRO horizontal axis
is not to scale ih these photographs,
however the period of each cycle is
exactly 1ms.
(a) This is what an IHF standard tone
burst looks like, or at least a small
part of it. The entire tone burst
waveform lasts for a whole 500ms
and the short + 20dB burst is normally quite hard to capture on a
CRO. Our program provides a convenient sync pulse, which can be
positioned comfortably ahead of the
tone burst.
(b) Here we have programmed a
much shorter burst so we can check
out the waveform quality. As you can
see, it is virtually perfect except for a
small gap between the end of one
burst and the start of the next. This
gap occurs when the TURBO BASIC
program completes its high-speed
output loop and checks the
keyboard input. We used this small
time interval to put out the CRO sync
pulse.
(c) This is how the 1kHz waveform
looks without a low-pass filter. The
relatively low data rate of 20k
samples/sec makes a filter essential
for good waveform purity (see the
March 1 990 issue for complete
details of a filter suitable for this data
rate).
(a)
(b)
(c)
Fig.3: these three photographs
show typical waveforms produced
by the GW BASIC program of Listing
2, which has been optimised for
continuous waveform generation.
The horizontal axes are 100µs per
division. The output amplitude of the
D-A converter has been trimmed to
2.8V peak-to-peak, which gives a
nice round figure of 1 V RMS for the
sinewave.
(a) This is what an unfiltered 1 kHz
sinewave looks like at a data rate of
100k samples/sec. For most audio
applications, filtering may not be
necessary. Note that there are no
gaps between the end of one cycle
and the start of the next.
(b) The triangle wave option, again at
1kHz.
(c) The square wave option, also at
1kHz. If you want the square wave
to have the same RMS amplitude as
the sinewave, use the values 0 and
180 or, better still, 38 and 218 instead of 0 and 255 as in the
program .
down to increase the - 0. ldB passband from 4kHz to 20-25kHz (ie,
divide all the L and C values by 5).
This will be fine for sinewaves but
square and triangle waves will
show a tiny bit of ringing.
To do justice to this program, we
are currently working on a 5th
order filter which has a smoother
phase characteristic. This will be
built onto a small PC board which
will also accommodate the simple
A-D converter featured in February.
We will also be making available
a 360K 5.25-inch floppy disc containing source listings of all the
software described so far, along
with compiled and executable versions of all the programs (so you
won't need to buy a BASIC compiler). These programs will have a
number of " user friendly" enhancements which had to be left out
of the published listing; because
they would have taken up several
more pages.
~
MAY 1990
97
|