; CLOCK.ASM - December 20, 1987 - Walt Wheeler, Yankee Osborne Users (CT)
;
; This program uses the remnants of the software clock built into the earliest
; versions of the Osborne 1 BIOS. Most of that code was removed in vers. 1.1 or
; 1.2. However, OCC left a routine (in ROM) thru vers. 1.44, which increments
; a two byte counter beginning at 0efccH (which is within the BIOS scratch area
; in high RAM). The ROM routine bumps the counter each 1/60th of a second --
; i.e. each time VERTICAL_RETRACE generates an interrupt -- with some relatively
; minor exceptions, such as disk accesses. The exceptions limit the accuracy
; of this clock, but I have found that in my typical use (and with the machine
; "on" 24 hours a day, this clock tends to run only a few seconds/day slow.
; That's good enough for my purposes, including use on our RCPM.
;
; This program owes its existance to the work of others. Many of the routines
; here are adapted (or simply "lifted") from public domain contributions by
; such CP/M gurus as Ward Christensen, Irv Hoff (W6FFC), Sigi Kluger, George
; Peace, Keith Petersen (W8SDZ), and Kelley Smith. Jim Enright's artical
; on the OCC timer routine, reprinted from TOGGLE in FOGHORN (VI, 12, p.50)
; renewed a latent interest in the OCC clock and provided the starting point
; for this effort by locating the tick_counter, TICNTR. (You'll find additional
; information beyond the scope of these comments in his text.) Next, a belated
; disassembley and review of Michael Rubenstein's OCLOCK.COM (written early in
; OCC's life, for use in profiling 'C' programs) led me to the interrupt_jump_
; vector, INTJVC. INTJVC seems to be the only point at which one can reliably
; intercept ROM calls under all (most?) circumstances. Other vectors into the
; ROM -- including those testing CON_STATUS -- go "unused" at times for reasons
; that I can't explain (yet?). OCLOCK.COM also provides a neat algorithm for
; relocating and protecting its working code just beneath the CCP, for those
; who chose not to implement ZCPR3.X. Finally, Barry Siegfried (K2MF) of NYC
; worked out most of the "hard parts" in the disassembley, and in this code.
; Without his considerable investment of time and genius, on this and prelim-
; inary efforts, CLOCK.ASM would not exist.
;
; As presented here, this code has these KNOWN DEPENDENCIES: OCC1, 1.4x ROM,
; my particular implementation of ZCPR3 and NON-USE of RST2, whose vector
; occupies the basepage addresses at which this program keeps date and time in
; BCD (binary coded decimal).
;
; HOWEVER, NONE OF THESE DEPENDENCIES IS ABSOLUTE. This should be readily
; modifiable for any Z80-based machine which utilizes a clocked_interrupt for
; routines like SCAN_KEYBOARD. Addresses -- including the locations used here
; for the relocated "working" code and for data -- can be changed to suit
; your system. As it stands, it IS COMPATIBLE with DATTIM.COM, a
; proprietary program bundled with SuperCalc as distributed with the O1.
;
; PHILOSOPHICAL DIGRESSIONS: First: Z80-based computing still has a lot of
; untapped potential, available at very low cost. Second, good public domain
; code (of which this is NOT held out as an example, except as it derives from
; some VERY good code and the work of others) is widely available for inspir-
; ation, emulation, and modification to suit our current and future wishes.
;
; LASM, a public domain improvement on ASM, will assemble this code.
;
DATE EQU 011H ; Addresses nominally reserved ...
TIME EQU 013H ; ...for RST 2 but generally unused for that
; purpose, and used for this one by DATTIM.COM.
DEST EQU 0D980H ; IMPLEMENTATION DEPENDANT: this just happens to ...
; ...be handily available in WCW's system.
TICNTR EQU 0EFCCH ; ABSOLUTE, non-relocatable address under...
INTJVC EQU 0EFF8H ; ...1.4x OCC ROM BIOS
;
ORG 100H
;
LHLD INTJVC ; get int jump vector addr in HL
MOV A,H ; get int jump vector addr (high byte)
CPI 40H ; Does INTVJC point into ROM ( < 4000H )?
JRNC ZERO ; no, already installed
;
SHLD NINT ; yes, store orig int jump vector addr...
; ...at new int jump vector
;
MOVUP: LXI D,DEST ; dest addr in DE
PUSH D ; save dest addr
LXI H,BNDRY ; source addr in HL
LXI B,LEN ; program length in BC
LDIR ; move ticker code
POP D ; store new int jump vector...
SDED INTJVC ; ...at orig int jump vector addr
;
ZERO: LXI H,0 ; Zero the tick counter when this is initially run
SHLD TICNTR ; to avoid TICNTR > 0E10H (i.e., past 1 minute)
RET ; quit -- done with this .COM file
;
;Code for relocation, which actually tracks the clock, follows...
;
BNDRY EQU $ ; for explanation of this notation, see such
; code as early versions of BYE.ASM
;
DI ; disable interrupts
OUT 1 ; switch in RAM bank
;
;---------------------------------------------------------------------
;
;NEXT 2 STACK INSTRUCTIONS PERHAPS NOT NECESSARY
;
SSPD STK ; save ROM stack pointer...
LXI SP,STK ; ...at top of temp stack
;
;---------------------------------------------------------------------
;
PUSH PSW ; save regs
PUSH H
LHLD TICNTR ; get the tick count
MOV A,H ; look at high byte
CPI 0EH ; are we near 0E10H ticks which is 1 minute?
JRC EXIT ; no, exit if less than 0EH
;
MOV A,L ; yes, look at low byte
CPI 10H ; and have we reached or exceeded 1 minute?
JRC EXIT ; no, exit if less than 10H
;
LXI H,0 ; but if we have reached 1 minute...
SHLD TICNTR ; ...ZERO the TICK COUNTER
LHLD TIME ; now, get the old TIME (hrs & mins)
MOV A,H ; move MINS to
INR A ; bump it up
MOV H,A ; store it back to - it may be OK
ANI 0FH ; look at LEAST SIG BCD digit
CPI 0AH ; has it reached 10 decimal?
JRNZ ZTICK ; if not, store the new time
;
MOV A,H ; else, get the bad BCD MINS back from
ANI 0F0H ; mask off the low nibble - i.e. ZERO it
ADI 10H ; bump the high nibble (TENS) up one
MOV H,A ; store it back to - it may be OK
CPI 60H ; and see if we're up to an hour
JRNZ ZTICK ; if not, store the new time
;
XRA A ; zero - eg. 1:60 AM will become 2:00 AM
MOV H,A ; and put the new MINS value back in
MOV A,L ; now get the HOURS byte
INR A ; bump it
MOV L,A ; store it back to - it may be OK
CPI 24H ; and see if we're up to a new date
JRZ ZDATE ; if so, bump the date and zero the time
;
ANI 0FH ; else, look at LEAST SIG BCD digit
CPI 0AH ; has it reached 10 decimal?
JRNZ ZTICK ; if not, store the new time
;
MOV A,L ; else, get the bad BCD HOURS back from
ANI 0F0H ; mask off the low nibble - i.e. ZERO it
ADI 10H ; bump the high nibble (TENS) up one
MOV L,A ; correct # of TENS, 0 units to
JR ZTICK ; and store the time
;
ZDATE: LDA DATE ; get the DATE byte in
INR A ; bump it
STA DATE ; store it to back
ANI 0fH ; mask to LEAST SIG BCD digit
CPI 0AH ; is it a legal decimal digit
JRNZ DATEX ; if ok, get out
LDA DATE ; but if not, get both BCD digits back
ANI 0f0H ; mask off LEAST SIG BCD digit, i.e. 0 it
ADI 010H ; and bump the MORE SIG BCD digit
STA DATE ; and store it...
; DAY 32? too bad -- LET THE USER RESET each month
; ----> could be tested & fixed, but requires more
; code like that above AND adds necessity for
; table of month_lengths, coding for leap years, etc.
;
DATEX: LXI H,0 ; and ZERO the register
;
ZTICK: SHLD TIME ; store the time from
;
EXIT: POP H ; restore regs
POP PSW
;
;---------------------------------------------------------------------
;
;NEXT STACK INSTRUCTION PERHAPS NOT NECESSARY
;
LSPD STK ; restore ROM stack pointer
;
;---------------------------------------------------------------------
;
OUT 0 ; switch ROM bank back in
;
NINT EQU $+1 ; addr for the following new int...
; ...jump vector filled in at runtime
;
JMP 0 ; jump to orig int jump vector addr
;
;---------------------------------------------------------------------
;
;TEMPORARY STACK DEFINITION PERHAPS NOT NECESSARY
;
DW 0 ; 10 deep temporary stack
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
DW 0
;
STK EQU $-BNDRY+DEST
;
;---------------------------------------------------------------------
;
LEN EQU $-BNDRY
;
END