mirror of https://github.com/wwarthen/RomWBW.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
826 lines
21 KiB
826 lines
21 KiB
;===============================================================================
|
|
; TIMER - Display system timer value
|
|
; Version 1.4 25-September-2025
|
|
;===============================================================================
|
|
;
|
|
; Author: Wayne Warthen (wwarthen@gmail.com)
|
|
; Updated: MartinR (September 2025) - A user of uppercase Z80 mnemonics
|
|
;_______________________________________________________________________________
|
|
;
|
|
; This source code would benefit from a complete resstructuring to aid clarity.
|
|
;_______________________________________________________________________________
|
|
;
|
|
; Usage:
|
|
; TIMER [/C] [Z] [/?]
|
|
; ex: TIMER Display current timer value
|
|
; TIMER /? Display version and usage
|
|
; TIMER /C Display timer value continuously
|
|
; TIMER /Z Set the system timer to zero
|
|
;
|
|
; Operation:
|
|
; Reads and displays system timer value.
|
|
;
|
|
; This code will only execute on a Z80 CPU (or derivitive)
|
|
;
|
|
; This source code assembles with TASM V3.2 under Windows-11 using the
|
|
; following command line:
|
|
; tasm -80 -g3 -l TIMER.ASM TIMER.COM
|
|
; ie: Z80 CPU, output format 'binary' named .COM (rather than .OBJ)
|
|
; and includes a symbol table as part of the listing file.
|
|
;_______________________________________________________________________________
|
|
;
|
|
; Change Log:
|
|
; 2018-01-14 [WBW] Initial release
|
|
; 2018-01-17 [WBW] Add HBIOS check
|
|
; 2019-11-08 [WBW] Add seconds support
|
|
; 2024-06-30 [MR ] Display values in decimal rather than hexadecimal
|
|
; 2024-07-24 [MR ] Also display value in Hours-Mins-Secs format
|
|
; 2025-09-25 [MR ] Add the option to set the system timer back to zero
|
|
;_______________________________________________________________________________
|
|
;
|
|
; Includes binary-to-decimal subroutine by Alwin Henseler
|
|
; Located at: https://www.msx.org/forum/development/msx-development/32-bit-long-ascii
|
|
;_______________________________________________________________________________
|
|
;
|
|
; Includes division subroutines from: https://wikiti.brandonw.net/
|
|
;;_______________________________________________________________________________
|
|
;
|
|
#include "../../ver.inc" ; Used for building RomWBW
|
|
;#include "ver.inc" ; Used for testing purposes during code development
|
|
;
|
|
;===============================================================================
|
|
; Definitions
|
|
;===============================================================================
|
|
;
|
|
STKSIZ .EQU $40 ; Working stack size
|
|
;
|
|
RESTART .EQU $0000 ; CP/M restart vector
|
|
BDOS .EQU $0005 ; BDOS invocation vector
|
|
;
|
|
IDENT .EQU $FFFC ; loc of RomWBW HBIOS ident ptr
|
|
;
|
|
BF_SYSVER .EQU $F1 ; BIOS: VER function
|
|
BF_SYSGET .EQU $F8 ; HBIOS: SYSGET function
|
|
BF_SYSSET .EQU $F9 ; HBIOS: SYSSET function
|
|
BF_SYSGETTIMER .EQU $D0 ; TIMER subfunction
|
|
BF_SYSSETTIMER .EQU $D0 ; TIMER subfunction
|
|
BF_SYSGETSECS .EQU $D1 ; SECONDS subfunction
|
|
BF_SYSSETSECS .EQU $D1 ; SECONDS subfunction
|
|
;
|
|
;ASCII Control Characters
|
|
LF .EQU $0A ; Line Feed
|
|
CR .EQU $0D ; Carriage Return
|
|
;
|
|
;===============================================================================
|
|
; Code Section
|
|
;===============================================================================
|
|
;
|
|
.ORG $0100
|
|
;
|
|
; setup stack (save old value)
|
|
LD (STKSAV),SP ; save stack
|
|
LD SP,STACK ; set new stack
|
|
;
|
|
; initialization
|
|
CALL INIT ; initialize
|
|
JR NZ,EXIT ; abort if init fails
|
|
;
|
|
; process
|
|
CALL PROCESS ; do main processing
|
|
JR NZ,EXIT ; abort on error
|
|
;
|
|
EXIT: ; clean up and return to command processor
|
|
CALL CRLF ; formatting
|
|
LD SP,(STKSAV) ; restore stack
|
|
;JP RESTART ; return to CP/M via restart
|
|
RET ; return to CP/M w/o restart
|
|
;
|
|
; Initialization
|
|
;
|
|
INIT:
|
|
CALL CRLF ; formatting
|
|
LD DE,MSGBAN ; point to version message part 1
|
|
CALL PRTSTR ; print it
|
|
;
|
|
CALL IDBIO ; identify active BIOS
|
|
CP 1 ; check for HBIOS
|
|
JP NZ,ERRBIO ; handle BIOS error
|
|
;
|
|
LD A,RMJ << 4 | RMN ; expected HBIOS ver
|
|
CP D ; compare with result above
|
|
JP NZ,ERRBIO ; handle BIOS error
|
|
;
|
|
INITX:
|
|
; initialization complete
|
|
XOR A ; signal success
|
|
RET ; return
|
|
;
|
|
; Process
|
|
;
|
|
PROCESS:
|
|
; look for start of parms
|
|
LD HL,$81 ; point to start of parm area (past len byte)
|
|
;
|
|
PROCESS00:
|
|
CALL NONBLANK ; skip to next non-blank char
|
|
JP Z,PROCESS0 ; no more parms, go to display
|
|
;
|
|
; check for option, introduced by a "/"
|
|
CP '/' ; start of options?
|
|
JP NZ,USAGE ; yes, handle option
|
|
CALL OPTION ; do option processing
|
|
RET NZ ; done if non-zero return
|
|
JR PROCESS00 ; continue looking for options
|
|
;
|
|
PROCESS0:
|
|
;
|
|
; Test of API function to set seconds value
|
|
;LD B,BF_SYSSET ; HBIOS SYSGET function
|
|
;LD C,BF_SYSSETSECS ; SECONDS subfunction
|
|
;LD DE,0 ; set seconds value
|
|
;LD HL,1000 ; ... to 1000
|
|
;RST 08 ; call HBIOS, DE:HL := seconds value
|
|
;
|
|
; get and print seconds value
|
|
CALL CRLF2 ; formatting
|
|
;
|
|
PROCESS1:
|
|
LD B,BF_SYSGET ; HBIOS SYSGET function
|
|
LD C,BF_SYSGETTIMER ; TIMER subfunction
|
|
RST 08 ; call HBIOS, DE:HL := timer value
|
|
|
|
LD A,(FIRST)
|
|
OR A
|
|
LD A,0
|
|
LD (FIRST),A
|
|
JR NZ,PROCESS1A
|
|
|
|
; TEST FOR NEW VALUE
|
|
LD A,(LAST) ; last LSB value to A
|
|
CP L ; compare to current LSB
|
|
JP Z,PROCESS2 ; if equal, bypass display
|
|
|
|
;*******************************************************************************
|
|
;*******************************************************************************
|
|
|
|
; Formatting code added/amended to print values in decimal and Hours-Mins-Secs
|
|
; MartinR June & July-2024
|
|
|
|
PROCESS1A:
|
|
; Save and print new value
|
|
LD A,L ; new LSB value to A
|
|
LD (LAST),A ; save as last value
|
|
CALL PRTCR ; back to start of line
|
|
|
|
CALL B2D32 ; Convert DE:HL into ASCII; Start of ASCII buffer returned in HL
|
|
EX DE,HL
|
|
CALL PRTSTR ; Display the value
|
|
|
|
LD DE,STRTICK ; "Ticks" message
|
|
CALL PRTSTR ; Display it
|
|
|
|
; Get and print seconds value in decimal
|
|
|
|
LD B,BF_SYSGET ; HBIOS SYSGET function
|
|
LD C,BF_SYSGETSECS ; SECONDS subfunction
|
|
RST 08 ; Call HBIOS; DE:HL := seconds value; C := fractional part
|
|
|
|
LD (SEC_MS),DE ; Store the most significant part of the 'seconds' value
|
|
LD (SEC_LS),HL ; And the less significant......
|
|
LD A,C ; And also the fractional part
|
|
SLA A ; But double the 50Hz 'ticks' value to give 1/100ths of a second
|
|
LD (SEC_FR),A
|
|
|
|
CALL B2D32 ; Convert DE:HL into ASCII; Start of ASCII buffer returned in HL
|
|
EX DE,HL
|
|
CALL PRTSTR ; Display the value
|
|
|
|
CALL PRTDOT ; Print a '.' seperator
|
|
|
|
LD A,(SEC_FR) ; Retrieve fractional part (1/100ths second)
|
|
CALL B2D8 ; Convert into ASCII - up to 3 digits. Umber of digits returned C
|
|
CALL PRT_LEAD0 ; Print a leading zero if there is exactly 1 character in the string
|
|
EX DE,HL ; Start of ASCII buffer returned in HL
|
|
CALL PRTSTR ; Display fractional part of the value
|
|
|
|
LD DE,STRSEC ; "Seconds" message
|
|
CALL PRTSTR ; Display it
|
|
|
|
; Now print the seconds value as HMS
|
|
|
|
LD BC,(SEC_MS) ; Retrive the 'seconds' value into AC:IX
|
|
LD A,B
|
|
LD IX,(SEC_LS)
|
|
LD DE,3600 ; 3600 seconds in an hour
|
|
CALL DIV32BY16 ; AC:IX divided by DE; Answer in AC:IX; Remainder in HL
|
|
|
|
PUSH HL ; Preserve the remainder on the stack
|
|
|
|
LD D,A ; Shuffle registers around ready for conversion to ASCII
|
|
LD A,C ; AC:IX into DE:HL
|
|
LD E,A
|
|
PUSH IX
|
|
POP HL
|
|
|
|
CALL B2D32 ; Convert DE:HL into ASCII; Start of ASCII buffer returned in HL
|
|
EX DE,HL
|
|
CALL PRTSTR ; Display the hours value
|
|
|
|
CALL PRTCOLON ; Print a ':' seperator
|
|
|
|
POP HL ; Retrive the remainder (seconds)
|
|
|
|
LD C,60 ; 60 seconds in a minute
|
|
CALL DIV_HL_C ; HL divided by C; Answer in HL; Remainder in A
|
|
|
|
PUSH AF ; Preserve the remainder (seconds) on the stack
|
|
|
|
CALL B2D16 ; Convert HL into ASCII; Start of ASCII buffer returned in HL; Count in C
|
|
CALL PRT_LEAD0 ; Print a leading zero if there is exactly 1 character in the string
|
|
EX DE,HL
|
|
CALL PRTSTR ; Display the minutes value
|
|
|
|
CALL PRTCOLON ; Print a ':' seperator
|
|
|
|
POP AF ; Retrive the remainder (seconds)
|
|
|
|
CALL B2D8 ; Convert A into ASCII; Start of ASCII buffer returned in HL; Count in C
|
|
CALL PRT_LEAD0 ; Print a leading zero if there is exactly 1 character in the string
|
|
EX DE,HL
|
|
CALL PRTSTR ; Display the seconds value
|
|
|
|
CALL PRTDOT ; Print a '.' seperator
|
|
|
|
LD A,(SEC_FR) ; Retrieve the fractional part (1/100ths) of the seconds
|
|
CALL B2D8 ; Convert A into ASCII; Start of ASCII buffer returned in HL
|
|
CALL PRT_LEAD0 ; Print a leading zero if there is exactly 1 character in the string
|
|
EX DE,HL
|
|
CALL PRTSTR ; Display the value
|
|
|
|
LD DE,STRHMS ; Print "HH:MM:SS" message
|
|
CALL PRTSTR
|
|
|
|
;*******************************************************************************
|
|
;*******************************************************************************
|
|
|
|
PROCESS2:
|
|
LD A,(CONT) ; continuous display?
|
|
OR A ; test for true/false
|
|
JR Z,PROCESS3 ; if false, get out
|
|
;
|
|
LD C,6 ; BDOS: direct console I/O
|
|
LD E,$FF ; input char
|
|
CALL BDOS ; call BDOS, A := char
|
|
OR A ; test for zero
|
|
JP Z,PROCESS1 ; loop until char pressed
|
|
;
|
|
PROCESS3:
|
|
XOR A ; signal success
|
|
RET
|
|
;
|
|
; Handle special options
|
|
;
|
|
OPTION:
|
|
INC HL ; next char
|
|
LD A,(HL) ; get it
|
|
OR A ; zero terminator?
|
|
RET Z ; done if so
|
|
CP ' ' ; blank?
|
|
RET Z ; done if so
|
|
CP '?' ; Is it a '?' for 'help' command?
|
|
JP Z,USAGE ; Yes, display usage
|
|
CP 'C' ; Is it a 'C' for 'continuous' command?
|
|
JP Z,SETCONT ; Yes, set continuous display mode
|
|
CP 'Z' ; Is it a 'Z' for 'zero timer' command?
|
|
JR Z,ZEROISE ; Set the timer to zero
|
|
JP ERRPRM ; anything else is an error
|
|
;
|
|
USAGE:
|
|
JP ERRUSE ; display usage and get out
|
|
;
|
|
SETCONT:
|
|
;
|
|
OR $FF ; Set A to true
|
|
LD (CONT),A ; and set CONT(inous) flag
|
|
JR OPTION ; Check for any more option letters.
|
|
;
|
|
ZEROISE:
|
|
;
|
|
LD DE,MSGZERO ; Confirm resetting system timer to zero
|
|
CALL PRTSTR
|
|
;
|
|
LD B,BF_SYSSET ; HBIOS SYSSET function
|
|
LD C,BF_SYSSETSECS ; SETSECS subfunction
|
|
LD DE,$0000 ; set seconds value
|
|
LD HL,$0000 ; ... to zero
|
|
RST 08 ; Call HBIOS, DE:HL := seconds value
|
|
JP EXIT ; Done - so exit back to CCP
|
|
;
|
|
; Identify active BIOS. RomWBW HBIOS=1, UNA UBIOS=2, else 0
|
|
;
|
|
IDBIO:
|
|
;
|
|
; CHECK FOR UNA (UBIOS)
|
|
LD A,($FFFD) ; fixed location of UNA API vector
|
|
CP $C3 ; jp instruction?
|
|
JR NZ,IDBIO1 ; if not, not UNA
|
|
LD HL,($FFFE) ; get jp address
|
|
LD A,(HL) ; get byte at target address
|
|
CP $FD ; first byte of UNA push ix instruction
|
|
JR NZ,IDBIO1 ; if not, not UNA
|
|
INC HL ; point to next byte
|
|
LD A,(HL) ; get next byte
|
|
CP $E5 ; second byte of UNA push ix instruction
|
|
JR NZ,IDBIO1 ; if not, not UNA, check others
|
|
;
|
|
LD BC,$04FA ; UNA: get BIOS date and version
|
|
RST 08 ; DE := ver, HL := date
|
|
;
|
|
LD A,2 ; UNA BIOS id = 2
|
|
RET ; and done
|
|
;
|
|
IDBIO1:
|
|
; Check for RomWBW (HBIOS)
|
|
LD HL,(IDENT) ; HL := HBIOS ident location
|
|
LD A,'W' ; First byte of ident
|
|
CP (HL) ; Compare
|
|
JR NZ,IDBIO2 ; Not HBIOS
|
|
INC HL ; Next byte of ident
|
|
LD A,~'W' ; Second byte of ident
|
|
CP (HL) ; Compare
|
|
JR NZ,IDBIO2 ; Not HBIOS
|
|
;
|
|
LD B,BF_SYSVER ; HBIOS: VER function
|
|
LD C,0 ; required reserved value
|
|
RST 08 ; DE := version, L := platform id
|
|
;
|
|
LD A,1 ; HBIOS BIOS id = 1
|
|
RET ; and done
|
|
;
|
|
IDBIO2:
|
|
; No idea what this is
|
|
XOR A ; Setup return value of 0
|
|
RET ; and done
|
|
;
|
|
; Print character in A without destroying any registers
|
|
;
|
|
PRTCHR:
|
|
PUSH BC ; save registers
|
|
PUSH DE
|
|
PUSH HL
|
|
LD E,A ; character to print in E
|
|
LD C,$02 ; BDOS function to output a character
|
|
CALL BDOS ; do it
|
|
POP HL ; restore registers
|
|
POP DE
|
|
POP BC
|
|
RET
|
|
;
|
|
; Print a 0 if C=1
|
|
;
|
|
PRT_LEAD0:
|
|
DEC C ; Decrement C, and a value of 1 becomee zero
|
|
RET NZ ; If C wasn't 1, then no leading space required
|
|
LD A,'0' ; Print the leading zero
|
|
JR Z,PRTCHR
|
|
;
|
|
PRTDOT:
|
|
;
|
|
; shortcut to print a dot preserving all regs
|
|
PUSH AF ; save af
|
|
LD A,'.' ; load dot char
|
|
CALL PRTCHR ; print it
|
|
POP AF ; restore af
|
|
RET ; done
|
|
;
|
|
PRTCOLON:
|
|
;
|
|
; shortcut to print a colon preserving all regs
|
|
PUSH AF ; save af
|
|
LD A,':' ; load colon char
|
|
CALL PRTCHR ; print it
|
|
POP AF ; restore af
|
|
RET ; done
|
|
;
|
|
PRTCR:
|
|
;
|
|
; shortcut to print a CR preserving all regs
|
|
PUSH AF ; save af
|
|
LD A,13 ; load CR value
|
|
CALL PRTCHR ; print it
|
|
POP AF ; restore af
|
|
RET ; done
|
|
;
|
|
; Print a zero terminated string at (DE) without destroying any registers
|
|
;
|
|
PRTSTR:
|
|
PUSH DE
|
|
;
|
|
PRTSTR1:
|
|
LD A,(DE) ; get next char
|
|
OR A
|
|
JR Z,PRTSTR2
|
|
CALL PRTCHR
|
|
INC DE
|
|
JR PRTSTR1
|
|
;
|
|
PRTSTR2:
|
|
POP DE ; restore registers
|
|
RET
|
|
;
|
|
; Print the value in A in hex without destroying any registers
|
|
;
|
|
PRTHEX:
|
|
PUSH AF ; save AF
|
|
PUSH DE ; save DE
|
|
CALL HEXASCII ; convert value in A to hex chars in DE
|
|
LD A,D ; get the high order hex char
|
|
CALL PRTCHR ; print it
|
|
LD A,E ; get the low order hex char
|
|
CALL PRTCHR ; print it
|
|
POP DE ; restore DE
|
|
POP AF ; restore AF
|
|
RET ; done
|
|
;
|
|
; print the hex word value in bc
|
|
;
|
|
PRTHEXWORD:
|
|
PUSH AF
|
|
LD A,B
|
|
CALL PRTHEX
|
|
LD A,C
|
|
CALL PRTHEX
|
|
POP AF
|
|
RET
|
|
;
|
|
; print the hex dword value in de:hl
|
|
;
|
|
PRTHEX32:
|
|
PUSH BC
|
|
PUSH DE
|
|
POP BC
|
|
CALL PRTHEXWORD
|
|
PUSH HL
|
|
POP BC
|
|
CALL PRTHEXWORD
|
|
POP BC
|
|
RET
|
|
;
|
|
; Convert binary value in A to ascii hex characters in DE
|
|
;
|
|
HEXASCII:
|
|
LD D,A ; save A in D
|
|
CALL HEXCONV ; convert low nibble of A to hex
|
|
LD E,A ; save it in E
|
|
LD A,D ; get original value back
|
|
RLCA ; rotate high order nibble to low bits
|
|
RLCA
|
|
RLCA
|
|
RLCA
|
|
CALL HEXCONV ; convert nibble
|
|
LD D,A ; save it in D
|
|
RET ; done
|
|
;
|
|
; Convert low nibble of A to ascii hex
|
|
;
|
|
HEXCONV:
|
|
AND $0F ; low nibble only
|
|
ADD A,$90
|
|
DAA
|
|
ADC A,$40
|
|
DAA
|
|
RET
|
|
;
|
|
; Print value of A or HL in decimal with leading zero suppression
|
|
; Use prtdecb for A or prtdecw for HL
|
|
;
|
|
PRTDECB:
|
|
PUSH HL
|
|
LD H,0
|
|
LD L,A
|
|
CALL PRTDECW ; print it
|
|
POP HL
|
|
RET
|
|
;
|
|
PRTDECW:
|
|
PUSH AF
|
|
PUSH BC
|
|
PUSH DE
|
|
PUSH HL
|
|
CALL PRTDEC0
|
|
POP HL
|
|
POP DE
|
|
POP BC
|
|
POP AF
|
|
RET
|
|
;
|
|
PRTDEC0:
|
|
LD E,'0'
|
|
LD BC,-10000
|
|
CALL PRTDEC1
|
|
LD BC,-1000
|
|
CALL PRTDEC1
|
|
LD BC,-100
|
|
CALL PRTDEC1
|
|
LD C,-10
|
|
CALL PRTDEC1
|
|
LD E,0
|
|
LD C,-1
|
|
PRTDEC1:
|
|
LD A,'0' - 1
|
|
PRTDEC2:
|
|
INC A
|
|
ADD HL,BC
|
|
JR C,PRTDEC2
|
|
SBC HL,BC
|
|
CP E
|
|
RET Z
|
|
LD E,0
|
|
CALL PRTCHR
|
|
RET
|
|
;
|
|
; Start a new line
|
|
;
|
|
CRLF2:
|
|
CALL CRLF ; two of them
|
|
CRLF:
|
|
PUSH AF ; preserve AF
|
|
LD A,CR
|
|
CALL PRTCHR ; print CR
|
|
LD A,LF
|
|
CALL PRTCHR ; print LF
|
|
POP AF ; restore AF
|
|
RET
|
|
;
|
|
; Get the next non-blank character from (HL).
|
|
;
|
|
NONBLANK:
|
|
LD A,(HL) ; load next character
|
|
OR A ; string ends with a null
|
|
RET Z ; if null, return pointing to null
|
|
CP ' ' ; check for blank
|
|
RET NZ ; return if not blank
|
|
INC HL ; if blank, increment character pointer
|
|
JR NONBLANK ; and loop
|
|
;
|
|
; Convert character in A to uppercase
|
|
;
|
|
UCASE:
|
|
CP 'a' ; if below 'a'
|
|
RET C ; ... do nothing and return
|
|
CP 'z' + 1 ; if above 'z'
|
|
RET NC ; ... do nothing and return
|
|
RES 5,A ; clear bit 5 to make lower case -> upper case
|
|
RET ; and return
|
|
;
|
|
; Add the value in A to HL (HL := HL + A)
|
|
;
|
|
ADDHL:
|
|
ADD A,L ; A := A + L
|
|
LD L,A ; Put result back in L
|
|
RET NC ; if no carry, we are done
|
|
INC H ; if carry, increment H
|
|
RET ; and return
|
|
;
|
|
; Jump indirect to address in HL
|
|
;
|
|
JPHL:
|
|
JP (HL)
|
|
;
|
|
; Errors
|
|
;
|
|
ERRUSE: ; command usage error (syntax)
|
|
LD DE,MSGUSE
|
|
JR ERR
|
|
;
|
|
ERRPRM: ; command parameter error (syntax)
|
|
LD DE,MSGPRM
|
|
JR ERR
|
|
;
|
|
ERRBIO: ; invalid BIOS or version
|
|
LD DE,MSGBIO
|
|
JR ERR
|
|
;
|
|
ERR: ; print error string and return error signal
|
|
CALL CRLF2 ; print newline
|
|
;
|
|
ERR1: ; without the leading crlf
|
|
CALL PRTSTR ; print error string
|
|
;
|
|
ERR2: ; without the string
|
|
; CALL CRLF ; print newline
|
|
OR $FF ; signal error
|
|
RET ; done
|
|
;
|
|
;
|
|
;===============================================================================
|
|
; Subroutine to print decimal numbers
|
|
;===============================================================================
|
|
;
|
|
; Combined routine for conversion of different sized binary numbers into
|
|
; directly printable ASCII(Z)-string
|
|
; Input value in registers, number size and -related to that- registers to fill
|
|
; is selected by calling the correct entry:
|
|
;
|
|
; entry inputregister(s) decimal value 0 to:
|
|
; B2D8 A 255 (3 digits)
|
|
; B2D16 HL 65535 5 "
|
|
; B2D24 E:HL 16777215 8 "
|
|
; B2D32 DE:HL 4294967295 10 "
|
|
; B2D48 BC:DE:HL 281474976710655 15 "
|
|
; B2D64 IX:BC:DE:HL 18446744073709551615 20 "
|
|
;
|
|
; The resulting string is placed into a small buffer attached to this routine,
|
|
; this buffer needs no initialization and can be modified as desired.
|
|
; The number is aligned to the right, and leading 0's are replaced with spaces.
|
|
; On exit HL points to the first digit, (B)C = number of decimals
|
|
; This way any re-alignment / postprocessing is made easy.
|
|
; Changes: AF,BC,DE,HL,IX
|
|
;
|
|
; by Alwin Henseler
|
|
; https://msx.org/forum/topic/who-who/dutch-hardware-guy-pops-back-sort
|
|
;
|
|
; Found at:
|
|
; https://www.msx.org/forum/development/msx-development/32-bit-long-ascii
|
|
;
|
|
; Tweaked to assemble using TASM 3.2 by MartinR 23June2024
|
|
;
|
|
B2D8: LD H,0
|
|
LD L,A
|
|
B2D16: LD E,0
|
|
B2D24: LD D,0
|
|
B2D32: LD BC,0
|
|
B2D48: LD IX,0 ; zero all non-used bits
|
|
B2D64: LD (B2DINV),HL
|
|
LD (B2DINV+2),DE
|
|
LD (B2DINV+4),BC
|
|
LD (B2DINV+6),IX ; place full 64-bit input value in buffer
|
|
LD HL,B2DBUF
|
|
LD DE,B2DBUF+1
|
|
LD (HL),' '
|
|
B2DFILC .EQU $-1 ; address of fill-character
|
|
LD BC,18
|
|
LDIR ; fill 1st 19 bytes of buffer with spaces
|
|
LD (B2DEND-1),BC ; set BCD value to "0" & place terminating 0
|
|
LD E,1 ; no. of bytes in BCD value
|
|
LD HL,B2DINV+8 ; (address MSB input)+1
|
|
LD BC,00909H
|
|
XOR A
|
|
B2DSKP0:DEC B
|
|
JR Z,B2DSIZ ; all 0: continue with postprocessing
|
|
DEC HL
|
|
OR (HL) ; find first byte <>0
|
|
JR Z,B2DSKP0
|
|
B2DFND1:DEC C
|
|
RLA
|
|
JR NC,B2DFND1 ; determine no. of most significant 1-bit
|
|
RRA
|
|
LD D,A ; byte from binary input value
|
|
B2DLUS2:PUSH HL
|
|
PUSH BC
|
|
B2DLUS1:LD HL,B2DEND-1 ; address LSB of BCD value
|
|
LD B,E ; current length of BCD value in bytes
|
|
RL D ; highest bit from input value -> carry
|
|
B2DLUS0:LD A,(HL)
|
|
ADC A,A
|
|
DAA
|
|
LD (HL),A ; double 1 BCD byte from intermediate result
|
|
DEC HL
|
|
DJNZ B2DLUS0 ; and go on to double entire BCD value (+carry!)
|
|
JR NC,B2DNXT
|
|
INC E ; carry at MSB -> BCD value grew 1 byte larger
|
|
LD (HL),1 ; initialize new MSB of BCD value
|
|
B2DNXT: DEC C
|
|
JR NZ,B2DLUS1 ; repeat for remaining bits from 1 input byte
|
|
POP BC ; no. of remaining bytes in input value
|
|
LD C,8 ; reset bit-counter
|
|
POP HL ; pointer to byte from input value
|
|
DEC HL
|
|
LD D,(HL) ; get next group of 8 bits
|
|
DJNZ B2DLUS2 ; and repeat until last byte from input value
|
|
B2DSIZ: LD HL,B2DEND ; address of terminating 0
|
|
LD C,E ; size of BCD value in bytes
|
|
OR A
|
|
SBC HL,BC ; calculate address of MSB BCD
|
|
LD D,H
|
|
LD E,L
|
|
SBC HL,BC
|
|
EX DE,HL ; HL=address BCD value, DE=start of decimal value
|
|
LD B,C ; no. of bytes BCD
|
|
SLA C ; no. of bytes decimal (possibly 1 too high)
|
|
LD A,'0'
|
|
RLD ; shift bits 4-7 of (HL) into bit 0-3 of A
|
|
CP '0' ; (HL) was > 9h?
|
|
JR NZ,B2DEXPH ; if yes, start with recording high digit
|
|
DEC C ; correct number of decimals
|
|
INC DE ; correct start address
|
|
JR B2DEXPL ; continue with converting low digit
|
|
B2DEXP: RLD ; shift high digit (HL) into low digit of A
|
|
B2DEXPH:LD (DE),A ; record resulting ASCII-code
|
|
INC DE
|
|
B2DEXPL:RLD
|
|
LD (DE),A
|
|
INC DE
|
|
INC HL ; next BCD-byte
|
|
DJNZ B2DEXP ; and go on to convert each BCD-byte into 2 ASCII
|
|
SBC HL,BC ; return with HL pointing to 1st decimal
|
|
RET
|
|
|
|
B2DINV: .FILL 8 ; space for 64-bit input value (LSB first)
|
|
B2DBUF: .FILL 20 ; space for 20 decimal digits
|
|
B2DEND: .DB 000H ; space for terminating character
|
|
|
|
;*******************************************************************************
|
|
|
|
; The following routine divides AC:IX by DE and places the quotient
|
|
; in AC:IX and the remainder in HL
|
|
|
|
; https://wikiti.brandonw.net/
|
|
|
|
; IN: ACIX=dividend, DE=divisor
|
|
; OUT: ACIX=quotient, DE=divisor, HL=remainder, B=0
|
|
|
|
DIV32BY16:
|
|
LD HL,0
|
|
LD B,32
|
|
DIV32BY16_LOOP:
|
|
ADD IX,IX
|
|
RL C
|
|
RLA
|
|
ADC HL,HL
|
|
JR C,DIV32BY16_OVERFLOW
|
|
SBC HL,DE
|
|
JR NC,DIV32BY16_SETBIT
|
|
ADD HL,DE
|
|
DJNZ DIV32BY16_LOOP
|
|
RET
|
|
DIV32BY16_OVERFLOW:
|
|
OR A
|
|
SBC HL,DE
|
|
DIV32BY16_SETBIT:
|
|
INC IX
|
|
DJNZ DIV32BY16_LOOP
|
|
RET
|
|
|
|
;*******************************************************************************
|
|
|
|
; The following routine divides HL by C and places the quotient in HL
|
|
; and the remainder in A
|
|
|
|
; https://wikiti.brandonw.net/
|
|
|
|
DIV_HL_C:
|
|
XOR A
|
|
LD B, 16
|
|
|
|
LOOPDIV1:
|
|
ADD HL, HL
|
|
RLA
|
|
JR C, $+5
|
|
CP C
|
|
JR C, $+4
|
|
|
|
SUB C
|
|
INC L
|
|
|
|
DJNZ LOOPDIV1
|
|
|
|
RET
|
|
|
|
;===============================================================================
|
|
; Messages Section
|
|
;===============================================================================
|
|
|
|
MSGBAN .DB "TIMER v1.4, 25-Sep-2025",CR,LF
|
|
.DB "Copyright (C) 2019, Wayne Warthen, GNU GPL v3",CR,LF
|
|
.DB "Updated by MartinR 2024 & 2025",CR,LF,0
|
|
MSGUSE .DB "Usage: TIMER [/C] [Z] [/?]",CR,LF
|
|
.DB " ex. TIMER Display current timer value",CR,LF
|
|
.DB " TIMER /? Display version and usage",CR,LF
|
|
.DB " TIMER /C Display timer value continuously",CR,LF
|
|
.DB " TIMER /Z Set the timer to zero",0
|
|
MSGPRM .DB "Parameter error (TIMER /? for usage)",0
|
|
MSGZERO .DB "Setting system timer to zero",0
|
|
MSGBIO .DB "Incompatible BIOS or version, "
|
|
.DB "HBIOS v", '0' + rmj, ".", '0' + rmn, " required",0
|
|
STRTICK .DB " Ticks ",0
|
|
STRSEC .DB " Seconds ",0
|
|
STRHMS .DB " HH:MM:SS",0
|
|
|
|
;===============================================================================
|
|
; Storage Section
|
|
;===============================================================================
|
|
|
|
SEC_MS .DW 0 ; Storage space to preserve the seconds value as
|
|
SEC_LS .DW 0 ; most and less significant parts
|
|
SEC_FR .DB 0 ; And the fractional part (1/100s of a second)
|
|
|
|
LAST .DB 0 ; last LSB of timer value
|
|
CONT .DB 0 ; non-zero indicates continuous display
|
|
FIRST .DB $FF ; first pass flag (true at start)
|
|
|
|
STKSAV .DW 0 ; stack pointer saved at start
|
|
.FILL STKSIZ,0 ; stack
|
|
STACK .EQU $ ; new stack top
|
|
|
|
.END
|
|
|