From : Dan Bridges 3:640/820.2
Subj : PIT tone generation (1/5)
────────────────────────────────────────────────────────────────────────────────
Here's some snippets on producing tones using the PIT (Programable Interval
Timer #2) in the 8254 chip on most machines. (An XT used a 8253 which will also
work with this particular example.) It comes from a series I wrote on "Learning
Assembler using DEBUG" that appeared in the Brisbug PCUG magazine Significant
Bits. These snippets come from "Part 10: Resident Virus Detection & PIT Tone
Generation" that appeared in the Mar 95 issue.
The articles were aimed at novices and used DEBUG, where possible, to both
interest readers who might not yet own an assembler and to demonstrate the
usefulness of DEBUG.
First off, here's a DEBUG session to demonstrate PIT programming:
C:\> DEBUG
; Syntax of DEBUG port commands:
; "I p" = Input from port p.
; "O p b" = Output to port p byte b.
-O 43 B6 ; Set Timer 2 to Sq Wave,
; Binary Counting, LSB first mode.
-O 42 00 ; Set count to 0400h (1024).
-O 42 04 ; Freq=1,192,180/1024=1165.
-I 61 ; What's the current setting
30 ; of port 61h? Remember this.
-A100 ; Calculate "OR current, 3h"
xxxx:0100 MOV AX,30 ; to work out the
xxxx:0103 OR AX,3 ; value required to
xxxx:0106 ; only set bits 1&0.
-G=100 106
AX=0033 ; Only AX shown.
-O 61 33 ; Turn on the tone.
-O 42 00 ; Halve the frequency
-O 42 08 ; of the tone.
-O 61 30 ; Restore original port 61h
; value i.e. stop the tone.
-Q ; Quit DEBUG.
There was a program written for the XT called REBEEP that kept up a pulsed
beeping until you pressed any key to stop it. This is handy if you're in another
room and would miss a single beep and is more distinct that a continuous tone.
Unfortunately on modern machinery REBEEP sounds quite frenetic since the time
delays in it were loop-based. What is needed ts a means of creating a
machine-independent time delay. The AT BIOS includes INT 15h Func 86h Delay and
INT 15h Func 83h Start Coundown Interval Timer routines. (XT users won't be able
to use this method.)
Here is a DEBUG session that demonstrates a Int 15h Func 86 delay:
C:\> DEBUG
-A100 ; Start assembling
xxxx:0100 MOV AH,86 ; Int 15h, Func 86h. Wait for CX:DX µs.
xxxx:0102 XOR DX,DX ; In this example zero DX.
xxxx:0104 MOV CX,40 ; CX:DX = 400000h = 4,194,304 = 4.2s.
xxxx:0107 INT 15 ; Start the delay.
xxxx:0112 MOV AX,0E07 ; Int 10h, Func 0Eh. Write char from AL.
xxxx:0115 INT 10 ; ASCII 07h is Bell char. Produces a beep.
xxxx:010E ; Press Enter to terminating assembling.
-G=100 10E ; Execute the routine.
Note: The CX:DX notation does not indicate Seg:Off format here, rather a dword
with CX as the most significant byte and DX as the least significant byte.
The Int 15h Func 83h Start Countdown Timer method isn't needed for REBEEP-style
utility. It differs in functionality from its Func 86h cousin in that Func 83h
allows you to do other actions during the countdown period. However you have to
ensure that you don't try to reinvoke it while it's already in use.
Here's a DEBUG session to demonstrate its operation:
C:\> DEBUG
-A100
0CA6:0100 MOV AX,8300 ; Int 15h, Func 83h, Subservice 00h.
; Start countdown interval timer.
0CA6:0103 XOR DX,DX ; DX=00h. CX:DX is interval in µs.
0CA6:0105 MOV CX,A0 ; 00A0:0000h = 10,485,760 = 10.4s.
0CA6:0108 MOV BX,130 ; ES:BX points to an indicator byte. Here
; I've decided on a byte at 0CA6:0130h.
0CA6:010B MOV BYTE PTR [130],0 ; Zero this memory loc.
0CA6:0110 INT 15 ; Start the countdown.
0CA6:0112 MOV AX,0E07 ; Int 10h, Func 0Eh. Write AL char.
0CA6:0115 INT 10 ; ASCII 07h is Bell char. Produces a beep.
0CA6:0117 CMP BYTE PTR [0130],80 ; When interval has completed
; bit 7 in the ind byte will be set. With Bit 7 ON, 00h -> 80h.
0CA6:011C JNZ 112 ; Keep looping (beeping) until timeout.
0CA6:011E ; Press Enter to terminate assembling.
-G=100 11E ; Execute only this routine.
-G=100 112 ; This time concentrate on the timing.
NC ; Note that the successful start of the
; timing is signalled by No Carry flag.
-G=100 112 ; Press F3 once to quickly repeat routine.
CY ; Original interval has not finished
; yet so failure to start new interval
; is signalled by Carry flag.
-G=100 112 ; Wait 10s or more and try F3 again.
NC ; OK this time.
-D130 L1 ; Quickly check the status of indicator.
0CA6:0130 00 ; 10s interval hasn't finished yet.
-D130 L1 ; Keep checking using F3 key repeating.
0CA6:0130 00 ; Not yet.
-D130 L1
0CA6:0130 80 ; The interval has passed.
-A105
0CA6:0105 MOV CX,2000 ; Set a longer delay. 2000:0000h = 536s.
0CA6:0108
-G=100 112 ; Restart timing routine.
NC ; Started OK.
-D40:98 L9 ; Show 9 bytes in BIOS Data Area (Seg 40h)
0040:0090 30 01 A6 0C A0 78 89 1B 01 ; starting at 98h.
-D40:98 L9 ; F3-key repeat.
0040:0090 30 01 A6 0C 30 2E 05 1B 01
; Dword at 98h is location of indicator byte. (0CA6:0130h)
; Dword at 9Ch is countdown in µs. (1B052E30 in 2nd example).
; Word at A0h is interval event flag. 01h = interval in progress.
-A200 ; Assemble at a different loc to preserve other routine.
0CA6:0200 MOV AX,8301 ; Int 15h, Func 83h, Subservice 01h.
0CA6:0203 INT 15 ; Cancel Interval.
0CA6:0205
-G=200 205 ; Execute the cancel routine.
NC ; No problems cancelling.
-D40:A0 L1 ; Inspect just the BDA wait flag.
0040:00A0 00 ; No interval in progress.
; NEWBEEP.COM produces pulsed beeping until you press a key.
; TASM/MASM defaults to using decimal values.
Beep_Freq EQU 500 ; 500 Hz beep.
Beep_Duration EQU 1 ; Changes Beep Duration.
Pause_Duration EQU 3 ; Changes Pause Duration.
Sq_Wave_Period EQU 1193180/Beep_Freq ; Don't alter this.
; With MASM 5.1 you must work out the value of the last equate.
CodeAndData SEGMENT WORD PUBLIC 'CODE'
ASSUME CS:CodeAndData
ORG 0100h ; COM file starting point.
Starting_Point:
Mov dx, offset Message
Mov ah, 09h ; Int 21h, Func 09h. Display string from [DX].
Int 21h ; The string must be terminated with an "$".
Check_Keyboard_Status:
Mov ah, 0Bh ; Int 21h, Func 0Bh. Check STDIN Status.
Int 21h ; AL=FFh if keyboard has a char ready.
Cmp al, 0FFh ; Has a key been pressed yet?
Jz Got_A_Keystroke ; Yes.
Call Produce_A_Beep ; Otherwise, keep beeping.
Mov cx, Pause_Duration
Call Wait_Routine
JMP Check_Keyboard_Status ; Time to check the keyboard again.
Got_A_Keystroke: ; A key was pressed. We now use Int 21h,
Mov ah, 07h ; Func 07h (Input Char From Console
Int 21h ; Without Echo) to swallow the keystroke.
Mov ah, 4Ch
Int 21h ; Int 21h, Func 4Ch. Terminate Prog.
;***********************************************************
Message DB 0Dh, 0Ah, "Press any key to continue...$"
Produce_A_Beep PROC NEAR
Mov al, 0B6h ; Prepare to set Timer2 for Periodic
; Square Wave mode with Binary Counting
Out 43h, al ; and LSB-then-MSB input. Set mode.
Mov ax, Sq_Wave_Period ; Period value is a word.
Call IO_Delay
Out 42h, al ; Set Timer 2 Period value, Low Byte (first).
Mov al, ah ; Transfer count high byte to AL.
Call IO_Delay
Out 42h, al ; Set Timer 2 Period value, High Byte (next).
Call IO_Delay
In al, 61h ; Get current Speaker & Misc. setting.
Mov ah, al ; Save this in AH as well.
Or al, 03h ; Prepare to set Speaker ctrl bits 1&0.
Call IO_Delay
Out 61h, al ; Make sure Speaker ctrl bits 1&0 are ON
Mov cx, Beep_Duration
Call Wait_Routine
Mov al, ah ; Transfer stored original ctrl value.
Out 61h, al ; Reset original control byte value.
Ret
Produce_A_Beep ENDP
Wait_Routine PROC NEAR ; Produces a constant delay.
Mov ah, 86h ; Int 15h, Func 86h.
Mov dx, 0000h ; Wait for CX:DX µs.
Int 15h ; Each increment of CX is worth 66ms.
Ret
Wait_Routine ENDP
; Most programming books recommend that you have a delay of at least 1
; microsecond between I/O access processes to ensure maximum hardware
; compatiblity. This need for a delay may not apply to modern machinery.
; The required time delay is too small for a Int 15h Func 86h delay so I've
; used our old friend: the empty-loop delay. There is large difference
; in empty-loop execution speed for different machines with Pentiums being
; much quicker in this operation than its clock speed compared to other CPUs
; might suggest. Based an Frank Van Gilluwe's "The Undocumented PC",
; Addison-Wesley Publishing, 1994, and precision timing of the following in
; my 486DX/50 (7 loops for a 1-microsecond delay), here is a suitable delay
; for all machines.
IO_Delay PROC NEAR ; Produces a delay of at least 1 µs.
Mov cx, 64h ; Suitable for 90Mhz Pentium or less.
A_Waste_Of_Time: ; 486 and less could use 0Eh instead.
Loop A_Waste_Of_Time
Ret
IO_Delay ENDP
CodeAndData ENDS
END Starting_Point ; Where execution will start.
--------
; WEEWAH/WABBLE switches between two tones until a key is pressed.
; TASM/MASM defaults to using decimal values.
Freq_1 EQU 1600 ; Hertz
Freq_2 EQU 1200 ; Hertz
Tone_Duration_MSB EQU 5 ; Changes Beep Duration.
Tone_Duration_LSB EQU 0 ; Changes Beep Duration.
; Use the following values for a wabble tone.
; Tone_Duration_MSB EQU 0
; Tone_Duration_LSB EQU 28000
Freq1 EQU 1193180/Freq_1 ; Don't alter this.
Freq2 EQU 1193180/Freq_2 ; Don't alter this.
; With MASM 5.1 you must work out the value of the Freq 1&2 equates
CodeAndData SEGMENT WORD PUBLIC 'CODE'
ASSUME CS:CodeAndData, DS:CodeAndData, ES:CodeAndData
ORG 0100h ; COM file.
Starting_Point:
Mov dx, offset Message
Mov ah, 09h ; Int 21h, Func 09h. Display string from [DX].
Int 21h
In al, 61h ; Read & store original Timer 2 setup.
Push ax
Mov al, 0B6h ; Set Timer 2 for Square Wave Mode.
Call IO_Delay
Out 43h, al
Check_Keyboard_Status:
Mov ah, 0Bh ; Int 21h, Func 0Bh. Check STDIN Status.
Int 21h
Cmp al, 0FFh ; Has a key been pressed yet?
Jz Got_A_Keystroke
Mov bx, Freq1
Call Produce_A_Tone
Mov bx, Freq2
Call Produce_A_Tone
JMP Check_Keyboard_Status
Got_A_Keystroke: ; A key was pressed.
Mov ah, 07h ; Clear the keystroke so it
Int 21h ; doesn't appear on screen.
Pop ax
Out 61h, al ; Reset Timer 2 to original configuration.
Mov ah, 4Ch
Int 21h ; Int 21h, Func 4Ch. Terminate Program.
;*************************************************************
Message DB 0Dh,0Ah,"Press any key to continue ...$"
Produce_A_Tone PROC NEAR
Mov ax, bx
Out 42h, al
Call IO_Delay
Mov al, ah
Out 42h, al
Call IO_Delay
In al, 61h
Or al, 03h
Call IO_Delay
Out 61h, al
Call IO_Delay
Mov cx, Tone_Duration_MSB
Mov dx, Tone_Duration_LSB
Mov ah, 86h
Int 15h
Ret
Produce_A_Tone ENDP
IO_Delay PROC NEAR
;The same as in NEWBEEP.ASM
IO_Delay ENDP
CodeAndData ENDS
END Starting_Point