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  

