home *** CD-ROM | disk | FTP | other *** search
- ; 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: clok [/c]
- ; /c to toggle chiming. No chimes by default.
- ;
- ; Clock display can be toggled on and off by repeating 'clok'.
- ;
- ; 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 clok off and on (typing clok, 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.
-
- 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
-
- 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
-
- code_seg segment
- assume cs:code_seg
- 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)
-
- display 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 clok 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
- lea si,display ; Set up the display in memory as source
- mov cx,16 ; To move char and attributes
- scan_low:
- ;taking out the snow avoidance routines for CGA
- ;scan_low: ; Start waiting for a new horizontal scan
- ; in al,dx ; i.e. the the vidio controller scan
- ; test al,1 ; status is low.
- ; jnz scan_low
-
- mov ah,cs:[si] ; Move byte to be written into AH
-
- ;scan_high: ; After port has gone low, it must go high
- ; in al,dx ; before it is safe to write directly
- ; test al,1 ; to the screen buffer in memory
- ; jz scan_high
-
- 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 foe 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 everytheing that
- push bx ; gets destroyed
- push cx
- push dx
-
- cmp cs:flash,1 ; do date or time?
- jz dtime ; TIME
- ; DATE
-
- lea bx,display ; Set up BX as pointer to display in memory
- 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 ax,dx ; prepare remainder for seconds
- ; xor dx,dx
- ; div cs:ssdiv ; compute seconds
- ; mov cs:sc,ax ; save it
-
- lea bx,display ; Set up BX as pointer to display in memory
-
- 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 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
- cmp ax,3135h ; on the 1/4 hour (single cuckoo)
- jz alarm2
- cmp ax,3330h ; on the 1/2 hour (single cuckoo)
- jz alarm2
- cmp ax,3435h ; on the 3/4 hour (single cuckoo)
- jz alarm2
- 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
- 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
- jz sw_c
- jmp next1 ; wrong switch
-
- sw_c:
- 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 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
- cmp ax,1 ; beep if it's been turned on
- jz beepmsg
- mov dx,offset chimoffmsg ; chimes off
- jmp print
- beepmsg: mov dx,offset chimonmsg ; chimes on
- print: 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 bx,3840 ;;;;;;;;;;;; *** move to lower right corner ***
- ; add cursor,bx ;;;;;;;;;; *** move to lower right corner ***
- 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
- lea di,display
- color: mov cs:[di],ax
- inc di
- inc di
- loop color
-
- get_time:
- mov bx,offset clock ; yes ; get addr of clok
- 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',0dh,0ah,'$'
- chimoffmsg db 'Chime OFF',0dh,0ah,'$'
- hello db 'usage: clok [/c] (/c: toggle chimes)',0dh,0ah
- db 'clok by itself toggles display',0dh,0ah
- db 'CLOK INSTALLED',0dh,0ah,'$'
-
- load_clock endp
-
- code_seg ends
-
- end first ; END FIRST so 8088 will go to FIRST first
-
- )B
-