;*******************************************************************************
;* Include   : MACROS.INC
;* Programmer: Tony Papadimitriou <tonyp@acm.org>
;* Purpose   : Sample macro definitions for ASM11 (Win32 & Linux versions, only)
;* Language  : Motorola/Freescale/NXP 68HC11 Assembly Language (aspisys.com/ASM11)
;* Status    : FREEWARE Copyright (c) 2020 by Tony Papadimitriou <tonyp@acm.org>
;* Original  : http://www.aspisys.com/code/hc11/macros.html
;* Note(s)   : Use: #Uses macros.inc
;*******************************************************************************

                    #Exit     _MACROS_
_MACROS_

;*******************************************************************************
; My personal preferences for most situations (adjust to suit your needs)

MyDefaultDirectives macro
                    #CaseOn                       ;Case-sensitive labels
                    #OptRelOn                     ;Jump->Branch warnings
                    #OptRtsOff                    ;No redundant RTS warnings
                    #SpacesOff                    ;No non-string spaces in operands
                    #ExtraOn                      ;Extra mnemonics enabled
                    #MapOn                        ;Source-level mapping enabled
                    #TraceOff                     ;No source-level macro mapping
                    #@Macro                       ;Macro syntax is @macro
                    #Parms                        ;Default macro parameter delimiter
                    endm

;*******************************************************************************
;*******************************************************************************
                    @MyDefaultDirectives          ;this one called at inclusion
;*******************************************************************************
;*******************************************************************************

;*******************************************************************************
; Assign a default value to a symbol (if the symbol is not already defined).
; If <expr> is missing, simply define the symbol with current address pointer

;*******************************************************************************

_cop_               macro
                    lda       #$55
                    sta       COPRST
                    coma
                    sta       COPRST
                    endm

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

COP                 macro
          #ifdef KickCOP
                    !jsr      KickCOP
                    mexit
          #endif
                    psha
                    @@_cop_
                    pula
                    endm

;*******************************************************************************
; Simulate 68HC08's RSP instruction

rsp                 macro     [[#]StackTop]       ;does LDS with most-used value
                    mdef      1,#STACKTOP
                    lds       ~1~
                    endm

;*******************************************************************************
; Clear a range to zero (or given value)
; Note: X is destroyed

ClrRange            macro     [#]FromAddress,[#]ToAddress[,[#]WithValue]
                    mreq      1,2:[#]FromAddress,[#]ToAddress[,[#]WithValue]
          #ifparm ~,2~ = ,x
                    merror    X-indexed mode in ToAddress not supported
          #endif
                    ldx       ~1~
                    #ppc
          #ifparm ~3~
                    psha
                    lda       ~3~
                    sta       ,x
                    pula
          #else
                    clr       ,x
          #endif
                    inx
                    cmpx      ~2~
                    blo       :ppc
                    endm

;*******************************************************************************
; Swap the values of two symbols (using old XOR technique, and no temp symbol)

SwapSymbols         macro     Symbol1,Symbol2
                    mreq      1,2:Symbol1,Symbol2
~1~                 set       {~1~}^{~2~}
~2~                 set       {~1~}^{~2~}
~1~                 set       {~1~}^{~2~}
                    endm

;*******************************************************************************
; Align the current segment (e.g, #ROM) to specific power-of-two block size.
; If a label is present on the same line as the macro call, set the label to the
; value after the alignment occurs.  Default power is 0, byte alignment.
; If the "UnalignedValue" expression is present (and its value is other than
; :PC, then instead of aligning the current segment, it only sets the label to
; aligned value.  This way, it can be used to align just a label without the
; segment.

Align2              macro     Power[,UnalignedValue]
                    mdef      1,0
                    mdef      2,:PC
                    mset      1,{1<{~1~}+{~2~}-1(h)}&{1<{~1~}-1^$FFFFFF(h)}
          #ifparm ~2~ = :PC
                    org       ~1~
          #endif
          #ifparm ~label~
~label~             set       ~1~
          #endif
                    mexit     ~1~
                    endm

;*******************************************************************************
; Define PIN names (PIN for port, PIN. for pin number/mask)
; (It can also be used for bit-mapped registers or variables)
; PinName can be either the 1st parameter (when three parameters are present),
; or the label on the left side of the macro (when two parameters are present)

Pin                 macro     [[PinName,]PORT[,BitNumber]]
          #ifb ~@~
              #ifb ~label~
                    merror    A label for the pin is required
              #endif
              #ifb ~text~
                    merror    PORT parm required on first use
              #endif
                    #temp     ~text~.+1
~label~             set       ~text~
~label~.            equ       1<{:temp}
                    mset      0,~label~
          #endif
          #ifb ~label~
                    mreq      1,2:PinName,PORT[,BitNumber]
                    mdef      3,0
                    mset      0,~1~
                    #temp     ~3~
~1~                 set       ~2~
~1~.                set       1<~3~
          #else
              #ifnb ~@~
                    mreq      1:PORT[,BitNumber]
                    mdef      2,0
                    mset      0,~label~
                    #temp     ~2~
~label~             set       ~1~
~label~.            equ       1<~2~
              #endif
          #endif
          #if :temp > 7
                    #Warning  BitNumber ({:temp}) > 7
          #endif
                    endm

;*******************************************************************************
; Check if a pin has been defined (normally via @PIN), and issue error otherwise
; (It can also be used for bit-mapped registers or variables)
; Place it inside a general-purpose module to warn the user including the module
; about missing but required pin definitions.  The user then simply needs to add
; the correct @pin definitions for each missing pin.
; (Note: MSET allows us to do a simple trick; use parameter 2 as an 'embedded
; macro' to define the error directive once, even though we use it twice.)

CheckPin            macro     PinName[,PinName]*
                    mset      0,#Fatal Pin \@~{:loop}.~\@ not defined with @Pin
          #ifndef ~{:loop}.~
                    ~text~
                    mtop      :n
                    mexit
          #endif
          #ifndef ~{:loop}.~.
                    ~text~
                    mtop      :n
          #endif
                    endm

;*******************************************************************************
; Define Bit names using Bit.

BitNum              macro     BinName,BitNumber
                    mreq      1,2:BinName,BitNumber
~1~.                equ       1<~2~
                    endm

;*******************************************************************************
; Define all BitName bits from MinNumber to MaxNumber

Bits                macro     BitName,MinNumber,MaxNumber[,FirstBit]
                    mreq      1,2,3:BitName,MinNumber,MaxNumber[,FirstBit]
                    mdef      4,0
                    mdo
~1~{~2~+:mloop-1}.  equ       1<{~4~+:mloop-1}
                    mloop     {~3~-~2~+1}
                    endm

;*******************************************************************************
; Make PIN an input or output, accordingly

Input               macro     PinName[,RegX|RegY]
                    mreq      1:PinName[,RegX|RegY]
          #ifz ]~1~
                    bclr      [~1~+DDR,#~1~.
          #else
                    bclr      [~1~+DDR,~2~,#~1~.
          #endif
                    endm

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

Output              macro     PinName[,RegX|RegY]
                    mreq      1:PinName[,RegX|RegY]
          #ifz ]~1~
                    bset      [~1~+DDR,#~1~.
          #else
                    bset      [~1~+DDR,~2~,#~1~.
          #endif
                    endm

;*******************************************************************************
; Delay X number of msec.  Automatically adjust delay to bus speed (BUS_KHZ).

DelayXms            macro     [#]msec
                    mreq      1:[#]msec
~0~~1~ms            psha
                    lda       #~@~
Loop$$$             bsr       DelayMS
                    deca
                    bne       Loop$$$
                    pula

          #ifndef DelayMS
                    bra       ?Skip$$$

DelayMS             proc
                    pshd
                    ldd       #DELAY@@
                              #Cycles
Loop@@              decd
                    bne       Loop@@
DELAY@@             equ       BUS_KHZ/:cycles
                    puld
                    rts
?Skip$$$
          #endif
                    endm

;*******************************************************************************
; Turn PIN On or Off, accordingly, and make sure it's an output.

On                  macro     PinName[,RegX|RegY]
                    mreq      1:PinName[,RegX|RegY]
          #ifz ]~1~
                    bset      ~1~,#~1~.
                    bset      ~1~+DDR,#~1~.
          #else
             #ifnoparm ~2~
                    merror    Usage: @~0~ PinName[,RegX|RegY]
             #endif
                    bset      [~1~,~2~,#~1~.
                    bset      [~1~+DDR,~2~,#~1~.
          #endif
                    endm

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

Off                 macro     PinName[,RegX|RegY]
                    mreq      1:PinName[,RegX|RegY]
          #ifz ]~1~
                    bclr      ~1~,#~1~.
                    bset      ~1~+DDR,#~1~.
          #else
             #ifnoparm ~2~
                    merror    Usage: @~0~ PinName[,RegX|RegY]
             #endif
                    bclr      [~1~,~2~,#~1~.
                    bset      [~1~+DDR,~2~,#~1~.
          #endif
                    endm

;*******************************************************************************
; Toggle Pin name

Toggle              macro     PinName
                    mreq      1:PinName
                    psha
                    lda       ~1~
                    eora      #~1~.               ;toggle pin
                    sta       ~1~
                    pula
                    endm

;*******************************************************************************
; CBEQA HC08/9S08 equivalent, and similar ones

cbeqa               macro     Value,Address
                    mreq      1,2:Value,Address
          #ifparm ~2~ = *
                    mset      2,{*}
          #endif
                    cmpa      ~1~
                    beq       ~2~
                    endm

cjeqa               macro     Value,Address
                    mreq      1,2:Value,Address
          #ifparm ~2~ = *
                    mset      2,{*}
          #endif
                    cmpa      ~1~
                    jeq       ~2~
                    endm

cbnea               macro     Value,Address
                    mreq      1,2:Value,Address
          #ifparm ~2~ = *
                    mset      2,{*}
          #endif
                    cmpa      ~1~
                    bne       ~2~
                    endm

cjnea               macro     Value,Address
                    mreq      1,2:Value,Address
          #ifparm ~2~ = *
                    mset      2,{*}
          #endif
                    cmpa      ~1~
                    jne       ~2~
                    endm

;*******************************************************************************
; ADD with Carry D

adcd                macro     [#]Operand
                    mreq      1:[#]Operand
          #ifparm ~#~
                    adcb      #~1~&$FF
                    adca      #~1~>8
                    mexit
          #endif
                    adcb      ~1,~+1~,1~,~2~
                    adca      ~@~
                    endm

;*******************************************************************************
; LSL for words

lsl.w               macro     Operand
                    mreq      1:Operand
                    lsl       ~1,~+1~,1~,~2~
                    rol       ~@~
                    endm

;*******************************************************************************

Copyright           macro     [SinceYear]
                    mdef      1,{:year}
          #ifparm ~1~ = {:year}
                    mset      1
          #else
                    mset      1,~1~-
          #endif
                    #Message  Copyright (c) ASPiSYS ~1~{:year}
                    fcs       'Copyright (c) ASPiSYS ~1~{:year}'
                    endm

;*******************************************************************************
; Some commonly-used OS11-related macros
;*******************************************************************************

;*******************************************************************************
; Give up current task's remaining timeslice if running under OS11

fNextTask           macro
          #ifdef _MTOS_
                    os        fNextTask
          #else
                    cli
                    nop
          #endif
                    endm

;*******************************************************************************
; Define one (or more) semaphore(s).  Skip already defined ones.

sema                macro     Sema1[,Sema2]*
                    mswap     1,:loop
          #ifndef ~1~
~1~                 exp       :index
MAXSEMAS            set       ~1~
          #endif
                    mtop      :n                  ;repeat for all parms
                    endm

;*******************************************************************************
; Drop macros without warnings (Can't drop itself)

Drop                macro     Macro1[,Macro2]*
                    mreq      1:Macro1[,Macro2]*
                    #push
                    #NoWarn
                    mdo
                    mswap     1,:mloop
          #ifnoparm ~1~ = ~0~                     ;;skip self
                    #Drop     ~1~
          #endif
                    mloop     :n
                    #pull
                    endm

;*******************************************************************************
; FCB (Form Constant Byte) with BCD value of the parameter constant (upto 99)

BCD                 macro     Constant[,Constant]*
                    mreq      1:Constant[,Constant]*
                    mswap     1,:loop
                    fcb       ~1~\10|{~1~\100/10<4}  ;;\100 to truncate high byte
                    mtop      :n
                    endm

;*******************************************************************************
; Define a Pascal-style string

StrPas              macro     'string text'
                    mset      #
                    mreq      1:'string text'
                    mstr      1
                    fcc       :1-2,~1~            ;length, string text
                    endm

;*******************************************************************************
; Fill a (normally) non-RAM memory range with specific value or address low byte

FillROM             macro     From,To[,Value]     ;Fill a ROM range with value
                    mreq      1,2:From,To[,Value]
                    org       ~1~                 ;beginning at specified location
                    mset      1,{~2~-~1~+1}       ;now 1 holds the number of bytes
          #ifnoparm ~3~
                    mdo                           ;place value (low byte of address)
                    fcb       {:pc&$FF(h)}
                    mloop     ~1~                 ;repeat with next location
                    mexit
          #endif
                    mdo                           ;place value (user-supplied)
                    fcb       ~3~
                    mloop     ~1~                 ;repeat with next location
                    endm

;*******************************************************************************
; Symbol to the left of macro call (if present) and :MEXIT internal variable
; are SET to the integer Log2 (log base two) of the given expression.
; Quick-n-dirty calculation of integer part of log2(n) for values upto 2^31-1
; Useful to get power from value (e.g., as when used with various prescalers.)
; With the optional ShiftLeftBits parameter, one can shift the result into the
; expected bit positions (e.g., within a larger bitmap).

#ifnomdef Log2
Log2                macro     Expr[,ShiftLeftBits]
                    mreq      1:Usage: Label @~0~ Expression[,ShiftLeftBits]
                    mdef      2,0
                    #temp     :loop-2
#ifz ~1~
                    #temp     :temp<{~2~}
          #ifparm ~label~
~label~             set       :temp
          #endif
                    mexit     :temp
#endif
                    mset      1,{~1~>1}
                    mtop
                    endm
#endif

;*******************************************************************************
; Macro for showing end-of-program statistics (to be updated as needed)

EndStats            macro     [Module Start Label]
          #ifincluded
                    mexit
          #endif
                    #Message  +-------------------------------------------------
                    #Message  | Statistics (from \@~mfilename~/~0~\@ macro)
                    #Message  +-------------------------------------------------
          #ifdef ?_OBJECT_?
                    mdef      1,?_OBJECT_?
          #endif
          #ifnb ~1~
                    #Message  | Module size...: {*-~1~} bytes
          #endif
                    #temp                         ;;initialize total to zero
                    mset      0
          #ifdef XRAM
                    #temp     XRAM_END-:XRAM+1    ;;count XRAM if available
                    mset      0,[includes XRAM]
          #endif
                    #temp     RAM_END-:RAM+1+:temp ;;add RAM
                    #Message  | Available RAM : {:temp} byte(s) ~text~
          #if :temp-REQUIRED_STACK < 0
                    mset      0,[WARNING] ~text~
          #endif
          #ifdef _MTOS_
                    #temp     :temp-{REQUIRED_STACK*MAXTASKS}
          #else
                    #temp     :temp-REQUIRED_STACK
          #endif
                    #Message  | Non-stack RAM : {:temp} byte(s) ~text~
          #if ROM_END-:ROM+1 >= 0
                    #Message  | Available ROM : {ROM_END-:ROM+1} byte(s)
          #endif
                    mset      0
          #ifdef XROM
                    #Message  | Available XROM: {XROM_END-:XROM+1} byte(s)
          #endif
          #ifdef NUMBER_OF_OS_CALLS
                    #Message  | Total OS calls: {NUMBER_OF_OS_CALLS} (of {MAX_OS_CALLS})
          #endif
                    #Message  | Macros called : {:totalmacrocalls}
                    #Message  | [#]PROCs used : {:proc}
                    #Message  | Max stack used: {:spmax}
                    #Message  +-------------------------------------------------
                    endm

;*******************************************************************************
                    #Exit
;*******************************************************************************
;                   Test various macro expansions
;*******************************************************************************

                    #ListOff
                    #Uses     mcu.inc
                    #ListOn

                    @MyDefaultDirectives

                    @Pin      LED,PORTA,0
                    @On       LED,x
                    @Off      LED,y

                    @BitNum   COP,1
                    @Bits     Flag,5,8            ;define Flag5..Flag8 bits & masks
                    @Bits     A,1,5,3             ;define A1..A5 starting from 3

                    @DelayXms 10
                    @DelayXms 20

                    fcb       :year\100,:month,:date  ;decimal date stored as is
                    @bcd      :year,:month,:date  ;decimal date stored as BCD

MyCopyright         @Copyright

                    @StrPas   'Hello World!'      ;a Pascal string
                    @StrPas   '.. etc ..'         ;and another one
                    @StrPas   and w/o quotes

                    @FillROM  $D000,$D0FF,$AA     ;fill range with $AA
                    @FillROM  $D100,$D1FF         ;fill range with low address byte

LogOf4096           @Log2     2*2048              ;Set symbol to log2 of following expression
                    #Message  Log2 returned: {LogOf4096}