Newsgroups: comp.sys.hp48
Path: cdc486.cdc.polimi.it!ghost.dsi.unimi.it!batcomputer!caen!spool.mu.edu!howland.reston.ans.net!europa.eng.gtefsd.com!uunet!decwrl!pa.dec.com!nntpd2.cxo.dec.com!nntpd.lkg.dec.com!peavax.mlo.dec.com!pinbot.enet.dec.com!ervin
From: ervin@pinbot.enet.dec.com (Joseph James Ervin)
Subject: Re: Beep Frequencies
Message-ID: <1993Oct14.212226.24955@peavax.mlo.dec.com>
Lines: 384
Sender: usenet@peavax.mlo.dec.com (USENET News System)
Reply-To: ervin@pinbot.enet.dec.com (Joseph James Ervin)
Organization: Digital Equipment Corporation
X-Newsreader: dxrn 6.18-6
References: <01H42HN4M8C28ZDXOJ@VXC.OCIS.UNCWIL.EDU>
Date: Thu, 14 Oct 1993 21:22:26 GMT
In article <01H42HN4M8C28ZDXOJ@VXC.OCIS.UNCWIL.EDU>, ETLANE@UTCVM.BITNET (Eric T. Lane) writes:
|>BEEP Sound on the HP-48 Version 2.4 Wed 13 Oct 93
|>
|>by Eric T. Lane, Physics Department ETLane@UTCVM.UTC.EDU
|>University of Tennessee at Chattanooga 615-755-4523
|>
|>
|>Contrary to what many people think, the HP48 calculator does
|>not generate tones of arbitrary frequency. The HP48 seems to
|>generate tones based on a time delay between clicking the
|>piezoelectric transducer. The calculator picks the
|>appropriate time delay for a given range of frequencies and
|>uses that to represent all frequencies in that range.
The HP48 does not intentionally choose to produce the same
tone for a range of specified frequencies. It is just a side
effect of the granularity of the software producing the sound.
|>
|>The numbers that separate one generated tone from the next
|>higher one, all seem to end in one-half, .5. For example, a
|>series of separation numbers might be as follows.
|>
|> 7562.5 6446.5 5617.5 4977.5 4467.5 4053.5 3708.5 3418.5 ...
|>
Fractional Hz are always rounded off. No doubt it rounds up from .5 to
the next whole number.
|>Thus any number between approximately 3708.5 and
|>4053.499999999, inclusive, will give the SAME tone. Try it
|>and see.
No mystery here. Remember that as the frequency goes up, the apparent
affects of the granularity of the software become more apparent.
At lower frequencies like 500 Hz the granularity seems very fine indeed.
|>
|>Use << DUP .1 BEEP 1 + .1 BEEP >> produce two different tones
|>when you want to find a separation number.
|>
|>The numbers that separate the different tones seem to follow a
|>progression given approximately by the following formula.
|>
|> F = 45000 / ( N + 5.77 ) N=0,1,2,...
|>
|>This is consistent with the idea that the HP48 generates tones
|>by clicking its transducer every 22 N microseconds. N is the
|>number of time delays in the loop that generates the clicks to
|>produce the tone.
If you look at the software, you'll see that the level of granularity
is around 20 CPU cycles, which on an HP48-SX is pretty close to 22 usecs.
|>
|>Based on the formula for separation numbers above, the basic
|>timing loop for the tone generator takes about 5.77/45000 sec
|>= 128 microseconds. This is the minimum time interval between
|>clicks that the HP48 can make on the transducer. The formula
|>also implies that the timing loop for the tone generator takes
|>about 1/45000 second = 22 microseconds. We get a lower tone
|>for each higher number N of cycles the processor waits between
|>clicks. This means that any number between 3708.5 and
|>4053.499999 should give about a 4000 Hz tone.
You have gone to great pains to prove experimentally how the software
functions. Examining the code directly confirms your analysis, to your
credit.
|>
|>The parameters corresponding to 45000 and 5.77 must be found
|>anew each time the ON-C reset is used. The parameters
|>obtained seem to depend on the temperature, going down for
|>higher temperatures and up for lower. This would be
|>consistent with the design effort to compensate for the
|>variation in frequency of the timing crystal in an effort to
|>keep the frequency produced by the beeper as accurate as
|>possible. Apparently the designers wanted the HP48 to give
|>frequencies that are as accurate as possible at any given
|>temperature.
|>
Yes. The clock circuit used in the HP48 can vary considerably. The
calculator performs a self-calibration on warm start, and possibly at
other times, to determine the cycles/second of the CPU's clock. This is
adjusted for the overhead of the display refresh.
|>After the parameters have been correctly determined, this
|>formula is accurate to plus or minus 1 in the lower range of
|>numbers, N greater than 10, but only to plus or minus 10 in
|>the highest range, where N = 0 to 5.
|>
|>The HP48 Manual says that the highest tone generated in 4400
|>Hz. The numbers go higher than that, but the tones generated
|>by numbers larger than 4400 are apparently limited by the
|>piezoelectric transducer that produces the sounds. They sound
|>grainy and sometimes even sound at apparently lower
|>frequencies, probably because of resonance in the transducer.
|>
|><< 7800 .1 BEEP >> seems to give the highest number for which
|>the HP48 will produce a different tone, and so the shortest
|>time delay. Perhaps the designers only wanted to specify 4400
|>Hz as the highest frequency that could be produced with
|>certainty.
|>
|>I've developed quite a bit more detail on the separation
|>frequencies at different temperatures and the paramters for
|>obtaining them. I'd be glad to share it if anyone is
|>interested.
|>
I doubt that it would be of any use. The variation of one HP48 to
the next is significant, and the granularity of the frequency control
in the sound software might be too course to make any emperical data
useful.
|>Eric T. Lane, Univ.Tenn.Chattanooga ETLane@UTCVM.UTC.EDU
|>
Eric, your attention to detail is commendable, and you obviously have
a good measure of patience and determination to persue this topic this
far. Let me suggest that if you are really interested in a topic such
as this, you should not hesitate to post a request or two to the net.
There are a lot of folks reading this newsgroup who have already solved
a lot of complex problems.
For your enjoyment, I have attached my disassembly of the sound routine
employed by the HP48 rev E ROM. It is quite enlightening as to how
sound is produced. Of course, you seem to already have discovered any
secrets that the software may hold.... :-)
Take care, and keep hacking...
>>>Joe Ervin
By the way, do you have an HP48S or SX? Do you also have a PC? If so
I'll mail you a uuencoded zipped file containing my sound creation kit that
lets you make some really interesting sounds for the HP48, (lasers,
explosions, whatever you like). I haven't ported it to the G/X models
yet, but if people are interested I could be persuaded.
*************************************************************************
Here's the disassembly of the sound code, as promised...
1A5C4: ; *** BEEP (XLIB 2 52) ***
1A5C4: 02D9D ! Program
1A5C9: 18EDF ; Save current command, verify DEPTH >= 2 and check args.
1A5CE: 04099 ; <11h>
1A5D3: 1415A ; Internal BEEP (2:Real Number,1:Real Number)
1A5D8: 0312B ! End Marker
1415A: 02D9D ! Program
1415F: 02DCC ! Code
14164: 00035 ! 53 nibbles (next RPL at 14199)
14169: 8FADF92 GOSBVL 29FDA ; MC: pop stk1(Real Number)to A.W, save regs
14170: 32994 LCHEX 499
14175: 9B621 ?A>C X ; Check for excessively large exponent.
GOYES 1418A ; Branch of exponent leq 499.
1417A: 306 LCHEX 6 ;
1417D: 98A50 ?C>=A P
GOYES 14185
14182: A8E ACEX P
14185: 958A0 ?A=0 M
GOYES 14192
1418A: 32300 LCHEX 003 ; Multiply by 1000 to make time
; value into milliseconds.
1418F: A3A A=A+C X
14192: 8DD32A2 GOVLNG 2A23D ; MC: push A.W as Real Number, restore
; regs and continue RPL
. . . . . ! End of Code
14199: 62E7B ; Internal R->SB and SWAP (1:Real Number)
1419E: 18CEA ; Internal R->SB (1:Real Number)
141A3: 141B2 ; Internal BEEP (System Binary) (2:msec,1:Hz)
141A8: 4241B
141AD: 0312B ! End Marker
141B2: ; *** Internal BEEP (System Binary) (2:msec,1:Hz) ***
141B2: 141B7 ! Machine Code at 141B7
141B7: 8F14660 GOSBVL 06641 ; MC: pop stk1 (System Binary) into A.A
141BE: D6 C=A A ; This is the frequency in Hz.
141C0: 06 RSTK=C
141C2: 8F14660 GOSBVL 06641 ; MC: pop stk1 (System Binary) into A.A
; This is the duration in msecs.
141C9: 7110 GOSUB 141DE ; MC XFER: save D0,D1,B,D (uses C,D0),
; clear carry
141CD: 07 C=RSTK ; Get the Hz from the stack.
141CF: D7 D=C A ; D = Hz.
141D1: D6 C=A A ; C = msec.
141D3: 8F6A710 GOSBVL 017A6 ; MC: beep (C=msec,D=Hz)
141DA: 6820 GOTO 14203 ; MC XFER: restore D,B,D1,D0 (C=D0),
; clear carry and continue RPL
017A6: ; *** MC: beep (C=msec,D=Hz) ***
017A6: 1B2D607 D0=HEX 706D2 ; RPL System Flags -53 thru -56
017AD: 14A A=DAT0 B
017B0: 8087300 ?ABIT=1 3 ; Error Beep suppressed?
RTNYES
017B7: 8AB00 ?D=0 A ; frequency = 0?
RTNYES ; If yes then just return.
017BC: D5 B=C A ; Save time in B.
017BE: AF2 C=0 W
017C1: D9 C=B A ; Zero out upper nibbles of time...
017C3: 109 R1=C ; and save resulting word in r1.
017C6: DB C=D A ; Copy the Hz into C.
017C8: 1B6D407 D0=HEX 704D6 ; CSPEED. This reflects
; the speed of the CPU in 16 Hz units.
; This is needed since the BEEP code
; is software timed.
017CF: AF0 A=0 W
017D2: 142 A=DAT0 A ; Read the CSPEED word. Reading this
; via the memory scanner reveals a
; value of 1744000/16.
017D5: BF0 ASL W
017D8: 102 R2=A ; Multiply by 16 to get the actual
; cycles/second and save in R2. The
; resulting number is 1.744 Mhz. This
; reflects the effective cycles/second
; of the CPU after taking the overhead
; of the display controller.
017DB: A7A A=A+C W ; Add in the BEEP Hz. This is done
; prevent the accumulation of error
; due to discarded remainder from the
; upcoming integer divide.
017DE: A76 C=C+C W ; And double the Hz value. This
; makes some sense since we need to
; toggle the speaker at 2x the
; frequency.
017E1: 8F70856 GOSBVL 65807 ; A = A/C. This results in
; (cpu cycles/sec) (beep cyc/sec)
; ---------------- + ---------------
; halfwaves/sec halfwaves/sec
; Note that the second term simply increases the result of the integer
; divide by 0.5 on the average. This keeps the loss of the remainder from
; introducing excessive error into the calculations. This technique is used
; frequently when an integer divide is performed (see below).
; This gives us a measure of CPU cycles/halfwave. This tells us how much
; time we have to use in the overall loop between toggles of the speaker.
; The result is returned in A.
017E8: AF2 C=0 W ;
017EB: 20 P= 0
017ED: 3106 LCHEX 60 ; = 96 decimal.
017F1: B7A A=A-C W ; Subtract #60 from the number of
; cycles to use up per halfwave. This
; corresponds to the fixed amount of
; time used up in the inner loop.
017F4: 550 GONC 017FA ; Was the cycles/halfwave smaller
; than #60? If not, then skip down
; to 17FA.
017F7: AF0 A=0 W ; (can't have negative value here,
; so zero it out.)
017FA: 3141 LCHEX 14 ; Else, divide the resulting
; cycles/halfwave by 20 decimal. This
; is likely because the inner null
; loop takes around 20 cycles to
; execute.
017FE: 8F70856 GOSBVL 65807 ; Integer Divide (a=a/c). A = the
; number of iterations of the idle
; loop required to give the outer
; loop the desired period. Note that
; the accuracy technique of adding in
; 1/2 was not necessary since the
; divisor was so small compared to the
; dividend.
01805: AF2 C=0 W
01808: A3E C=C-1 X ; C.x = #FFF.
0180B: 9FA50 ?C>=A W ; Is resulting idle loop counter less
; than FFF?
GOYES 01813 ; If yes, then skip down to 1813.
01810: AFA A=C W ; Else use #FFF as the idle loop cntr.
; This prevents us from trying to
; generate frequencies too low to
; hear.
01813: 103 R3=A ; R3 = idle loop counter.
01816: 112 A=R2 ; Fetch the CPU speed from R2.
01819: 119 C=R1 ; Fetch the msec from R1.
0181C: 8F4EE35 GOSBVL 53EE4 ; Multiply. This gives the total
; number of CPU cycles that will
; transpire during the beep, but
; actually 1000 times too high
; because we measured time in
; msecs rather than seconds.
01823: 100 R0=A ; Save the total cycles in R0.
01826: 113 A=R3 ; A = Idle loop counter.
01829: AF2 C=0 W
0182C: 3302E4 LCHEX 4E20 ; C = 20*1000. This is the number
; of cycles used in the inner loop
; multiplied by 1000. We are working a
; factor of 1000 larger than reality
; here because of the use of msecs.
01832: 8F4EE35 GOSBVL 53EE4 ; A = A*C, which equals the number
; of cycles used up in the idle loop
; during one tick of the speaker
; (x1000).
01839: AF2 C=0 W
0183C: 3401E91 LCHEX 19E10 ; C = 106,000 decimal.
01843: A72 C=C+A W ; Add in the 106 cycles used by the
; rest of the outer loop. Again,
; this is multiplied by a factor of
; 1000 to take into consideration the
; fact that we're working with msecs
; rather than seconds.
01846: AF7 D=C W ; Save subtotal in D. This now equals
; the total number of cycles used up
; in one tick of the speaker (x1000).
01849: 81E CSRB ; Divide by 2. This is going to be
; added in before an integer divide
; for accuracy, as described above.
0184C: 110 A=R0 ; A = total number of cycles for whole
; beep (x1000).
0184F: A7A A=A+C W ; Add half of the divisor to the
; dividend before the division to
; account for loss of remainder.
01852: AFB C=D W ; C = cycles per tick (x1000)
; Divide total cycles by cycles per
; iteration of the outer loop.
01855: 8F70856 GOSBVL 65807 ; Integer divide. This gives
; the number of iterations of the
; outer loop.
0185C: A7C A=A-1 W ; Decrement the result by 1 since we
; will go through the loop once more
; than the loop counter value.
0185F: 550 GONC 01865 ; ...or if it was already zero then
01862: AF0 A=0 W ; just preserve the zero.
01865: AF6 C=A W ;
01868: AF7 D=C W ; D = outer loop counter.
0186B: 11B C=R3 ; C = idle loop counter.
0186E: AB5 B=C X ; B = idle loop counter.
01871: 808F INTOFF
01875: 1FE2100 D1=HEX 0012E ; Timer1 control.
0187C: 1574 C=DAT1 S ; Read the timer1 control nibble.
01880: D2 C=0 A
01882: 15D0 DAT1=C 1 ; Clear timer1 control nibble. This
; prevents time1 from generating any
; interrupts or wakes. This is
; restored at the end of the beep.
01886: 1B97607 D0=HEX 70679 ; ATTN flag. We watch for [on]
; presses while the sound is playing.
0188D: 1F3C407 D1=HEX 704C3 ; ORghost...just so as not to screw
; up the OUT register bits used for
; keyboard scanning.
01894: 1573 C=DAT1 X ; Get the ORghost.
01898: 8089B CBIT=1 11 ; Set the speaker bit ON.
0189D: 80D2 P=C 2 ; Save the speaker bit in P.
018A1: 8088B CBIT=0 11 ; Set the speaker bit OFF. This
; ensures that P and C.2 have opposite
; bit polarities at bit 11. This is
; used to toggle the speaker bit
; between successive iterations of the
; outer loop, by simply exchanging
; C.2 with P (at 18BE).
018A6: 6300 GOTO 018AA ; NOP
018AA: 142 A=DAT0 A ; [ON] pressed?
018AD: 8AC81 ?A#0 A
GOYES 018C8 ; Abort if [ON] has been pressed.
018B2: 801 OUT=C ; Tick the speaker on.
018B5: AB4 A=B X ; Get null loop counter.
018B8: A3C A=A-1 X
018BB: 5CF GONC 018B8 ; Two-instruction loop to waste time.
018BE: 80F2 CPEX 2 ; Toggle the speaker nibble.
018C2: A7F D=D-1 W ; Decrement the outer loop count.
018C5: 54E GONC 018AA
018C8: 1573 C=DAT1 X ; ********* Clean-up and exit *******
018CC: 801 OUT=C ; Toggle the speaker off.
018CF: 1FE2100 D1=HEX 0012E
018D6: 1554 DAT1=C S ; Restore the saved TIME1 control.
018DA: 20 P= 0
018DC: 8080 INTON ; Restore keyboard interrupts.
018E0: 03 RTNCC