\ DTMF.SEQ, Dual Tone Multiple Frequency Synthesizer,
20aug94cht
comment:
Right to Assemble
Lesson 1.
Dual Tone Multiple Frequency (DTMF) Synthesizer
The most important reason to use assembly programming
is when the code
produced by high level language is too slow,
especially in some time
critical routines. The advantage in Forth is that one can
code an
application entirely in high level. When time becomes an issue, one
can identify the time-critical parts of the code and
substitute them
with routines coded in assembly. Forth provides an ideal environment
for this optimization, because a program is generally
modularized in
words. A
word can be coded either in high level or in assembly. As
long as the data stack usage and the memory usage are
the same, a
word coded in assembly behaves identically as the one
coded in high
level.
The only difference is that the assembly word runs faster.
DTMF is such an example. But, you may ask, what is DTMF?
DTMF is the dial tones you hear when you dial a
telephone number
on a telephone with push-buttons. When you push a key, say "1",
on
this phone, it generates a short pulse of two
different tones, one
of 697 Hz and one of 1209 Hz. These tones are sent to the computerized
switch board in the telephone company, notifuing the
switch board that
a "1" is dialed by you. When you complete pushing the buttons
defining the telephone number you like to get
connected, the switch
board decodes the telephone number from the dual-tone
pulses and
ring the telephone you want.
The advantage of DTMF is that after you are connected
to the telephone
you just dialed, you can push the buttons and the
corresponding dual-tone
pulses are sent to the destination telephone. If the destination
telephone is equiped with a DTMF decoder connected to
an intelligent
device, you can interact with this device
remotely. The most prevailing
of such devices is the automatic answering
machine. You must have
dealed with many of them, as you hear the message
such as: "If you
want to place an order, push 1. If you want more information, push 2.
If you want ..." Wonder of DTMF.
The frequencies of the dual-tones corresponding to
all the keys on
a push-button telephone are listed in the following
table:
Col
0 Col 1 Col 2 Col 3
1209 Hz 1336 Hz 1477 Hz 1633 Hz
Row 0 697 Hz 1 2 3 A
Row 1 770 Hz 4 5 6 B
Row 2 852 Hz 7 8 9 C
Row 3 941 Hz * 0 # D
Now, how can I generate these dual-tones in my PC?
In all PC's and the clones, there is a speaker. Can one generate
dual-tones using this speaker? The answer is no. This speaker is a
simple binary device. You can turn it on or off to generate a
single
tone, but you cannot produce a dual-tone. You need two such speakers
to do the job.
The easiest way is to hang two small speakers to two
digital output lines in the parallel printer
port. Each of the two
speakers will generate one tone. If you can hold these two speakers
close to a telephone handset, you can sent the
dual-tone pulses into
the telephone.
If you connected two small 8 ohm speakers to bit 0
and bit 1 output
pins in the parallel printer port, you can generate
two tones by
switching these two bits on and off. You are producing square waves,
not the sine waves preferred by the telephone
company. However, the
cheap 8 ohm speakers are themselves very good filters
which strongly
attenuate the high frequency spectrum in a square
wave. Those
who studied physics carefully would remember that the
first harmonics
in a square wave has a frequency 3 times that of the
fundamental
frequency.
The lowest DTMF tone has a frequency of 697 Hz. Its
first harmonic has a frequency of 2091 Hz, which is
much higher than
the highest DTMF tone of 1633 Hz. Therefore, the harmonics in the
square waves do not interfere with the fundamental
tones in DTMF.
At the other end of the telephone line, the switch
board in the
telephone company uses very sharp band-pass filters
to detect the
dial tones.
These filters reject noises produced in this DTMF
synthesizer,and we don't have to worry too much about
the noises.
At the first sight, using a PC to genrate tones is a
trivial task,
as the frequency required is below 2000 Hz, and the
computer can
execute instrunctions at a rate of a million
instructions per second.
However, generating one tone with high precision in
frequency is
not that easy, and generating two very accuracy tones
simultaneouly
is a tough challenge to a software engineer. Using high level
languages, there is no solution. Using assembly, we have a fighting
chance.
Experiment 1.
Connecting the speacker
Connect two leads of a small 8 ohm speaker to pin 2
and pin 25 of the
DB25 female parallel printer port at the back of the
PC. You may want to
use a parallel port extension cable to bring the
parallel port to the
front of the computer so you can see what you are
doing.
The parallel port is operated by sending byte data to
an output port
with an I/O address of hex 3BC or 378. In Forth, you can use the
commands:
HEX
1 3BC PC!
to turn on the speaker, and
0
3BC PC!
to turn off the speaker.
Executing these commands in rapid succession will
generate a tone on
the speaker.
I am using a 4.77 MHz XT type PC. If you use a different PC, the timing
has to be adjusted accordingly.
comment;
empty
Hex
3BC constant DataPort
\ 378 constant DataPort
\ alternate parallel port address
: ramp 1000 0 do
\ bit 0 period: 170 us, 6 KHz
i
dataport pc!
loop
;
: rr begin ramp key? until ;
comment:
Executing rr will produce a tone of about 6 KHz
frequency on the
speaker.
If you have an oscilloscope, you can probe pins 2-9 and you
will see square waves of decending frequencies on
these pins.
It takes about 170 us to switch the speaker on and
off once. This
170 us time resolution is very poor. You cannot accurately generate
even the single tones required by DTMF, not to
mention dual-tones. The
frequencies of the tones you can generate are 6000/n
Hz, where n is
1, 2, 3, 4, ...
You don't have many choices.
Experiment 2.
Now, let's code the same square-wave producing
routine in assembly
and see what kind of improvements we can get.
comment;
code fastRamp
\ Bit 0 period 17 us, 60 KHz
mov
cx, 0 #
\ use CX as counter register
mov
dx, DataPort #
\ DX must have output port address
here
inc al
\ 8 byt pattern to be sent out
out dx, al
\ to the parallel port
loop
\ repeat 65536 times
next
\ return
end-code
: rrr
begin
fastramp \
loop 65536 times
key?
\ allow user to break
until ;
comment:
The switching period is thus reduced to 17 us, a 10
times improvement
on the speed.
So the frequencies we can generate is 60000/n Hz.
The accuracy of tones in the 700-1600 Hz region is
now about 3% at
the high frequency end and 1% at the low frequency
end. This is
adequate for DTMF.
Experiment 3.
Dual Tone Synthesizer
To implement a dual tone synthesizer, we need two
fast counters to
control the tones and a big counter to control the
duration of the
dual-tone pulses. The fast counters are 8 bit in width and
the big
count needs 16 bits. The algorithm is then:
1. Set duration counter
2. Select output port
3. Begin duration loop
4. Decrement tone counter 1
5. If tone counter 1 is 0,
then:
Reset tone counter 1 to preset counter 1 value
Toggle bit 0 in output register
6. Decrement tone counter 2
7. If tone counter 2 is 0,
then:
Reset tone counter 2 to preset counter 2 value
Toggle bit 1 in output register
8. Send pattern in output
register to output port
9. Decrement duration
counter
10.
If duration counter is non-zero, go back to (3.)
11.
Duration counter is zero, done and exit
The register allocation is as follows:
AL
Output pattern register
DX
Output port address register
CX
Duration counter
BL
Tone counter 1
BH
Tone counter 2
The frequencies of tone 1 and tone 2 are determined
by the contents
in variables Tone1 and Tone2. The duration of the dual-tone pulse
is determined by the contents in variable Duration.
The code which generates a dual-tone pulse is
DualTone. The word
dd is used to test DualTone.
comment;
variable Tone1
\ tone 1 counter
variable Tone2
\ tone 2 counter
variable Duration
\ duration counter
code DualTone
\ 37 us for one loop in this routine
mov
cx, Duration \
init duration counter
mov
dx, DataPort #
\ set output port
here
\ start of big loop
dec
bl
\ decrement tone 1 counter
jnz
1 $
\ jump if not 0
mov
bl, tone1
\ counter 1 is 0, reset it
xor
al, 1 #
\ toggle counter 1 output bit
1 $:
dec bh
\ decrement tone 2 counter
jnz
2 $
\ jump if not 0
mov
bh, tone2
\ counter 2 is 0, reset it
xor
al, 2 #
\ toggle counter 2 output bit
2 $:
out dx, al
\ send out output pattern
loop
\ decrement duration counter and loop
next
\ duration counter is 0, done
end-code
: dd ( tone1 tone2 -- )
\ convenient test code
tone2 !
\ init tone counter 1
tone1 !
\ init tone counter 2
DualTone
\ generrate dual-tone pulse
;
comment:
The principal loop in DualTone averages to 37 us per
loop. This is
the time resolution of this dural-tone
synthesizer. From this time
slice value, we can compute the delay counts for the
variables Tone1
and Tone2:
DelayCount = 1000000/(Frequecy*37)
From this equation, we can deduce the following
table:
Frequency Count Measured Hz Error (%)
697
39
690
1.0
770
35
770
0.0
852
32
840
1.5
941
29
940
0.0
1209
22
1220
0.7
1336
20
1320
1.2
1477
18
1520
2.9
1633
16
1690
3.5
The dual-tone synthesizer is pretty good up to 1300
Hz. The highest
frequencies at 1477 and 1633 Hz suffer an error to
3%. This is about
the best we can do with a 4.77 MHz XT. A faster CPU can reduce this
error in proportion to the speed. Using a 10 MHz AT, the error should
be about 1.5%.
With a 33 MHz 386, the error can be reduced to 0.5%,
which will make the phone company very happy.
Experiment 4. The Dial Tones
comment;
\ tone table
\ row 697, 770, 852, 941 Hz
\ column 1209, 1336, 1477, 1633 Hz
decimal
10000 Duration ! \
about a 0.5 second pulse
39 22 2constant dt1
\ key "1"
39 20 2constant dt2
\ key "2"
39 18 2constant dt3
\ key "3"
39 16 2constant dtA
\ key "A"
35 22 2constant dt4
\ key "4"
35 20 2constant dt5
\ key "5"
35 18 2constant dt6
\ key "6"
35 16 2constant dtB
\ key "B"
32 22 2constant dt7
\ key "7"
32 20 2constant dt8
\ key "8"
32 18 2constant dt9
\ key "9"
32 16 2constant dtC
\ key "C"
29 22 2constant dt*
\ key "*"
29 20 2constant dt0
\ key "0"
29 18 2constant dt#
\ key "#"
29 16 2constant dtD
\ key "D"
: PlayDualTone ( char -- ) \ play a
dual-tone selected by a character
CASE
ascii 0 OF dt0 dd ENDOF
ascii 1 OF dt1 dd ENDOF
ascii 2 OF dt2 dd ENDOF
ascii 3 OF dt3 dd ENDOF
ascii 4 OF dt4 dd ENDOF
ascii 5 OF dt5 dd ENDOF
ascii 6 OF dt6 dd ENDOF
ascii 7 OF dt7 dd ENDOF
ascii 8 OF dt8 dd ENDOF
ascii 9 OF dt9 dd ENDOF
ascii A OF dtA dd ENDOF
ascii
B OF dtB dd ENDOF
ascii C OF dtC dd ENDOF
ascii D OF dtD dd ENDOF
ascii # OF dt# dd ENDOF
ascii * OF dt* dd ENDOF
drop
ENDCASE
100
ms
;
: DIAL (
play a dual-tone sequence specifiedd by a 11 digit phone number )
32
word count
0
do count PlayDualTone
loop
drop
;
\
Example: DIAL
0118869651234
\ which dials a phone in Taipei, Taiwan
comment:
DIAL expects a number string corresponding to the
telephone number
you want to dial. The number string takes numerals 0 to 9,
capitalized
characters A to D, "*" and
"#". It will ignor all
other characters,
including lower case "a" to
"d". It stops at the end
of the string,
terminated by a carriage return or a space.
Experiment 5. Interactive Dialing
DIAL sens out a dual-tone sequence. We can program the PC and use its
numeric keypad as a telephone keyboard. It then sends out one dual-tone
pulse when a valid key is pressed. Press "ESC" terminates the
manual
dialing.
As all the tools are available now, this code is
trivial.
comment;
: ManualDialing
begin key dup
27 = abort" Dialing done"
PlayDualTone
again
;