;*******************************************************************************
;* Module    : DELAYMS.SUB
;* Programmer: Tony Papadimitriou <tonyp@acm.org>
;* Purpose   : Hard delay D msec, regardless of system clock
;* Language  : Motorola/Freescale/NXP 68HC11 Assembly Language (aspisys.com/ASM11)
;* Status    : FREEWARE Copyright (c) 2020 by Tony Papadimitriou <tonyp@acm.org>
;* Note(s)   : Use: #Include delayms.sub
;*           :
;*           : The default version allows to get any msec delay upto 65535 msec
;*           : using a fixed bus speed defined at assembly time.
;*           :
;*           : The number of msec is passed in RegD. (Zero returns immediately.)
;*           :
;*           : An alternate version is possible by using the conditional ANY_BUS
;*           : When ANY_BUS is undefined, the fixed BUS_KHZ value is used.
;*           : However, when the conditional ANY_BUS is defined, a second
;*           : parameter is required in RegX, which holds the number of cycles
;*           : that represent a single millisecond.  This is useful for programs
;*           : that dynamically change the bus clock speed for different parts
;*           : of the program.  This one routine can accommodate all delays upto
;*           : 65535 msec with extreme accuracy for any bus speed upto 65535 KHz.
;*           :
;*           : Example calls:
;*           :                ldd       #MSEC     ;number of milliseconds
;*           :                jsr       DelayMS
;*           :
;*           :                ldd       #MSEC     ;number of milliseconds
;*           :                ldx       #BUS_KHZ  ;(assemble w/ cond. ANY_BUS)
;*           :                jsr       DelayMS
;*           :
;* History   : 10.01.17 v1.00 Original
;*           : 18.06.10       Added #spauto for easier local variable manipulation
;*           :                Minor X->Y optimization by moving TSX up one instruction [-1 byte]
;*******************************************************************************

#ifmain ;-----------------------------------------------------------------------
                    #ListOff
                    #Uses     mcu.inc
                    #ListOn

                    #ROM

Start               proc
                    lds       #STACKTOP
                    clrd                          ;(to keep simulator happy)
                    clrx                          ;         -//-
                    clry                          ;         -//-

                    ldd       #1000               ;sample delay = 1000 msec
          #ifdef ANY_BUS
                    ldx       #4000               ;sample bus = 4MHz (=4000 KHz)
          #endif
                    bsr       DelayMS

                    bra       *

                    @vector   Vreset,Start

                    end       :s19crc

                    #ROM
#endif ;------------------------------------------------------------------------

;*******************************************************************************
; Purpose: Delay upto 65535 msec
; Input  : D = number of milliseconds (zero returns immediately)
;        : X = number of cycles that represent a msec for current bus speed
; Output : None
; Note(s): X parameter is only required if assembled with conditional ANY_BUS

DelayMS             macro     [#]msec[,[#]BUS_KHZ]
                    mreq      1:[#]msec - number of milliseconds to delay
          #ifdef ANY_BUS
                    mreq      2:[#]BUS_KHZ - current bus speed in KHz
          #endif
                    ldd       ~1~       ;number of milliseconds
          #ifparm ~2~
                    ldx       ~2~       ;;(if assembled w/ cond. ANY_BUS)
          #endif
                    jsr       ~0~
                    endm

;-------------------------------------------------------------------------------

                    #spauto
                              #Cycles             ;reset the cycle counter
DelayMS             proc
                    cmpd      #0
                    jeq       Done@@              ;nothing to do, get out

                    pshy
                    pshx      user_x@@
                    pshb      user_b@@
                    psha      user_a@@

                    #ais
                    getx      #4                  ;allocate local variables
burn_cycles@@       equ       ::,4                ;32-bit cycle total
                    tsy

          ;multiply 1ms cycles by number of requested msec [D*BUS_KHZ]

          #ifdef ANY_BUS
                    #Message  DelayMS requires as parm in X the bus KHz

                    lda       user_x@@+1,spx
          #else
                    lda       #[BUS_KHZ
          #endif
                    ldb       user_b@@,spx
                    mul
                    std       burn_cycles@@+2,spx
                    clrd
                    std       burn_cycles@@,spx
          #ifdef ANY_BUS
                    lda       user_x@@,spx
          #else
                    lda       #]BUS_KHZ
          #endif
                    ldb       user_b@@,spx
                    mul
                    addd      burn_cycles@@+1,spx
                    std       burn_cycles@@+1,spx

                    clra
                    adca      burn_cycles@@,spx
                    sta       burn_cycles@@,spx
          #ifdef ANY_BUS
                    lda       user_x@@+1,spx
          #else
                    lda       #[BUS_KHZ
          #endif
                    ldb       user_a@@,spx
                    mul
                    addd      burn_cycles@@+1,spx
                    std       burn_cycles@@+1,spx

                    clra
                    adca      burn_cycles@@,spx
                    sta       burn_cycles@@,spx

          #ifdef ANY_BUS
                    lda       user_x@@,spx
          #else
                    lda       #]BUS_KHZ
          #endif
                    ldb       user_a@@,spx
                    mul
                    addd      burn_cycles@@,spx
                    std       burn_cycles@@,spx

          ;subtract the overhead cycles

                    ldd       burn_cycles@@+2,spx
                    subd      #ExtraCycles@@
                    std       burn_cycles@@+2,spx

                    ldd       burn_cycles@@,spx
                    sbcb      #0
                    sbca      #0
                    std       burn_cycles@@,spx

          ;divide total burn cycles by loop cycles

                    clra
                    ldb       burn_cycles@@,spx
                    ldx       #LoopCycles@@       ;divisor
                    idiv                          ;D/X -> X,D
                    xgdx
                    stb       burn_cycles@@,spy
                    xgdx

                    tba
                    ldb       burn_cycles@@+1,spy
                    ldx       #LoopCycles@@       ;divisor
                    idiv                          ;D/X -> X,D
                    xgdx
                    stb       burn_cycles@@+1,spy
                    xgdx

                    tba
                    ldb       burn_cycles@@+2,spy
                    ldx       #LoopCycles@@       ;divisor
                    idiv                          ;D/X -> X,D
                    xgdx
                    stb       burn_cycles@@+2,spy
                    xgdx

                    tba
                    ldb       burn_cycles@@+3,spy
                    ldx       #LoopCycles@@       ;divisor
                    idiv                          ;D/X -> X,D
                    xgdx
                    tsx                           ;X -> stack frame
                    stb       burn_cycles@@+3,spx

ExtraCycles@@       equ       :cycles

Loop@@              ldd       burn_cycles@@+2,spx
                    decd
                    std       burn_cycles@@+2,spx

                    ldd       burn_cycles@@,spx
                    sbcb      #0
                    sbca      #0
                    std       burn_cycles@@,spx

                    @cop                          ;in case of many iterations

                    lda       burn_cycles@@,spx
                    ora       burn_cycles@@+1,spx
                    ora       burn_cycles@@+2,spx
                    ora       burn_cycles@@+3,spx
                    bne       Loop@@              ;repeat for all cycles

LoopCycles@@        equ       :cycles

                    givex     #:ais               ;de-allocate local variables
                    pull
Done@@              rts

ExtraCycles@@       set       ExtraCycles@@+:cycles

                    #sp
;*******************************************************************************
                    #Exit
;*******************************************************************************
                    #Message  Size: {*-DelayMS} bytes