; ; CLOCK.MAC -- Version 1.1 -- for Commodore C-128 CP/M Plus ; ; A utility to read "The Right Time" clock-calendar and set the ; system time and date under CP/M Plus on the Commodore 128. ; ; USAGE: ; ; CLOCK ; ; No parameters are needed. ; ; There is one small bug. When you call CLOCK from the CP/M command line, ; if the system time was previously set to a time later than the real time ; that the clock provides, the date will be advanced a day by the warm ; boot. The apparent reason for this is that when the BDOS or CCP checks ; the time and finds it is earlier than the last time it checked, it ; assumes it's a new day and that the date should be incremented. I don't ; know a way around this, but there may be a flag somewhere that I haven't ; found yet. This situation should be a rare occurrence for most people. ; In any case running CLOCK a second time will set the date correctly. ; ; Much of this code is not original. For the clock reading routines I ; relied heavily on the C-128 native mode driver written and copyrighted ; by Stephen Ardelt of Ardelt Engineering, manufacturers of The Right Time ; clock-calendar. The most important changes were translating 6502 code ; to Z80 code and using Z80 in and out instructions to access the I/O chips, ; which are not visible in banks 1 or 0, the banks in which CP/M operates. ; ; Most of the subroutine to put the date in DRI format was taken from ; UNDATE.ASM, a procedure written by S. Kluger of the El Paso RCP/M. It ; is in the public domain. The original routine was written to read the ; time and date as ASCII strings, so it was modified to accept binary ; input. ; ; The Right Time is a trademark of Ardelt Engineering Company for their ; battery-backed clock-calendar for the Commodore 64 and 128. The clock ; plugs into the User Port and, if you use a 1670 modem, it will plug into ; the back of the clock. The RTC does not interfere with any CP/M functions, ; including Drive M: (the RAM disk). You can get more information from ; Ardelt Engineering Company ; 8175 East 39th Avenue ; Denver, CO 80207 ; (800) 237-2943 for orders ; (303) 355-1763 for technical information ; ; HISTORY: ; ; Version 1.0 -- December 5, 1987 ; Gene Pizzetta ; 481 Revere Street ; Revere, MA 02151 ; Voice: (617) 284-0891 ; CompuServe: 72060,505 ; GEnie: E.Pizzetta ; Q-Link: GeneP ; ; Version 1.1 -- December 7, 1987 -- Added date and time display to console. ; Gene Pizzetta ; ; This program was developed using SLRMAC from SLR Systems, just possibly ; the world's best assembler. This source file can be assembled by MAC and ; HEXCOM (much more slowly, of course) by changing the filetype from .MAC ; to .ASM. MAC will also require that you have Z80.LIB on the default drive. ; ; Note also that MAC will generate a meaningless error when it finds a line ; with an exclamation point in a comment. Ignore the error; the generated ; code will be fine. ; ; ; System addresses ; WBoot equ 0000h ; warm boot Bdos equ 0005h ; BDOS entry PrtStr equ 09h ; BDOS print string SetSCB equ 031h ; BDOS set/get system control block BEL equ 07h LF equ 0Ah CR equ 0Dh ; ; CIA #1 ; Cia1PA equ 0DC00h ; port A Cia1PB equ 0DC01h ; port B Cia1DA equ 0DC02h ; data direction register A Cia1DB equ 0DC03h ; data direction register B Cia1Ten equ 0DC08h ; tenths of a second Cia1Sec equ 0DC09h ; seconds Cia1Min equ 0DC0Ah ; minutes Cia1Hrs equ 0DC0Bh ; hours Cia1IC equ 0DC0Dh ; interrupt control register Cia1CA equ 0DC0Eh ; control register A Cia1CB equ 0DC0Fh ; control register B ; ; CIA #2 ; Cia2PA equ 0DD00h ; port A Cia2PB equ 0DD01h ; port B Cia2DA equ 0DD02h ; data direction register A Cia2DB equ 0DD03h ; data direction register B Cia2Ten equ 0DD08h ; tenths of a second Cia2Sec equ 0DD09h ; seconds Cia2Min equ 0DD0Ah ; minutes Cia2Hrs equ 0DD0Bh ; hours Cia2IC equ 0DD0Dh ; interrupt control register Cia2CA equ 0DD0Eh ; control register A Cia2CB equ 0DD0Fh ; control register B ; MACLIB Z80 ; org 100h ; jmp START ; ; data storage and tables ; CiaSav: db 0,0,0,0 ; original CIA #2 port configuration LoNib: db 0 ; low nibble data during RTC read Secs: db 0 ; seconds read from RTC Mins: db 0 ; minutes read from RTC Hours: db 0 ; hours read from RTC Month: db 0 ; month read from RTC BMonth db 0 ; binary month Date: db 0 ; date read from RTC BDate: db 0 ; binary date Year: db 0 ; year read from RTC BYear db 0 ; binary year Jan: db 31 ; January Feb: db 28,31,30,31,30 ; February to June db 31,31,30,31,30 ; July to November Days: dw 0 ; total days sum for UNDATE ScbPB: db 58h,0FEh ; SCB parameter block for ScbVal: dw 0 ; ..BDOS Function 49 ; ; messages ; MsgSOn: db 'C-128 CLOCK Version 1.1',CR,LF,LF,'$' MsgNCk: db BEL,' Clock not installed.',CR,LF,'$' MsgBat: db BEL,' Clock not set or battery dead.',CR,LF,'$' MsgDRI: db BEL,' CP/M date out of range.',CR,LF,'$' MsgDat: db ' Date: ' AMonth: db 0,0,'/' ADay: db 0,0,'/' AYear: db 0,0,CR,LF,'$' MsgTim: db ' Time: ' AHours: db 0,0,':' AMins: db 0,0,':' ASecs: db 0,0,CR,LF,'$' ; START: lxi d,MsgSOn ; print sign-on mvi c,PrtStr call Bdos ; di ; disable interrupts mvi a,07Fh ; disable NMI's lxi b,Cia2IC outp a lxi b,Cia2CA ; set bit 7 of CIA#2 control register A inp a ; to 60hz ani 07Fh outp a lxi b,Cia2CB ; set bit 7 of CIA#2 control register B inp a ; to clock ani 07Fh outp a lxi b,Cia1CA ; set bit 7 of CIA#1 control register A inp a ; to 60hz ani 07Fh outp a lxi b,Cia1CB ; set bit 7 of CIA#1 control register B inp a ; to clock ani 07Fh outp a lxi b,Cia2PA ; save CIA#2 port setup (4 bytes) lxi d,CiaSav inp a stax d ; (1) lxi b,Cia2PB inx d inp a stax d ; (2) lxi b,Cia2DA inx d inp a stax d ; (3) lxi b,Cia2DB inx d inp a stax d ; (4) ; ; Now we're ready to open the channels to The Right Time clock ; mvi a,03Fh ; set CIA#2 DDR A to default lxi b,Cia2DA outp a mvi a,0FFh ; set CIA#2 DDR B to outputs lxi b,Cia2DB outp a lxi b,Cia2PA ; make sure PA2 is high (bit 3 = 4) inp a ori 04h outp a lxi b,Cia2PB ; set PB5 high, all others low mvi a,20h outp a lxi b,Cia2PA ; set PA2 low (chip select) inp a ani 0FBh outp a lxi b,Cia2PB ; set PB5 low first time (counter goes to 1) mvi a,00h outp a mvi d,08h ; load reg D for loop LOOP: mvi a,20h ; bring PB5 high (no count) outp a mvi a,00h ; bring PB5 low (advance count) outp a ; 8 times only (2 thru 9) dcr d jrz STOP jr LOOP STOP: lxi b,Cia1Hrs ; stop CIA clocks outp a lxi b,Cia2Hrs outp a ; ; The RTC channels are open and the CIA time-of-day clocks are stopped. ; Now we read the clock and set the CIA time. First, the seconds ... ; CLKSET: mvi d,00h ; clear D for start of read call RDRTC ; read seconds (result in A, D=0 on entry, ; ..D=2 on return cpi 60h ; less than 60 seconds BCD? jnc NOCLK ; (no, no clock) lxi b,Cia1Sec ; set CIA seconds outp a lxi b,Cia2Sec outp a sta Secs ; ; We've set the seconds. Now for the minutes ... ; call RDRTC ; read minutes (D=2 on entry, D=4 on return) lxi b,Cia1Min ; set CIA minutes outp a lxi b,Cia2Min outp a sta Mins ; ; Now for the hours. RTC hours are in 24-hour format, but the CIA clocks ; use a 12-hour format with an am-pm flag. We'll have to make the conversion ; remembering that we're dealing with time in Binary Coded Decimal ... ; call RDRTC ; read hours and covert to 12 hour format mov e,a ; store data in E ani 3Fh ; strip bits 6 and 7 (time is 0-23 hours) sta Hours cpi 00h ; is it 0 o'clock? jrz Add92 ; (yes, 12 am) cpi 12h ; less than 12 (am)? jrc NoFlag ; (yes, no pm flag needed) cpi 20h ; less than 20 BCD? jrc Sub18 ; (yes) cpi 22h ; less than 22 BCD? jrc Sub24 ; (yes) jr Sub18 ; here we have 22 or 23 hours Add92: mvi a,92h ; save 92 (flag goes down on CIA writes) NoFlag: jr Store Sub18: sui 12h ; subtract 18 decimal ori 80h ; set pm flag jr Store Sub24: sui 18h ; subtract 24 decimal ori 80h ; set pm flag Store: lxi b,Cia1Hrs ; set CIA hours and am-pm flag outp a lxi b,Cia2Hrs outp a ; ; The clocks are set. Now we get the date information and store it. ; We'll deal with it after we've finished with the clock and re-enabled ; interrupts. (We're trying to move fast here.) ; call WRADDR ; read day of week from RTC call RDDATA ; ..and discard it (CP/M doesn't use it) inr d ; D=7 ; call RDRTC ; read date (D=7 on entry; D=9 on return) sta Date ; ..and store it cpi 00h ; is date 0? jz NOBAT ; (yes, there's a problem) ; call RDRTC ; read month (D=9 on entry; D=11 on return) sta Month ; ..and store it ; call RDRTC ; read year (D=11 on entry; D=13 on return) sta Year ; ..and store it ; mvi d,00h ; clear D so we can check for RTC ripple call RDRTC mov e,a ; move RTC seconds to E lxi b,Cia2Sec ; read CIA #2 seconds cmp e ; are they the same jrz DATSET ; (yes) jmp CLKSET ; if they're different read and set again ; ; We've set the CIA clocks, which the BIOS uses to update the CP/M time ; in the System Control Block. We've also read the date from The Right ; Time clock and stored it. Now we have to convert it to DRI CP/M format ; and let CP/M know what it is. ; DATSET: call CLSCLK ; close down the clock channels ei ; enable interrupts ; lda Month ; convert month to binary call BCDBIN sta BMonth lda Date ; convert day to binary call BCDBIN sta BDate lda Year ; convert year in to binary call BCDBIN sta BYear cpi 04Eh ; is year less that 1978? jc NOTDRI ; (yes, invalid date) call UNDATE ; put the date in DRI format ; ; Now everything's ready to set the date in the System Control Block. ; BDOS Function 104 is for setting the date and time, but we won't be ; using it. You can't set the date with Function 104 without setting ; the time, but we've already set the time more accurately (Function 104 ; won't accept seconds in the time specification). Instead, we'll use ; BDOS Function 49, Get/Set System Control Block, and write the date ; directly into the SCB. ; shld ScbVal ; the date in DRI format is in HL lxi d,ScbPB ; ..put it in the SCB parameter block mvi c,SetSCB ; ..and call the BDOS call Bdos ; ; We're done now, but before we leave we'll report the date and time we ; just set. We've saved the BCD from the RTC so we'll convert it to ; hexadecimal ASCII and print in at the console. ; lda Month ; get month call BINHEX ; ..convert it shld AMonth ; ..and store it in string lda Date ; get date call BINHEX shld ADay lda Year ; get year call BINHEX shld AYear ; lxi d,MsgDat ; print date mvi c,PrtStr call Bdos ; lda Hours ; get hours call BINHEX shld AHours lda Mins ; get minutes call BINHEX shld AMins lda Secs ; get seconds call BINHEX shld ASecs ; lxi d,MsgTim ; print time mvi c,PrtStr call Bdos ; jmp WBoot ; salute! and happy days! ; ; We come here if the year was earlier than 1978 ; NOTDRI: lxi d,MsgDRI ; tell 'em it's wrong mvi c,PrtStr call Bdos jmp WBoot ; ..and exit ; ; We come here if the clock is not installed in the User Port ; NOCLK: call CLSCLK ; close the clock so we don't crash ei ; enable interrupts lxi d,MsgNCk mvi c,PrtStr ; say the obvious call Bdos jmp WBoot ; ..and exit ; ; We come here if the clock is not set, or if the battery is dead ; NOBAT: call CLSCLK ; system crashes if we leave it open ei ; enable interrupts lxi d,MsgBat mvi c,PrtStr ; give 'em the bad news call Bdos jmp WBoot ; ..and exit ; ; Subroutine: RDRTC -- read The Right Time clock ; RDRTC: lxi h,LoNib call WRADDR call RDDATA mov m,a ; store low nibble inr d ; increment D call WRADDR call RDDATA ; reading high nibble ral ; shift 4 bits left ral ral ral ora m ; add tens and ones inr d ret ; ; Subroutine: WRADDR -- write read address to The Right Time clock ; WRADDR: mvi a,0FFh lxi b,Cia2DB ; set CIA #2 port B to output outp a mov a,d ; move D to A adi 10h ; add address write to data address lxi b,Cia2PB ; send to port B outp a nop ; delay .5 microsecond minimum nop mov a,d ; move data address to A outp a ; ..and send to port B mvi a,00h ; clear data lines outp a ret ; ; Subroutine: RDDATA -- read data from The Right Time clock ; RDDATA: lxi b,Cia2DB ; set CIA #2 DDR for port B inp a ; msn=output; lsn=input ani 0F0h outp a mvi a,00h ; clear lines lxi b,Cia2PB outp a mvi a,40h ; open read line outp a nop ; delay 6 microseconds minimum nop nop nop nop nop nop nop inp a ; get data + 64 mov e,a ; ..and move it to E mvi a,00h ; close read line outp a nop ; delay 1 microsecond minimum nop mov a,e ; get data back in A ani 0Fh ; mask off non-data ret ; ; Subroutine: CLSCLK -- close The Right Time clock ; CLSCLK: mvi a,00h ; clear for write to 1/10 second register lxi b,Cia1Ten outp a ; this restarts CIA clocks lxi b,Cia2Ten outp a lxi b,Cia2PA ; set PA2 high (close RTC enable) inp a ori 04h ; only that bit is changed outp a ; lxi b,Cia2PA ; restore CIA#2 port setup (4 bytes) lxi d,CiaSav ldax d outp a ; (1) lxi b,Cia2PB inx d ldax d outp a ; (2) lxi b,Cia2DA inx d ldax d outp a ; (3) lxi b,Cia2DB inx d ldax d outp a ; (4) ret ; ; Subroutine: BCDBIN -- converts BDC number in A to binary number in A ; BCDBIN: mov b,a ; save original value in B ani 0F0h ; mask high nibble rrc ; shift right mov c,a ; C = high nibble * 8 rrc ; shift right twice more rrc ; A = high nibble * 2 add c mov c,a ; C = high nibble * (8 + 2) mov a,b ; get original value back ani 0Fh ; make high nibble add c ; add to binary high nibble ret ; ; Subroutine: BINHEX -- converts binary number to hexadecimal ASCII (or ; Binary Coded Decimal to ASCII decimal). Binary byte in A, two hexadecimal ; characters in HL (MSB in L, so we can SHLD it into a string). ; BINHEX: mov b,a ; save original binary ani 0F0h ; get high nibble rrc ; move high nibble to low nibble rrc rrc rrc call NASCII ; convert high nibble to ASCII mov l,a ; return high nibble to H mov a,b ani 0Fh ; get low nibble call NASCII ; convert low nibble to ASCII mov h,a ; return low nibble in L ret ; ; Subroutine: NASCII -- (used by BINHEX) converts a hexadecimal digit to ; ASCII. Binary data in A (lower nibble), returns ASCII character in A. ; Uses only A and F. ; NASCII: cpi 10 jrc Nas1 ; jump if high nibble < 10 adi 7 ; or add 7 so after adding '0' the ; ..character with be 'A'..'F' Nas1: adi '0' ; add ASCII 0 to make a character ret ; ; Subroutine: UNDATE -- converts a binary date to DRI format, a 16-bit ; binary integer for the number of days since January 1, 1978. ; UNDATE: lda BYear ; get the binary date mvi b,78 ; set up a years counter mov c,a ; year into C ani 0FCh ; is it a leap year? cmp c mvi a,28 ; assume it's not jrnz ItsNot inr a ItsNot: sta Feb lxi h,0 ; set a day counter YLoop: mov a,c ; get the year cmp b ; is it 78 yet? jrz YDone ; (yes, we're through here) lxi d,365 ; set number of days in a year dcr c ; decrement the year dcr a ; ..both times ani 0FCh ; ..and check for a leap year cmp c jrnz NoLeap ; (no leap) inx d ; make days 366 NoLeap: dad d ; now sum the days up jr YLoop ; ; Years are done, now for the months ; YDone: shld Days ; save days so far lda BMonth ; get the binary month lxi b,Jan ; point to the months table mvi h,0 mov l,a dad b ; set the months table pointer mov b,h mov c,l dcx b lhld Days mvi d,0 ; get ready to add Mloop: dcr a ; decrement the year dcx b jrz MDone ; (we're done with months) push psw ldax b ; get days mov e,a dad d ; ..and add to total pop psw jr Mloop ; ; Months are done, now finally the days ; Mdone: lda BDate ; get the days in binary mov e,a ; get ready to add (this is easy) mvi d,0 dad d ; ..add them to total ret ; ..and we're done (total in HL) ; end ;