; chime1.asm Chimes ONLY on the hour. ; Uses interrupt 1Ch ; ; RESIDENT TIME CLOCK DISPLAY with optional chime ; ; Original author unidentified ; ; Revised by Thomas A. Lundin ; Graphics Unlimited Inc. ; 3000 Second St. No. ; Minneapolis, MN 55411 ; (612) 588-7571 ; ; Later revisions -- 1/22/91 ; ; usage: chime [/c] ; /c to toggle chiming. No chimes by default. ; ; Clock display can be toggled on and off by repeating 'chime'. ; ; Chimes should be toggled off when using heavy interrupt-driven ; software such as communications to avoid losing characters or ; hanging the system. ; ; 9/4/87 note: ; This version uses the clock tick to regulate the duration of the ; chimes, meaning that the chimes should be the same length from ; one system to another, no matter what the CPU speed. ; Also modified start-up routines to automatically set the time ; display background to color or monochrome. ; ; 9/21/87 note: ; This version alterates a date display with the time display, every ; 1.5 seconds. The date display is static, i.e., it is not updated ; at midnight (it would be inefficient to check every hour for an ; event which occurs once every 24 hours). ; ; Turning the chime off and on (typing chime, twice) will reset the date. ; ; 1/05/88 note: ; This revision sets the clock display in the lower right corner ; instead of the upper right. ; ; 1/22/91 note: ; This revision causes the clock to automatically position itself ; according to the number of columns in the display. The program did this before, ; but only when it was initially loaded. Thus if you switched between an ; 80 and 132 column mode, the clock would no longer be displayed in the ; upper right hand corner or wherever. The program now reads the number ; of columns every time the clock is displayed. ; ; 2/1/91 The chime has now been replaced by a ®cuckooŻ. The clock will now ; cuckoo for the appropriate hourly figure and cuckoo once on the quarter hour. ; ; v1.1 Toad Hall Tweak, 17 Apr 93 ; - Retabified, reformatted for consistent upper, lower case. ; - Minor tweaks, tightening. ; David Kirschbaum, Toad Hall TICKS EQU 27 ; number of ticks between updates (1.5 sec) ;old value -- 27 BEEPS1 EQU 1 ; duration of first beep in clock ticks BEEPS2 EQU 3 ; duration of second beep in clock ticks BEEPS3 EQU 1 ; pause between cuckoos TONE1 EQU 5e7h ; first chime tone 5f7h TONE2 EQU 74ch ; second chime tone CR EQU 0DH ;v1.1 LF EQU 0AH ;v1.1 INTERRUPTS segment at 0h org 1ch*4 ; This is to use INT 1Ch timer_int label dword ; which is the timer interupt INTERRUPTS ends SCREEN SEGMENT AT 0B000H ; A dummy segment to use SCREEN ENDS ; as the Extra Segment CSEG SEGMENT ASSUME CS:CSEG ORG 100h ; org = 100 to make this into ; a .COM file First: jmp Load_Clock old_time_int dd ? ; The address INT 1Ch normally uses count500 dw TICKS ; Used to update clock every nnn counts beepticks dw 0 ; number of ticks for a BEEP beepsleft db 0 ; number of beeps to output cuckoo dw 0 ; number of cuckoos to output cursor dw 0 ; Location of the cursor on the screen beept db 0 ; have-we-beeped flag inbeep db 0 ; beep-in-progress flag flash db 1 ; flashing colon flag spkrstat db 0 ; old speaker status video_port dw ? ; Video status port - check for scanning numcol db ? ; number of columns hh dw 0 ; hours mm dw 0 ; minutes sc dw 0 ; seconds hh1 dw 0 ; 12-hour value for cuckoo hhdiv dw 32771 ; hours divisor (65543/2) mmdiv dw 546 ; minutes divisor (1092/2) ssdiv dw 9 ; second divisor (18/2) disply dw (7020h) ; leading space dw 5 dup(703ah) ; Initial value for the clock dw 2 dup(7020h) ; Add 2 ' 's for am/pm. day_of_wk dw 0 ; day of the week month dw 0 ; month day dw 0 ; day chimon dw 1 ; flag for chime in use or not clokon dw 1 ; flag for chime in use or not Clock PROC NEAR ; The timer INT will now come here cmp CS:clokon,1 ; is this interrupt silent ? jz NewInt ; no, go execute it jmp dword ptr old_time_int ; silent, just execute old int NewInt: pushf ; First call old time interrupt to update count call dword ptr old_time_int call NeedBeep ; need to continue beep ? dec CS:count500 ; should recalculate the time ? jnz Dont_Recalculate push ax ; Save the registers - good form push cx push di push si push ES xor CS:flash,1 ; toggle the flashing colon call Calc ; Recalculate the time mov CS:count500,TICKS ; Reset Count500 ; ;Dont_Recalculate: ;This is the routine for recalculating clock position when you switch in and ;out of different column modes mov ax,0 ;move memory location for column numbers mov ES,ax ;into ES:di mov ax,044ah ; mov di,ax mov ah,ES:[di] ;move number of columns into ah sub ah,8 ; Move to eight places before edge shl ah,1 ; Mult by two (char and attribute bytes) ;end of clock position routine assume ES:SCREEN ; Set up screen as the Extra Segment mov cx,SCREEN mov ES,cx mov dx,video_port ; This is the screen status port mov byte ptr cursor,ah ; Move cursor to it's memory location mov di,cursor ; Set up cursor on screen as destination mov si,offset disply ;v1.1 mov cx,16 ; To move char and attributes Scan_Low: mov ah,CS:[si] ; Move byte to be written into AH mov ES:[di],ah ; Move to screen one byte at a time. inc di ; Position to attribute byte inc si ; on screen. loop Scan_Low ; Go back for next byte pop ES ; Here are required pops to exit pop si pop di pop cx pop ax Dont_Recalculate: iret ; An interrupt needs an IRET Clock ENDP Calc PROC NEAR ; Here we recalculate the time and store it push ax ; Puushes to save everything that push bx ; gets destroyed push cx push dx cmp CS:flash,1 ; do date or time? jz Dtime ; TIME mov bx,offset disply mov ax,CS:month mov CS:[bx+0],ah ; Move first month digit into display mov CS:[bx+2],al ; Then the second digit mov byte ptr CS:[bx+4],'-' ; a hyphen mov ax,CS:day ; get day mov CS:[bx+6],ah ; and move them into the display in memory mov CS:[bx+8],al mov byte ptr CS:[bx+10],' ' ; move space into display mov ax,CS:day_of_wk mov CS:[bx+12],ah ; move day of the week into display mov CS:[bx+14],al jmp Restore Dtime: ; note: Peter Norton p.223 explains that the time formula is more precisely ; shown as: ; hh = clkcount / 65543 ; mm = hh.remainder / 1092 ; ss = mm.remainder / 18 ; ; trouble is, the 65543 value won't work as a single-word divisor, ; so our trick is to divide the clock count and divisor values in half, ; which should have no appreciable affect on the accuracy of the time. xor ax,ax ; Set up for clock read. INT 1Ah ; Read the clock. mov bx,dx ; Save low(minutes) portion. mov dx,cx ; Move high(hours) portion to AX. mov ax,bx ; dx:ax = clock count clc rcr dx,1 ; div count by 2 so we can use a rcr ax,1 ; single precision dividend div CS:hhdiv ; compute hours mov CS:hh,ax ; save it mov ax,dx ; prepare remainder for minutes xor dx,dx div CS:mmdiv ; compute minutes cmp ax,60 ; 60 minutes shows up sometimes jl Mm_Ok ; mostly it doesn't xor ax,ax ; but if it does, zero out the minutes inc CS:hh ; and bump up the hour Mm_Ok: mov CS:mm,ax ; save it mov bx,offset disply ;v1.1 mov byte ptr CS:[bx],' ' ; blank out first and last positions mov byte ptr CS:[bx+14],' ' mov ax,CS:hh cmp ax,12 ; is it am or pm? jl Am ; am ;Pm: mov byte ptr CS:[bx+12],'p' ; Otherwise move 'P' into the display. sub ax,12 ; pm, subtract 12 jmp short Chek12 ; Continue. Am: mov byte ptr CS:[bx+12],'a' ; Move an 'A' into the display. Chek12: or ax,ax ; Make zero hour... jnz Am_Pm_Done mov ax,12 ; ...a twelve Am_Pm_Done: mov CS:hh1,ax ; hour value for cuckoo aam ; Convert AX to BCD - a nice command add ax,3030h ; Add '0' to both bytes in AX to make ascii cmp ah,'0' ; Is the 1st digit '0'? jne Dont_Edit ; Then don't blank the character. mov ah,' ' ; Otherwise, put a space in AH. Dont_Edit: mov CS:[bx+2],ah ; Move first hours digit into display mov CS:[bx+4],al ; Then the second digit ;---------------------------------- mov byte ptr CS:[bx+6],':' ; in which case use a colon mov ax,CS:mm ; get minutes aam ; Again convert AX to Binary Coded Decimal add ax,3030h ; Add to make two ASCII characters mov CS:[bx+8],ah ; and move them into the display in memory mov CS:[bx+10],al ;---------routine for alarm chime goes here------------------------------------ cmp CS:chimon,0 ; chimes off? jz Restore ; yes, don't beep cmp CS:inbeep,1 ; already in a beep loop? jz Restore ; yes, don't be redundant cmp ax,3030h ; on the hour (full cuckoo routine) jz Alarm3 mov CS:beept,0 ; we have not beeped ;------------------------------------------------------------------------------ Restore: ; Restore registers pop dx pop cx ImRet2: pop bx ImRet1: pop ax ImRet: ret ;----------------------------------------------------------------------------- NeedBeep: cmp CS:inbeep,1 ; are we beeping right now ? jnz ImRet ; no, immediate return dec CS:beepticks ; yes, done beeping? jnz ImRet ; no, immediate return push ax mov al,CS:spkrstat ; yes, shut off speaker out 61h,al dec CS:beepsleft ; any more beeps waiting? jz NoBeeps ; no, go home cmp CS:beepsleft,1 ; how many await? jnz Onward call Pause jmp ImRet1 Onward: push bx mov bx,TONE2 ; second tone in cuckoo mov CS:beepticks,BEEPS2 ; second tone is longer than first tone call Tone_a ; start it beeping jmp ImRet2 ; go home NoBeeps: dec CS:cuckoo ; are there any more cuckoos? jz No_Cuckoo ; no more cuckoos push bx mov bx,TONE1 ; beginning of next cuckoo mov CS:beepsleft,3 call Tone ;start it beeping jmp ImRet2 ;------------------------------------------------------------------------------ Alarm3: push ax ;save the register mov ax,hh1 ;move hour value into ax mov CS:cuckoo,ax ; hourly cuckoo - repeat for number of hours pop ax mov bx,TONE1 ; send tone 1 mov CS:beepsleft,3 call Tone jmp Restore Alarm2: mov CS:cuckoo,1 ; only a single cuckoo for quarter hour ; intervals mov bx,TONE1 ; send tone 2 mov CS:beepsleft,2 call Tone jmp Restore ;--------------------------------------- No_Cuckoo: mov CS:beept,1 ; we have beeped mov CS:inbeep,0 ; and we're not in one any more jmp ImRet1 ;--------------------------------------- Tone: cmp CS:beept,1 ; do nothing if chime has been beeped jz NoTone ; earlier in this clock update mov CS:beepticks,BEEPS1 ; this long on beeps Tone_a: MOV AL,0B6H ; else condition the timer OUT 43H,AL MOV AX,BX ; this is the freq OUT 42H,AL MOV AL,AH OUT 42H,AL ; out you all go IN AL,61H ; read spkr port MOV CS:spkrstat,AL OR AL,3 OUT 61H,AL ; send a beep mov CS:inbeep,1 NoTone: ret ;-------------------------------------------------------- Pause: mov CS:beepticks,BEEPS3 ret ;------------------------------------------------------------------------------ Calc ENDP Load_Clock PROC NEAR ; This procedure initializes everything assume DS:INTERRUPTS ; The Data Segment will be the interrupt area ; mov ax,INTERRUPTS xor ax,ax ;0 v1.1 mov DS,ax MOV SI,0081H ; addr of command line arguments Next: MOV AL,CS:[SI] ; get command line char CMP AL,0DH ; Return ends it. JZ Again CMP AL,'/' ; switch char JZ GetSwitch ; see what it is Next1: INC SI ; else point to next char JMP Next ; and loop GetSwitch: inc si mov al,CS:[si] cmp al,'c' ; chime toggle switch jnz Next1 ; wrong switch mov CS:togchim,1 ; toggle them chimes jmp Next1 ; get next switch if there is one Again: mov ax,CS:sig_vector ; get signature vector cmp ax,5fh ; if less than 0x60 jg Vok jmp NoLoad ; forget it Vok: add ax,3500h int 21h mov ax,ES cmp ax,434ch ; are we already loaded ? jnz NoSig ; no cmp bx,4f4bh jnz NoSig ; and no mov bx,word ptr timer_int ; yes mov ES,word ptr timer_int+2 call Gdate ; get the system date cmp CS:togchim,1 jnz No1 ; no toggle chimes call ToglChim ; go toggle chimes jmp short Exit No1: mov ax,ES:[bx-2] xor ax,1 ; toggle activation mode mov word ptr ES:[bx-2],ax Exit: mov ax,4c00h ; return to DOS int 21h ;--------------------------------------- ToglChim: mov ax,ES:[bx-4] ; get the chime flag xor ax,1 ; toggle it mov word ptr ES:[bx-4],ax push DS mov di,CS mov DS,di mov dx,offset chimonmsg ; chimes on cmp ax,1 ; beep if it's been turned on jz BeepMsg mov dx,offset chimoffmsg ; chimes off BeepMsg: mov ah,9 int 21h pop DS ret ;--------------------------------------- NoSig: mov ax,ES ; no current signature... or ax,bx ; ...but is it safe to load one? jz SetSig ; yes mov ax,CS:sig_vector ; no, try another slot dec ax mov CS:sig_vector,ax jmp Again ;--------------------------------------- Gdate: push bx mov ah,2ah ; call DOS GetDate command int 21h xor ah,ah ; make an offset out of the day of the week clc rcl ax,1 mov si,ax lea bp,days mov ax,[bp+si] ; get the ASCII day of the week pop bx mov word ptr ES:[bx-10],ax ; save it mov al,dh ; month xor ah,ah aam ; Convert AX to BCD - a nice command add ax,3030h ; Add '0' to both bytes in AX to make ASCII cmp ah,'0' ; Is the 1st digit '0'? jne Gd1 ; Then don't blank the character. mov ah,' ' ; Otherwise, put a space in AH. Gd1: mov word ptr ES:[bx-8],ax mov al,dl ; day xor ah,ah aam ; Convert AX to BCD - a nice command add ax,3030h ; Add '0' to both bytes in AX to make ASCII mov word ptr ES:[bx-6],ax ret ; go home ;--------------------------------------- NoLoad label near mov ax,4c01h ; abort with error int 21h SetSig: cli push DS mov ax,CS:sig_vector add ax,2500h ; signature reads: mov dx,434ch ; 'CL' mov DS,dx mov dx,4f4bh ; 'OK' int 21h pop DS mov ax,word ptr timer_int ; Get the old interrupt service routine mov word ptr old_time_int,ax ; address and put it into our location mov ax,word ptr timer_int+2 ; OLD_TIME_INT so we can still call it mov word ptr old_time_int+2,ax mov word ptr timer_int,offset Clock ; Now load the address of our clock mov word ptr timer_int+2,CS ; routine into TIMER_INT so the timer ; interrupt will call CLOCK sti mov ah,15 ; Ask for service 15 of INT 10H int 10h ; This tells us how the display is set up sub ah,8 ; Move to eight places before edge shl ah,1 ; Mult by two (char and attribute bytes) mov byte ptr cursor,ah ; Move cursor to it's memory location mov video_port,03bah ; Assume this is a monochrome display test al,4 ; Is it? jnz Get_Time ; Yes - jump out add cursor,8000h ; No - set up for graphics display mov video_port,03dah mov cx,8 mov ax,4f20h mov di,offset disply ;v1.1 Color: mov CS:[di],ax inc di inc di loop Color Get_Time: mov bx,offset Clock ; yes ; get addr of chime mov ax,CS mov ES,ax cmp CS:togchim,1 ; need to toggle ? jnz Noct ; no call ToglChim Noct: call Gdate mov di,CS mov DS,di mov dx,offset hello ; chimes on mov ah,9 int 21h call Calc ; This is to avoid showing ; 00:00 for first 500 counts mov dx,offset Load_Clock ; Set up for everything but Load_Clock int 27h ; to stay attached to DOS sig_vector dw 67h ; signature vector togchim db 0 ; to toggle chimes days db 'uSoMuTeWhTrFaS' chimonmsg db 'Chime ON',CR,LF,'$' chimoffmsg db 'Chime OFF',CR,LF,'$' hello db CR,LF,'USAGE: ',CR,LF,CR,LF db ' chime [/c] (/c: toggle chimes)',CR,LF db ' chime by itself toggles display',CR,LF db CR,LF,'CHIME INSTALLED',CR,LF,'$' Load_Clock ENDP CSEG ENDS END First