; 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