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