;   ***************************************************************
;   * Copyright (C) 2008, Embed Inc (http://www.embedinc.com)     *
;   *                                                             *
;   * Permission to copy this file is granted as long as this     *
;   * copyright notice is included in its entirety at the         *
;   * beginning of the file, whether the file is copied in whole  *
;   * or in part and regardless of whether other information is   *
;   * added to the copy.                                          *
;   *                                                             *
;   * The contents of this file may be used in any way,           *
;   * commercial or otherwise.  This file is provided "as is",    *
;   * and Embed Inc makes no claims of suitability for a          *
;   * particular purpose nor assumes any liability resulting from *
;   * its use.                                                    *
;   ***************************************************************
;
;   Standard include file assumed by most PIC source modules.  The specifics
;   must be configured to the particular processor and application.  This
;   is done by setting assembly values before this file is included.  The
;   include file STD_DEF.INS.ASPIC is provided to set defaults for all
;   the required assembly values.  An application should include STD_DEF.INS.ASPIC,
;   then set any values it knows and cares about, then include STD.INS.ASPIC.
;   In this way, applications are protected from changes to this include
;   file that may require additional values to be set.
;
;   The following assembly values are assumed by this file, some of these
;   are set to default values in STD_DEF.INS.ASPIC.
;
;     NREGBANKS  -  Number of register banks the processor has.  This
;       configures macros that set the direct and indirect register banks.
;
;     NCODEPAGES  -  Number of code pages the processor has.  A
;       value of 1 disables code page selection code.
;
;     STACKLAST  -  The address of the last (highest) software stack byte.
;       This is where the first pushed byte is written.  On the 18 family,
;       this is actually lowest address of the stack since the stack grows
;       towards higher addresses on that family.
;
;     STACKSIZE  -  Number of bytes to allocate for the stack.
;
;     FREQ_OSC  -  Processor oscillator frequency in Hz.  This is the
;       effective oscillator frequency on the 18 family, after the PLL,
;       if enabled, is applied.  The instruction clock is FREQ_OSC / 4.
;
;     FAM_12  -  1 if the processor is a 12xxx, meaning 12 bti core,
;       0 otherwise.  Some PICs, like the 12F629, are really 16 family
;       devices using the 14 bit core.  These should have FAM_16, not
;       FAM_12 set to 1.
;
;     FAM_16c5  -  1 if the processor is a 16C5x, 0 otherwise.
;
;     FAM_16  -  1 if the processor is a 16xxx except a 16C5x, 0 otherwise.
;       This is meant to indicate the 14 bit "midrange" core devices, which
;       includes some parts called "12F", like the 12F629.
;
;     FAM_17  -  1 if the processor is a 17xxx, 0 otherwise.
;
;     FAM_18  -  1 if the processor is a 18xxx, 0 otherwise.
;
;     REGSTART  -  Starting address of general registers (REG0 address).
;       The remaining registers immediately follow REG0 at higher addresses.
;
;     NUMREGS  -  Number of general registers.
;
;     COMMREGS_FIRST, COMMREGS_LAST  -  These indicate the range of
;       address offsets within each bank that are mapped to the same
;       memory.  For example, many 16 family processors have 70h - 7Fh
;       within each bank mapped to the same physical registers.
;       COMMREGS_FIRST > COMMREGS_LAST indicates this processor has
;       no such common mapped registers.
;
;     N_IREGS  -  The number of IREG0 - IREGn register to allocate.  These
;       registers are for the exclusive use by interrupt code, since it
;       would be impractical for that code to use the general registers
;       REG0 - REGn.  This constant will be created and set to 0 if it
;       does not already exist.
;
;     INTR_OFF_BUG  -  Set to TRUE if the processor has the interrupt
;       disable bug where the interrupt bit must be checked in a loop
;       to make sure interrupts really got disabled.  FALSE indicates
;       interrupt bit can be altered without checking.
;
;     UART_TYPE  -  Indicates the type of USART this part has.  The
;       possible values are:
;
;       0  -  This part has no UART.
;
;       1  -  Normal 17 family USART, like 17C756A.
;
;       2  -  Normal 16 family USART, like as 16F876.
;
;       3  -  Advanced USART on some 18 family, like 18F1320.
;
;     ADR_WORD  -  Number of address increments per program memory word.
;       This is 1 for 12, 16, and 17 family PICs, and 2 for 18 family.
;
;     PULLUPS_PORTx  -  Mask of which bits of port X (PORTA, PORTB, etc)
;       have passive pullups that can be enabled.  A 1 bit indicates that
;       the I/O pin at that bit position can have an internal passive
;       pullup.  If the high bit (80000000h) is set, then all pullups
;       of this port can only be enabled or disabled together.  If a
;       symbol of this name does not exist for a port, then it is assumed
;       that the port has not passive pullups at all.
;
;     EE_START  -  Starting address of the EEPROM mapped into the program
;       memory space in the HEX file.  This is the starting address to use
;       for the CODE section containing EEPROM initial values.  Note that
;       linker files are set up so that a code section called ".EEDATA"
;       will automatically get mapped to the EEPROM.  If it is permissable
;       to allow the linker to place data in EEPROM, then the code section
;       need not be given the explicit address of EE_START.
;
;     ACCLAST  -  Last location in the bank 0 access bank region of 18
;       family PICs.  This is 7Fh for most 18 family PICs, but some of the
;       larger ones have more SFRs and decrease the access bank region
;       in bank 0.  The existance of ACCLAST indicates the existance of
;       an access bank.
;
;     C18COMP  -  A value of 1 indicates that this code must be compatible
;       with modules compiled with the MPASM C18 compiler.  This code
;       must be able to call routines compiled with C18 and must also be
;       callable from C18 code.  This forces the following differences
;       from the normal (and more efficient) Embed Inc assembler build
;       environment:
;
;         1  -  FSR1 is reserved as the stack pointer and FSR2 as the data
;           stack frame pointer.  The default Embed Inc conventions only
;           reserve FSR2 as the data stack pointer and the application is
;           free to use FSR0 and FSR1 in any way it wants.
;
;         2  -  The stack pointer always points to the next empty byte
;           instead of the byte on the top of the stack.  In both cases
;           the stack grows towards higher addresses.  Leaving the stack
;           pointer pointing to the next byte after the top of stack is
;           less efficient because a PUSH would require a postincrement
;           and a POP a predecrement.  However the PIC 18 architecture
;           has no predecrement, only preincrement.  This requires an
;           extra instruction for each group of bytes popped.
;
;         3  -  Interrupt code advances the stack pointer by one before
;           saving context.  The C18 convention is that the stack pointer
;           points to the next free entry past the top of the stack (where
;           the next pushed byte would go).  However, to deal with the lack
;           of a predecrement addressing mode, the compiler emits code that
;           performs a POP by decrementing the stack pointer then
;           using it to read the top stack byte in two separate operations.
;           An interrupt could occur between these two operations, so any
;           interrupt routine must preserve the byte being pointed to by
;           the stack pointer.  It does this by leaving one untouched byte
;           on the stack before saving any context, then removing the
;           unused byte before returning from the interrupt.
;
;       A value of 0 indicates this code need not be compatible with
;       C18 code.  All other values are illegal.  If C18COMP does not
;       exist, it will be created and set to 0 (defaulting to Embed Inc
;       normal conventions).
;
;     FSRSTACK  -  Indicate the 0-2 number of the FSR to be used as the
;       data stack pointer.  For 18 family only.  Will be created and
;       set to 2 if not previously existing unless C18COMP is 1.  When
;       C18COMP is 1 FSR1 will always be used for the stack, and it is
;       an error if FSRSTACK is defined but not set to 1.
;
;     FSRSC1  -  The 0-2 number of the first FSR that can be used as a
;       scratch pointer.  By convention, this FSR may be trashed by
;       subroutines.  This is never the same FSR as the stack pointer.
;
;     FSRSC2  -  The 0-2 number of the second FSR that can be used as a
;       scratch pointer.  This FSR must be preserved by subroutines when
;       FSRSC2_SAVE is 1.  This is never the same FSR as the stack pointer.
;
;     FSRSC2_SAVE  -  Indicates whether the scratch FSR indicated by FSRSC2
;       must be preserved by subroutines.  0 means it does not need to be
;       preserved, and 1 means it does.
;
;     PROGADRB  -  Number of bytes required to store a full program
;       memory address for this processor.  Only required for 18 family.
;       The 18 family architecture allows for 21 bit program memory
;       addresses, which require 3 bytes to store.  However many PICs
;       of this family have 64Kb or less of program memory, requiring
;       only 2 bytes to store.  Optimizations that avoid storing or
;       otherwise manipulating the unused upper address byte on these
;       PICs can be made when PROGADRB is 2 instead of 3.  (No PIC18
;       currently has 256 bytes or less of program memory, but if that
;       were the case PROGADRB would be 1 on those machines and
;       manipulation of the upper two bytes could be optimized out).
;
;       PROGADRB can also be artifically set lower on PICs with more
;       than 64Kb of program memory if it is absolutely known that no
;       program memory above 64Kb is used.  The default will be set
;       according to the total program memory available.  Don't mess
;       with the default unless you are absolutely sure you know
;       what you're doing.
;
;     CREATE_FLAGS  -  Create the global FLAGS variable.  This is used
;       by various library arithmetic routines to indicate information
;       about the operation or result.  CREATE_FLAGS is initialized to
;       1 in STD_DEF.INS.ASPIC so that FLAGS is created by default.
;       CREATE_FLAGS can be set to 0 to save 1 RAM location by not
;       creating the FLAGS variable.
;
;     CREATE_TEMPW  -  Create the TEMPW global variable in bank 0 when
;       this symbol is 1.  TEMPW will not be created when it is 0.
;       CREATE_TEMPW is initialized to 1 in STD_DEF.INS.ASPIC so that
;       TEMPW is created by default.  TEMPW is a local temporary save
;       area for W used by some of the macros in this file.
;
;     USING_INTERRUPTS  -  Always TRUE (1) or FALSE (0).  The default is
;       TRUE, meaning the code might be using interrupts.  In this case
;       the INTR_ON and INTR_OFF macros actually emit code to turn
;       interrupts on and off.  If interrupts are not in use, then a
;       section of code that tries to temporarily turn off interrupts
;       will have the effect of enabling interrupts at the end.  Setting
;       this switch to FALSE prevents re-using code that temporarily
;       disables interrupts when interrupts are not in use at all.
;
;     DEBUG_ICD  -  Indicate debugging with a in-circuit debugger.  These have
;       the annoying habit of skidding at breakpoints.  Some macros add NOPs
;       to give the appearance of breakpoints and single stepping stopping
;       as expected.
;
;   Check for required constants and abort on error if any is missing.
;
/if [exist "freq_osc"] then
  ifndef freq_osc
freq_osc equ     [rnd freq_osc]
    endif
  /const freq_inst real = [/ freq_osc 4]
  /endif

  ifndef freq_osc
         error   "Assembly constant FREQ_OSC not set before STD include file called."
    endif

  if fam_18
    ifndef progadrb
         error   PROGADRB not define. Should be defined in STD_DEF.INS.ASPIC.
      endif
    endif

  ifndef n_iregs
n_iregs  equ     0           ;assume no private registers required by interrupt code
    endif

  ifndef debug_icd
debug_icd equ    0
    endif
;
;   Derived constants.
;
freq_inst equ    freq_osc / 4 ;instruction clock frequency in Hz
nsec_inst equ    1000000000 / freq_inst ;instruction time in nanseconds
;
;   Global assembly time state.
;
w_trashed set    false       ;used by some macros to indicate W got trashed
;
;   Other symbols.
;
;   Define JUMP adr
;
;   This inline substitution macro expands to the opcode for doing a simple
;   jump local to the module.
;
  if fam_18                  ;18 family processor
#define jump bra
    else                     ;all other processors
#define jump goto
    endif
;
;   Fix bug in some of the 18F include files where the INT2IP bit in
;   INTCON3 is named INT2P instead.
;
  ifdef intcon3              ;this part has INTCON3 register ?
    ifndef int2ip            ;INT2IP is not defined ?
      ifdef int2p            ;but INT2P is ?
#define int2ip 7             ;define the correct bit name
        endif
      endif
    endif
;
;   Make sure C18COMP is defined.
;
  ifndef c18comp
c18comp  equ     0           ;default to don't need to be compatible with C18 code
    endif
;
;   Define the FSR numbers if this is a 18 family PIC.
;
  if fam_18
    ifndef fsrstack
      if c18comp
fsrstack set     1           ;C18 compiler uses FSR1 for stack pointer
        else
fsrstack set     2           ;default to FSR2 as stack pointer
        endif
      endif
    if (fsrstack < 0) || (fsrstack > 2)
         error   Out of range value for FSRSTACK found in STD.INS.ASPIC.
      endif

    if c18comp && (fsrstack != 1)
         error   FSRSTACK set to #v(fsrstack), must be 1 for C18 compatibility.
      endif

    if c18comp
fsrsc1   equ     0
fsrsc2   equ     2
fsrsc2_save equ  1
      else
fsrsc1   equ     0
fsrsc2   equ     1
fsrsc2_save equ  0
      endif
    endif
;
;***********************************************************************
;
;   General registers
;
;   Several registers are declared in global (not banked) memory that are
;   intended to act like general registers of other processors.
;   Subroutines are expected to preserve these registers except as
;   they are explicitly used to pass return values as documented
;   individually for each subroutine.  It is assumed that subroutines
;   trash other state, such as W and FSR unless otherwise documented.
;
;   The general registers (REG0 - REGn) are guaranteed to be mapped to
;   memory in that order.
;
;   The register addresses are declared here as constants so that their
;   addresses are know at assembly time.  The registers are defined in
;   module REGS.ASPIC.  The main routine must have an external reference
;   to REGS to insure that the memory space for the registers is actually
;   allocated.
;
;   Create one REGn and REGFn symbol for each register.  The REGn symbols
;   are the memory addresses of the registers.  The REGFn symbols are flags
;   used to identify a set of registers.  Each REGFn symbol has one
;   unique bit set.  These symbols can be ORed together to identify an
;   arbitrary set of registers.
;
tempasm1 set     0           ;init symbol number
  while tempasm1 < numregs
reg#v(tempasm1) equ regstart + tempasm1 ;define REGn register address symbol
regf#v(tempasm1) equ 1 << tempasm1 ;define REGFn register flag symbol
tempasm1 set     tempasm1 + 1 ;advance to next symbol number
    endw
noregs   equ     0           ;special flag value to indicate no registers
;
;   Declare the 32 bit registers A thru D and their flags if general registers
;   are declared to cover them.
;
  if numregs >= 4
rega     equ     regstart
regfa    equ     b'1111'
    endif
  if numregs >= 8
regb     equ     regstart + 4
regfb    equ     b'1111' << 4
    endif
  if numregs >= 12
regc     equ     regstart + 8
regfc    equ     b'1111' << 8
    endif
  if numregs >= 16
regd     equ     regstart + 12
regfd    equ     b'1111' << 12
    endif

regf_allregs equ ~(-1 << numregs) ;mask for all REG0-REGn general registers
;
;   Create FLAGS register.  This register receives the result of compare
;   and other operations.  It is always located immediately following the
;   general registers.
;
  if create_flags
flags    equ     regstart + numregs ;declare FLAGS register address
regff    equ     1 << numregs ;declare flag for pushing/popping FLAGS register
    else
regff    equ     0           ;there is no FLAGS register to push/pop
    endif
regf_all equ     regf_allregs | regff ;all possible push/pop flags
;
;   Identify particular bits in the FLAGS register.  These constants represent
;   bit numbers so that they can be used directly with bit manipulation
;   instructions.
;
  if create_flags

flagb_lt equ     0           ;comparison result was "less than"
flagb_eq equ     1           ;comparison result was "equal"
flagb_gt equ     2           ;comparison result was "greater than"

flagb_err equ    3           ;error, failed to perform action

flagb_quoa equ   4           ;store quotient in 32 bit A register
flagb_quoc equ   5           ;store quotient in 32 bit C register
flagb_rema equ   6           ;store remainder in 32 bit A register
flagb_remc equ   7           ;store remainder in 32 bit C register

    endif
;
;   Bit masks for the flags defined above
;
  if create_flags

flag_lt  equ     1 << flagb_lt
flag_eq  equ     1 << flagb_eq
flag_gt  equ     1 << flagb_gt
flag_err equ     1 << flagb_err
flag_quoa equ    1 << flagb_quoa
flag_quoc equ    1 << flagb_quoc
flag_rema equ    1 << flagb_rema
flag_remc equ    1 << flagb_remc
         ;
         ;   Mask for all flags that are set as a result of arithmetic
         ;   operations.
         ;
flag_ar  equ     flag_lt | flag_eq | flag_gt

    endif
;
;   Macros for skipping the next instruction depending on some of the flags
;   in FLAGS.
;
  if create_flags

skip_flt macro               ;skip on less than
         dbankif flags
         btfss   flags, flagb_lt
         endm

skip_fle macro               ;skip on less than or equal to
         dbankif flags
         btfsc   flags, flagb_gt
         endm

skip_feq macro               ;skip on equal
         dbankif flags
         btfss   flags, flagb_eq
         endm

skip_fgt macro               ;skip on greater than
         dbankif flags
         btfss   flags, flagb_gt
         endm

skip_fge macro               ;skip on greater than or equal to
         dbankif flags
         btfsc   flags, flagb_lt
         endm

skip_fne macro               ;skip on not equal
         dbankif flags
         btfsc   flags, flagb_eq
         endm

skip_err macro               ;skip on error flag bit set
         dbankif flags
         btfss   flags, flagb_err
         endm

skip_nerr macro              ;skip on error flag bit not set
         dbankif flags
         btfsc   flags, flagb_err
         endm

    endif
;
;   Define the CONFIGnx constants that are the addresses of the
;   configuration words for the 18 family.  These are listed in the
;   manual and mentioned in the standard include file comments, but
;   not defined in the include file for some strange reason.
;
  if fam_18
config_word_first equ h'300000' ;address of first configuration word
config_word_last equ h'30000D' ;address of last configuration word

ii       set     config_word_first ;init address of next config word to define
         variable jj
    while ii <= config_word_last ;once for each config word
jj       set     ((ii - config_word_first) >> 1) + 1 ;1-N config word number
      if (ii & 1) == 0       ;at low word of low/high pair ?
CONFIG#V(jj)L equ ii         ;define LOW config word of this number
        else                 ;at high word of low/high pair
CONFIG#V(jj)H equ ii         ;define HIGH config word of this number
        endif
ii       set     ii + 1      ;advance to next config word address
      endw                   ;back to do next pair of config words
    endif                    ;end of 18 family case
;
;***********************************************************************
;
;   General utility macros.
;
;********************
;
;   Macro SET_TBLPTR adr
;
;   Set the full TBLPTR to the indicated address.  This macro is only
;   defined on machines that have TBLPTR.  PROGADRB must be defined
;   correctly to indicate the number of bytes required to store a
;   program memory address on this machine.
;
  ifdef tblptrl

set_tblptr macro adr
         movlw   low (adr)
         movwf   tblptrl

    if progadrb < 2
         clrf    tblptrh
      else
         movlw   high (adr)
         movwf   tblptrh
      endif

    if progadrb < 3
         clrf    tblptru
      else
         movlw   upper (adr)
         movwf   tblptru
      endif
         endm

    endif
;
;********************
;
;   Macro IREGS_DEFINE
;
;   Define all the IREG0 - IREGn interrupt routine registers.  The number
;   of these registers is set by the constant N_IREGS.
;
iregs_define macro
         local   ii
ii       set     0           ;init loop counter
  while ii < n_iregs         ;once for each IREG to define
ireg#v(ii) res   1           ;define this IREG
         global  ireg#v(ii)  ;declare it global
ii       set     ii + 1
    endw
         endm
;
;********************
;
;   Macro EXTERN_IREGS
;
;   Declare all the IREGn register external to this module.
;
extern_iregs macro
         local   ii
ii       set     0           ;init loop counter
  while ii < n_iregs         ;once for each IREG
         extern  ireg#v(ii)  ;declare it external
ii       set     ii + 1
    endw
         endm
;
;********************
;
;   Macro GETF <adrf>
;
;   Move the contents of the file register ADRF into W.  The Z flag is
;   trashed.
;
getf     macro   adrf

  if fam_16                  ;16C devices
         movf    (adrf), w
         exitm
    endif

  if fam_17                  ;17C devices
         movfp   (adrf), wreg
         exitm
    endif

  if fam_18                  ;18 family devices
         movf    (adrf), w
         exitm
    endif

         error   "GETF macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro GETFZ <adrf>
;
;   Move the contents of the file register ADRF into W.  The Z flag is
;   set according to the value moved.
;
getfz    macro   adrf

  if fam_16                  ;16C devices
         movf    (adrf), w
         exitm
    endif

  if fam_17                  ;17C devices
         movfp   (adrf), wreg
         iorlw   0
         exitm
    endif

  if fam_18                  ;18 family devices
         movf    (adrf), w
         exitm
    endif

         error   "GETFZ macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro TESTFZ <adrf>
;
;   Set the Z flag according to the contents of the file register ADRF.
;   W is preserved.
;
testfz   macro   adrf
  if fam_16                  ;16C devices
         movf    (adrf), f
         exitm
    endif

  if fam_17                  ;17C devices
         comf    (adrf), f
         comf    (adrf), f
         exitm
    endif

  if fam_18                  ;18 family devices
         movf    (adrf), f
         exitm
    endif

         error   "GETF macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro WAITCY NCY
;
;   Generates code that does nothing for the next NCY cycles.  Note that this
;   is not accurate for timing unless interrupts are disabled.  This macro
;   generates no code if NCY is zero or less.  This macro causes no changes
;   to status bits, banking, or other state other than some number of
;   instructions are executed.  The number of instructions emitted is not
;   guaranteed, only that they will take NCY instruction cycles to execute.
;
;   This macro replaces the old WAITNOP macro for most cases.  See the
;   WAITNOP comments below for a discussion of this.
;
waitcy   macro   ncy
         local   n
n        set     (ncy)       ;init number of NOPs left to write

  if fam_12 | fam_16c5 | fam_16
    while n >= 2
         goto    $+1
n        set     n - 2
      endw
    endif

  if fam_18
    while n >= 2
         bra     $+2
n        set     n - 2
      endw
    endif

  while n > 0
         nop
n        set     n - 1
    endw
         endm
;
;********************
;
;   Macro WAITNOP NNOP
;
;   This macro now causes an error so that all uses of it can be examined
;   to determine the reason for its use, then replaced by the specific
;   macro for that use.
;
;   WAITNOP originally just wrote the indicated number of sequential
;   NOP instructions although it was intended for instruction timing.
;   Some time later it was modified to use GOTO $+1 on PIC 16 and BRA $+2
;   on PIC 18 to act as NOPs that took 2 cycles but only one instruction
;   word.  The was beneficial for the intended use.
;
;   However, WAITNOP was also used to create a table of sequential NOP
;   instructions that could be indexed into to exactly synchronize with
;   a timer.  Using 2 cycle NOP instructions defeated this purpose and
;   caused bugs.
;
;   Two separate macros now exist to serve the two purposes, WAITCY (above)
;   and NNOPS (below).  WAITNOP now causes an assembly error so that
;   the purpose can be examined and the appropriate replacement macro
;   chosen.  Note that the newer WAITUS and WAITNS macros may be a
;   better choice than WAITCY in some cases since they automatically
;   adjust to the processor clock speed.
;
waitnop  macro   nnop
         error   WAITNOP has been depricated. See comments in STD.INS.ASPIC.
         endm
;
;********************
;
;   Macro NNOPS nnop
;
;   Write NNOP consecutive NOP instructions.  Nothing is done if NNOP
;   is zero or negative.  The WAITCY macro should be used if the intent
;   is to waste a specific number of instruction cycles, since WAITCY
;   does that and possibly takes fewer instruction words.  Use NNOPS
;   only when a number of consecutive NOP instructions are needed.  This
;   could be the case, for example, when indexing into a list of NOPs
;   to exactly synchronize to a timer value.
;
nnops    macro   nnop
         local   n
n        set     nnop        ;init number of NOPs left to write

  while n > 0
         nop
n        set     n - 1
    endw
         endm
;
;********************
;
;   Macro WAITNS ns, cy
;
;   Generates the minimum necessary inline instructions to wait at least
;   NS nanoseconds minus CY instruction cycles.  This macro is only meant
;   for short delays since inline instructions are generated and the
;   the processor is consumed executing them.  Timing is not accurate unless
;   interrupts are disabled.
;
;   The purpose of this macro is to allow for accurate timing between two
;   instructions with other fixed instructions also between the two events
;   and without having to know the instruction rate.  For example, if a
;   minimum of 1uS is required between two instructions and 3 other instructions
;   are always executed between the two outside this macro, then
;
;     WAITNS 1000, 4
;
;   will add the minimum necessary wait.  Note that the CY parameter is 4
;   and not 3 since there is 1 instruction cycle between consecutive
;   instructions even with no additional instructions in between.
;
;   This macro produces no code if the minimum wait time is already met
;   by the CY instructions.
;
waitns   macro   ns, cy
         local   waitns_ii

waitns_ii set    2000000000 / (freq_inst / 5) ;instruction time in 100fs units
waitns_ii set    (((ns) * 10) + waitns_ii - 1) / waitns_ii ;total instr wait needed
waitns_ii set    waitns_ii - (cy) ;subtract off instructions already waited
  if waitns_ii & h'80000000' ;remaining instructions to wait is negative ?
         exitm
    endif
         waitcy  waitns_ii   ;add the wait instructions
         endm
;
;********************
;
;   Macro WAITUS us, cy
;
;   Just like WAITNS except that the wait time parameter US is in microseconds
;   instead of nanoseconds.
;
waitus   macro   us, cy
         waitns  ((us) * 1000), cy
         endm
;
;********************
;
;   Macro SKIP_WLE
;
;   Skip the next instruction if W was less than or equal to the value
;   it was subtracted from.  This assumes that the carry flag has been
;   preserved from the last SUBWF or SUBLW instruction.
;
skip_wle macro

  if fam_12 || fam_16 || fam_18
         btfss   status, c   ;skip if no borrow occurred
         exitm
    endif

  if fam_17
         btfss   alusta, c
         exitm
    endif

         error   "SKIP_WLE macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro SKIP_WGT
;
;   Skip the next instruction if W was greater than the value
;   it was subtracted from.  This assumes that the carry flag has been
;   preserved from the last SUBWF or SUBLW instruction.
;
skip_wgt macro

  if fam_12 || fam_16 || fam_18
         btfsc   status, c   ;skip if a borrow occurred
         exitm
    endif

  if fam_17
         btfsc   alusta, c
         exitm
    endif

         error   "INTR_WGT macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro SKIP_Z
;
;   Skip the next instruction if the zero flag is set.
;
skip_z   macro

  if fam_12 || fam_16 || fam_18
         btfss   status, z
         exitm
    endif

  if fam_17
         btfss   alusta, z
         exitm
    endif

         error   "SKIP_Z macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro SKIP_NZ
;
;   Skip the next instruction if the zero flag is not set.
;
skip_nz  macro

  if fam_12 || fam_16 || fam_18
         btfsc   status, z
         exitm
    endif

  if fam_17
         btfsc   alusta, z
         exitm
    endif

         error   "SKIP_NZ macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro SKIP_CARR
;
;   Skip the next instruction if a carry occurred.
;
skip_carr macro

  if fam_12 || fam_16 || fam_18
         btfss   status, c
         exitm
    endif

  if fam_17
         btfss   alusta, c
         exitm
    endif

         error   "SKIP_CARR macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro SKIP_NCARR
;
;   Skip the next instruction if no carry occurred.
;
skip_ncarr macro

  if fam_12 || fam_16 || fam_18
         btfsc   status, c
         exitm
    endif

  if fam_17
         btfsc   alusta, c
         exitm
    endif

         error   "SKIP_NCARR macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro SKIP_BORR
;
;   Skip the next instruction if a borrow occurred.
;
skip_borr macro

  if fam_12 || fam_16 || fam_18
         btfsc   status, c
         exitm
    endif

  if fam_17
         btfsc   alusta, c
         exitm
    endif

         error   "SKIP_BORR macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro SKIP_NBORR
;
;   Skip the next instruction if no borrow occurred.
;
skip_nborr macro

  if fam_12 || fam_16 || fam_18
         btfss   status, c
         exitm
    endif

  if fam_17
         btfss   alusta, c
         exitm
    endif

         error   "SKIP_NBORR macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro JMP_WEQ adr
;
;   Jump to ADR if W was equal to the value it was subtracted from.
;   This assumes the Z flag has been preserved from the last SUBWF or
;   SUBLW instruction.
;
jmp_weq  macro   adr

  if fam_18
         bz      adr
         exitm
    endif

         error   "JMP_WEQ macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro JMP_WNE adr
;
;   Jump to ADR if W was not equal to the value it was subtracted from.
;   This assumes the Z flag has been preserved from the last SUBWF or
;   SUBLW instruction.
;
jmp_wne  macro   adr

  if fam_18
         bnz     adr
         exitm
    endif

         error   "JMP_WNE macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro JMP_WLE
;
;   Jump to ADR if W was less than or equal to the value it was
;   subtracted from.  This assumes the C flag has been preserved from the
;   last SUBWF or SUBLW instruction.
;
jmp_wle  macro   adr

  if fam_18
         bc      adr
         exitm
    endif

         error   "JMP_WLE macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro JMP_WGT
;
;   Jump to ADR if W was greater than to the value it was subtracted
;   from.  This assumes the C flag has been preserved from the last SUBWF
;   or SUBLW instruction.
;
jmp_wgt  macro   adr

  if fam_18
         bnc     adr
         exitm
    endif

         error   "JMP_WGT macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro INTR_OFF
;
;   Globally disable interrupts without changing which interrupts are
;   individually disabled.  This macro together with INTR_ON can be used
;   around small sections of code that need to run with interrupts off.
;
;   This macro works around the interrupt off bug on some processors where
;   an interrupt can occur immediately after interrupts are disabled.  This
;   causes interrupts to be re-enabled by the RETFIE in the interrupt service
;   routine.  The work around is to verify that interrupts were indeed
;   disabled on the next instruction and loop back if they weren't.
;   The assembler switch INTR_OFF_BUG is set to TRUE if this processor
;   has the bug.
;
intr_off macro
  if ! using_interrupts
         exitm
    endif

         local   retry

  if fam_16
retry
         bcf     intcon, gie
    if intr_off_bug
         btfsc   intcon, gie
         jump    retry
      endif
         exitm
    endif

  if fam_17
retry
         bsf     cpusta, glintd
    if intr_off_bug
         btfss   cpusta, glintd
         jump    retry
      endif
         exitm
    endif

  if fam_18
         bcf     intcon, gieh
         exitm
    endif

         error   "INTR_OFF macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro INTR_ON
;
;   Globally enable interrupts without changing which interrupts are
;   individually enabled.  This macro together with INTR_OFF can be used
;   around small sections of code that need to run with interrupts off.
;
intr_on  macro
  if ! using_interrupts
         exitm
    endif

  if fam_16
         bsf     intcon, gie
         exitm
    endif

  if fam_17
         bcf     cpusta, glintd
         exitm
    endif

  if fam_18
         bsf     intcon, gieh
         exitm
    endif

         error   "INTR_ON macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro INTR_OFF_LOW
;
;   Disable low priority interrupts.
;
;   Operation is undefined unless separate high and low interrupt
;   priorities are enabled.  This is not checked.
;
;   It is an error to call this macro on a processor that is not
;   capable of high and low priority interrupts.
;
intr_off_low macro
  if ! using_interrupts
         exitm
    endif

  if fam_18
         bcf     intcon, giel
         exitm
    endif

         error   "INTR_OFF_LOW macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro INTR_ON_LOW
;
;   Re-enable low priority interrupts.
;
;   Operation is undefined unless separate high and low interrupt
;   priorities are enabled.  This is not checked.
;
;   It is an error to call this macro on a processor that is not
;   capable of high and low priority interrupts.
;
intr_on_low macro
  if ! using_interrupts
         exitm
    endif

  if fam_18
         bsf     intcon, giel
         exitm
    endif

         error   "INTR_ON_LOW macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro DTWORD val16
;
;   Define a 16 bit value in a table.  The low byte is stored first, the
;   high byte second.
;
dtword   macro   val16

  if fam_16
         retlw   low (val16)
         retlw   high (val16)
         exitm
    endif

  if fam_17 || fam_18
         db      low (val16), high (val16)
         exitm
    endif

         error   "DTWORD macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro DT32I val32
;
;   Define a 32 bit integer value in a table.  The low byte is stored first,
;   the high byte last.
;
dt32i    macro   val32

  if fam_16
         retlw   (val32) & h'FF'
         retlw   ((val32) >> 8) & h'FF'
         retlw   ((val32) >> 16) & h'FF'
         retlw   ((val32) >> 24) & h'FF'
         exitm
    endif

  if fam_17 || fam_18
         data    (val32) & h'FFFF'
         data    ((val32) >> 16)) & h'FFFF'
         exitm
    endif

         error   "DT32I macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro LOADK32 <adr>, <32 bit constant>
;
;   Load the 32 bit constant into the memory locations starting at ADR.
;   ADR is the  address of the least significant byte.  The remaining
;   bytes follow at increasing memory addresses.  The direct register
;   bank must already be set for access to the four data bytes.
;
;   This macro uses CLRF instructions when possible and avoids re-loading
;   W with the same value.
;
loadk32  macro   adr, kk
         local   currw, newval, ii
currw    set     256         ;init current W to invalid value to force next load
ii       set     0           ;init offset of next byte to write into

  while ii < 4               ;once for the offset of each byte to write into
newval   set     ((kk) >> (ii * 8)) & 255 ;make value to write into this byte
    if newval == 0
         clrf    (adr) + ii  ;value is 0, use single instruction
      else
      if fam_18 && (newval == 255)
         setf    (adr) + ii  ;value is all bits on, use single instruction
        else
        if currw != newval   ;W not already set to the value for this byte ?
         movlw   newval      ;load W with the value for this byte
currw    set     newval      ;remember how W is loaded
          endif
         movwf   (adr) + ii  ;write W into this byte
        endif
      endif
ii       set     ii + 1      ;make offset for next byte
    endw                     ;back to do the next byte
         endm
;
;********************
;
;   Macro LOADK24 <adr>, <24 bit constant>
;
;   Load the 24 bit constant into the memory locations starting at ADR.
;   ADR is the  address of the least significant byte.  The remaining
;   bytes follow at increasing memory addresses.  The direct register
;   bank must already be set for access to the data bytes at ADR.
;
;   This macro uses CLRF instructions when possible and avoids re-loading
;   W with the same value.
;
loadk24  macro   adr, kk
         local   currw, newval, ii
currw    set     256         ;init current W to invalid value to force next load
ii       set     0           ;init offset of next byte to write into

  while ii < 3               ;once for the offset of each byte to write into
newval   set     ((kk) >> (ii * 8)) & 255 ;make value to write into this byte
    if newval == 0
         clrf    (adr) + ii  ;value is 0, use single instruction
      else
      if currw != newval     ;W not already set to the value for this byte ?
         movlw   newval      ;load W with the value for this byte
currw    set     newval      ;remember how W is loaded
        endif
         movwf   (adr) + ii  ;write W into this byte
      endif
ii       set     ii + 1      ;make offset for next byte
    endw                     ;back to do the next byte
         endm
;
;********************
;
;   Macro LOADK16 <adr>, <16 bit constant>
;
;   Load the 16 bit constant into the memory locations starting at ADR.
;   ADR is the  address of the least significant byte.  The remaining
;   bytes follow at increasing memory addresses.  The direct register
;   bank must already be set for access to the data bytes at ADR.
;
;   This macro uses CLRF instructions when possible and avoids re-loading
;   W with the same value.
;
loadk16  macro   adr, kk
         local   currw, newval, ii
currw    set     256         ;init current W to invalid value to force next load
ii       set     0           ;init offset of next byte to write into

  while ii < 2               ;once for the offset of each byte to write into
newval   set     ((kk) >> (ii * 8)) & 255 ;make value to write into this byte
    if newval == 0
         clrf    (adr) + ii  ;value is 0, use single instruction
      else
      if currw != newval     ;W not already set to the value for this byte ?
         movlw   newval      ;load W with the value for this byte
currw    set     newval      ;remember how W is loaded
        endif
         movwf   (adr) + ii  ;write W into this byte
      endif
ii       set     ii + 1      ;make offset for next byte
    endw                     ;back to do the next byte
         endm
;
;********************
;
;   Macro LOADK8 <adr>, <8 bit constant>
;
;   Load the 8 bit constant into the memory location at ADR.  The direct register
;   bank must already be set for access ADR.
;
;   This macro uses CLRF and other instructions to load the value into ADR when
;   possible.
;
loadk8   macro   adr, kk
         local   newval
newval   set     low (kk)    ;make the 8 bit value to store in NEWVAL

  if newval == 0
         clrf    (adr)       ;set to 0 in single cycle
         exitm
    endif

  if fam_18 && (newval == h'FF')
         setf    (adr)       ;set to FFh in single cycle
         exitm
    endif

         movlw   newval      ;get the data value into W
         movwf   (adr)       ;write it to the target
         endm
;
;********************
;
;   Macro LOADADR16 dest, adr
;
;   Load the 16 bit address ADR into the two bytes starting at DEST.  The direct register
;   bank state must be set up for access to DEST.  The low byte will be stored at DEST and
;   the high byte at DEST+1.  ADR may be a label or resolvable relocatable expressions
;   that is not known at assembly time.
;
loadadr16 macro  dest, adr
         movlw   low (adr)
         movwf   dest
         movlw   high (adr)
         movwf   (dest)+1
         endm
;
;********************
;
;   Macro LOADADR24 dest, adr
;
;   Load the 24 bit address ADR into the three bytes starting at DEST.  The direct register
;   bank state must be set up for access to DEST.  The low byte will be stored at DEST.
;   ADR may be a label or resolvable relocatable expressions that is not known at assembly
;   time.
;
loadadr24 macro  dest, adr
         movlw   low (adr)
         movwf   dest
         movlw   high (adr)
         movwf   (dest)+1
         movlw   upper (adr)
         movwf   (dest)+2
         endm
;
;********************
;
;   Macro COPYN <dest>, <src>, <n>
;
;   Copy the N bytes starting at SRC into the N bytes starting at DEST.
;   The direct register bank must be set for access to all bytes of SRC
;   and DEST.  The results are undefined if SRC and DEST overlap.  The
;   copy instructions are written successively instead of a runtime
;   loop.  This macros is therefore intended for copying "short" values
;   only.
;
copyn    macro   dest, src, n
         local   ii, didit
ii       set     0           ;init offset from start of SRC and DEST
didit    set     false
  while ii < n               ;once for each byte to copy

    if fam_16 | fam_16c5 | fam_12
         movf    src+ii, w   ;get the source byte
         movwf   dest+ii     ;write it into the destination
didit    set     true
      endif

    if fam_17
         movfp   src+ii, wreg ;get the source byte
         movwf   dest+ii     ;write it into the destination
didit    set     true
      endif

    if fam_18
         movff   src+ii, dest+ii ;copy this byte
didit    set     true
      endif

    if ! didit
         error   "COPYN in STD.INS.ASPIC not implemented for this processor family"
      endif
ii       set     ii + 1      ;increment byte offset for next iteration
    endw
         endm
;
;********************
;
;   Macro COPY32 <dest>, <src>
;
;   Copy the 32 bit value at SRC into DEST.  The direct register bank
;   must be set for access to all bytes of SRC and DEST.  The results
;   are undefined if SRC and DEST overlap.
;
copy32   macro   dest, src
         copyn   dest, src, 4
         endm
;
;********************
;
;   Macro COPY24 <dest>, <src>
;
;   Copy the 24 bit value at SRC into DEST.  The direct register bank
;   must be set for access to all bytes of SRC and DEST.  The results
;   are undefined if SRC and DEST overlap.
;
copy24   macro   dest, src
         copyn   dest, src, 3
         endm
;
;********************
;
;   Macro COPY16 <dest>, <src>
;
;   Copy the 16 bit value at SRC into DEST.  The direct register bank
;   must be set for access to all bytes of SRC and DEST.  The results
;   are undefined if SRC and DEST overlap.
;
copy16   macro   dest, src
         copyn   dest, src, 2
         endm
;
;********************
;
;   Macro SHIFT32RL1 <adr>
;
;   Perform a logical right shift of 1 bit on a 32 bit value.  ADR is the
;   address of the least significant byte.  The remaining bytes follow
;   at increasing memory addresses.  The direct register bank must already
;   be set for access to the four data bytes.
;
shift32rl1 macro adr

  if fam_16
         bcf     status, c   ;set value to shift into high bit
         rrf     (adr) + 3
         rrf     (adr) + 2
         rrf     (adr) + 1
         rrf     (adr) + 0
         exitm
    endif

  if fam_17
         bcf     alusta, c
         rrcf    (adr) + 3
         rrcf    (adr) + 2
         rrcf    (adr) + 1
         rrcf    (adr) + 0
         exitm
    endif

  if fam_18
         bcf     status, c
         rrcf    (adr) + 3
         rrcf    (adr) + 2
         rrcf    (adr) + 1
         rrcf    (adr) + 0
         exitm
    endif

         error   "SHIFT32RL1 macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro SHIFT32RA1 <adr>
;
;   Perform an arithmetic right shift of 1 bit on a 32 bit value.  ADR is the
;   address of the least significant byte.  The remaining bytes follow
;   at increasing memory addresses.  The direct register bank must already
;   be set for access to the four data bytes.
;
;   W is trashed.
;
shift32ra1 macro adr

  if fam_16
         rlf     (adr) + 3, w ;set carry flag to bit value to shift in from left
         rrf     (adr) + 3
         rrf     (adr) + 2
         rrf     (adr) + 1
         rrf     (adr) + 0
         exitm
    endif

  if fam_17
         rlcf    (adr) + 3, w ;set carry flag to bit value to shift in from left
         rrcf    (adr) + 3
         rrcf    (adr) + 2
         rrcf    (adr) + 1
         rrcf    (adr) + 0
         exitm
    endif

  if fam_18
         rlcf    (adr) + 3, w ;set carry flag to bit value to shift in from left
         rrcf    (adr) + 3
         rrcf    (adr) + 2
         rrcf    (adr) + 1
         rrcf    (adr) + 0
         exitm
    endif

         error   "SHIFT32RA1 macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro SHIFT32L1 <adr>
;
;   Perform a left shift of 1 bit on a 32 bit value.  ADR is the
;   address of the least significant byte.  The remaining bytes follow
;   at increasing memory addresses.  The direct register bank must already
;   be set for access to the four data bytes.
;
shift32l1 macro  adr

  if fam_16
         bcf     status, c   ;set value to shift into low bit
         rlf     (adr) + 0
         rlf     (adr) + 1
         rlf     (adr) + 2
         rlf     (adr) + 3
         exitm
    endif

  if fam_17
         bcf     alusta, c
         rlcf    (adr) + 0
         rlcf    (adr) + 1
         rlcf    (adr) + 2
         rlcf    (adr) + 3
         exitm
    endif

  if fam_18
         bcf     status, c
         rlcf    (adr) + 0
         rlcf    (adr) + 1
         rlcf    (adr) + 2
         rlcf    (adr) + 3
         exitm
    endif

         error   "SHIFT32L1 macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro ADD32 <dest>, <src>, <temp>
;
;   Add the 32 bit source value into the 32 bit destination value.  TEMP
;   will be used for temporary storage, and will be trashed.  The 32 bit
;   values are stored with the low byte first.  The current direct access
;   page must be set for access to all state.
;
add32    macro   dest, src, temp

;**********
;
;   For 16C family processors.
;
  if fam_16
         movf    src, w
         addwf   dest        ;do add for byte 0
         clrw                ;init to no carry
         skip_ncarr
         movlw   1

         addwf   dest+1      ;propagate carry
         clrf    temp        ;init to no carry
         skip_ncarr
         incf    temp        ;carry happened on propagate carry
         movf    src+1, w
         addwf   dest+1      ;do the add for this byte
         skip_ncarr
         incf    temp

         movf    temp, w
         addwf   dest+2      ;propagate carry
         clrf    temp        ;init to no carry
         skip_ncarr
         incf    temp        ;carry happened on propagate carry
         movf    src+2, w
         addwf   dest+2      ;do the add for this byte
         skip_ncarr
         incf    temp

         movf    temp, w
         addwf   dest+3      ;propagate carry
         movf    src+3, w
         addwf   dest+3      ;do the add for this byte
         exitm
    endif                    ;end of 16C processor case

;**********
;
;   For 17C family processors.
;
  if fam_17
         movfp   src+0, wreg
         addwf   dest+0      ;add byte 0

         movfp   src+1, wreg
         addwfc  dest+1      ;add byte 1

         movfp   src+2, wreg
         addwfc  dest+2      ;add byte 2

         movfp   src+3, wreg
         addwfc  dest+3      ;add byte 3
         exitm
    endif                    ;end of 17C processor case

;**********
;
;   For 18 family processors.
;
  if fam_18
         movf    (src)+0, w
         addwf   (dest)+0    ;add byte 0

         movf    (src)+1, w
         addwfc  (dest)+1    ;add byte 1

         movf    (src)+2, w
         addwfc  (dest)+2    ;add byte 2

         movf    (src)+3, w
         addwfc  (dest)+3    ;add byte 3
         exitm
    endif                    ;end of 18 family case

         error   "ADD32 macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro ADD24 <dest>, <src>, <temp>
;
;   Add the 24 bit source value into the 24 bit destination value.  TEMP
;   will be used for temporary storage, and will be trashed.  The 24 bit
;   values are stored with the low byte first.  The current direct access
;   page must be set for access to all state.
;
add24    macro   dest, src, temp

;**********
;
;   For 16C family processors.
;
  if fam_16
         movf    src, w
         addwf   dest        ;do add for byte 0
         clrw                ;init to no carry
         skip_ncarr
         movlw   1

         addwf   dest+1      ;propagate carry
         clrf    temp        ;init to no carry
         skip_ncarr
         incf    temp        ;carry happened on propagate carry
         movf    src+1, w
         addwf   dest+1      ;do the add for this byte
         skip_ncarr
         incf    temp

         movf    temp, w
         addwf   dest+2      ;propagate carry
         movf    src+2, w
         addwf   dest+2      ;do the add for this byte
         exitm
    endif                    ;end of 16C processor case

;**********
;
;   For 17C family processors.
;
  if fam_17
         movfp   src+0, wreg
         addwf   dest+0      ;add byte 0

         movfp   src+1, wreg
         addwfc  dest+1      ;add byte 1

         movfp   src+2, wreg
         addwfc  dest+2      ;add byte 2
         exitm
    endif                    ;end of 17C processor case

;**********
;
;   For 18 family processors.
;
  if fam_18
         movf    (src)+0, w
         addwf   (dest)+0    ;add byte 0

         movf    (src)+1, w
         addwfc  (dest)+1    ;add byte 1

         movf    (src)+2, w
         addwfc  (dest)+2    ;add byte 2
         exitm
    endif                    ;end of 18 family case

         error   "ADD24 macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro ADD16 <dest>, <src>
;
;   Add the 16 bit source value into the 16 bit destination value.  The
;   16 bit values are stored with the low byte first.  The current direct
;   access page must be set for access to all state.
;
add16    macro   dest, src

;**********
;
;   For 16C family processors.
;
  if fam_16
         movf    src+0, w
         addwf   dest+0      ;do add for byte 0
         skip_ncarr          ;no carry into upper byte ?
         incf    dest+1      ;propagate the carry

         movf    src+1, w
         addwf   dest+1      ;do the add for byte 1
         exitm
    endif                    ;end of 16C processor case

;**********
;
;   For 17C family processors.
;
  if fam_17
         movfp   src+0, wreg
         addwf   dest+0      ;add byte 0

         movfp   src+1, wreg
         addwfc  dest+1      ;add byte 1
         exitm
    endif                    ;end of 17C processor case

;**********
;
;   For 18 family processors.
;
  if fam_18
         movf    (src)+0, w
         addwf   (dest)+0    ;add byte 0

         movf    (src)+1, w
         addwfc  (dest)+1    ;add byte 1
         exitm
    endif                    ;end of 18 family case

         error   "ADD16 macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro SUB32 <dest>, <src>, <temp>
;
;   Subtract the 32 bit source value from the 32 bit destination value
;   and put the result into the destination.  TEMP will be used for temporary
;   storage, and will be trashed.  The 32 bit values are stored with the
;   low byte first.  The current direct access page must be set for access
;   to all state.
;
sub32    macro   dest, src, temp

;**********
;
;   For 16C family processors.
;
  if fam_16
         movf    src+0, w
         subwf   dest+0      ;do subtract for byte 0
         clrw                ;init to no borrow
         skip_nborr
         movlw   1

         subwf   dest+1      ;propagate borrow
         clrf    temp        ;init borrow from this byte
         skip_nborr
         incf    temp        ;borrow happened on propagate borrow
         movf    src+1, w
         subwf   dest+1      ;do the subtract for this byte
         skip_nborr
         incf    temp        ;set borrow from this byte

         movf    temp, w     ;get borrow value from last byte
         subwf   dest+2      ;propagate borrow
         clrf    temp        ;init borrow from this byte
         skip_nborr
         incf    temp        ;borrow happened on propagate borrow
         movf    src+2, w
         subwf   dest+2      ;do the subtract for this byte
         skip_nborr
         incf    temp        ;set borrow from this byte

         movf    temp, w     ;get borrow value from last byte
         subwf   dest+3      ;propagate borrow
         movf    src+3, w
         subwf   dest+3
         exitm
    endif                    ;end of 16C processor case

;**********
;
;   For 17C family processors.
;
  if fam_17
         movfp   src+0, wreg
         subwf   dest+0      ;subtract byte 0

         movfp   src+1, wreg
         subwfb  dest+1      ;subtract byte 1

         movfp   src+2, wreg
         subwfb  dest+2      ;subtract byte 2

         movfp   src+3, wreg
         subwfb  dest+3      ;subtract byte 3
         exitm
    endif                    ;end of 17C processor case

;**********
;
;   For 18 family processors.
;
  if fam_18
         movf    (src)+0, w
         subwf   (dest)+0    ;subtract byte 0

         movf    (src)+1, w
         subwfb  (dest)+1    ;subtract byte 1

         movf    (src)+2, w
         subwfb  (dest)+2    ;subtract byte 2

         movf    (src)+3, w
         subwfb  (dest)+3    ;subtract byte 3
         exitm
    endif                    ;end of 18 family case

         error   "SUB32 macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro SUB24 <dest>, <src>
;
;   Subtract the 24 bit source value from the 24 bit destination value
;   and put the result into the destination.  The 24 bit values are stored
;   with the low byte first.  The current direct access page must be set
;   for access to all state.
;
sub24    macro   dest, src

;**********
;
;   For 16C family processors.
;
  if fam_16
         local   nborr

         movf    src+0, w
         subwf   dest+0      ;do subtract for byte 0
         skip_borr           ;borrow from byte 1 ?
         jump    nborr       ;no borrow
         movlw   1
         subwf   dest+1      ;propagate the borrow to byte 1
         skip_nborr          ;no borrow from byte 2 ?
         decf    dest+2      ;propagate the borrow from byte 1 to byte 2
nborr                        ;skip to here on no borrow from byte 0

         movf    src+1, w
         subwf   dest+1      ;do the subtract for byte 1
         skip_nborr          ;no borrow from next higher byte ?
         decf    dest+2      ;propagate the borrow

         movf    src+2, w
         subwf   dest+2      ;do the subtract for byte 2
         exitm
    endif                    ;end of 16C processor case

;**********
;
;   For 17C family processors.
;
  if fam_17
sub24    macro   dest, src

         movfp   src+0, wreg
         subwf   dest+0      ;subtract byte 0

         movfp   src+1, wreg
         subwfb  dest+1      ;subtract byte 1

         movfp   src+2, wreg
         subwfb  dest+2      ;subtract byte 2
         exitm
    endif                    ;end of 17C processor case

;**********
;
;   For 18 family processors.
;
  if fam_18
         movf    (src)+0, w
         subwf   (dest)+0    ;subtract byte 0

         movf    (src)+1, w
         subwfb  (dest)+1    ;subtract byte 1

         movf    (src)+2, w
         subwfb  (dest)+2    ;subtract byte 2
         exitm
    endif                    ;end of 18 family case

         error   "SUB24 macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro SUB16 <dest>, <src>
;
;   Subtract the 16 bit source value from the 16 bit destination value
;   and put the result into the destination.  The 16 bit values are
;   stored with the low byte first.  The current direct access page must
;   be set for access to all state.
;
sub16    macro   dest, src

;**********
;
;   For 16C family processors.
;
  if fam_16
         movf    src+0, w
         subwf   dest+0      ;do subtract for byte 0
         skip_nborr          ;no borrow from high byte ?
         decf    dest+1      ;propagate the borrow

         movf    src+1, w
         subwf   dest+1      ;do subtract for byte 1
         exitm
    endif                    ;end of 16C processor case

;**********
;
;   For 17C family processors.
;
  if fam_17
         movfp   src+0, wreg
         subwf   dest+0      ;subtract byte 0

         movfp   src+1, wreg
         subwfb  dest+1      ;subtract byte 1
         exitm
    endif                    ;end of 17C processor case

;**********
;
;   For 18 family processors.
;
  if fam_18
         movf    (src)+0, w
         subwf   (dest)+0    ;subtract byte 0

         movf    (src)+1, w
         subwfb  (dest)+1    ;subtract byte 1
         exitm
    endif                    ;end of 18 family case

         error   "SUB16 macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro NEGATE <dest>, <n>
;
;   Negate the twos complement value starting at address DEST.  DEST is
;   N bytes long and is stored with the least significant byte first.
;   N must be greater than zero, and all bytes of DEST must be accessible
;   with the current direct register bank selection.
;
negate   macro   dest, n
         local   ii
  if n <= 0
         error   "N parameter to macro NEGATE must be at least 1."
    endif

ii       set     0           ;init byte offset into DEST
  while ii < n               ;once for each byte in DEST
         comf    dest+ii     ;ones complement each byte
ii       set     ii + 1      ;make byte offset for next loop
    endw

         incf    dest+0      ;increment the least significant byte
ii       set     1           ;init byte offset into DEST
  while ii < n               ;once for each byte in DEST
         btfsc   status, z   ;no carry from previous byte (wrapped to 0 on inc) ?
         incf    dest+ii     ;propagate the carry to this byte
ii       set     ii + 1      ;make byte offset for next loop
    endw
         endm
;
;********************
;
;   Macro FP24 <name>
;
;   Declare memory for a 24 bit floating point number.  The first (least
;   significant) byte will have the label NAME.  This macro simply reserves
;   3 bytes, documents that these bytes are intended to be one floating
;   point value.
;
fp24     macro   name
name     res     3           ;least to most significant byte order
         endm
;
;********************
;
;   Macro FP24NEG <dest>
;
;   Negate the 24 bit floating point number at DEST.  The register bank setting
;   must be set for direct access to DEST.
;
fp24neg  macro   dest

  if fam_16
         movlw   h'80'       ;get mask for bits to flip
         movf    (dest) + 2  ;set Z flag on the sign and exponent byte
         skip_z              ;FP value is zero, don't change anything ?
         xorwf   (dest) + 2  ;flip the sign bit
         exitm
    endif

  if fam_18
         movf    (dest)+2    ;set Z flag if FP value is zero
         skip_z              ;FP value is zero ?
         btg     (dest)+2, 7 ;flip the sign bit
         exitm
    endif

         error   "FP24NEG macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro FP24ABS <dest>
;
;   Set the 24 bit floating point number at DEST to its absolute value.
;
fp24abs  macro   dest
         bcf     (dest) + 2, 7 ;make sure the sign bit indicates zero or positive
         endm
;
;********************
;
;   Macro UART_SELECT <n>
;
;   Select which UART will be accessed when UART registers and other symbols
;   without a qualifier are used.
;
;   For example, on machines with two UARTs, there is no TXREG, but TXREG1
;   and TXREG2 instead.  This macro would define TXREG to reference whichever
;   is selected by the N argument.
;
;   This macro allows code to be written that uses generic symbols, but that
;   can be reused to drive all UARTs on machines that have multiple UARTs.
;
;   Specifically, the following symbols will exist when the processor has
;   the associated features:
;
;     RCSTA  -  Receive status register
;
;     TXSTA  -  Transmit status register
;
;     SPBRG  -  Baud rate generator register
;
;     TXREG  -  Transmit data register
;
;     RCREG  -  Receive data register
;
;     TXIF_REG  -  Register containing TXIF transmitter ready flag bit
;     TXIF_BIT  -  TXIF bit number within its register
;     TXIF_FLAG  -  String substitution macro for TXIF register and bit number
;
;     TXIE_REG  -  Register containing TXIE transmitter ready flag bit
;     TXIE_BIT  -  TXIE bit number within its register
;     TXIE_FLAG  -  String substitution macro for TXIE register and bit number
;
;     TXIP_REG  -  Register containing TXIP transmitter ready flag bit
;     TXIP_BIT  -  TXIP bit number within its register
;     TXIP_FLAG  -  String substitution macro for TXIP register and bit number
;
;     RCIF_REG  -  Register containing RCIF transmitter ready flag bit
;     RCIF_BIT  -  RCIF bit number within its register
;     RCIF_FLAG  -  String substitution macro for RCIF register and bit number
;
;     RCIE_REG  -  Register containing RCIE transmitter ready flag bit
;     RCIE_BIT  -  RCIE bit number within its register
;     RCIE_FLAG  -  String substitution macro for RCIE register and bit number
;
;     RCIP_REG  -  Register containing RCIP transmitter ready flag bit
;     RCIP_BIT  -  RCIP bit number within its register
;     RCIP_FLAG  -  String substitution macro for RCIP register and bit number
;
;   N must be greater than zero.  It is an error if the processor does not
;   have at least N UARTs when N is greater than 1.  Nothing is done when
;   N is 1 and the processor has no UARTS.  N must be 1 on processors
;   with only 1 UART.
;
uart_select macro n
  if n < 1
         error   "Invalid parameter to UART_SELECT macro.  Must be 1 or more."
         exitm
    endif
;
;***
;
;   Handle first and only UART is being selected.  In this case the
;   normal symbols are already defined in the include file.  Only the
;   special symbols relating to the interrupt bits will be defined.
;
  if n == 1
    ifndef txreg1

ii       set     0
      ifdef txreg
ii       set     1
        endif
      ifdef txreg1
ii       set     1
        endif
      if ii == 0
         exitm               ;this processor has no UARTs
        endif

txif_reg set     pir1
txif_bit set     txif
      ifdef txif_flag
#undefine txif_flag
        endif
#define txif_flag txif_reg, txif_bit

txie_reg set     pie1
txie_bit set     txie
      ifdef txie_flag
#undefine txie_flag
        endif
#define txie_flag txie_reg, txie_bit

      ifdef txip
txip_reg set     ipr1
txip_bit set     txip
        ifdef txip_flag
#undefine txip_flag
          endif
#define txip_flag txip_reg, txip_bit
        endif

rcif_reg set     pir1
rcif_bit set     rcif
      ifdef rcif_flag
#undefine rcif_flag
        endif
#define rcif_flag rcif_reg, rcif_bit

rcie_reg set     pie1
rcie_bit set     rcie
      ifdef rcie_flag
#undefine rcie_flag
        endif
#define rcie_flag rcie_reg, rcie_bit

      ifdef rcip
rcip_reg set     ipr1
rcip_bit set     rcip
        ifdef rcip_flag
#undefine rcip_flag
          endif
#define rcip_flag rcip_reg, rcip_bit
        endif
         exitm
      endif
    endif
;
;***
;
;   Select UART 1.
;
  if n == 1

    ifndef txreg1
         error   "Attempt to select non-existent UART with UART_SELECT macro."
         exitm
      endif

txif_reg set     pir1
txie_reg set     pie1
    ifdef tx1ip
txip_reg set     ipr1
      endif

rcif_reg set     pir1
rcie_reg set     pie1
    ifdef rc1ip
rcip_reg set     ipr1
      endif

rcsta    set     rcsta1
txsta    set     txsta1
spbrg    set     spbrg1
txreg    set     txreg1
rcreg    set     rcreg1

txif_bit set     tx1if
    ifdef txif_flag
#undefine txif_flag
      endif
#define txif_flag txif_reg, txif_bit

txie_bit set     tx1ie
    ifdef txie_flag
#undefine txie_flag
      endif
#define txie_flag txie_reg, txie_bit

    ifdef tx1ip
txip_bit set     tx1ip
      ifdef txip_flag
#undefine txip_flag
        endif
#define txip_flag txip_reg, txip_bit
      endif

rcif_bit set     rc1if
    ifdef rcif_flag
#undefine rcif_flag
      endif
#define rcif_flag rcif_reg, rcif_bit

rcie_bit set     rc1ie
    ifdef rcie_flag
#undefine rcie_flag
      endif
#define rcie_flag rcie_reg, rcie_bit

    ifdef rc1ip
rcip_bit set     rc1ip
      ifdef rcip_flag
#undefine rcip_flag
        endif
#define rcip_flag rcip_reg, rcip_bit
      endif

         exitm
    endif                    ;end of UART 1 case
;
;***
;
;   Select UART 2.
;
  if n == 2

txif_reg set     pir3
txie_reg set     pie3
    ifdef tx2ip
txip_reg set     ipr3
      endif

rcif_reg set     pir3
rcie_reg set     pie3
    ifdef rc2ip
rcip_reg set     ipr3
      endif

rcsta    set     rcsta2
txsta    set     txsta2
spbrg    set     spbrg2
txreg    set     txreg2
rcreg    set     rcreg2

txif_bit set     tx2if
    ifdef txif_flag
#undefine txif_flag
      endif
#define txif_flag txif_reg, txif_bit

txie_bit set     tx2ie
    ifdef txie_flag
#undefine txie_flag
      endif
#define txie_flag txie_reg, txie_bit

    ifdef tx2ip
txip_bit set     tx2ip
      ifdef txip_flag
#undefine txip_flag
        endif
#define txip_flag txip_reg, txip_bit
      endif

rcif_bit set     rc2if
    ifdef rcif_flag
#undefine rcif_flag
      endif
#define rcif_flag rcif_reg, rcif_bit

rcie_bit set     rc2ie
    ifdef rcie_flag
#undefine rcie_flag
      endif
#define rcie_flag rcie_reg, rcie_bit

    ifdef rc2ip
rcip_bit set     rc2ip
      ifdef rcip_flag
#undefine rcip_flag
        endif
#define rcip_flag rcip_reg, rcip_bit
      endif

         exitm
    endif                    ;end of UART 2 case

         error   "Selected UART does not exist or not implemented in UART_SELECT."
         endm
;
;********************
;
;   Make sure unqualified UART symbols exist for the first or only UART.
;
         uart_select 1       ;select first UART, if any present
;
;********************
;
;   Macro UART_BAUD <baud>
;
;   This macro sets assembler variables to indicate the best baud rate
;   generator configuration for the USART in asynchronous mode, given the
;   desired baud rate and the oscillator frequency.  No code is generated.
;   The following assembler variables are set:
;
;     VAL_SPBRG  -  Value for the SPBRG baud rate generator register.
;       For UARTs with 16 bit baud rate generators this will be the
;       full 16 bit value.  The low byte is then intended for SPBRG,
;       and the high byte for SPBRGH.
;
;     BAUD_REAL  -  Real (not desired) baud rate resulting from the
;       selected settings.
;
;     VAL_TXSTA  -  Value for the TXSTA register.  In addition to
;       selecting the proper baud rate generator configuration, this
;       value also selects the following:
;
;       Asynchronous mode
;       8 bits per character
;       Transmitter enabled
;
;     VAL_RCSTA  -  Value for the RCSTA register.  The value will
;       select the following setup:
;
;       Serial port enabled
;       8 bits per character
;       Reception enabled
;       Address detection disabled
;
;     VAL_BAUDCTL  -  Value for the BAUDCTL register for those processors
;       that have this.  This register is part of the enhanced USART of
;       parts like the 18F1320.
;
;   This macro produces an assembly time warning if the closest available
;   baud rate is more then 2.9% from the desired baud rate (off by 25% of
;   a bit time in the middle of the last bit).  It produces an assembly
;   error if the baud rate error is 5.8% or higher.
;
uart_baud macro  baud
         local   err         ;baud rate error in parts per 1000

  if uart_type == 0
         error   "UART_BAUD macro called, but this part has no UART."
         exitm
    endif
;
;**********
;
;   Handle 18F1320 style advanced USART.
;
  if uart_type == 3          ;18F1320 type advanced USART ?

val_txsta set    b'00100100' ;set transmitter configuration
                 ; X-------  not used in asynchronous mode
                 ; -0------  select 8 bits (not 9 bits) per char
                 ; --1-----  enable the transmitter
                 ; ---0----  select asynchronous mode
                 ; ----0---  do not send BREAK next char
                 ; -----1--  init to using high speed baud rate mode
                 ; ------X-  read-only status bit
                 ; -------X  9th bit of transmit data, not used

val_rcsta set    b'10010000' ;set receiver configuration
                 ; 1-------  enable the serial port hardware
                 ; -0------  select 8 bits per received character
                 ; --X-----  unused in asynchronous mode
                 ; ---1----  enable the receiver
                 ; ----0---  disable address detection
                 ; -----XXX  read-only status bits

val_baudctl set  b'00001000' ;set baud rate configuration
                 ; X-X--X--  unused bits
                 ; -X------  read-only status bit
                 ; ---X----  unused in asynchronous mode
                 ; ----1---  use full 16 bit buad rate generator
                 ; ------0-  no wakeup on next falling edge
                 ; -------0  disable auto baud rate detection on next char

val_spbrg set    (freq_osc / baud) - 4 ;baud rate value, 2 fraction bits
val_spbrg set    (val_spbrg + 2) >> 2 ;round and scale to SPBRG value
    if val_spbrg > 65535
val_spbrg set    65535       ;clip at largest allowable value
      endif

baud_real set    freq_osc / (4 * (val_spbrg + 1)) ;find actual baud rate
err      set     (1000 * (baud_real - baud)) / baud ;baud rate err in parts/1000
    if err < 0
err      set     -err        ;make absolute value of error
      endif
    if err > 58
         error   "Baud rate error exceeds 5.8%"
      endif
    if err > 29
         messg   "WARNING: Baud rate error exceeds 2.9%"
      endif
         exitm
    endif                    ;end of 18F1320 type advanced USART
;
;**********
;
;   Assume normal 16 or 17 family USART.
;
;   Init the non-baud rate bits of TXSTA.
;
val_txsta set    b'00100000'
                 ; X-------  not used in asynchronous mode
                 ; -0------  select 8 bits (not 9 bits) per char
                 ; --1-----  enable the transmitter
                 ; ---0----  select asynchronous mode
                 ; ----X---  unused
                 ; -----0--  high/low baud rate select, will be set later
                 ; ------X-  read-only status bit
                 ; -------X  9th bit of transmit data, not used

val_rcsta set    b'10010000' ;set receiver configuration
                 ; 1-------  enable the serial port hardware
                 ; -0------  select 8 bits per received character
                 ; --X-----  unused in asynchronous mode
                 ; ---1----  enable the receiver
                 ; ----0---  disable address detection
                 ; -----XXX  read-only status bits
;
;   Init values assuming will use high speed mode.
;
  if fam_16 || fam_18        ;init to high speed mode if supported
val_txsta set    val_txsta | (1 << brgh)
    endif
val_spbrg set    freq_osc * 16 / baud - 256 ;find divider value, 8 fraction bits
val_spbrg set    (val_spbrg + 128) >> 8 ;round and scale to final SPBRG value
baud_real set    freq_osc / (16 * (val_spbrg + 1)) ;find actual selected baud rate
;
;   Switch to low speed mode if the baud rate generator value would require
;   more than 8 bits to represent, or this chip has no high speed mode.
;
  if (val_spbrg > 255) || fam_17 ;too slow for high speed mode or no HS ?
    if ! fam_17
val_txsta set    val_txsta & ~(1 << brgh) ;disable high speed mode
      endif
val_spbrg set    freq_osc * 4 / baud - 256 ;find divider value, 8 fraction bits
val_spbrg set    (val_spbrg + 128) >> 8 ;round and scale to final SPBRG value
    if val_spbrg > 255       ;clip at largest allowable baud rate generator value
val_spbrg set    255
      endif
baud_real set    freq_osc / (64 * (val_spbrg + 1)) ;find actual selected baud rate
    endif

err      set     (1000 * (baud_real - baud)) / baud ;baud rate err in parts/1000
  if err < 0
err      set     -err        ;make absolute value of error
    endif
  if err > 58
         error   "Baud rate error exceeds 5.8%"
    endif
  if err > 29
         messg   "WARNING: Baud rate error exceeds 2.9%"
    endif
         endm
;
;********************
;
;   Macro UART_SETUP
;
;   Set up the UART according to the assembler variables VAL_SPBRG,
;   VAL_TXSTA, VAL_RCSTA, and VAL_BAUDCTL.  See the documentation for
;   the UART_BAUD macro for a description of these variables.
;
;   The values do not need to be set by the UART_BAUD macro, although
;   that may be a convenient way to do so.
;
uart_setup macro
         dbankif rcsta
         clrf    rcsta       ;disable UART and clear any error conditions

         dbankif txsta
         movlw   val_txsta   ;set transmitter configuration
         movwf   txsta

  ifdef baudctl              ;this processor has BAUDCTL register ?
         dbankif baudctl
         movlw   val_baudctl
         movwf   baudctl
    endif

         dbankif spbrg
         movlw   low val_spbrg
         movwf   spbrg       ;set baud rate generator period low byte
  ifdef spbrgh
         dbankif spbrgh
         movlw   high val_spbrg
         movwf   spbrgh      ;set baud rate generator period high byte
    endif

         dbankif rcsta
         movlw   val_rcsta
         movwf   rcsta       ;set receiver configuration and enable UART
         endm
;
;********************
;
;   Macro SETREG val, reg
;
;   Set the register REG to the value VAL.  Both REG and VAL must be
;   constants.  The instructions are optimized.  For example, CLRF is
;   used if the value is zero.
;
setreg   macro   val, reg
         dbankif (reg)
  if (val) == 0              ;value is exactly zero
         clrf    (reg)
    else                     ;value is not zero
         movlw   (val)
         movwf   (reg)
    endif
         endm
;
;********************
;
;   Macro TIMER0_PER cy
;
;   Update timer 0 so that it next wraps CY cycles from the previous wrap.  This
;   can be useful in a timer 0 interrupt routine to set the exact number of
;   cycles until the next timer 0 interrupt.  Timer 0 is assumed to be running
;   from the instruction clock.  The appropriate value is added into timer 0,
;   so this macro does not need to be invoked a fixed delay after the last
;   timer 0 wrap.  CY must be a constant.
;
;   The timer sets its interrupt flag when counting from 255, which wraps back
;   to 0.  If left alone, the timer therefore has a period of 256 instruction
;   cycles.  When adding a value into the timer, the increment is lost during
;   the add instruction, and the timer is not incremented for two additional
;   cycles when the TMR0 register is written to.  This effectively adds 3 more
;   cycles to the timer 0 wrap period.  These additional cycles are taken
;   into account in computing the value to add to TMR0.
;
timer0_per macro cy
         dbankif tmr0
         movlw   256 + 3 - (cy)
         addwf   tmr0
         endm
;
;********************
;
;   Macro TIMER2_CYCLES tmr2ncy
;
;   Calculate a timer 2 setup to achieve the interrupt period of TMR2NCY
;   instruction cycles.  This macro generates no code, but sets the following
;   assembler variables:
;
;     TMR2_PRE  -  timer 2 prescaler value: 1, 4, or 16
;
;     TMR2_PER  -  timer 2 period divide value: 1 - 256
;
;     TMR2_POS  -  timer 2 postscaler value: 1 - 16
;
;   The hardware PWM period, if used, is defined by the prescaler and period
;   values only.  The number of instructions per PWM period is therefore
;   TMR2_PRE * TMR2_PER, whereas the number of instructions per timer 2
;   interrupt (if enabled) is TMR2_PRE * TMR2_PER * TMR2_POS.
;
;   An error is generated if the indicated period can not be attained exactly
;   within the constraints of the timer 2 hardware.  The postscaler is
;   set to the minimum possible value that results in the specified
;   period.
;
timer2_cycles macro tmr2ncy

tmr2_pos set     1           ;init the postscaler to minimum
  while tmr2_pos <= 16       ;loop thru increasing postscaler values
tmr2_pre set     1           ;init the prescaler to minimum
    while tmr2_pre <= 16     ;loop thru increasing prescaler values
tmr2_per set     tmr2ncy / (tmr2_pre * tmr2_pos) ;make period value with this pre/post
      if (tmr2_per >= 1) && (tmr2_per <= 256) ;period within range ?
        if (tmr2_pre * tmr2_per * tmr2_pos) == tmr2ncy ;result achieved exactly ?
         exitm               ;found exact result, all done
          endif
        endif
tmr2_pre set     tmr2_pre * 4 ;advance to next prescaler value to try
      endw                   ;back to try with this new prescaler value
tmr2_pos set     tmr2_pos + 1 ;advance to next postscaler value to try
    endw                     ;back to try with this new postscaler value

         error   "Requested timer 2 period can not be achieved in TIMER2_USEC."
         endm
;
;********************
;
;   Macro TIMER2_USEC usec
;
;   Calculate a timer 2 setup to achieve an interrupt period of USEC
;   microseconds.  This macro is a wrapper around macro TIMER2_CYCLES where the
;   period is expressed in microseconds instead of instruction cycles.  See the
;   TIMER2_CYCLES documentation (above) for details.
;
timer2_usec macro usec
         local   nsec        ;period in nanoseconds
         local   ninstr      ;number of instruction cycles in period

ninstr   set     ((freq_inst / 16) * usec) / 62500 ;instructions in period
  if ((62500 * ninstr) / (freq_inst / 16)) != usec
         error   "Requested period not multiple of instruction time in TIMER2_USEC"
         exitm
    endif

         timer2_cycles ninstr
         endm
;
;********************
;
;   Macro TIMER2_SETUP
;
;   Set up timer 2 to periodically set the timer 2 interrupt flag.
;   This macro only sets up the timer hardware and initializes the flag
;   to reset.  It does not enable the timer 2 interrupt.
;
;   Timer 2 divides the instruction clock by a prescaler, period, and
;   postscaler to make the interrupt period.  The following assembler
;   variables must be previously set to define the timer 2 divider chain:
;
;     TMR2_PRE  -  timer 2 prescaler value: 1, 4, or 16
;
;     TMR2_PER  -  timer 2 period divide value: 1 - 256
;
;     TMR2_POS  -  timer 2 postscaler value: 1 - 16
;
;   Note that the TIMER2_USEC macro can be used to compute these values
;   given a desired interrupt period.  It is an error if any of these
;   assembler variables don't exist or are set to invalid values.
;
timer2_setup macro
         local   t2con_val
;
;   Verify prescaler value is valid.
;
  ifndef tmr2_pre
         error   "TMR2_PRE variable not defined before TIMER2_SETUP_INTR called."
         exitm
    endif
  if !((tmr2_pre == 1) || (tmr2_pre == 4) || (tmr2_pre == 16))
         error   "Illegal TMR2_PRE value found in TIMER2_SETUP_INTR macro."
         exitm
    endif
;
;   Verify period value is valid.
;
  ifndef tmr2_per
         error   "TMR2_PER variable not defined before TIMER2_SETUP_INTR called."
         exitm
    endif
  if (tmr2_per < 1) || (tmr2_per > 256)
         error   "Illegal TMR2_PER value found in TIMER2_SETUP_INTR macro."
         exitm
    endif
;
;   Verify postscaler value is valid.
;
  ifndef tmr2_pos
         error   "TMR2_POS variable not defined before TIMER2_SETUP_INTR called."
         exitm
    endif
  if (tmr2_pos < 1) || (tmr2_pos > 16)
         error   "Illegal TMR2_POS value found in TIMER2_SETUP_INTR macro."
         exitm
    endif
;
;   All configuration value are within range.  Now set up the timer 2
;   state and the associated interrupt.
;
         dbankif pr2
         movlw   tmr2_per - 1 ;set the period register
         movwf   pr2

         dbankif t2con
t2con_val set    b'00000100' ;set static timer 2 control bits
                 ; X-------  unused
                 ; -0000---  postscaler selection, set below
                 ; -----1--  enable timer 2
                 ; ------00  init prescaler to 1, altered below if different
t2con_val set    t2con_val | ((tmr2_pos - 1) << 3) ;merge in postscaler field
  if tmr2_pre == 4           ;prescaler divide value is 4 ?
t2con_val set    t2con_val | 1 ;merge in value for prescaler divide by 4
    endif
  if tmr2_pre == 16          ;prescaler divide value is 16 ?
t2con_val set    t2con_val | 2 ;merge in value for prescaler divide by 16
    endif
         movlw   t2con_val   ;set timer 2 control register
         movwf   t2con

         dbankif tmr2
         clrf    tmr2        ;leave max time before first interrupt
         endm
;
;********************
;
;   Macro TIMER2_SETUP_INTR
;
;   Same ast TIMER2_SETUP (above), but also enables the timer 2
;   interrupt.
;
timer2_setup_intr macro
         timer2_setup
         dbankif pir1
         bcf     pir1, tmr2if ;clear any existing interrupt condition
         dbankif pie1
         bsf     pie1, tmr2ie ;enable timer 2 interrupt
         endm
;
;***********************************************************************
;
;   I/O bits and ports handling.
;
;   Create aliases for the data direction registers of the 17C family.
;   Names will be created called TRISp to be compatible with the 16C
;   naming convention.
;
;   Create the aliases PORTA and TRISA on processors that have a single
;   port called GPIO and an associated TRIS register called TRISIO.
;   All the subsequent port handling code assumes that ports and their
;   tristate registers are called PORTx and TRISx, with "X" starting at
;   "A" for the first and continuing with consecutive letters for any
;   additional ports.
;
  ifdef gpio
porta    equ     gpio
    endif
  ifdef trisio
trisa    equ     trisio
    endif

  if fam_17                  ;special handling for PIC 17 family
    ifdef ddra
trisa    equ     ddra
      endif
    ifdef ddrb
trisb    equ     ddrb
      endif
    ifdef ddrc
trisc    equ     ddrc
      endif
    ifdef ddrd
trisd    equ     ddrd
      endif
    ifdef ddre
trise    equ     ddre
      endif
    ifdef ddrf
trisf    equ     ddrf
      endif
    ifdef ddrg
trisg    equ     ddrg
      endif
    ifdef ddrh
trish    equ     ddrh
      endif
    ifdef ddrj
trisj    equ     ddrj
      endif
    endif                    ;end of 17C family case
;
;   Set up the following assembler variables:
;
;     VAL_TRISp  -  Used to track the desired TRISp register value for
;       each port, where P is the port name, as the "A" and "B" in PORTA,
;       PORTB, etc.  The values are initialized to 0, meaning all bits of
;       the port are assumed to be outputs.  In general, unused bits are
;       left unconnected, and should be driven to avoid picking up spurious
;       noise and causing unnecessary current flow and other potential
;       problems.  Bits in these registers are explicitly set by the
;       /INBIT and /OUTBIT preprocessor directives.  See the PREPIC program
;       documentation file for a description of preprocessor directives.
;
;     VAL_PORTp  -  Used to track the desired initial value for port
;       registers.  These symbols are all initialized to 0.  Individual
;       bits can be set with the /OUTBIT preprocessor directive if an
;       initial bit value is supplied.
;
;     VAL_PULLUPp  -  Used to track which bits for each port have a
;       passive pullup enabled with the /INBIT preprocessor directive.
;
;     PULLUPS_PORTp  -  Indicates which bits for each port can have
;       passive pullups enabled.  See comments at top of file for
;       details.  Any of these symbols not already defined for an
;       existing port are defined here and set to 0, indicating the
;       port has not passive pullups.
;
  ifdef porta
val_trisa set    0
val_porta set    0
val_pullupa set  0
    ifndef pullups_porta
pullups_porta equ 0
      endif
    endif

  ifdef portb
val_trisb set    0
val_portb set    0
val_pullupb set  0
    ifndef pullups_portb
pullups_portb equ 0
      endif
    endif

  ifdef portc
val_trisc set    0
val_portc set    0
val_pullupc set  0
    ifndef pullups_portc
pullups_portc equ 0
      endif
    endif

  ifdef portd
val_trisd set    0
val_portd set    0
val_pullupd set  0
    ifndef pullups_portd
pullups_portd equ 0
      endif
    endif

  ifdef porte
val_trise set    0
val_porte set    0
val_pullupe set  0
    ifndef pullups_porte
pullups_porte equ 0
      endif
    endif

  ifdef portf
val_trisf set    0
val_portf set    0
val_pullupf set  0
    ifndef pullups_portf
pullups_portf equ 0
      endif
    endif

  ifdef portg
val_trisg set    0
val_portg set    0
val_pullupg set  0
    ifndef pullups_portg
pullups_portg equ 0
      endif
    endif

  ifdef porth
val_trish set    0
val_porth set    0
val_pulluph set  0
    ifndef pullups_porth
pullups_porth equ 0
      endif
    endif

  ifdef portj
val_trisj set    0
val_portj set    0
val_pullupj set  0
    ifndef pullups_portj
pullups_portj equ 0
      endif
    endif
;
;***********************************************************************
;
;   Bank and page switching.
;
;   The assembler variables CURRIB and CURRDB are used to keep track of the
;   current indirect and direct register bank settings.  Note that these
;   are only assembly time, not run time variables.  They reflect which
;   bank is currently ASSUMED to be selected.  The programmer must therefore
;   play an active role in making sure these variables are set correctly
;   if they are used.  Proper use can avoid unneccessary bank switching
;   instructions.
;
;   On the 17Cxxx series, similar assembler variables CURRRB and CURRGB
;   are used.  CURRRB is the current special function register bank set
;   by the low nibble of BSR.  CURRGB is the current general register
;   bank set by the high nibble of BSR.
;
;   On the 18 family, CURRIB is not used because each FSR contains the
;   full target address.  There are no RAM banks on the 18 family.
;
nobank   equ     -1          ;value to indicate current bank is unknown

  if fam_16                  ;16Cxxx except 16C5xx ?
currdb   set     nobank      ;init current direct register bank assumption
currib   set     nobank      ;init current indirect register bank assumption
    endif

  if fam_17                  ;17Cxxx ?
currrb   set     nobank      ;init current special function register bank
currgb   set     nobank      ;init current general register bank
    endif

  if fam_18                  ;18 family
currdb   set     nobank      ;init current direct register bank assumption
    endif
;
;********************
;
;   Inline macro functions related to register banks, code pages, and
;   other addressing issues.
;

;
;   BANKOF (ADR)
;
;   Returns the direct register bank number containing the RAM address ADR.
;
  if fam_12
#define bankof(adr) (((adr) >> 5) & 3)
    endif
  if fam_16
#define bankof(adr) (((adr) >> 7) & 3)
    endif
  if fam_17
#define bankof(adr) (((adr) >> 8) & 15)
    endif
  if fam_18
#define bankof(adr) (((adr) >> 8) & 15)
    endif
;
;   IBANKOF (ADR)
;
;   Returns the indirect register bank number containing the RAM address ADR.
;
  if fam_12
#define ibankof(adr) (((adr) >> 5) & 3)
    endif
  if fam_16
#define ibankof(adr) (((adr) >> 8) & 1)
    endif
  if fam_17
#define ibankof(adr) (((adr) >> 8) & 15)
    endif
  if fam_18
#define ibankof(adr) (0)     ;this processor has no indirect banks
    endif
;
;   BANKADRR (BANK)
;
;   Returns an address within the special function register bank BANK.
;
  if fam_17
#define bankadrr(bank) ((((bank) & 15) << 8) + h'10')
    endif
;
;   BANKADRG (BANK)
;
;   Returns an address within the general RAM register bank BANK.
;
  if fam_17
#define bankadrg(bank) ((((bank) & 15) << 8) + h'20')
    endif
;
;   BANKADR (BANK)
;
;   Returns an address within the direct register bank BANK.  An arbitrary
;   address within the bank is returned, except that DBANKIF will not
;   recognize it as an unbanked location.
;
  if fam_12
#define bankadr(bank) (((bank) & 3) << 5)
    endif
  if fam_16
#define bankadr(bank) (((bank) & 3) << 7)
    endif
  if fam_17
#define bankadr(bank) ((((bank) & 15) << 8) + h'20')
    endif
  if fam_18
#define bankadr(bank) ((((bank) & 15) << 8) + h'FF' - (((bank) & 15) << 4))
    endif
;
;   IBANKADR (BANK)
;
;   Returns an address within the indirect register bank BANK.
;
  if fam_12
#define ibankadr(bank) (((bank) & 3) << 5)
    endif
  if fam_16
#define ibankadr(bank) (((bank) & 1) << 8)
    endif
  if fam_17
#define ibankadr(bank) ((((bank) & 15) << 8) + h'20')
    endif
  if fam_18
#define ibankadr(bank) (0)   ;this processor has not indirect banks
    endif
;
;   INACCESSBANK (ADR)
;
;   Returns 1 if ADR is within the access bank, 0 otherwise.  Always
;   returns 0 on processors that don't have an access bank.
;
  if fam_18
#define inaccessbank(adr) (((adr) <= acclast) || ((adr) > (h'F00'+acclast)))
    else
#define inaccessbank(adr) (0)
    endif
;
;   INBANKED (ADR)
;
;   Returns 1 if ADR is in banked memory, 0 otherwise.  Banked memory
;   means some sort of bank settings must be properly set to access
;   the memory.
;
  if fam_18
#define inbanked(adr) (((adr) > (acclast)) && ((adr) <= (h'F00'+acclast)))
    endif
  if fam_16
#define inbanked(adr) (((adr) >= commregs_first) && ((adr) <= commregs_last)))
    endif
;
;   These constants provide addresses that are guaranteed to be within
;   particular register banks.  Note that the bank switching macros below
;   take addresses, not bank numbers.  These constants can be used to
;   convert from bank numbers to addresses within the banks by using the
;   #v(n) assembler syntax.  For example, if "b" is a bank number, then:
;
;     bank#v(b)adr
;
;   is an address within bank b, which can be passed to the DBANKIF macro,
;   for example.
;
ii       set     0           ;init loop counter
  while ii < nregbanks       ;once for each possible direct register bank
bank#v(ii)adr equ bankadr(ii)
ii       set     ii + 1      ;advance to number of next register bank
    endw
;
;********************
;
;   Macro DBANK?
;
;   Invalidate the current direct register data bank assumption.
;   This macro produces no code, only sets assembly time state.
;
;   On 17Cxxx processors, this sets both the special and general register
;   bank assumptions to unknown.  These processors make no distinction
;   between a direct or indirect bank selection.
;
dbank?   macro

  if fam_17
currrb   set     nobank
currgb   set     nobank
         exitm
    endif

currdb   set     nobank
         endm
;
;********************
;
;   Macro IBANK?
;
;   Invalidate the current indirect register data bank assumption.
;   This macro produces no code, only sets assembly time state.
;
;   On 17Cxxx processors, this sets both the special and general register
;   bank assumptions to unknown.  These processors make no distinction
;   between a direct or indirect bank selection.
;
ibank?   macro
  if fam_18
         exitm               ;this processor has no indirect banks
    endif

  if fam_17
currrb   set     nobank
currgb   set     nobank
         exitm
    endif

currib   set     nobank
         endm
;
;********************
;
;   Macro UNBANK
;
;   Invalidates all register bank assumptions.
;
unbank   macro
         dbank?
         ibank?
         endm
;
;********************
;
;   Macro DBANKIS <adr>
;
;   Set the current direct register bank assumption to the bank containing
;   ADR.  This macro generates no code to switch the register bank.  It only
;   updates assembly time state.
;
dbankis  macro   adr

  if fam_17
currrb   set     bankof(adr)
currgb   set     bankof(adr)
         exitm
    endif

  if fam_18
    if inaccessbank(adr)
currdb   set     nobank
         exitm
      endif
    endif

currdb   set     bankof(adr) ;set assumed direct register bank number
         endm
;
;********************
;
;   Macro IBANKIS <adr>
;
;   Set the current indirect register bank assumption to the bank containing
;   ADR.  This macro generates no code to switch the register bank.  It only
;   updates assembly time state.
;
ibankis  macro   adr
  if fam_18
         exitm               ;this processor has no indirect banks
    endif

  if fam_17
currrb   set     bankof(adr)
currgb   set     bankof(adr)
         exitm
    endif

currib   set     ibankof(adr) ;set assumed indirect register bank number
         endm
;
;********************
;
;   Macro RBANKIS <adr>
;
;   Set the current special function register bank assumption to the bank
;   containing ADR.
;
  if fam_17

rbankis  macro   adr
currrb   set     bankof(adr)
         endm

    endif
;
;********************
;
;   Macro GBANKIS <adr>
;
;   Set the current general RAM register bank assumption to the bank
;   containing ADR.
;
  if fam_17

gbankis  macro   adr
currgb   set     bankof(adr)
         endm

    endif
;
;********************
;
;   Macro RBANK?
;
;   Invalidates the current special function register bank assumption.
;
  if fam_17

rbank?   macro
currrb   set     nobank
         endm

    endif
;
;********************
;
;   Macro GBANK?
;
;   Invalidates the current general RAM register bank assumption.
;
  if fam_17

gbank?   macro
currgb   set     nobank
         endm

    endif
;
;********************
;
;   Macro DBANKIF <adr>
;
;   Set the register bank for direct access to address ADR.  This macro
;   sets the RP0, RP1 bits of STATUS appropriately.  The bank bits are
;   not set if they are assumed to already be set correctly as indicated
;   by the assembler variable CURRDB.  CURRDB is updated to the new
;   setting.
;
;   On 18 family processors, this macro sets BSR.  No code is emitted
;   and the bank setting is not altered if ADR is within the access bank,
;   and can therefore be accessed regardless of the BSR setting.
;
dbankif  macro   adr
         local   newbank, ii

newbank  set     bankof(adr) ;find 0-N desired bank number

;*****
;
;   12 bit core devices.
;
  if fam_12
    if nregbanks <= 1          ;this processor only has one register bank ?
         exitm
      endif
    endif

;*****
;
;   17 family devices.
;
  if fam_17
    if nregbanks <= 1          ;this processor only has one register bank ?
         exitm
      endif

ii       set     (adr) & h'FF' ;make address offset within bank
         ;
         ;   Avoid switching the bank if the target address is in an unbanked
         ;   region.  Note that both bank selects are always switched for
         ;   offset 0 even though this address is unbanked.  This is because
         ;   BANKADR always returns the first address within a bank.
         ;
    if ii == 0               ;special case to set both bank selects ?
      if currrb != newbank
         movlb   newbank     ;select new special function registers bank
currrb   set     newbank
        endif
      if currgb != newbank
         movlr   newbank     ;select new general registers bank
currgb   set     newbank
        endif
      endif

    if (ii >= h'10') && (ii <= h'17') ;adr is in banked special func regs region ?
      if currrb != newbank
         movlb   newbank     ;select new special function registers bank
currrb   set     newbank
        endif
      endif

    if (ii >= h'20') && (ii <= h'FF') ;adr is in banked general regs region ?
      if currgb != newbank
         movlr   newbank     ;select new general registers bank
currgb   set     newbank
        endif
      endif

         exitm
    endif                    ;end of 17Cxxx case

;*****
;
;   16 family devices.
;
  if fam_16
    if nregbanks <= 1          ;this processor only has one register bank ?
         exitm
      endif
ii       set     (adr) & h'7F' ;make address offset within bank

    if (ii >= commregs_first) && (ii <= commregs_last) ;in unbanked region ?
         exitm
      endif

    if nregbanks > 1         ;bank bit 0 is meaningful ?
      if (currdb < 0) || (currdb > 3) || ((currdb & 1) != (newbank & 1)) ;need update ?
        if newbank & 1
         bsf     status, rp0
          else
         bcf     status, rp0
          endif
        endif
      endif

    if nregbanks > 2         ;bank bit 1 is meaningful ?
      if (currdb < 0) || (currdb > 3) || ((currdb & 2) != (newbank & 2)) ;need update ?
        if newbank & 2
         bsf     status, rp1
          else
         bcf     status, rp1
          endif
        endif
      endif

currdb   set     newbank     ;update current bank assumption
         exitm
    endif                    ;end of 16Cxxx case

;*****
;
;   18 family devices.
;
  if fam_18

    if inbanked(adr)         ;address is in banked memory ?
      if currdb != newbank   ;not already in this bank ?
         movlb   newbank     ;switch to the new bank
currdb   set     newbank     ;update the current bank setting assumption
        endif
      endif

         exitm
    endif                    ;end of 18 family case

         error   "DBANKIF macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro DBANK <adr>
;
;   Unconditionally set the direct access register bank for access to address
;   ADR.  The assembler state is updated.
;
dbank    macro   adr
         dbank?              ;invalidate current register bank assumption
         dbankif adr         ;set the new register bank
         endm
;
;********************
;
;   Macro IBANKIF <adr>
;
;   Set the register bank for indirect access to address ADR, if needed.
;   The assembler variable CURRIB is assumed to indicate the current
;   indirect register bank setting.  CURRIB is updated.  This macro
;   sets the IRP bit of STATUS appropriately.
;
ibankif  macro   adr
         local   newbank, ii

  if fam_18
         exitm               ;this processor has no indirect banks
    endif

newbank  set     ibankof(adr) ;find 0-N desired bank number

;*****
;
;   17Cxxx devices.
;
  if fam_17
         dbankif adr         ;no direct/indirect distinction on these devices
         exitm
    endif                    ;end of 17Cxxx case

;*****
;
;   16Cxxx devices.
;
  if fam_16
    if nregbanks <= 2        ;this processor only has one indirect register bank ?
         exitm
      endif
ii       set     (adr) & h'7F' ;make address offset within direct bank

    if (ii >= commregs_first) && (ii <= commregs_last) ;in unbanked region ?
         exitm
      endif

    if nregbanks > 2         ;there is more than one indirect bank ?
      if (currib < 0) || (currib > 1) || (currib != newbank) ;need update ?
        if newbank & 1
         bsf     status, irp
          else
         bcf     status, irp
          endif
        endif
      endif
currib   set     newbank     ;update current bank assumption
         exitm
    endif                    ;end of 16Cxxx case

         error   "IBANKIF macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro IBANK <adr>
;
;   Unconditionally set the indirect access register bank for access to
;   address ADR.  CURRIB is updated.
;
ibank    macro   adr
         ibank?              ;invalidate current register bank assumption
         ibankif adr         ;set the new register bank
         endm
;
;********************
;
;   Macro RBANKIF <adr>
;
;   This macro is only defined for processors that have different bank settings
;   for the built in special function registers and the general RAM registers.
;
;   Set the special function register bank for access to ADR.  Nothing is done
;   if ADR is not within the special banked special function register range.
;
  if fam_17

rbankif  macro   adr
         local   newbank, ii

newbank  set     bankof(adr) ;find 0-N desired bank number
ii       set     (adr) & h'FF' ;make address offset within bank

    if (ii >= h'10') && (ii <= h'17') ;adr is in banked special func regs region ?
      if currrb != newbank
         movlb   newbank     ;select new special function registers bank
currrb   set     newbank
        endif
      endif
         endm

    endif                    ;end of FAM_17 case
;
;********************
;
;   Macro GBANKIF <adr>
;
;   This macro is only defined for processors that have different bank settings
;   for the built in special function registers and the general RAM registers.
;
;   Set the general RAM register bank for access to ADR.  Nothing is done
;   if ADR is not within the general RAM register range.
;
  if fam_17

gbankif  macro   adr
         local   newbank, ii

newbank  set     bankof(adr) ;find 0-N desired bank number
ii       set     (adr) & h'FF' ;make address offset within bank

    if (ii >= h'20') && (ii <= h'FF') ;adr is in banked general regs region ?
      if currgb != newbank
         movlr   newbank     ;select new general registers bank
currgb   set     newbank
        endif
      endif
         endm

    endif                    ;end of FAM_17 case
;
;********************
;
;   Macro DEFRAM <address>
;
;   Set up for allocating RAM with subsequent RES directives.  ADDRESS
;   is the address that will be passed to DBANKIF to access any of the
;   RAM defined in this section.  If ADDRESS indicates normal banked
;   memory, then the appropriate .BANKn linker section will be started
;   with a UDATA directive.  If ADDRESS is in common RAM, then the
;   RAM will be in the .UDATA_SHR section.  If ADDRESS is in the access
;   bank of an 18 family, then the RAM will be allocated in the
;   .UDATA_ACS section.
;
;   If the new linker section would be the same as the linker section
;   defined by the previous DEFRAM, then no new directives are written
;   since only one occurrence of a linker section is allowed per source
;   module.
;
;   The assembler symbol DEFRAM_LAST is used to track linker section of
;   the last DEFRAM invocation.  This symbol has the following values:
;
;     -1  -  No previous DEFRAM section
;
;     -2  -  Last linker section was .UDATA_SHR
;
;     -3  -  Last linker section was .UDATA_ACS
;
;     0-N  -  Last linker section was .BANKn
;
defram_last set  -1          ;init to DEFRAM not previously invoked

defram   macro   address
         local   badr
badr     set     address

;*****
;
;   16 family devices.
;
  if fam_16
         local   ii

ii       set     badr & h'7F' ;make address offset within bank
    if (ii >= commregs_first) && (ii <= commregs_last) ;in unbanked region ?
      if defram_last == -2   ;already in this region ?
         exitm
        endif
         udata_shr           ;start the new RAM linker section
defram_last set  -2          ;remember which linker section now in
         exitm
      endif

ii       set     bankof(badr) ;make 0-N register bank number
    if defram_last == ii     ;already in this linker section ?
         exitm
      endif
.bank#v(ii) udata            ;start the new RAM linker section
defram_last set  ii          ;remember which linker section now in
         exitm
    endif                    ;end of 16 family case

;*****
;
;   18 family devices.
;
  if fam_18
         local   ii

    if ((badr >= 0) && (badr <= h'7F')) || ((badr >= h'F80') && (badr <= h'FFF'))
      if defram_last == -3   ;already in access RAM ?
         exitm
        endif
         udata_acs           ;start the new RAM linker section
defram_last set  -3          ;remember which linker section now in
         exitm
      endif                  ;end of access RAM case

ii       set     bankof(badr) ;make 0-N register bank number
    if defram_last == ii     ;already in this linker section ?
         exitm
      endif
.bank#v(ii) udata            ;start the new RAM linker section
defram_last set  ii          ;remember which linker section now in
         exitm
    endif                    ;end of 18 family case

         error   "DEFRAM macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;***********************************************************************
;
;   Software stack.
;
;   The processor has a limited stack which is only used for subroutine
;   return addresses.  It can not be accessed directly as a general
;   data stack.  This section implements a general data stack.
;
;   Subroutines are normally expected to preserve the general registers
;   REG0 - REGn.  This can be done by using the software stack.
;
;   On the 16 and 17 family the stack grows from high to low addresses,
;   and from low to high addresses on the 18 family due to the available
;   auto increment and decrement operations available for the FSR
;   registers.
;
;   The global register STACKP contains the address of the last byte
;   pushed onto the stack.  This is a software register on the 16 family,
;   and one of the FSR registers on the 17 and 18 families.  STACKP
;   is assumed to be accessible without requiring bank switching.  It is
;   defined in the STACK module when it is a software register.
;
;   The stack is actually defined in module STACK.ASPIC.  Routine
;   STACK_INIT must be called to initialize the stack if it is used at
;   all.  The external reference to STACK_INIT also forces the STACK
;   module to be loaded, thereby defining the stack.  Otherwise, the
;   stack is not loaded and it does not consume any memory.
;
stackbank equ    bankof(stacklast) ;direct register bank containing the stack

  if fam_17
stackp   equ     fsr1        ;second pointer reg will be used as stack pointer
    endif
  if fam_18
stackp   equ     fsr#v(fsrstack) ;FSR will be reserved as stack pointer
    endif
;
;********************
;
;   Macro STACK_SET adr
;
;   Set the data stack so that ADR will be the next byte pushed.  The
;   stack is handled and initialized differently depending on whether
;   C18 compatibility is enabled.
;
stack_set macro  adr
  if fam_18 == 0
         error   Macro STACK_SET only defined for PIC 18 family.
    endif

  if c18comp
;
;   C18 compatibility mode.
;
         lfsr    1, adr
         lfsr    2, adr
    else
;
;   Normal Embed Inc environment.
;
         lfsr    fsrstack, (adr) - 1
    endif

         endm
;
;********************
;
;   Macro STACK_MAKEBUF n, reg16
;
;   Allocate a buffer of N bytes on the data stack and write the starting
;   address of the new region to the 16 byte variable REG16.  The address will
;   be written to REG16 in low then high byte order.  REG16 must be directly
;   accessible with the current bank setting.  The POPSTACK macro can be used
;   to deallocate the buffer from the stack.
;
;   On a PIC 18, REG16 can be one of the FSR registers but must not be the data
;   stack pointer.
;
;   NOTE: The assembly value USING_INTERRUPTS must be set correctly to indicate
;     whether interrupts are currently in use or not.
;
stack_makebuf macro n, reg16
;
;   PIC 18
;
  if fam_18
         ;
         ;   Handle C18 compatibility.  In this mode the data stack pointer
         ;   points to where the next-pushed byte will be written.
         ;
    if c18comp
         movff   fsr#v(fsrstack)l, (reg16) ;save buffer start address
         movff   fsr#v(fsrstack)h, (reg16)+1

         movlw   low (n)
         intr_off            ;disable interrupts while stack pointer inconsistant
         addwf   fsr#v(fsrstack)l
         movlw   high (n)
         addwfc  fsr#v(fsrstack)h
         intr_on             ;re-enable interrupts
         ;
         ;   Handle normal Embed Inc data stack conventions.  In this mode the
         ;   stack pointer points to the last-pushed byte.
         ;
      else
         incf    fsr#v(fsrstack)l, w
         movwf   reg16
         movf    fsr#v(fsrstcak)h, w
         skip_ncarr
         addlw   1
         movwf   (reg16)+1

         movlw   low (n)
         intr_off            ;disable interrupts while stack pointer inconsistant
         addwf   fsr#v(fsrstack)l
         movlw   high (n)
         addwfc  fsr#v(fsrstack)h
         intr_on             ;re-enable interrupts
      endif
         exitm
    endif                    ;end of 18 family case

         error   "STACK_MAKEBUF macro not implemented for this processor"
         endm

;********************
;
;   Macro PUSHREG reg
;
;   Push the contents of the register REG onto the stack.  The direct bank
;   must already be set for access to REG.  On some processors, it is more
;   efficient to push a set of general registers onto the stack with one
;   PUSHREGS macro than to use the PUSHREG macro several times.  This macro
;   also trashes W on some processors.  In that case the assembler variable
;   W_TRASHED is set to TRUE, otherwise it is left unaltered.  Some of
;   the specifics per processor are:
;
;   16 family  -  Relatively inefficient to push a single value.  W
;     trashed.
;
;   18 family  -  Efficient to push a single value.  Multiple PUSHREG
;     just as efficient as a single PUSHREGS pushing the same state.
;
pushreg  macro   reg
;
;   18 family processor.  The FSR indicated by FSRSTACK is reserved as the
;   software stack pointer.  Auto increment/decrement allows a push or pop
;   in a single instruction.
;
  if fam_18
    if c18comp
         movff   reg, postinc#v(fsrstack) ;use C18 stack convention
      else
         movff   reg, preinc#v(fsrstack) ;use normal stack convention
      endif
         exitm
    endif

         error   "PUSHREG macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro PUSHW
;
;   Push the contents of W onto the data stack.
;
pushw    macro
  if fam_18
    if c18comp
         movwf   postinc#v(fsrstack)
      else
         movwf   preinc#v(fsrstack)
      endif
         exitm
    endif

         error   "PUSHW macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro POPREG reg
;
;   Pop the top of the software stack into REG.  The direct bank
;   must already be set for access to REG.  On some processors, it is more
;   efficient to pop a set of general registers onto the stack with one
;   POPREGS macro than to use the POPREG macro several times.  This macro
;   also trashes W on some processors.  In that case the assembler variable
;   W_TRASHED is set to TRUE, otherwise it is left unaltered.  Some of
;   the specifics per processor are:
;
;   16 family  -  Relatively inefficient to pop a single value.  W
;     trashed.
;
;   18 family  -  Efficient to pop a single value if normal Embed Inc conventions
;     used.  Multiple POPREG just as efficient as a single POPREGS.  If C18
;     compiler compatibility enabled, then popping multiple registers with
;     one POPREGS invocation is more efficient than popping the same registers
;     with individual POPREG invocations.
;
popreg   macro   reg
;
;   18 family processor.
;
  if fam_18
    if c18comp
         movf    postdec#v(fsrstack) ;C18 stack convention
         movff   indf#v(fsrstack), reg
      else
         movff   postdec#v(fsrstack), reg ;normal stack convention
      endif
         exitm
    endif

         error   "POPREG macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro POPW
;
;   Pop the top of the data stack into W.
;
popw     macro
  if fam_18
    if c18comp
         movf    postdec#v(fsrstack)
         movf    indf#v(fsrstack), w
      else
         movf    postdec#v(fsrstack), w
      endif
         exitm
    endif

         error   "POPW macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro POPSTACK n
;
;   Pop N bytes off the top of the data stack.  The byte values are lost.
;   W is trashed.
;
;   WARNING:  This macro assumes that interrupts are enabled.  It must not be used
;     if interrupts are disabled because it could enable them.
;
popstack macro   n
         local   psii

  if fam_18

    if (n) <= 4
psii     set     0
      while psii < (n)
         movf    postdec#v(fsrstack)
psii     set     psii + 1
        endw
         exitm
      endif

         movlw   low n
         intr_off            ;temp disable interrupts while stack pointer inconsistent
         subwf   fsr#v(fsrstack)l
         movlw   high n
         subwfb  fsr#v(fsrstack)h
         intr_on             ;re-enable interrupts after stack pointer consistent again
         exitm
    endif                    ;end of FAM_18 case

         error   "POPSTACK macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Stack utility macros that are private to the other macros below.
;   These are not intended to be used directly in source code.
;
  if fam_17

dopush   macro   adr         ;push an individual register onto the stack
    if (bankof(adr) == bankof(stacklast)) || ((adr & h'FF') < h'20') ;same bank ?
         dbankif stacklast
         movfp   adr, indf1  ;move value directly to the stack
      else                   ;variable and stack not in same bank
         dbankif adr
         movfp   adr, wreg   ;temp get value to push into WREG
         dbankif stacklast
         movwf   indf1       ;move the WREG value onto the stack
      endif
         endm

dopop    macro   adr         ;pop an individual register from the stack
    if (bankof(adr) == bankof(stacklast)) || ((adr & h'FF') < h'20') ;same bank ?
         dbankif stacklast
         movpf   indf1, adr  ;get the value directly from the stack
      else                   ;variable and stack not in same bank
         dbankif stacklast
         movpf   indf1, wreg ;temp save the value from the stack in WREG
         dbankif adr
         movwf   adr         ;move the WREG value to its final destination
      endif
         endm

    endif                    ;end of 17 family private stack macros
;
;********************
;
;   Macro PUSHREGS <regflags>
;
;   Push the indicated general registers onto the stack.  The REGFLAGS
;   argument is the OR of the REGFn flags for all the registers that
;   are to be pushed.  The registers are pushed in low to high order.
;
;   It may be more efficient to call this macro once with all the registers
;   to push than to call it separately for each register.  See the
;   PUSHREG macro description for details.
;
;   The assembler variable N_BYTES_PUSHED is set to the number of bytes that
;   were pushed onto the stack.
;
;   NOTE: On PICs with indirect banks, the current indirect bank assumption
;     must be correct.  If the current indirect bank setting is not known,
;     then the IBANK? macro should be invoked before this macro.
;
;   Trashed:
;
;     W, FSR, indirect register bank with CURRIB updated.
;
pushregs macro   regflags    ;push the indicated general registers onto SW stack
         local   f, reg

n_bytes_pushed set 0         ;init to no bytes pushed

f        set     (regflags) & regf_all ;make local sanitized flags value
  if f == 0                  ;nothing to push ?
         exitm
    endif

;*****
;
;   16 family processors.  These use a software stack pointer.
;
  if fam_16

    ifndef stackp
         extern  stackp
      endif
         ibankif stacklast   ;make sure indirect reg bank set for stack access
         movf    stackp, w   ;set indirect pointer to current stack top
         movwf   fsr
;
;   There is at least one register to push and indirect addressing has been
;   set up for the current top of the stack.
;
;   Now push all the registers that have been selected.
;
reg      set     0           ;init register number
    while reg < numregs      ;once for each possible register
      if f & regf#v(reg)     ;this register enabled ?
         decf    fsr         ;point to new stack location
         movf    reg#v(reg), w ;get the register value in W
         movwf   indf        ;write it to the new stack location
n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack
        endif
reg      set     reg + 1     ;advance to next register number
      endw                   ;back to process this new register number

    if f & regff             ;FLAGS register enabled ?
         decf    fsr         ;point to new stack location
         movf    flags, w    ;get the register value in W
         movwf   indf        ;write it to the new stack location
n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack
      endif
;
;   Done pushing individual registers.
;
         movf    fsr, w      ;get new top of stack address
         movwf   stackp      ;update stack pointer register
         exitm
    endif                    ;end of 16 family case

;*****
;
;   For the 17 family processors.  FSR1 is reserved as the stack pointer.
;
  if fam_17
;
;   There is at least one register to push.  FSR1 is pointing to the last
;   byte pushed onto the stack.
;
;   Set up FSR1 for post decrement, then decrement it once to be ready to
;   write the first byte to the stack.
;
         bcf     alusta, fs3 ;set FSR1 mode to post decrement
         bcf     alusta, fs2
         decf    fsr1        ;set up for first write to the stack
;
;   Now push all the registers that have been selected.
;
reg      set     0           ;init register number

    while reg < numregs      ;once for each possible register
      if f & regf#v(reg)     ;this register enabled ?
         dopush  reg#v(reg)
n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack
        endif
reg      set     reg + 1     ;advance to next register number
      endw                   ;back to process this new register number

    if f & regff             ;FLAGS register enabled ?
         dopush  flags
n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack
      endif
;
;   Done pushing individual registers.
;
         incf    fsr1        ;leave stack pointer pointing to last byte pushed
         exitm
    endif                    ;end of 17 family case

;*****
;
;   18 Family processors.  The FSR indicated by FSRSTACK is reserved as the
;   stack pointer.
;
;   The normal Embed Inc convention is that the stack pointer points to the
;   top byte on the stack.  The C18 convention is that it points to the next
;   byte after the top on the stack (where the next pushed byte would go).
;   In both cases the stack grows towards higher addresses.
;
  if fam_18

reg      set     0           ;init register number
    while reg < numregs      ;once for each possible register
      if f & regf#v(reg)     ;this register enabled ?
        if c18comp
         movff   reg#v(reg), postinc#v(fsrstack) ;C18 stack convention
          else
         movff   reg#v(reg), preinc#v(fsrstack) ;normal stack convention
          endif
n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack
        endif
reg      set     reg + 1     ;advance to next register number
      endw                   ;back to process this new register number

    if f & regff             ;FLAGS register enabled ?
      if c18comp
         movff   flags, postinc#v(fsrstack) ;C18 stack convention
        else
         movff   flags, preinc#v(fsrstack) ;normal stack convention
        endif
n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack
      endif
         exitm
    endif                    ;end of 18 family case

;*****
;
         error   "PUSHREGS macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro POPREGS <regflags>
;
;   This macro performs the reverse operation to the PUSHREGS macro.  The
;   REGFLAGS argument is the OR of a set of REGFn flags to indicate the
;   registers to be popped from the stack.  The registers are popped in
;   high to low order.
;
;   It may be more efficient to call this macro once with all the registers
;   to pop than to call it separately for each register.  See the
;   POPREG macro description for details.
;
;   NOTE: On PICs with indirect banks, the current indirect bank assumption
;     must be correct.  If the current indirect bank setting is not known,
;     then the IBANK? macro should be invoked before this macro.
;
;   Trashed:
;
;     W, FSR, indirect register bank, CURRIB updated
;
popregs  macro   regflags    ;pop the indicated general registers from the SW stack
         local   f, reg

f        set     (regflags) & regf_all ;make local sanitized flags value
  if f == 0                  ;nothing to pop ?
         exitm
    endif

;*****
;
;   16 family processors.  These use a software stack pointer.
;
  if fam_16

    ifndef stackp
         extern  stackp
      endif
         ibankif stacklast   ;make sure indirect reg bank set for stack access
         movf    stackp, w   ;set indirect pointer to current stack top
         movwf   fsr
;
;   There is at least one register to pop and indirect addressing has been
;   set up for the current top of the stack.
;
;   Now pop all the registers that have been selected.
;
    if f & regff             ;FLAGS register enabled ?
         movf    indf, w     ;get the top stack value
         movwf   flags       ;update the register value
         incf    fsr         ;point to new top of stack location
      endif

reg      set     numregs - 1 ;init register number
    while reg >= 0           ;once for each possible register
      if f & regf#v(reg)     ;this register selected ?
         movf    indf, w     ;get the top stack value
         movwf   reg#v(reg)  ;update the register value
         incf    fsr         ;point to new top of stack location
        endif
reg      set     reg - 1     ;advance to next register number
      endw                   ;back to process this new register number
;
;   Done poping poping individual registers.
;
         movf    fsr, w      ;get new top of stack address
         movwf   stackp      ;update stack pointer register
         exitm
    endif                    ;end of 16 family case

;*****
;
;   For the 17 family processors.  FSR1 is reserved as the stack pointer.
;
  if fam_17
         local   f, reg
f        set     (regflags) & regf_all ;make local sanitized flags value
    if f == 0                ;nothing to push ?
         exitm
      endif
;
;   There is at least one register to pop.  FSR1 is pointing to the last
;   byte pushed onto the stack, which is the first byte to pop.
;
;   Set up FSR1 for post increment.
;
         bcf     alusta, fs3 ;set FSR1 mode to post decrement
         bsf     alusta, fs2
;
;   Now pop all the registers that have been selected.
;
    if f & regff             ;FLAGS register enabled ?
         dopop   flags
      endif

reg      set     numregs - 1 ;init register number
    while reg >= 0           ;once for each possible register
      if f & regf#v(reg)     ;this register selected ?
         dopop   reg#v(reg)
        endif
reg      set     reg - 1     ;advance to next register number
      endw                   ;back to process this new register number
         exitm
    endif                    ;end of 17 family case

;*****
;
;   18 family processors.  The FSR indicated by FSRSTACK is reserved as the
;   stack pointer.
;
;   The normal Embed Inc convention is that the stack pointer points to the
;   top byte on the stack.  The C18 convention is that it points to the next
;   byte after the top on the stack (where the next pushed byte would go).
;   In both cases the stack grows towards higher addresses.
;
  if fam_18

    if c18comp               ;using C18 stack convention ?
         movf    postdec#v(fsrstack) ;point to the top byte on the stack
      endif

    if f & regff             ;FLAGS register enabled ?
f        set     f & ~regff  ;disable this register flag
      if c18comp
        if f
         movff   postdec#v(fsrstack), flags ;at least one more to pop after this
          else
         movff   indf#v(fsrstack), flags ;this is last pop this macro invocation
          endif
        else
         movff   postdec#v(fsrstack), flags ;pop using normal stack convention
        endif
      endif

reg      set     numregs - 1 ;init register number
    while reg >= 0           ;once for each possible register
      if f & regf#v(reg)     ;this register selected ?
f        set     f & ~regf#v(reg) ;disable this register flag
        if c18comp
          if f
         movff   postdec#v(fsrstack), reg#v(reg) ;at least one more to pop after this
            else
         movff   indf#v(fsrstack), reg#v(reg) ;this is last pop this macro invocation
            endif
          else
         movff   postdec#v(fsrstack), reg#v(reg) ;pop using normal stack convention
          endif
        endif
reg      set     reg - 1     ;advance to next register number
      endw                   ;back to process this new register number
         exitm
    endif                    ;end of 18 family case

;*****
;
         error   "POPREGS macro in STD.INS.ASPIC not implemented for this processor"
         endm
;
;********************
;
;   Macro READSTACK <n>, <dest>
;
;   Read the Nth byte from the top of the data stack into DEST.  A N value of 0
;   indicates  the top (last pushed) byte on the data stack, a value of 1 the
;   next most recently pushed byte, etc.  The direct register bank must be set
;   for access to DEST.  W is trashed.
;
;   Additional details for some implementations:
;
;     PIC 18 family  -  The register bank setting is irrelevant.  It need not
;       be set for access to DEST.
;
readstack macro  n, dest
         local   ofs

;*****
;
;   18 family processors.
;
  if fam_18
ofs      set     (n)         ;init offset from stack pointer
    if c18comp               ;using C18 stack convention ?
ofs      set     ofs + 1     ;stack pointer points to first free location
      endif

    if ofs == 0              ;no offset from where stack pointer is pointing ?
         movff   indf#v(fsrstack), (dest)
         exitm
      endif

    if (ofs < -128) || (ofs > 127)
         error   Stack offset of #v(ofs) out of range in READSTACK macro.
         exitm
      endif

         movlw   low -ofs    ;put address offset from stack pointer target in W
         movff   plusw#v(fsrstack), (dest)
         exitm
    endif                    ;end of PIC 18 case

;*****
;
         error   READSTACK macro not implemented for this processor.
         endm
;
;***********************************************************************
;
;   Subroutine linkage.
;
;   Normally, subroutines preserve the general registers REG0 - REGn,
;   and are free to trash other state, such as W, FSR, and the current
;   bank settings.
;
;   Subroutines external to a module are assumed to be on any page, whereas
;   all code of a module is always on the same page.  By convention, PCLATH
;   is left pointing to the current code page.  This means it must be set
;   before and restored after external calls.
;
;   On the 18 family, the GOTO and CALL instructions contain all address
;   bits, so PCLATH and PCLATU do not matter.  On these processors, PCLATH
;   and PCLATU are not maintained to match the current execution address.
;   There PCLATH and PCLATU must be explicitly set before PCL is modified,
;   such as with computed GOTOs and some table operations.
;
;********************
;
;   Macro ENTER <regflags>
;
;   This macro performs the "standard" operations on subroutine entry.
;   These actions are:
;
;     1 - Invalidate the current register bank assumptions.
;
;     2 - Save the registers indicated by REGFLAGS onto the software stack.
;         REGFLAGS is the or of the REGFn flags for each register to save.
;         REGFLAGS may be NOREGS to indicate no registers are to be saved.
;
;     3 - Sets SAVEDREGS to the flags indicating which registers were saved.
;         This can be used to automatically restore the saved registers
;         later.
;
enter    macro   regflags
         unbank              ;invalidate all register bank assumptions
         pushregs regflags   ;save the indicated registers on the software stack
savedregs set    regflags    ;remember what was saved when routines was entered
         endm
;
;********************
;
;   Macro LEAVE <regflags>
;
;   This macro performs the "standard" operations on subroutine exit.
;   These actions are:
;
;     1 - Restore the registers indicated by REGFLAGS from the software stack.
;         They are assumed to have been pushed in low to high register number
;         order, and will be popped in high to low order.  REGFLAGS may be NOREGS
;         to indicate no registers are to be restored.
;
;     2 - Return from the subroutine.
;
;     3 - Invalidate the current register bank assumptions.  Note that this
;         sets assembly state, not runtime state.  The assumptions are therefore
;         propagated in source code, not execution order.
;
;   NOTE:  The current indirect register bank assumption must be correct when
;     this macro is called.  Note that it is set correctly by the ENTER
;     macro, but may have been altered by intervening code.  Use the IBANK?
;     macro to invalidate the current assumption if not sure.
;
leave    macro   regflags
         popregs regflags    ;restore the indicated register from the software stack
         return              ;return from the subroutine
         unbank              ;invalidate all register bank assumptions
         endm
;
;********************
;
;   Macro LEAVEREST
;
;   Just like LEAVE except that the registers indicated by SAVEDREGS are
;   automatically restored.  SAVEDREGS should have been set by the ENTER
;   macro to the routine being left.
;
leaverest macro
         leave   savedregs
         endm
;
;********************
;
;   Macro GLBSUB <name>, <regflags>
;
;   Declare the start of a new global subroutine.  <name> will be declared
;   as the global subroutine entry point label at the current location.
;   The subroutine entry point will be immediately followed by ENTER <regflags>.
;   In other words, the new subroutine will be start by pushing the registers
;   identified by <regflags> onto the stack.  Nothing will be pushed and no
;   code generated if <regflags> has the value NOREGS.
;
glbsub   macro   name, regflags
name
         global  name
         enter   regflags
         endm
;
;********************
;
;   Macro HLLSUB <name>, <regflags>
;
;   Declare the start of the new global subroutine that must be callable
;   from the current high level language.
;
;   The supported high level languages are:
;
;     C18  -   Microchip MPLAB C18 compiler for the PIC 18.  C18COMP must
;       be set to TRUE.
;
hllsub   macro   name, regflags
nexthllarg set   1           ;init number of next HLL argument to fetch
         glbsub  name, regflags
nexthllarg set   nexthllarg + n_bytes_pushed
         endm
;
;********************
;
;   Macro GETARG8 dest
;
;   Get the next high level language argument, which is assumed to be 8
;   bits in size.  DEST is the location to save the argument value in.  It
;   is assumed that DEST is directly accessible without banking, or that the
;   bank is already set for access to DEST.
;
;   The argument passing conventions differ between compilers.  This macro
;   will generate an error if one of the supported compilers is not in use.
;
;   The supported high level languages are:
;
;     C18  -   Microchip MPLAB C18 compiler for the PIC 18.  C18COMP must
;       be set to TRUE.
;
getarg8  macro   dest
;
;   C18 compiler.  The caller pushes the call arguments onto the data stack
;   in last to first order.  The stack pointer points to the location where
;   the next pushed byte will be written.  On subroutine entry, the stack
;   pointer is pointing to the next byte after where the first call argument
;   is on the stack.
;
  if c18comp
         movlw   low -nexthllarg
         movff   plusw#v(fsrstack), dest
nexthllarg set   nexthllarg + 1
         exitm
    endif

         error   Compiler not defined or supported in GETARG8 macro.

         endm
;
;********************
;
;   Macro GETARG16 dest
;
;   Get the next high level language call argument, which is assumed to be
;   16 bits in size.  The 16 bit value is saved in DEST+1:DEST.
;
getarg16 macro   dest

  if c18comp                 ;Microchip MPLAB C18 compiler
         getarg8 dest+1
         getarg8 dest
         exitm
    endif

         error   Compiler not defined or supported in GETARG16 macro.

         endm
;
;********************
;
;   Macro LOCSUB <name>, <regflags>
;
;   Just like GLBSUB except that the subroutine will not be global.  It
;   will only be known within its module.
;
locsub   macro   name, regflags
name
         enter   regflags
         endm
;
;********************
;
;   Macro GLBENT <name>
;
;   Declare a global entry point that can be jumped to from other modules.
;
glbent   macro   name
name
         global  name
         unbank
         endm
;
;********************
;
;   Macro LOCENT <name>
;
;   Declare a local entry point that can only be jumped to from within
;   the same module.
;
locent   macro   name
name
         unbank
         endm
;
;********************
;
;   Macro SETPAGE <address>
;
;   Select the code page (by setting PCLATH) containing the indicated address.
;
setpage  macro   adr

  if ncodepages <= 1         ;only one code page exists, no selection required ?
         exitm
    endif

         pagesel adr

;   if fam_16 && (ncodepages == 2) ;need NOP bug workaround ?
;          nop
;     endif

         endm
;
;********************
;
;   Macro MYPAGE
;
;   Sets PCLATH to the current code page, if this machine has multiple
;   code pages.  W may be trashed.
;
mypage   macro

  if ncodepages <= 1         ;only one code page exists, no need to set PCLATH ?
         exitm
    endif

  if fam_17                  ;17Cxxx processor ?
         movfp   pcl, wreg   ;read PCL to load PCH into PCLATH
         exitm
    endif

         local   here
         setpage here
here
         endm
;
;********************
;
;   Macro GCALL <subroutine>
;
;   Call a global subroutine, which could be anywhere in program memory.
;   W is trashed between the caller and the subroutine, and can therefore not
;   be used to pass data to the subroutine.  The assumed register banks are
;   set to invalid after the subroutine returns.
;
;   No code page selection code is generated on machines that only have one
;   code page.
;
gcall    macro   subroutine
         extern  subroutine
         setpage subroutine  ;select the code page the subroutine is on
  if debug_icd
         nop
    endif
         call    subroutine  ;call the subroutine
  if debug_icd
         nop
    endif
         unbank              ;invalidate register bank assumptions
         mypage              ;restore PCLATH to the local page
         endm
;
;********************
;
;   Macro GCALLNR <subroutine>
;
;   Just like GCALL except that the current program memory page is not restored
;   after the subroutine returns.  This saves unnecessary instructions if the
;   current code page setting is not used by subsequent code.  This could
;   be the case, for example, if another global subroutine was called immediately
;   afterwards.
;
;   WARNING:  This macro will cause subsequent code to fail if it does rely
;     on the current program memory page setting.  Local CALLs and GOTOs
;     rely on the current page setting.
;
gcallnr  macro   subroutine
         extern  subroutine
         setpage subroutine  ;select the code page the subroutine is on
  if debug_icd
         nop
    endif
         call    subroutine  ;call the subroutine
  if debug_icd
         nop
    endif
         unbank              ;invalidate register bank assumptions
         endm
;
;********************
;
;   Macro MCALL <subroutine>
;
;   Call a local subroutine within the same module (code section, actually).
;   The subroutine is therefore guaranteed to be on the same code page.
;   The assumed register banks are invalidated after the subroutine returns.
;
mcall    macro   subroutine
  if debug_icd
         nop
    endif
  if fam_18
         rcall   subroutine  ;use short call within the module
    else
         call    subroutine  ;call the subroutine
    endif
  if debug_icd
         nop
    endif
         unbank              ;invalidate register bank assumptions
         endm
;
;********************
;
;   Macro MCALLL <subroutine>
;
;   Like MCALL, except that a "long" call is used instead of the normal and
;   possibly "short" call.  If the processor has no long/short call distinction,
;   then MCALLL is identical to MCALL.
;
mcalll   macro   subroutine
  if debug_icd
         nop
    endif
  if fam_18
         call    subroutine  ;use long call
    else
         call    subroutine  ;call the subroutine
    endif
  if debug_icd
         nop
    endif
         unbank              ;invalidate register bank assumptions
         endm
;
;********************
;
;   Macro GCALLWR <subroutine>
;
;   Just like GCALL, except that W is preserved on return from the subroutine.
;   Note that W is still trashed from the caller to the subroutine.
;
gcallwr  macro   subroutine
         extern  subroutine
         setpage subroutine  ;select the code page the subroutine is on
  if debug_icd
         nop
    endif
         call    subroutine  ;call the subroutine
  if debug_icd
         nop
    endif
         unbank              ;invalidate register bank assumptions
  if ncodepages > 1          ;code page selection is meaningful ?
         dbankif bankadr(0)
         extern  tempw
         movwf   tempw       ;save W before it gets trashed by PAGESEL
         mypage              ;restore PCLATH to the local page
         movf    tempw, w    ;restore W returned by subroutine
    endif
         endm
;
;********************
;
;   Macro GCALLR <subroutine> <putret>
;
;   Just like GCALL, except that the subroutine is assumed to return a value
;   in W.  This return value will be stored in PUTRET.  Note that PUTRET must
;   be in global (any bank) memory unless the subroutine is known to explicitly
;   set the direct register bank.  W is trashed.  The direct and indirect
;   register bank settings are preserved from the subroutine.
;
gcallr   macro   subroutine, putret
         extern  subroutine
         setpage subroutine  ;select the code page the subroutine is on
  if debug_icd
         nop
    endif
         call    subroutine  ;call the subroutine
  if debug_icd
         nop
    endif
         unbank              ;invalidate register bank assumptions
         movwf   putret      ;save the subroutine return value
         mypage              ;restore PCLATH to the local page
         endm
;
;********************
;
;   Macro MCALLR <subroutine> <putret>
;
;   Just like MCALL, except that the subroutine is assumed to return a value
;   in W.  This return value will be stored in PUTRET.  Note that PUTRET must
;   be in global (any bank) memory unless the subroutine is known to explicitly
;   set the direct register bank.  W is trashed.  The direct and indirect
;   register bank settings are preserved from the subroutine.
;
mcallr   macro   subroutine, putret
  if debug_icd
         nop
    endif
  if fam_18
         rcall   subroutine  ;use short call within the module
    else
         call    subroutine  ;call the subroutine
    endif
  if debug_icd
         nop
    endif
         unbank              ;invalidate register bank assumptions
         movwf   putret      ;save the subroutine return value
         endm
;
;********************
;
;   Macro GJUMP <program address>
;
;   Jump to a location that could be anywhere in program memory.  W is trashed
;   before execution starts at the new location.  The assumed register banks
;   are set to invalid for source code immediately following the GJUMP.
;
gjump    macro   adr
         setpage adr         ;select code page of target
  if debug_icd
         nop
    endif
         goto    adr         ;jump to the target
         unbank              ;invalidate register bank assumptions
         endm
;
;***********************************************************************
;
;   Global flag handling.  Global flags are just globally known 1-bit
;   variables.  The macros in this section are intended to work with
;   the /FLAG preprocessor directive.  The /FLAG directive is used
;   to define each flag and update NFLAGB to the total number of bytes
;   used for all the flag bits.  The macros in this section assume
;   that NFLAGB is already set correctly.
;
;   The flag bytes are assumed to be named GFL0 thru GFLn.  Flags are
;   allocated in the flag bytes in least to most significant order.
;
nflagb   set     0           ;init number of GFLx variables allocated
;
;********************
;
;   Macro FLAGS_DEFINE
;
;   Define the GFLx variables required to hold all the flags.  This macro
;   will create one RES directive for each flag variable and declare it
;   GLOBAL.  This macro must only be called after all the /FLAG directives,
;   else NFLAGB will not be valid.
;
flags_define macro
         local   ii
ii       set     0
  while ii < nflagb
gfl#v(ii) res    1
         global  gfl#v(ii)
ii       set     ii + 1
    endw
         endm
;
;********************
;
;   Macro FLAGS_CLEAR
;
;   Clear all the global flags defined with the FLAG macro.  The GFLx variables
;   are assumed to be declared in the GBANK register bank, and NFLAGB is
;   the number of GFLx variables.
;
flags_clear macro
         local   ii

  ifdef gbankadr
         dbankif gbankadr
    endif
ii       set     0
  while ii < nflagb
         clrf    gfl#v(ii)
ii       set     ii + 1
    endw
         endm
;
;********************
;
;   Macro EXTERN_FLAGS
;
;   Declare all the GFLx flag bytes as external.
;
extern_flags macro
         local   ii
ii       set     0
  while ii < nflagb
         extern  gfl#v(ii)
ii       set     ii + 1
    endw
         endm
;
;***********************************************************************
;
;   FIFO (first in, first out) queue handling.
;
;   The format of a FIFO in memory is:
;
;     name+0  -  number of data bytes currently in the queue
;
;     name+1  -  offset into the queue of where to put the next byte + 1
;
;     name+2  -  offset into the queue of where to get the next byte from + 1
;
;     name+3 thru name+2+size  -  Data buffer.  Sequential bytes are written
;       and read at decreasing addresses until the start of the buffer is
;       reached, in which case the next address wraps back to the end of the
;       buffer.
;
;   Define symbolic constants for the offset of various special bytes from
;   the start of a FIFO.  All subsequent code uses these symbols instead
;   of assuming the bytes are at fixed offsets.
;
fifo_ofs_n equ   0           ;offset from FIFO label for number of bytes in buffer
fifo_ofs_put equ 1           ;offset from FIFO label for PUT index
fifo_ofs_get equ 2           ;offset from FIFO label for GET index
fifo_ofs_buf equ 3           ;offset from FIFO label for start of buffer
;
;********************
;
;   Macro FIFO_DEFINE name size
;
;   Define a first in, first out queue.  The symbol NAME will be defined as
;   the first byte of the queue structure.  Size is the maximum number of
;   data bytes the queue will be able to hold.
;
fifo_define macro name, size
name     res     1           ;number of data bytes currently in the queue
         res     1           ;put offset
         res     1           ;get offset
         res     size        ;the data buffer
         endm
;
;********************
;
;   Macro FIFO_INIT name
;
;   Initialize the FIFO at NAME.  The register bank must be set for access
;   to the FIFO state.
;
fifo_init macro  name
         clrf    name + fifo_ofs_n ;indicate the FIFO is empty
         movlw   1
         movwf   name + fifo_ofs_put ;init PUT index
         movwf   name + fifo_ofs_get ;init GET index
         endm
;
;********************
;
;   Macro FIFO_SKIP_EMPTY name
;
;   Skips the next instruction after the macro if the FIFO at NAME is
;   empty.  The register bank must be set for access to the FIFO state.
;
fifo_skip_empty macro name

  if fam_16 || fam_12
         movf    name + fifo_ofs_n ;set Z if FIFO empty
         skip_z
         exitm
    endif

  if fam_17 || fam_18
         tstfsz  name + fifo_ofs_n ;skip if FIFO empty
         exitm
    endif

         error   "Macro FIFO_SKIP_EMPTY not implemented for this processor"
         endm
;
;********************
;
;   Macro FIFO_SKIP_NEMPTY name
;
;   Skips the next instruction after the macro if the FIFO at NAME
;   contains at least one data byte.  The register bank must be set for
;   access to the FIFO state.
;
;   W may be trashed.
;
fifo_skip_nempty macro name

  if fam_16 || fam_12 || fam_18
         movf    name + fifo_ofs_n ;set Z if FIFO empty
         skip_nz             ;FIFO not empty ?
         exitm
    endif

  if fam_17
         decf    name + fifo_ofs_n, w ;set borrow flag if FIFO is empty
         skip_nborr          ;FIFO not empty ?
         exitm
    endif

         error   "Macro FIFO_SKIP_NEMPTY not implemented for this processor"
         endm
;
;********************
;
;   Macro FIFO_N_EMPTY name size
;
;   Returns the number of empty locations in the FIFO in W.  This is the number of
;   consecutive PUSH operations that are guaranteed to work.  The register bank must
;   be set for access to the FIFO state.
;
fifo_n_empty macro name, size
         movf    name + fifo_ofs_n, w ;get number of bytes in the FIFO
         sublw   size        ;subtract it from the maximum bytes FIFO can hold
         endm
;
;********************
;
;   Macro FIFO_SKIP_FULL name size
;
;   Skips the next instruction after the macro if the FIFO at NAME is
;   completely full.  The register bank must be set for access to the
;   FIFO state.
;
;   W is trashed.
;
fifo_skip_full macro name, size
         movlw   size        ;get max bytes the FIFO can hold
         subwf   name + fifo_ofs_n, w ;compare to number of bytes currently in FIFO
         skip_wle            ;FIFO is completely full ?
         endm
;
;********************
;
;   Macro FIFO_SKIP_NFULL name size
;
;   Skips the next instruction after the macro if the FIFO at NAME is
;   not completely full.  The register bank must be set for access to
;   the FIFO state.
;
;   W is trashed.
;
fifo_skip_nfull macro name, size
         movlw   size        ;get max bytes the FIFO can hold
         subwf   name + fifo_ofs_n, w ;compare to number of bytes currently in FIFO
         skip_wgt            ;FIFO is not completely full ?
         endm
;
;********************
;
;   Macro FIFO_N_FULL name
;
;   Returns the number bytes in the FIFO in W.  This is the number of consecutive POP
;   operations that are guaranteed to work.  The register bank must be set for access
;   to the FIFO state.
;
fifo_n_full macro name
         movf    name + fifo_ofs_n, w ;get the number of bytes in the FIFO
         endm
;
;********************
;
;   Macro FIFO_PUT name size data
;
;   Add the byte in DATA as the last byte in the FIFO at NAME.  SIZE must
;   be the maximum number of bytes the FIFO was defined to hold.  The
;   register bank must be set for access to the FIFO and DATA.  Since the
;   FIFO is usually not in global RAM, this means DATA must be either in
;   global RAM or in the same register bank as the FIFO.
;
;   The FIFO may be trashed if it is already full.  This should be checked
;   before this macro is called.
;
;   Note that if the FIFO could be accessed from the interrupt service
;   routine, then interrupts should be temporarily disabled around this
;   macro.
;
;   The indirect register bank must be set for access to the FIFO.
;
;   W and the first FSR are trashed.
;
fifo_put macro   name, size, data
;
;   12 and 16 family processors.
;
  if fam_16 || fam_12
         movlw   name + (fifo_ofs_buf - 1) ;get address for buffer index 0
         addwf   name + fifo_ofs_put, w ;make address of where to write this new byte
         movwf   fsr         ;point to where to write the byte
         movf    data, w     ;get the data byte into W
         movwf   indf        ;write the data byte into the buffer

         incf    name + fifo_ofs_n ;indicate one more byte now in the FIFO
         movlw   size        ;get offset for last byte in buffer
         decf    name + fifo_ofs_put ;update buffer index to next byte, set Z on wrap
         skip_nz             ;not just decrement past beginning of buffer ?
         movwf   name + fifo_ofs_put ;wrap back to end of buffer
         exitm
    endif
;
;   17 family processors.
;
  if fam_17
         movlw   name + (fifo_ofs_buf - 1) ;get address for buffer index 0
         addwf   name + fifo_ofs_put, w ;make address of where to write this new byte
         movwf   fsr0        ;point to where to write the byte
         movfp   data, indf0 ;write the data byte into the buffer

         incf    name + fifo_ofs_n ;indicate one more byte now in the FIFO
         movlw   size        ;get offset for last byte in buffer
         dcfsnz  name + fifo_ofs_put ;update buffer index, skip on not wrap
         movwf   name + fifo_ofs_put ;wrap back to end of buffer
         exitm
    endif
;
;   18 family processors.
;
  if fam_18
         lfsr    0, name + (fifo_ofs_buf - 1) ;point to base for PUT offset
         movf    name + fifo_ofs_put, w ;get PUT offset into W
         movff   data, plusw0 ;copy the byte into the data buffer

         incf    name + fifo_ofs_n ;indicate one more byte now in the FIFO
         movlw   size        ;get new PUT offset if wrapped to end of FIFO
         dcfsnz  name + fifo_ofs_put ;update the PUT index, skip on not wrap
         movwf   name + fifo_ofs_put ;wrap back to the end of the buffer
         exitm
    endif

         error   "Macro FIFO_PUT not implemented for this processor"
         endm
;
;********************
;
;   Macro FIFO_GET name size data
;
;   Get the next byte from the FIFO at NAME into DATA.  SIZE must
;   be the maximum number of bytes the FIFO was defined to hold.  The
;   register bank must be set for access to the FIFO and DATA.  Since the
;   FIFO is usually not in global RAM, this means DATA must be either in
;   global RAM or in the same register bank as the FIFO.
;
;   The FIFO may be trashed if it is already empty.  This should be
;   checked before this macro is called.
;
;   Note that if the FIFO could be accessed from the interrupt service
;   routine, then interrupts should be temporarily disabled around this
;   macro.
;
;   The indirect register bank must be set for access to the FIFO.
;
;   W and the first FSR are trashed.
;
fifo_get macro   name, size, data
;
;   12 and 16 family processors.
;
  if fam_16 || fam_12
         movlw   name + (fifo_ofs_buf - 1) ;get address for buffer index 0
         addwf   name + fifo_ofs_get, w ;make address of where to read new byte from
         movwf   fsr         ;point to where to read the byte from
         movf    indf, w     ;get the data byte
         movwf   data        ;pass it back

         decf    name + fifo_ofs_n ;indicate one less byte now in the FIFO
         movlw   size        ;get offset for last byte in buffer
         decf    name + fifo_ofs_get ;update buffer index to next byte, set Z on wrap
         skip_nz             ;not just decrement past beginning of buffer ?
         movwf   name + fifo_ofs_get ;wrap back to end of buffer
         exitm
    endif
;
;   17 family processors.
;
  if fam_17
         movlw   name + (fifo_ofs_buf - 1) ;get address for buffer index 0
         addwf   name + fifo_ofs_get, w ;make address of where to read new byte from
         movwf   fsr0        ;point to where to read the byte from
         movpf   indf0, data ;get the data byte and pass it back

         decf    name + fifo_ofs_n ;indicate one less byte now in the FIFO
         movlw   size        ;get offset for last byte in buffer
         dcfsnz  name + fifo_ofs_get ;update buffer index, skip on not wrap
         movwf   name + fifo_ofs_get ;wrap back to end of buffer
         exitm
    endif
;
;   18 family processors.
;
  if fam_18
         lfsr    0, name + (fifo_ofs_buf - 1) ;point to base for GET offset
         movf    name + fifo_ofs_get, w ;get GET offset into W
         movff   plusw0, data ;copy the byte from the buffer into DATA

         decf    name + fifo_ofs_n ;indicate one less byte now in the FIFO
         movlw   size        ;get offset for last byte in buffer
         dcfsnz  name + fifo_ofs_get ;update buffer index, skip on not wrap
         movwf   name + fifo_ofs_get ;wrap back to end of buffer
         exitm
    endif

         error   "Macro FIFO_GET not implemented for this processor"
         endm
;
;***********************************************************************
;
;   Dispatch tables.
;
;********************
;
;   Macro DISPATCH name
;
;   Jump to the address selected from a dispatch table.
;
;   REG0 contains the 0-N table entry number.  If REG0 contains 0, this
;   macro will jump to the address in the first table entry, if 1 it will
;   jump to the address in the second table entry, etc.  If REG0 indicates
;   an entry past the end of the table, then execution falls thru to the
;   end of this macro.
;
;   NAME is the name of the table as defined with the DSP_START and
;   DSP_END macros.
;
;   The table must be defined with DSP_START, DSP_ENTRY, and DSP_END
;   macros.  These and the DISPATCH macro communicate with each other
;   via various implicitly defined symbols.
;
;   The following symbols are assumed to be defined.  These are normally
;   defined automatically by the DSP_START and DSP_END macros.
;
;     <name>  -  Address of the start of the table.
;
;     <name>0nent  -  Number of entries in the table.
;
;   All the REGn general registers are preserved to the target routine.
;
dispatch macro   name
         local   noent
;
;   18 family processors.
;
  if fam_18
         ;
         ;   Check for the table index in REG0 is within range.
         ;   If not fall thru this macro.
         ;
         movf    reg0, w     ;get 0-N table index
         sublw   name#v(0)nent-1 ;compare to last valid index
         skip_wle            ;index is within bounds ?
         jump    noent       ;no, fall thru this macro
         ;
         ;   Make the table byte offset for the selected entry
         ;   in REG1:REG0.  This is either 2x or 3x the table index
         ;   depending on whether 2 byte or 3 byte program memory
         ;   addresses are in use.
         ;
         pushregs regf0 | regf1 ;temp save registers that will be trashed

         clrf    reg1        ;1x index now in REG1:REG0
    if progadrb > 2
         movf    reg0, w     ;save 1x index in W
      endif
         bcf     status, c   ;set 0 bit to shift in
         rlcf    reg0        ;make 2x index in REG1:REG0
         rlcf    reg1
    if progadrb > 2
         addwf   reg0        ;add 1x to make 3x index in REG1:REG0
         movlw   0
         addwfc  reg1
      endif
         ;
         ;   Set TBLPTR to the first (least significant) byte of the target
         ;   address.
         ;
         movlw   low (name)
         addwf   reg0, w
         movwf   tblptrl
         movlw   high (name)
         addwfc  reg1, w
         movwf   tblptrh
    if progadrb <= 2
         clrf    tblptru
      else
         movlw   upper (name)
         skip_ncarr
         addlw   1
         movwf   tblptru
      endif

         popregs regf0 | regf1 ;restore REG0 and REG1 saved earlier
         ;
         ;   Jump to the address starting at where TBLPTR is pointing.
         ;   If the low bit of the address is set (invalid execution
         ;   address), then the table entry is unimplemented and this
         ;   macro falls thru that same as if the index was past the
         ;   end of the table.
         ;
         tblrd*+             ;get jump address low byte into TABLAT
         btfsc   tablat, 0   ;this is a valid execution address ?
         jump    noent       ;no, all thru this macro
         movf    tablat, w   ;save low byte of jump address in W

         tblrd*+             ;set middle byte of jump address
         movff   tablat, pclath

    if progadrb <= 2
         clrf    pclatu      ;16 bit addressing, high byte always 0
      else
         tblrd*              ;24 bit addressing, set jump address high byte
         movff   tablat, pclatu
      endif
         movwf   pcl         ;jump to the address from the table entry

noent    unbank              ;jump to here on no matching table entry
         exitm
    endif                    ;end of PIC 18 family implementation

         error   "Macro DISPATCH not implemented for this processor"
         endm
;
;********************
;
;   Macro DSP_START name
;
;   Start a dispatch table.  Table entries must be created with the
;   DSP_ENTRY macro, and the table ended with the DSP_END macro.  The
;   DISPATCH macro will jump to a selected table entry.
;
;   NAME will be defined as the starting address of the table, and must
;   be the name passed to the DISPATCH macro.
;
;   On a PIC 18, this table must be defined in a CODE_PACK section, not an
;   ordinary CODE section.
;
dsp_start macro  name
name                         ;define the label for the start of the table
next_entry set   0           ;init 0-N number of next table entry
         endm
;
;********************
;
;   Macro DSP_ENTRY index, adr
;
;   Define one dispatch table entry.  INDEX is the 0-N sequential index for this
;   table entry, and ADR is the address of the routine to dispatch to for this
;   entry.
;
;   Entries must be defined in ascending index order.  An error is generated if
;   this is not the case.  A table entry is created for all values of INDEX up to
;   and including the last one.  Gaps are filled with invalid addresses.  These
;   are detected in the DISPATCH macro and treated the same as unimplemented
;   table entries past the end of the table.
;
dsp_entry macro  index, adr
;
;   18 family processors.  Table entries are either 2 or 3 bytes long, depending
;   on whether 2 or 3 byte program memory addresses are in use.
;
  if fam_18

    if index < next_entry
         error   Table entry #v(index) not in ascending order.
      endif

    while next_entry != index
         db      1           ;set low bit to indicate invalid address
         db      0
      if progadrb > 2
         db      0
        endif
next_entry set   next_entry + 1
      endw

         db      low (adr)
         db      high (adr)
    if progadrb > 2
         db      upper (adr)
      endif

next_entry set   next_entry + 1 ;update expected index for next time
         exitm
    endif                    ;end of PIC 18 family implementation

         error   "Macro DSP_ENTRY is not implemented for this processor"
         endm
;
;********************
;
;   Macro DSP_END name
;
;   End the definition of a dispatch table started with DSP_START and filled
;   in with DSP_ENTRY.  NAME must be the same name that was passed to DSP_START.
;
dsp_end  macro   name
name#v(0)nent equ next_entry ;define symbol for number of entries in this table
  if name#v(0)nent == 0
         error   Empty dispatch table, must have at least one entry.
    endif
         endm
;
;***********************************************************************
;
;   Support for the multi-tasking system.
;
;********************
;
;   Macro TASK_CREATE runadr, stackadr
;
;   Create a new task, assuming the Embed Inc multi-tasking system.  This macro
;   is just a wrapper around TASK_NEW.  It loads registers with the parameters
;   for TASK_NEW, then calls it.  RUNADR is the execution start address of the
;   new task, and STACKADR is the data stack start address for the new task.
;
;   REG0 - REG4 are trashed.
;
task_create macro runadr, stackadr

  if fam_18                  ;18 family devices
         movlw   low stackadr ;pass stack address in REG1:REG0
         movwf   reg0
         movlw   high stackadr
         movwf   reg1

         movlw   low runadr  ;pass run address in (REG4):REG3:REG2
         movwf   reg2
         movlw   high runadr
         movwf   reg3
  if progadrb > 2
         movlw   upper runadr
         movwf   reg4
    endif

         gcall   task_new    ;create the new task
         exitm
    endif                    ;end of PIC 18 family version

         error   "TASK_CREATE macro in STD.INS.ASPIC not implemented for this processor"
         endm