; TIME.COM (8080 version) Version 2.0. ; CP/M program to display and set the real-time clock on Kaypro 4-84. ; Written 04/22/84 by Bob Snider, Columbus Ohio. Greatly expanded 6/30/84. ; Dispaly is MM/DD HH:MM:SS and can be 24-hour format or 12-hour with ; AM/PM. Date is optional, and the ability to set the clock is optional ; for those who want a minimum length module. Customize this assembly ; by setting the options in the "Customization section". ; The clock is just displayed by the command "TIME". The clock is displayed ; and set by anything extra on the command line, ie. "TIME SET". The ; program will prompt for date and time value which must be entered as in the ; display format. Each of the following items can be set independently: ; Date (month/day) ; Day of the week ; Time of day (seconds may be omitted) ; If the 12-hour format is on, AM or PM must always follow the time. ; Any omitted items are not changed in the clock. ; The clock is set at the carriage return. The input line is parsed for ; proper format, but not checked for valid dates or times. If nothing is ; entered the time is unchanged. If an error is detected, the position of ; the error in the string is flagged with a '^' and nothing is set. ; The clock is checked to see if an update has occurred while being read, ; and it is re-read if so, ensuring valid time displays. ; Problems or enhancements should be directed to the Kaypro User's Group on ; CompuServe, page PCS-25. ; ; CONDITIONAL ASSEMBLY CONSTANTS FOR CUSTOMIZATION. NO EQU 0 ;VALUE FOR 'NO' IN OPTIONS. YES EQU 1 ;VALUE FOR 'YES' IN OPTIONS. ; ; CUSTOMIZATION SECTION. EACH OPTION MUST BE CODED 'YES' OR 'NO'. ; AMPM EQU YES ;12-HOUR CLOCK FORMAT WITH AM/PM. NO=24 HOUR. DATE EQU YES ;DATE IS DISPLAYED. WEEKDAY EQU YES ;DAY OF WEEK IS DISPLAYED. ZEROSUP EQU YES ;SUPPRESS LEADING 0'S IN NUMBERS. SETTIME EQU YES ;ALLOW TIME TO BE SET. NO=DISPLAY ONLY. ; ; COMPILE-TIME CONSTANTS. ; BOOT EQU 0000H ;SYSTEM BOOT ADDRESS BDOS EQU 0005H ;BDOS ENTRY POINT COMTAIL EQU 0080H ;COMMAND TAIL FROM COMMAND LINE (COUNT+CHARS) CONOUT EQU 2 ;CODE FOR CONSOLE OUTPUT REQUEST RTCA EQU 20H ;CLOCK ADDRESS SELECT REG RTCD EQU 24H ;CLOCK DATA REGISTER RTCS EQU 22H ;CLOCK STATUS REGISTER REGEND EQU 5+(DATE*3 OR WEEKDAY) ;ENDING REGISTER COUNT FOR TIME LOOP CR EQU 0DH ;CARRIAGE RETURN CHAR LF EQU 0AH ;LINE FEED CHAR ; ; WE BEGIN. ; ORG 0100H JMP START ;SKIP ID DB 'TIME V2.0 RAS 6/30/84' START: ; FIRST SAVE CP/M STACK AND SET UP OURS. LXI H,0 ;CLEAR HL DAD SP ;ADD SP+0 IN HL SHLD SAVESP ;SAVE SP OF CPM LXI SP,STACK ;POINT TO MY STACK AREA ; ; READ THE CLOCK TIME IN ONE BURST. ; READTIME: MVI A,0CFH ;INITIAL STATUS SETUP BYTE OUT RTCS ;SET PIO FOR MODE 3 IN/OUTPUT MVI A,0E0H ;LOW 5 BITS OUTPUT, TOP 3 INPUT OUT RTCS ;SET PIO IN/OUT BITS MVI A,03H ;DISABLE INTERRUPTS OUT RTCS ;DO IT MVI A,14H ;STATUS REG ADDR OUT RTCA ;SELECT IT IN RTCD ;RESET STATUS BIT DOREAD: LXI H,VALUE ;POINT TO TIME SAVE AREA MVI B,2 ;START WITH SECONDS BURST: MOV A,B ;A IS REGISTER WE WANT TO READ CPI REGEND ;GOTTEN ALL WE WANT? JNC CHECK ;YES, DONE GETTING TIME OUT RTCA ;SELECT THAT REGISTER OF CLOCK IN RTCD ;READ THE CLOCK DATA MOV M,A ;SAVE IN CORE INX H ;NEXT MEMORY LOCATION INR B ;NEXT REG ADDR JMP BURST ;GO GET MORE DATA CHECK: ; SEE IF THE CLOCK ROLLED OVER DURING THE READS. MVI A,14H ;STATUS REG ADDR OUT RTCA ;SELECT IT IN RTCD ;GET STATUS ORA A ;WAS CLOCK ROLL? JNZ DOREAD ;YES, GO READ AGAIN ; ; FORMAT THE DATE AND TIME AND SEND TO CONSOLE. ; GOTIT: IF DATE ;INCLUDE ONLY IF DATE DISPLAY OPTION LXI H,MONTH ;POINT TO MONTH VALUE CALL HEXOUTL ;PRINT IT (MAYBE ZERO SUPPRESSED) MVI A,'/' ;SEPARATOR CALL CHAROUT ;SEND A CHAR DCX H ;POINT TO DAY OF MONTH CALL HEXOUTL ;PRINT IT (ALSO ZERO-SUP) CALL BLANK ;PRINT A BLANK ENDIF ;(DATE) IF WEEKDAY ;INCLUDE IF DAY OF WEEK OPTION DCX H ;POINT TO DAY OF WEEK MOV L,M ;GET VALUE DCR L ;ADJUST FOR 0-6 MVI H,0 ;CLEAR TOP LXI D,DAYTAB ;GET ADDRESS OF TABLE XCHG ;FLIP HL,DE DAD D ;TABLE+3*(HL)=NAME TO USE DAD D DAD D MVI B,3 ;3 CHARS TO SHOW WKOUT: MOV A,M ;GET ONE CALL CHAROUT ;SEND IT INX H ;NEXT DCR B ;COUNT IT JNZ WKOUT ;LOOP CALL BLANK ;PUT SPACER ENDIF ;(WEEKDAY) LXI H,HOURS ;POINT TO HOURS IF AMPM ;INCLUDE ONLY IF 12 HOUR AM/PM DISPLAY OPT. MOV A,M ;GET HOURS MVI B,'A' ;ASSUME AM CPI 12H ;IS AFTERNOON? JC NOPMADJ ;NO, NO MORE ADJUSTMENTS MVI B,'P' ;YES, IS PM JZ NOPMADJ ;IF STILL IN HOUR 12, NO NUMBERIC ADJUST SBI 12H ;SUBTRACT 12 HOURS DAA ;DECIMAL ADJUST NOPMADJ: CPI 00H ;IS MIDNIGHT HOUR? JNZ NOMIDAM ;NO MVI A,12H ;YES, SET 12 AM NOMIDAM: MOV M,A ;RESET HOURS MOV A,B ;GET AM/PM FLAG STA AMPMFLAG ;SAVE IT FOR LATER ENDIF ;(AMPM) CALL HEXOUTL ;PRINT HOURS (MAYBE ZERO SUPPRESSED) CALL COLON ;PUT OUT A ":" FOLLOWED BY MINUTES CALL COLON ;':' AND SECONDS IF AMPM ;IF AM/PM DISPLAY OPTION CALL BLANK ;PRINT A BLANK LDA AMPMFLAG ;GET A OR P CALL CHAROUT ;PRINT IT MVI A,'M' ;GET AN M CALL CHAROUT ;PRINT IT ENDIF ;(AMPM) CALL CRLF ;PUT OUT CR LF ; ; SEE IF CLOCK SET REQUESTED BY ANYTHING IN THE COMMAND TAIL. ; PROMPT FOR THE DATE AND TIME TO SET THE CLOCK IF REQUESTED. ; IF SETTIME ;INCLUDE ONLY IF TIME SET OPTION LDA COMTAIL ;GET THE COUNT CPI 2 ;IS AT LEAST 2 CHARS? JC DONE ;NO, RETURN NOW ; SET THE TIME REQUESTED. DOSET: LXI H,VALUE ;POINT TO DATE/TIME VALUE AREA MVI B,6 ;COUNT TO CLEAR CLEAR: MVI M,0FFH ;SET NO VALUE INX H ;NEXT DCR B ;COUNT JNZ CLEAR ;LOOP FOR ALL LXI D,PROMPT ;POINT TO PROMPT MESSAGE MVI C,09H ;PRINT STRING FUNCTION CALL BDOS ;PUT TEXT ON SCREEN. MVI A,30 ;GET BUFFER LENGTH STA INMAX ;SET LENGTH LXI D,INMAX ;POINT TO START OF BUFFER PARMS MVI C,0AH ;READ BUFFER FUNCTION CALL BDOS ;READ CONSOLE INPUT CALL CRLF ;FEED A LINE LXI H,INLEN ;POINT TO INPUT BUFFER LENGTH MOV A,M ;GET RETURNED LENGTH ORA A ;WAS IT ZERO? JZ NOTSET ;YES, NOTHING TO SET MOV E,A ;SAVE COUNT MVI D,0 ;CLEAR D INX H ;POINT TO 1ST CHAR DAD D ;POINT BEYOND LAST CHAR MVI M,' ' ;SET A SCAN DELIMITER INX H ;POINT ONE MORE MVI M,0 ;SET FINAL PARSE DELIMITER LXI H,INBUF ;POINT TO FIRST CHAR ENDIF ;(SETTIME ALONE) IF SETTIME AND DATE ;INCLUDE IF DATE OPTION IS ON SHLD WKPTR ;SAVE SCAN POINTER CALL NUMBER ;DECODE A NUMBER AND DELIMITER JC NODATE ;INVALID NUM, NO DATE THERE CPI '/' ;WAS DELIM A SLASH? JZ OKDATE ;YES, WE HAVE A DATE LHLD WKPTR ;NO, RESTORE SCAN TO START JMP NODATE ;CONTINUE NEXT ITEM OKDATE: MOV A,C ;GET DECODED VALUE STA MONTH ;SET MONTH INX H ;NEXT CHAR CALL NUMBER ;TRY FOR DAY JC ERROR CPI ' ' ;WAS DELIM A BLANK? JNZ ERROR ;NO, BAD MOV A,C ;GET NUM STA DAY ;SET DAY OF MONTH INX H ;NEXT CHAR ENDIF ;(SETTIME AND DATE) NODATE: IF SETTIME AND WEEKDAY ;INCLUDE WITH DAY OF WEEK OPTION LXI D,DAYTAB ;POINT TO TABLE MVI C,1 ;INIT VALUE SHLD WKPTR ;SAVE START POINTER LOOKUP: LHLD WKPTR ;GET START ADDR MVI B,3 ;LENGTH LOOKUPLOOP: LDAX D ;GET TABLE CHAR CPI 0 ;END OF TABLE? JZ NOWEEKDAY ;YES, NOT FOUND, NOT ERROR YET XRA M ;BASIC COMPARE ANI 0DFH ;ELIMINATE CASE DIFFERENCE JZ LOOKMATCH ;MATCHES IF ZERO ADJUST: INX D ;NO MATCH, BUMP TABLE PTR DCR B ;FOR REST OF LENGTH JNZ ADJUST ;LOOP INR C ;BUMP VALUE JMP LOOKUP ;TRY NEXT LOOKMATCH: INX D ;NEXT IN TABLE INX H ;NEXT IN INPUT DCR B ;COUNT JNZ LOOKUPLOOP ;TRY NEXT CHAR ; GOOD WEEK DAY NAME FOUND. MOV A,C ;GET DECODED VALUE STA DAYOFWEEK ;SAVE MOV A,M ;GET NEXT CHAR CPI ' ' ;IS GOOD DELIM? JNZ ERROR ;NO, CRUSH IT INX H ;POINT TO NEXT CHAR ENDIF ;(WEEKDAY) NOWEEKDAY: IF SETTIME ;INCLUDE IF SET TIME OPTION CALL NUMBER ;GET NEXT NUMBER JC NOTIME ;NO TIME THERE CPI ':' ;WAS DELIM ':'? JNZ ERROR ;NO, BAD INPUT MOV A,C ;GET VALUE STA HOURS ;SAVE INX H ;NEXT CALL NUMBER ;TRY FOR MINUTES JC ERROR MOV A,C STA MINUTES ;SAVE MINUTES MOV A,M ;GET DELIM AGAIN CPI ':' ;CHECK DELIM JNZ NOSECS ;ALLOW SECONDS TO BE OMITTED INX H ;NEXT CALL NUMBER ;TRY FOR SECONDS JC ERROR ;BAD MOV A,C STA SECONDS ;SAVE SECONDS NOSECS: MOV A,M ;GET DELIM AGAIN CPI ' ' ;VALID FINAL DELIM? JNZ ERROR ;NO, GARBAGE INX H ;NEXT CHAR ENDIF ;(SETTIME) IF SETTIME AND AMPM ;INCLUDE DECODING FOR 12-HOUR FORMAT OPTION MOV A,M ;GET NEXT CHAR ANI 0DFH ;MAKE UPPER CASE CPI 'A' ;IS A? JZ AOK ;YES CPI 'P' ;IS P? JNZ ERROR ;NO, ERROR LDA HOURS ;FOR PM, GET HOURS BACK CPI 12H ;WAS IT NOON HOUR? JZ APOK ;YES, NO ADJUST ADI 12H ;+12 HOURS FOR PM DAA ;DECIMAL ADJUST STA HOURS ;UPDATE VALUE JMP APOK ;CONTINUE AOK: LDA HOURS ;GET HOURS CPI 12H ;WAS MIDNIGHT HOUR? JNZ APOK ;NO, NO ADJUST MVI A,00H ;YES, SET TO 00 HOURS STA HOURS ;UPDATE HOURS APOK: INX H ;POINT TO 'M' MOV A,M ;GET IT ANI 0DFH ;UPPER CASE CPI 'M' ;IS IT REALLY? JNZ ERROR ;AW, SHUCKS INX H ;LAST CHECK MOV A,M ;GET DELIM CPI ' ' ;VALID? JNZ ERROR ;NO INX H ;NEXT ENDIF ;(SETTIME AND AMPM) NOTIME: IF SETTIME ;NOW FOR ALL SETS MOV A,M ;LOOK AT LAST DELIM ORA A ;WAS END OF STRING? JNZ ERROR ;FOOEY ; NOW LOAD THE TIME VALUES INTO THE CLOCK DEVICE. LDA SECONDS ;FIRST CHECK FOR SECONDS GIVEN CPI 0FFH ;WAS OMITTED? JZ NOGO ;YES, NO GO COMMAND MVI A,15H ;GET ADDRESS FOR 'GO' COMMAND OUT RTCA ;RESET SECONDS AND BELOW MVI A,00H ;JUST CLEAR REG OUT RTCD ;CAUSE LOW REGS TO CLEAR NOGO: MVI B,2 ;START AT SECONDS REGISTER LXI H,SECONDS ;POINT TO SAVED SECONDS VALUE OUTSET: MOV A,B ;GET REG ADDR CPI REGEND ;DONE ALL WE WANT? JNC SETOK ;YES, SET IS DONE OUT RTCA ;SELECT THAT REG MOV A,M ;GET SAVED VALUE CPI 0FFH ;IS IT 'NO CHANGE'? JZ SKIPIT ;YES, SKIP THIS ONE OUT RTCD ;SET THE REGISTER SKIPIT: INX H ;POINT TO NEXT VALUE INR B ;NEXT REG ADDR JMP OUTSET ;LOOP FOR ALL REGS SETOK: LXI D,OKMSG ;POINT TO TIME SET OK ECHOEXIT: MVI C,09H ;PRINT STRING FUNCTION CALL BDOS ;PUT TEXT ON SCREEN. MVI A,0 ;GET A ZERO STA COMTAIL ;CLEAR COMMAND TAIL LENGTH JMP READTIME ;DISPLAY NEW TIME AND EXIT ERROR: PUSH H ;SAVE ADDR OF ERROR MVI B,30 ;SET UP TO CLEAR 30 CHARS LXI H,INBUF ;POINT TO BUFFER FLAGLOOP: MVI M,' ' ;CLEAR A CHAR INX H ;NEXT DCR B ;COUNT JNZ FLAGLOOP ;LOOP POP H ;GET ERROR ADDR BACK MVI M,'^' ;PUT A POINTER TO ERROR INX H ;NEXT MVI M,'$' ;PUT ENDING STRING LXI D,INBUF ;POINT TO ERROR FLAG LINE MVI C,09H ;PRINT STRING FUNCTION CALL BDOS ;DISPLAY FLAG LINE LXI D,ERRMSG ;POINT TO ERROR MESSAGE MVI C,09H ;PRINT STRING CALL BDOS ;DISPLAY ERROR MSG JMP READTIME ;RE-TRY INPUT ; NOTHING ENTERED IN RESPONSE TO PROMPT. SAY NOT SETTING AND DONE. NOTSET: LXI D,NOSETMSG ;POINT TO MSG JMP ECHOEXIT ;EXIT WITH TIME DISPLAY ; ; ROUTINE TO DECODE A NUMBER UP TO 2 DIGITS AND A DELIMITER. ; INPUT IS HL POINTING TO NEXT IN BUFFER. 0 BYTE IS END OF STRING. ; NUMBER VALUE (IN BCD) IS RETURNED IN C. DELIMITER IS IN A. ; ANY ERROR DETECTED IN NUMBER CAUSES CARRY FLAG SET AND HL RESTORED. ; NUMBER: MVI B,2 ;INIT MAX DIGIT COUNTER MVI C,0 ;CLEAR ANSWER WORK AREA SHLD WKPTR ;SAVE STARTING SCAN GETDIG: MOV A,M ;GET CHAR FROM BUFFER CPI '0' ;IS >= '0'? JC NONDIGIT ;NO, NOT A DIGIT CPI '9'+1 ;IS <= '9'? JNC NONDIGIT ;NO, NOT A DIGIT ANI 0FH ;ISOLATE DIGIT VALUE MOV D,A ;SAVE IT MOV A,C ;GET PREVIOUS VALUE RLC ! RLC ! RLC ! RLC ;SHIFT TO HIGH NYBBLE ORA D ;ADD IN NEW DIGIT MOV C,A ;SAVE NEW VALUE INX H ;POINT TO NEXT CHAR DCR B ;COUNT DIGIT JNZ GETDIG ;TRY FOR ANOTHER IF LENGTH LEFT NONDIGIT: MOV A,B ;LOOK AT COUNT CPI 2 ;WERE THERE ANY DIGITS? JZ BADNUM ;NO, ERROR MOV A,M ;GET DELIMITER BACK ORA A ;TURN OFF CARRY RET ;RETURN GOOD BADNUM: LHLD WKPTR ;RESTORE SCAN PTR STC ;SET WE HAD AN ERROR RET ;RETURN ERROR ; TIME SET MESSAGES. PROMPT: DB 'Enter values in above format. Omitted values are unchanged.' DB CR,LF,'$' OKMSG DB 'Set OK.',CR,LF,'$' ERRMSG DB CR,LF,'Invalid format.',CR,LF,'$' NOSETMSG: DB 'Nothing set.',CR,LF,'$' ; ENDIF ;(SETTIME) ; ; DONE. RESTORE PREVIOUS STACK AND RETURN TO CP/M. ; DONE: LHLD SAVESP ;GET SAVED SP SPHL ;SP=HL, RESTORE PREV SP RET ;RETURN TO CP/M ; ; SUBROUTINE TO PUT A CHARACTER TO CONSOLE. CHAR IN A. SAVES HL. ; CHAROUT: PUSH H ;SAVE NEEDED REG PUSH B MVI C,CONOUT ;SET BDOS REQUEST CODE MOV E,A ;PUT CHAR IN E CALL BDOS ;SYSTEM CALL POP B POP H ;RESTORE RET ;DONE ; ROUTINE ENTRY TO PUT A BLANK ON THE SCREEN. BLANK: MVI A,' ' ;GET A SPACE JMP CHAROUT ;CONTINUE IN CHAROUT SUBROUTINE ; ROUTINE ENTRY TO PUT CR AND LF ON THE SCREEN. CRLF: MVI A,0DH ;CARRIAGE RETURN CALL CHAROUT MVI A,0AH ;LINE FEED JMP CHAROUT ; ; SUBROUTINE TO OUTPUT BYTE AS TWO HEX ASCII DIGITS ON CONSOLE. ; INPUT AT (HL). ; HEXOUTL: ;LEADING DIGIT OUTPUT ROUTINE IF ZEROSUP ;IF LEADING ZERO SUPPRESSION OPTION MOV A,M ;GET BYTE ANI 0F0H ;ISOLATE HIGH NYBBLE JZ HEXLOW ;IF ZERO, DONT PRINT IT ENDIF ;(ZEROSUP) HEXOUT: MOV A,M ;GET BYTE RRC ;MOVE TO LOW NYBBLE RRC RRC RRC ANI 0FH ;ISOLATE LOW NYBBLE ORI 30H ;MAKE ASCII DIGIT CALL CHAROUT ;PRINT IT HEXLOW: MOV A,M ;GET BYTE AGAIN ANI 0FH ;ISOLATE LOW NYBBLE ORI 30H ;ASCII AGAIN CALL CHAROUT ;PRINT 2ND RET ;DONE ; ROUTINE ENTRY TO PUT OUT A COLON FOLLOWED BY NEXT VALUE FROM TIME. COLON: MVI A,':' ;GET A COLON CALL CHAROUT ;PUT IT OUT IN CHAROUT DCX H ;POINT TO NEXT VALUE TO GO JMP HEXOUT ;PRINT IT AND RETURN ; ; CONSTANTS. ; IF WEEKDAY ;FOR DAY OF WEEK OPTION DAYTAB: DB 'SUNMONTUEWEDTHUFRISAT',0 ;DAY OF WEEK TABLE ENDIF ; ; WORK AREA. ; WKPTR DS 2 ;INPUT SCAN POINTER SAVE AREA AMPMFLAG DS 1 ;'A' OR 'M' FOR AM/PM OPTION SAVESP DS 2 ;SAVE AREA FOR CP/M STACK POINTER VALUE EQU $ ;SAVED TIME VALUE WORK AREA SECONDS DS 1 ;SECONDS MINUTES DS 1 ;MINUTES HOURS DS 1 ;HOURS DAYOFWEEK DS 1 ;DAY OF WEEK DAY DS 1 ;DAY MONTH DS 1 ;MONTH IS LAST THING DS 32 ;16-POSITION STACK STACK: EQU $ ;STACK POINTER STARTS HERE INMAX DS 1 ;MAXIMUM INPUT SIZE PUT HERE INLEN DS 1 ;RETURNED INPUT CHAR COUNT INBUF DS 32 ;INPUT BUFFER + ENDING DELIMS ; END