title beep - routine to sound the speaker ;;; beep - a routine to sound the speaker ; ;; Beep() is passed a tone period (in uS) and duration (in mS). ; It sounds the IBM/PC speaker as specified. If the period ; is zero, a silent period is generated. ; ; C Calling Sequence: beep(tone, time); ; ; where (unsigned) "tone" is the period of the squarewave ; applied to the speaker in microseconds and (unsigned) ; "time" is the duration of the tone. ; ; Author: Jim Wilson for Wintek Corporation. ; Macro to flush prefetch queue (required for 80286, 80386) fpq macro jmp short $+2 endm ; Programmable Interval Timer registers/contents defined ; ; We assume the counters' input frequency is 1 Mhz (actually ; it's 1.19 Mhz) and we'll divide by 1000 (actually 1024) ; to get a millisecond clock. PT_CN0 equ 40H ; Counter 0 (input = 1.19Mhz, divisor = 65536) PT_CN2 equ 42H ; Counter 2 (speaker output period in microseconds) PT_CWR equ 43H ; Control Word (Command) Register PT_LT0 equ 00000000b ; Latch/Read Counter 0 PT_WT2 equ 10110110b ; Write Counter 2 (both bytes) ; Speaker sound control port. SP_PPI equ 61H ; Bits 0, 1 of Programmable Parallel Interface gate SP_ENA equ 03FCH ; sound to speaker (SP_ENA.hi = bits, .lo = mask) public _beep,_ticks,_resp,_pc,_prc,_tod .model small .data oldtime db ? ; PTM contents on previous ticks() call .code _beep proc push bp mov bp,sp mov al,PT_WT2 ; set up for write counter2 out PT_CWR,al mov bx,SP_ENA mov ax,[bp+4] ; ax = period or ax,ax jne beep1 ; bl = speaker mask xor bh,bh ; bh = silent? disable: enable ; Set up counter2 to generate square waves of the appropriate period. beep1: out PT_CN2,al ; counter2 = period fpq mov al,ah out PT_CN2,al in al,SP_PPI ; enable (or disable) sound gate and al,bl or al,bh out SP_PPI,al ; Now wait until time expires. call _ticks ; reset timer beep2: call _ticks ; ax = delta_T sub [bp+6],ax ; time -= delta_T ja beep2 in al,SP_PPI ; disable sound gate and ax,SP_ENA fpq out SP_PPI,al pop bp ret _beep endp ;;; ticks - return elapsed time since last call ; ;; Ticks() returns a count of milliseconds since it was last called. ; ; NOTE: The code assumes the PC's timer is driven by a 1.024Mhz ; source (actually it's 1.19Mhz) so the clock runs about 16% too ; fast. Since the PC's timer underflows 36.2 times per second, ; ticks() must be called at least every 27 m.s. to detect this ; underflow. The 8254 counter is set to "mode 3" so that its ; 16-bit counter decrements by two on every input clock. ; ; C Calling Sequence: elapsed_time = ticks(); ; ; where (unsigned) "elapsed_time" is the number of milliseconds ; since the last call to ticks(). ; ; Author: Jim Wilson for Wintek Corporation. _ticks proc mov al,PT_LT0 ; latch counter 0 out PT_CWR,al fpq in al,PT_CN0 ; fetch/discard l.s. byte fpq in al,PT_CN0 ; fetch/retain m.s. byte shr al,1 ; al = raw milliseconds (0-31) shr al,1 shr al,1 mov ah,oldtime ; ah = time of last call mov oldtime,al ; oldtime = time of this call xchg al,ah sub al,ah ; al = delta_T.lo jae tick1 ; if timer's underflowed add al,32 ; correct returned value. tick1: xor ah,ah ; ah = delta_t.hi ret _ticks endp ;;; resp - return keyboard character (non-blocking) ; ;; Resp() returns a character typed at the keyboard or '\0' if none. ; Codes a-z are converted to A-Z. ; ; NOTE: An earlier version of this program used the BIOS keyboard ; function AH = 01, "check keyboard status", to check if there ; was a character available. Unfortunately, some BIOS's take this ; opportunity to suspend the program (to do some housekeeping?) ; long enough so that ticks() overflows. This version examines ; the BIOS-defined keyboard circular buffer pointers to see if ; they are the same (indicating there is no character to be had). ; If there is a character, BIOS keyboard interrupt with AH = 00 is ; used to fetch it. ; ; C Calling Sequence: c = resp(); ; ; where (char) "c" is the ASCII key value converted to ; lower case or NUL if no character is present. _resp proc mov ax,40h ; Segment for BIOS keyboard circular buffer mov es,ax ; Into extra segment register mov ax,es:[1Ah] ; AX = input pointer sub ax,es:[1Ch] ; If same as output pointer, je res3 ; return immediately xor ah,ah ; AH = "Keyboard read" int 16h ; Call BIOS to fetch the key xor ah,ah ; Zero-pad to 16 bits res2: cmp al,'a' ; ax = toupper(ax) jb res3 cmp al,'z' ja res3 sub al,'a'-'A' res3: ret _resp endp ;;; prc - put character/attribute at specific screen location ; ;; prc() is passed an integer character/attribute and a screen row ;; and column for its display. (Row/column 0/0 is at the top/left ;; of the screen). Pcxy() moves the "cursor" to the location, writes ;; the character there, and then restores the old cursor location. ; These contortions are the easiest way to make the code display- ; adapter- and mode-independent. ; ; NOTE: The ASCII character code is in the LS byte; the attribute ; is in the MS byte of the (short) integer "char_attr" parameter. ; ; Calling Sequence: void prc(int char_attr, int row, int column); _prc proc push bp ; Save caller's frame mov bp,sp ; Point at parameter block mov ah,03h ; AH = "Read cursor Position" xor bh,bh ; BH = page number = 0 int 10h ; DH,DL = current cursor row, column push dx ; Save old cursor position mov dh,[bp+6] ; DH = row for character mov dl,[bp+8] ; DL = column for character mov ah,02h ; AH = "Set (new) Cursor Position" int 10h ; Call BIOS to move cursor pop dx ; DX = old cursor position mov ax,[bp+4] ; AX = ASCII character/attribute xor cx,cx ; CX = nr. chars to display (minus one) jmp short pc6 ; Shared code with pc(), below. _prc endp ;;; pc - put character (to screen) ; ;; Pc() is passed a character/attribute to display at the current cursor ;; position. It displays the character and advances the cursor. ; ; Pc() watches for two special characters: ; ; '\f': Select active page 0, clear the screen, and put the ; the cursor in column 0 on the first line. ; '\n': If the cursor is on the last line, scroll the bottom ; few lines (the code window). Then put the cursor on ; in column 0 on the next line. ; '\v': Move the cursor to the first column on display's last ; line. ; ; In these cases, the attribute (the parameter MS byte) is used ; to fill the empty line(s) created by the respective operation. ; ; Calling Sequence: void pc(int char_attr); CODE_HT equ 5 ; Height of code window (in lines) _pc proc push bp ; Save caller's frame mov bp,sp ; Point at parameter block mov ah,03h ; AH = "Read cursor Position" xor bh,bh ; BH = page number = 0 mov bl,24 ; BL = code window last row int 10h ; DH,DL = current cursor row, column mov ax,[bp+4] ; AX = ASCII character/attribute xor cx,cx ; CH,CL = screen upper-left row, column cmp al,0Bh ; If character is '\v', je pc3 ; just move cursor cmp al,0Ah ; if character not '\n', jne pc1 ; go see if character is '\f'. ; Have a newline. If we are on the bottom line, we must scroll ; the screen. Otherwise we can simply advance the cursor. mov al,1 ; AL = scroll(?) line displacement mov ch,25-CODE_HT ; CH = code window first row cmp dh,bl ; If we are on last line, je pc2 ; we need to scroll the display inc dh ; DH = next row jmp pc4 ; Go to move-cursor common code pc1: cmp al,0Ch ; if character not '\f', jne pc5 ; go to code that displays it. ; Have a formfeed. Select page 0, clear screen (all 25 lines), and ; move cursor to upper-left corner. mov ah,05h ; AH = "Select Active Display Page" xor al,al ; AL = page number = 0 int 10h ; Call BIOS to select page 0 xor bl,bl ; BL = screen first row pc2: mov ah,6 ; AH = "Scroll Active Page Up" mov dx,24*256+79 ; DH,DL = screen last row, column mov bh,[bp+5] ; Attribute to use on filled lines int 10h ; Call BIOS to scroll window/clear screen pc3: mov dh,bl ; DH = first or last row pc4: xor bh,bh ; BH = page number = 0 xor dl,dl ; DL = 1st column on display jmp short pc7 ; Move cursor to requested position ; Have "normal", displayable character/attribute. pc5: inc dl ; DL = next column pc6: inc cx ; CX = 1 = number characters to display mov bl,ah ; BL = attribute for display mov ah,9 ; AH = "Display Char/Attr (here)" int 10h ; Call BIOS to display character pc7: mov ah,02h ; AH = "Set Cursor Position" int 10h ; Call BIOS to reposition cursor pop bp ; Restore caller's frame pointer ret _pc endp ;;; tod - return TOD (in 18.2 Hz ticks) ;; ;; Tod() simply returns the l.s. 16 bits of the timer counter. ; This counter (which increments about 18 times each second) ; makes a good seed for the random number generator. _tod proc mov ax,40h mov es,ax ; ES = Segment of BIOS data area mov ax,es:[6Ch] ; AX = BIOS TOD clock ret _tod endp end