;   ***************************************************************
;   * 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 bit 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.  0 for the enhanced
;       14 bit core.
;
;     FAM_16B  -  1 if the processor is a enhanced 14 bit core part.  These are
;       usually called PIC 16 with a 4-digit part number.  The original PIC 16
;       have 3 digit part numbers.  These parts, for example, have 32 register
;       banks and a bank select register.
;
;     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 successive 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 no 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 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 number of the FSR to be used as the data stack
;       pointer.  This symbol is only used on processors that have multiple FSRs
;       and one is dedicated as the data stack pointer.
;
;       On a PIC 18, FSRSTACK 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.
;
;       On a enhanced PIC 16, FSRSTACK will default 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.
;
;       Automatically created and set to the default of 2 for enhanced PIC 16
;       (fam_16b) devices.
;
;     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 in STD_DEF.INS.ASPIC as appropriate for the processor.
;       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.
;
;     DEBUG  -  Being built for debugging, not production.  This is forced on
;       when DEBUG_ICD is TRUE.
;
;     FIFOS_NEW (preprocessor constant)
;
;       Indicates whether to use the "new" FIFOs implemented with the
;       preprocessor, instead of the old FIFOs implemented with MPASM macros.
;       TRUE selects the new FIFOs, and FALSE selects the old.  For
;       compatibility with existing code, the default is FALSE when FIFOS_NEW is
;       not defined.  See the discussion at the start of the FIFO section of
;       this file for details of the old versus new FIFOs.
;
;   This file is divided into sections of related features.  Each section starts
;   with two lines of stars.  Briefly, the sections, in order in this file, are:
;
;     Configuration constants.
;
;     General preprocessor string manipulation.
;
;     General registers, REG0 ... REGn.
;
;     Skip and branch macros.
;
;     General utility macros and preprocessor subroutines.
;
;     Timing and cycle counting.
;
;     Handling multi-byte data.
;
;     UART configuration.
;
;     I/O bits and ports handling.
;
;     Bank and page switching, memory allocation.
;
;     Software data stack.
;
;     Subroutine linkage.
;
;     Global flags.
;
;     FIFO (first in, first out) queues.
;
;     Dispatch tables.
;
;     Support for the multi-tasking system.
;
;     Support for writing constants to program memory.
;
;     Mutual exclusion locks for use with the Embed multi-tasking system.
;


;*******************************************************************************
;*******************************************************************************
;
;   Configuration constants.
;

////////////////////////////////////////////////////////////////////////////////
//
//   Set up the debugging environment.
//
//   If any DEBUG_xxx constants exist, then it is assumed that the old system
//   for setting debug switches is in use.  Otherwise, the MAKE_DEBUG program is
//   run to create the debug switches.  Either way, the new constant DEBUGGING
//   is always created.
//
/block
  /var local dbg bool = false //found at least one DEBUG_xxx constant
  /var local dbgon bool = false //OR of all DEBUG_xxx constants
  /var local sy string //scratch symbol name
  /var local fnam string //scratch file name
  /loop symbols sym const
    /set sy [sym sym name]
    /if [< [slen sy] 7] then
      /repeat
      /endif
    /if [<> [substr 1 6 sy] "debug_"] then
      /repeat
      /endif
    /if [<> [sym sym dtype] "BOOL"] then
      /repeat
      /endif
    /set dbg True
    /set dbgon [or dbgon [chars sym]]
    /endloop

  /if dbg then //old style debug switches in use ?
    /if [<> [evar "debug"] ""] then
      /show "  *** ERROR ***"
      /show "  Using the DEBUG environment variable is incompatible with debug switches"
      /show "  set in the project include file."
         .error  "Debug"
         .end
      /stop
      /endif
    /if [not [exist "debugging:const"]] then
      /const debugging bool = dbgon
      /endif
    /if [not [exist "debug:const"]] then
      /const debug bool = debugging
      /endif
    /if [not [exist "debug_icd:const"]] then
      /const debug_icd bool = false
      /endif
    /if [exist "debug_icdram:vcon"] then
      /if [and debug_icd [not debug_icdram]] then
        /del debug_icdram
        /endif
      /endif
    /if [not [exist "debug_icdram:vcon"]] then
      /const debug_icdram bool = debug_icd
      /endif
    /quit
    /endif
  //
  //   The debug switches are defined via the DEBUG environment variable.
  //
  /set fnam [str "(cog)src/" srcdir "/debug_" buildname ".ins.aspic"]
  /run "make_debug """ fnam """ icd"
  /include fnam
  /const debug bool = debugging //for compatibility with old code
  /endblock

debug    set     [if debugging 1 0]
debug_icd set    [if debug_icd 1 0]

;   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
/var exist oscdig integer = 4

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

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

  ifndef c18comp
c18comp  equ     0           ;default to don't need to be compatible with C18 code
    endif

  if fam_16b
    ifndef progadrb
progadrb equ     2           ;default to 2 bytes required to store prog mem address
      endif
    endif

  ifndef n_iregs
n_iregs  equ     0           ;assume no private registers required by interrupt code
    endif
;
;   Derived constants.
;
freq_inst equ    freq_osc / 4 ;instruction clock frequency in Hz
nsec_inst equ    1000000000 / freq_inst ;instruction time in nanoseconds
;
;   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 || fam_16b
    if bra_bug
#define jump goto
      else
#define jump bra
      endif
    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
;
;   Define which FSRs are used for specific purposes if this is a PIC 18.  These
;   have three FRSs (0-2).  By default, the Embed environment uses FSR2 as the
;   data stack pointer with the other two available for application use.  The
;   C18 compiler uses FSR1 as the stack pointer and FSR2 as a frame pointer with
;   only FSR0 left for the application.
;
  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
;
;   Define the FSR numbers if this is a enhanced PIC 16.  These have two FSRs.
;   By default, the Embed environment reserves FSR1 as the data stack pointer
;   and leaves FSR0 for application use.
;
  if fam_16b
    ifndef fsrstack
fsrstack set     1           ;default to FSR1 as data stack pointer
      endif
    if (fsrstack < 0) || (fsrstack > 1)
         error   FSRSTACK found at illegal values of #v(fsrstack) in STD.INS.ASPIC
      endif
    endif

  ifdef fsrstack
    if fsrstack
fsrsc1   equ     0
      else
fsrsc1   equ     1
      endif
    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

/if [not [exist "fifos_new"]] then
  /const fifos_new bool = false
  /endif
//
//   Make sure the NO_INTR_DISABLE variable exists, and initialize it to the
//   default value of FALSE if not.  This switch is used by some macros that
//   need to perform atomic operations.  With this switch off, interrupts are
//   disabled around such atomic operations.  When this switch is on,
//   interrupts are not disabled and re-enabled.
//
//   Reasons to set this switch to TRUE include:
//
//     1 - In an interrupt routine.  Interrupts are already off, and will be
//       re-enabled by the RETFIE instruction.  If this switch isn't off, then
//       nested macros would effectively enable interrupts early.
//
//     2 - Interrupts are not used in the project.  Nested macros would then
//       effectively turn on interrupts if this switch is not set.
//
//     3 - The data structure that needs to be atomically updated is never
//       accessed from interrupt code.  There is therefore no need to prevent
//       interrupts from running part way thru what should be an atomic
//       operation.
//
/var exist no_intr_disable bool = false

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine DEBUGSW name
//
//   Make sure the debug switch DEBUG_name exists.  If it does not exist, then
//   it is created as a BOOL constant set to FALSE.  NAME is a token, not a
//   string.
//
/subroutine debugsw
  /var local sym string = [str "debug_" [qstr [arg 1]]]

  /if [not [exist [str sym ":vcon"]]] then
    /const [chars sym] bool = false
    /endif
  /endsub


;*******************************************************************************
;*******************************************************************************
;
;   General preprocessor string manipulation.
;

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine PARSE_CMD var [suff]
//
//   Parse the contents of the constant or variable VAR.  VAR must be of data
//   type STRING.  The contents of VAR is:
//
//     n [entpnt]
//
//   N is a 0 to 255 decimal integer indicating the opcode value of a command.
//   ENTPNT is the name of the entry point to the command routine.  When ENTPNT
//   is not present, the entry point name is derived from the variable name.  In
//   that case the name of VAR must be "cmd_xxx".  The entry point name is
//   interpreted to be "cm_xxx".
//
//   The optional SUFF parameter is a unique string added to the fixed part of
//   the variable name.  The fixed part of the variable name is cmd_<suff>_,
//   which is removed from the variable name to make the default entry point
//   name.  With a variable name "cmd_xyz_abc" and SUFF "_xyz", the default
//   entry point name is "cm_abc", not "cm_xyz_abc".
//
//   This subroutine sets two variables:
//
//     OPC  -  Integer.  Opcode value.
//
//     ENTPNT  -  String.  Command routine entry point name.
//
//   OPC and ENTPNT are created if they do not already exist.
//
//   For exampe, if this routine is called with constant "cmd_abcd" containing
//   "5", then OPC will be 5 and ENTPNT "cm_abcd".  If the constant contains
//   "13 send_blork", then OPC will be 13 and ENTPNT "send_blork".
//
/subroutine parse_cmd
  /var exist opc integer //make sure return values exist
  /var exist entpnt string
  /var local vname string = [qstr [arg 1]] //get variable or constant name
  /var local vnam string = [sym vname name] //make bare var or const name
  /var local vstr string = [vnl [chars vname]] //get input string
  /var local pref string = [str "cmd" [qstr [arg 2]] "_"] //fixed var name prefix
  /var local p integer = 1 //parse index
  /var local tk string //token parsed from input string

  /call string_token [v vstr] p tk //get opcode token
  /set opc [chars tk]

  /if [> p [slen vstr]] then //no ENTPNT token ?
    /block //block to abort out of on error
      /set tk [substr 1 [slen pref] vnam]
      /if [<> tk pref] then //name doesn't start with "cmd_" ?
        /quit
        /endif
      /set tk [substr [+ [slen pref] 1] 99 vnam]
      /if [< [slen tk] 1] then //nothing after "cmd_" ?
        /quit
        /endif
      /set entpnt [str "cm_" tk] //make full default entry point name
      /return
      /endblock
    /show "  Bad name """ vname """ passed to PARSE_CMD."
         .error  "Bad var/const name"
    /stop
    /endif

  /call string_token [v vstr] p tk //get ENTPNT token
  /if [<= p [slen vstr]] then //extra tokens in string ?
    /show "  Extra token in variable or constant """ vname """".
    /show "  String is """ vstr """".
         .error  "Extra token"
    /stop
    /endif
  /set entpnt tk
  /endsub

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine TABTO varname column
//
//   Add blanks to the end of the string VARNAME so that the next next character
//   appended to its end will be at column COLUMN or later.
//
//   The Embed conventions for assembler code are:
//
//       1         2         3         4
//3456789_123456789_123456789_123456789_
//       opcode  operand     ;comment
//
//   which means the tabto columns are:
//
//     opcode    10
//     operand   18
//     comment   30
//
/subroutine tabto
  /block                     ;back here until at the right column
    /if [>= [slen [arg 1]] [- [arg 2] 1]] then
      /quit
      /endif
    /append [arg 1] " "
    /repeat
    /endblock
  /endsub

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine TABOPCODE varname
//
//   Append spaces as necessary to the end of the string in VARNAME so that the
//   next character will be at or after the opcode start column.  VARNAME is
//   always returned ending in a blank.
//
/subroutine tabopcode
  /if [<> [sindx [slen [arg 1]] [arg 1]] " "] then ;not already ending in blank ?
    /set [arg 1] [str [arg 1] " "] ;add one blank at end
    /endif
  /call tabto [arg 1] 10
  /endsub

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine TABOPERAND varname
//
//   Append spaces as necessary to the end of the string in VARNAME so that the
//   next character will be at or after the operand start column.  VARNAME is
//   always returned ending in a blank.
//
/subroutine taboperand
  /if [<> [sindx [slen [arg 1]] [arg 1]] " "] then ;not already ending in blank ?
    /set [arg 1] [str [arg 1] " "] ;add one blank at end
    /endif
  /call tabto [arg 1] 18
  /endsub

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine STARTCOMM varname
//
//   Add the start of a MPASM comment to the end of the string in the variable
//   VARNAME.  The string will end in the comment delimeter character, which
//   will be in the usual comment column or later.  There will always be at
//   least one blank before the comment start character.  The caller can
//   directly append the text of the comment to the string.
//
/subroutine startcomm
  /if [<> [sindx [slen [arg 1]] [arg 1]] " "] then ;not already ending in blank ?
    /set [arg 1] [str [arg 1] " "] ;add one blank at end
    /endif
  /call tabto [arg 1] 30
  /set [arg 1] [str [arg 1] ";"]
  /endsub

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine STRING_TOKEN str ind tok
//
//   Parses the next token from the string STR.  IND must be the name of a
//   integer variable that is the string index to start parsing at.  IND is
//   updated to after the token.  TOK must be a string variable into which the
//   parsed token is returned.  IND should be started at 1 in a sequence to get
//   all tokens from the string.  IND is returned past the end of the string
//   when the input string has been exhausted.
//
/subroutine string_token
  /set [arg 3] ""            ;init the token to the empty string
  /if [< [arg 2] 1] then     ;invalid IND ?
    /return
    /endif
  //
  //   Skip over leading blanks.
  //
  /block
    /if [> [arg 2] [slen [arg 1]]] then ;past end of string ?
      /return
      /endif
    /if [= [sindx [arg 2] [arg 1]] " "] then ;another blank ?
      /set [arg 2] [+ [arg 2] 1] ;advance the parse index
      /repeat
      /endif
    /endblock
  //
  //   Grab string characters up to the first blank or end of input string.
  //
  /block
    /if [= [sindx [arg 2] [arg 1]] " "] then ;hit a blank ?
      /set [arg 2] [+ [arg 2] 1] ;start at next character next time
      /return
      /endif
    /set [arg 3] [str [arg 3] [sindx [arg 2] [arg 1]]] ;add this char to token
    /set [arg 2] [+ [arg 2] 1] ;advance to next input string index
    /if [> [arg 2] [slen [arg 1]]] then ;past end of string ?
      /return
      /endif
    /repeat
    /endblock
  /endsub

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine SHOWVAL name [description]
//
//   Show the value of the preprocessor symbol NAME.  When no DESCRIPTION
//   parameter is present, the following will be written to standard output:
//
//     NAME <value>
//
//   When DESCRIPTION is present, this is followed by ", <description>".  The
//   description argument must be a single argument.  It will generally be a
//   string of characters  enclosed in quotes.
//
/subroutine showval
  /if [exist 2 arg]
    /then                    ;DESCRIPTION exists
      /var local desc string = [str ", " [arg 2]]
    /else                    ;DESCRIPTION not supplied
      /var local desc string = ""
    /endif
  /var local name string = [qstr [arg 1]]
  /var local val string = [vnl [chars name]]

  /if [= [sym name dtype] "STRING"] then
    /set val [str '"' val '"']
    /endif

  /show "  " name " " val desc
  /endsub

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine SHOWHEX name [description]
//
//   Like SHOWVAL, except that the value is shown in hexadecimal if the data
//   type of NAME is integer.
//
/subroutine showhex
  /if [exist 2 arg]
    /then                    ;DESCRIPTION exists
      /var local desc string = [str ", " [arg 2]]
    /else                    ;DESCRIPTION not supplied
      /var local desc string = ""
    /endif
  /var local name string = [qstr [arg 1]]
  /var local val string

  /block
    /if [= [sym name dtype] "REAL"] then
      /set val [eng [chars name] 4 ""]
      /quit
      /endif
    /if [= [sym name dtype] "STRING"] then
      /set val [str '"' [chars name] '"']
      /quit
      /endif
    /if [= [sym name dtype] "INTEGER"] then
      /set val [str [int [chars name] "base 16 usin"] "h"]
      /quit
      /endif
    /set val [chars name]
    /endblock

  /show "  " name " " val desc
  /endsub

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine SHOWBIN name [description]
//
//   Like SHOWVAL, except that the value is shown in binary if the data type of
//   NAME is integer.
//
/subroutine showbin
  /if [exist 2 arg]
    /then                    ;DESCRIPTION exists
      /var local desc string = [str ", " [arg 2]]
    /else                    ;DESCRIPTION not supplied
      /var local desc string = ""
    /endif
  /var local name string = [qstr [arg 1]]
  /var local val string

  /block
    /if [= [sym name dtype] "REAL"] then
      /set val [eng [chars name] 4 ""]
      /quit
      /endif
    /if [= [sym name dtype] "STRING"] then
      /set val [str '"' [chars name] '"']
      /quit
      /endif
    /if [= [sym name dtype] "INTEGER"] then
      /set val [str [int [chars name] "base 2 usin"] "b"]
      /quit
      /endif
    /set val [chars name]
    /endblock

  /show "  " name " " val desc
  /endsub


;*******************************************************************************
;*******************************************************************************
;
;   General registers, REG0 ... REGn
;
;   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
;   ensure 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 value 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.  REGA overlays REG3:REG2:REG1:REG0, REGB the
;   next 4 general registers, etc.  The REGA-REGD registers are used to hold
;   operands of 32 bit operations.
;
  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


;*******************************************************************************
;*******************************************************************************
;
;   Skip and branch macros.
;
;   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

;*******************************************************************************
;
;   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_16b || 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_16b || fam_18
         btfsc   status, c   ;skip if a borrow occurred
         exitm
    endif

  if fam_17
         btfsc   alusta, c
         exitm
    endif

         error   "SKIP_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_16b || 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_16b || 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_16b || 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_16b || 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_16b || 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_16b || 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 adr
;
;   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 adr
;
;   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 JMP_BORR adr
;
;   Jump to ADR if a borrow occurred.
;
jmp_borr macro   adr

  if fam_12 || fam_16 || fam_16b
         skip_nborr
         jump    adr
         exitm
    endif

  if fam_18
         bnc     adr
         exitm
    endif

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

;*******************************************************************************
;
;   Macro JMP_NBORR adr
;
;   Jump to ADR if a borrow did not occurr.
;
jmp_nborr macro  adr

  if fam_12 || fam_16 || fam_16b
         skip_borr
         jump    adr
         exitm
    endif

  if fam_18
         bc      adr
         exitm
    endif

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

;*******************************************************************************
;
;   Macro JMP_CARR adr
;
;   Jump to ADR if a carry occurred.
;
jmp_carr macro   adr

  if fam_12 || fam_16 || fam_16b
         skip_ncarr
         jump    adr
         exitm
    endif

  if fam_18
         bc      adr
         exitm
    endif

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

;*******************************************************************************
;
;   Macro JMP_NCARR adr
;
;   Jump to ADR if a carry did not occurr.
;
jmp_ncarr macro  adr

  if fam_12 || fam_16 || fam_16b
         skip_carr
         jump    adr
         exitm
    endif

  if fam_18
         bnc     adr
         exitm
    endif

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


;*******************************************************************************
;*******************************************************************************
;
;   General utility macros and preprocessor subroutines.
;

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine SHOW_DEBUG
//
//   Show the state of all the preprocessor debug switches.
//
/subroutine show_debug
  /var local sym string      ;generic name of variable or constant symbol

  /loop symbols qsym vcon    ;loop over all variables and constants
    /set sym [sym qsym name] ;get symbol generic name
    /if [< [slen sym] 7] then ;not long enough for DEBUG_x
      /repeat
      /endif
    /if [<> [substr 1 6 sym] "debug_"] then
      /repeat
      /endif
    /show "  " [ucase sym] " " [chars qsym]
    /endloop
  /endsub

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine DEBUG_EXIST name
//
//   Make sure that the debug switch DEBUG_name exists.  Name is a token, not a
//   string.  Nothing is done if the constant DEBUG_name already exists.  if not
//   then it is created as a bool and set to FALSE.
//
/subroutine debug_exist
  /var local name string = [str "debug_" [qstr [arg 1]]]

  /if [not [exist [str name ":vcon"]]] then
    /const [chars name] bool = false
    /endif
  /endsub

;*******************************************************************************
;
;   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 ADDW_TBLPTR
;
;   Add the 0-255 value in W to the full TBLPTR.  W is trashed.
;
  ifdef tblptrl

addw_tblptr macro
         addwf   tblptrl
         movlw   0
         addwfc  tblptrh
    if progadrb < 3
         addwfc  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

;*****
;
;   16 and enhanced 16 families.
;
  if fam_16 || fam_16b
         movf    (adrf), w
         exitm
    endif

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

;*****
;
;   18 family.
;
  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

;*****
;
;   16 and enhanced 16 families.
;
  if fam_16 || fam_16b       ;14 bit core
         movf    (adrf), f
         exitm
    endif

;*****
;
;   17 family.
;
  if fam_17
         comf    (adrf), f
         comf    (adrf), f
         exitm
    endif

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

;*****
;
         error   "TESTFZ 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 || fam_16b
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 || fam_16b
         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 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.
;
;   The direct bank is set to REG as needed.
;
setreg   macro   val, reg
         dbankif (reg)
  if (val) == 0              ;value is exactly zero
         clrf    (reg)
         exitm
    endif

  if ((val)==255) && fam_18
         setf    (reg)
         exitm
    endif

         movlw   (val)
         movwf   (reg)
         endm

;*******************************************************************************
;
;   Macro ADDFSR0 ofs
;
;   Add the fixed offset OFS into FSR0.  This macro points FSR0 OFS addresses
;   forwards.  Depending on the value of OFS, the constant value is either added
;   into FSR0 or auto inc/dec instructions are used, whichever takes fewer
;   cycles.
;
addfsr0  macro   ofs

;*****
;
;   PIC 18
;
  if fam_18
         local   ii
;
;   Handle offset of 0.
;
    if (ofs)==0
         exitm
      endif
;
;   Handle small positive offset.
;
    if ((ofs) > 0) && ((ofs) < 4) ;incrementing by 1-3 ?
ii       set     (ofs)
      while ii
         movf    postinc0
ii       set     ii - 1
        endw
         exitm
      endif
;
;   Handle small negative offset.
;
    if ((ofs) < 0) && ((ofs) > -4) ;decrementing by 1-3 ?
ii       set     -(ofs)
      while ii
         movf    postdec0
ii       set     ii - 1
        endw
         exitm
      endif
;
;   The offset is not a special case that can be done in less than 4
;   instructions.  Do the explicit add using 4 instructions.
;
         movlw   low (ofs)
         addwf   fsr0l
         movlw   high (ofs)
         addwfc  fsr0h
         exitm
    endif                    ;end of PIC 18 family case

;*****
;
;   Enhanced PIC 16.
;
  if fam_16b
    if ((ofs) < 32) && ((ofs) > -33))
         addfsr  fsr0, ofs
         exitm
      else
         movlw   low (ofs)
         addwf   fsr0l
         movlw   high (ofs)
         addwfc  fsr0h
         exitm
      endif
    endif                    ;end of enhanced 14 bit core case

;*****
;
         error   "ADDFSR0 macro not implemented for this processor"
         endm

////////////////////////////////////////////////////////////////////////////////
//
//   Preprocessor subroutine PARSE_IPADR ipdot ipname
//
//   Parse a IP address string in dot notation format into the four integer byte
//   values.
//
//   IPDOT is the IP address string.  This must be a preprocessor string.
//
//   IPNAME is the name prefix of the preprocessor variables that will be
//   assigned the 0-255 values of each of the 4 bytes in the IP address.  Each
//   variable name will start with the IPNAME argument, followed by a single
//   digit to indicate which byte of the four bytes it represents.  The least
//   significant byte is number 0, and the most significant number 3.  The
//   IPNAME argument is just characters, not a string.  If the IPNAMEn variables
//   do not previously exist, they will be created.
//
//   Example:
//
//     /call parse_ipadr "192.168.0.1" myip
//
//   This will set MYIP0 to 1, MYIP3 to 192, etc.
//
/subroutine parse_ipadr
  /var exist [arg 2]0 integer ;make sure the output variables exist
  /var exist [arg 2]1 integer
  /var exist [arg 2]2 integer
  /var exist [arg 2]3 integer
  /var local ipstr string = [arg 1] ;get IP address string
  /var local c string        ;single character string
  /var local s string        ;byte value string
  /var local ib integer = 3  ;0-3 byte number
  /var local p integer = 1   ;input string parse index

  /block                     ;back here each new byte
    /set s ""                ;init this byte string to empty
    /block                   ;back here each new character this byte
      /set c [substr p 1 ipstr] ;get next character from the input string
      /set p [+ p 1]         ;advance the parse index
      /if [not [or [= c ""] [= c "."]]] then
        /set s [str s c]     ;append this character to end of byte string
        /repeat              ;back to get next character
        /endif
      /endblock
    /set [arg 2][chars ib] [chars s] ;set this byte variable
    /set ib [- ib 1]         ;make 0-3 number of next byte
    /if [>= ib 0] then       ;still a valid byte number ?
      /repeat                ;back to get next byte
      /endif
    /endblock
  /endsub


;*******************************************************************************
;*******************************************************************************
;
;   Timing and cycle counting.
;

;*******************************************************************************
;
;   Macro WAITNOP NNOP  (deprecated)
;
;   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.
;
;   Two separate macros now exist to serve the two purposes, WAITCY and NNOPS.
;   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 WAITCY NCY
;
;   Generate 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_16b
    while n >= 2
         bra     $+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 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
;   1 us 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 100 fs 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 WAITSEC seconds
//
//   Wait the indicated number of seconds, which can be a floating point value.
//
//   This macro relies on the following subroutines to exist:
//
//     WAITMS  -  Wait the number of milliseconds in REG0.
//
//     WAITMS16  -  Wait the number of milliseconds in REG1:REG0.
//
//   The appropriate wait routine is chosen, depending on the length of the
//   wait.  Only REG0 is trashed for up to 255 ms.  REG1 and REG0 are trashed
//   for longer waits.
//
/macro waitsec
  /var local ms integer = [rnd [* 1000 [vnl [arg 1]]]] ;num of ms to wait

  /if [> ms 65535] then
    /show "  ERROR: Wait time too long, must be 65.535 seconds or less"
         .error  "Wait time"
         .end
    /stop
    /endif

  /if [<= ms 255]
    /then                    ;use single byte wait
         loadk8  reg0, [v ms]
         gcall   waitms
    /else                    ;use double byte wait
         loadk16 reg0, [v ms]
         gcall   waitms16
    /endif
  /endmac

;*******************************************************************************
;
;   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

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine TIMER2_CONFIG_SET pre per pos [desper]
//
//   Determine the timer 2 configuration constants according to the call
//   parameters.  No executable code is generated, only preprocessor and
//   assembly state is created and/or updated.  The call parameters are:
//
//     PRE  -  Prescaler: 1, 4, 16
//
//     PER  -  period after prescaler: 1 - 256
//
//     POS  -  Postscaler: 1 - 16
//
//     DESPER  -  Desired overall period, seconds.  This parameter is optional.
//       The constant TMR2_ERROR (see below) is created if DESPER is given.
//
//   The arguments will be checked, and assembly bombed with error on any
//   invalid values.
//
//   The following preprocessor constants will be set:
//
//     TMR2_PRE, integer -  timer 2 prescaler value: 1, 4, or 16
//
//     TMR2_PER, integer  -  timer 2 period divide value: 1 - 256
//
//     TMR2_POS, integer  -  timer 2 postscaler value: 1 - 16
//
//     TMR2_PERIOD, real  -  Actual timer 2 period in seconds.
//
//     TMR2_ERROR  -  Relative error of actual period with respect to the
//       desired period.  This value times 100 would be the percent error.  This
//       constant is only created when the DESPER call argument is provided.
//
//     TMR2_PERPWM, real  -  Period delivered to CCP module for PWM, seconds.
//       This is the period without the postscaler, taking into account only the
//       prescaler and period settings.
//
//     TMR2_CYCLES, integer  -  Actual timer 2 period in instruction cycles.
//
//     TMR2_FREQ, real  -  Actual timer 2 frequency, Hz.
//
//   Assembly constants will also be created: TMR2_PRE, TMR2_PER, TMR2_POS.
//
/subroutine timer2_config_set
  /var local pre integer = [vnl [arg 1]]
  /var local per integer = [vnl [arg 2]]
  /var local pos integer = [vnl [arg 3]]
  /var local desper_given bool = [exist 4 arg]
  /if desper_given then
    /var local desper real = [vnl [arg 4]]
    /endif
  //
  //   Validate the call arguments.
  //
  /pick first by pre
  /option 1 4 16
  /optionelse
    /show "  Invalid timer 2 prescaler of " pre
         error   "Timer 2 prescaler"
         end
    /stop
    /endpick
  /if [or [< per 1] [> per 256]] then
    /show "  Invalid timer 2 period value of " per
         error   "Timer 2 period"
         end
    /stop
    /endif
  /if [or [< pos 1] [> pos 16]] then
    /show "  Invalid timer 2 postscaler of " pos
         error   "Timer 2 postscaler"
         end
    /stop
    /endif
  //
  //   Create the basic timer 2 configuration constants.
  //
  /if [exist "tmr2_pre:vcon"] then
    /show "  TMR2_PRE previously created"
         error   TMR2_PRE
         end
    /stop
    /endif
  /const tmr2_pre integer = pre

  /if [exist "tmr2_per:vcon"] then
    /show "  TMR2_PER previously created"
         error   TMR2_PER
         end
    /stop
    /endif
  /const tmr2_per integer = per

  /if [exist "tmr2_pos:vcon"] then
    /show "  TMR2_POS previously created"
         error   TMR2_POS
         end
    /stop
    /endif
  /const tmr2_pos integer = pos

tmr2_pre equ     [v tmr2_pre] ;make assembly constants of the same names
tmr2_per equ     [v tmr2_per]
tmr2_pos equ     [v tmr2_pos]
  //
  //   Compute the derived values.
  //
  /const tmr2_cypwm integer = [* tmr2_pre tmr2_per] ;PWM period, cycles
  /const tmr2_perpwm real = [/ tmr2_cypwm freq_inst] ;PWM period, seconds
  /const tmr2_cycles integer = [* tmr2_cypwm tmr2_pos] ;full period, cycles
  /const tmr2_period real = [/ tmr2_cycles freq_inst] ;full period, seconds
  /const tmr2_freq real = [/ 1 tmr2_period] ;timer 2 frequency, Hz

  /if desper_given then //desired period is known ?
    /const tmr2_error real = [/ [- tmr2_period desper] desper] ;period error
    /endif
  /endsub

////////////////////////////////////////////////////////////////////////////////
//
//   Macro TMR2CY_CLOSEPER period
//
//   Computes the timer 2 setup to result in the desired period as close as
//   possible.  PERIOD is the desired timer 2 period in floating point seconds.
//
//   This macro produces no code.  It only creates preprocessor and assembler
//   constants.  See subroutine TIMER2_CONFIG_SET for a list of the constants.
//
//   The configuration resulting in the closest period to PERIOD is always
//   chosen.  When multiple configurations result in the same lowest error, then
//   the PWM period is minimized (the postscaler maximized).  Otherwise, the PWM
//   resolution is maximized by minimizing the prescaler.
//
/macro tmr2cy_closeper
  /var local dper real = [vnl [arg 1]] ;desired period, seconds
  /var local pre integer     ;current prescaler: 1, 4, 16
  /var local percy integer   ;current period, cycles
  /var local period real     ;current period, seconds
  /var local err real        ;current error
  /var local best_err real   ;best error so far
  /var local best_pre integer ;best prescaler so far
  /var local best_per integer ;best period divider so far
  /var local best_pos integer ;best postscaler so far

  /set best_err 1e35         ;init to out of range error so far

  /loop with pos from 16 to 1 by -1 ;try the various postscalers
    /loop with pren from 0 to 2 ;try prescalers in ascending order
      /set pre [exp 4 pren]  ;actual prescaler, power of 4
      /loop with per from 1 to 256
        /set percy [* pre per pos] ;total period, cycles
        /set period [/ percy freq_inst] ;total period, seconds
        /set err [abs [/ [- period dper] dper]] ;error at this setting
        /if [< err best_err] then ;found better config than previous ?
          /set best_pre pre  ;update best config found so far
          /set best_per per
          /set best_pos pos
          /set best_err err
          /endif
        /endloop             ;back for next higher period divider
      /endloop               ;back for next higher prescaler
    /endloop                 ;back for next lower postscaler

  /call timer2_config_set best_pre best_per best_pos dper
  /endmac

////////////////////////////////////////////////////////////////////////////////
//
//
//   Macro TMR2CY_MAXPER cycles
//
//   Like TIMER2_CYCLES except that the period value is maximized as the highest
//   priority and the postscaler maximized second.  This results in the highest
//   possible PWM resolution within the total period constraint, and the highest
//   PWM frequency for the chosen PWM period.
//
/macro tmr2cy_maxper
  /var local cycles integer = [arg 1] ;instruction cycles in whole period
  /var exist tmr2_pre integer ;prescaler value: 1, 4, or 16
  /var exist tmr2_per integer ;period value: 1 - 256
  /var exist tmr2_pos integer ;postscaler value: 1 - 16
  /var local ii integer

  /set tmr2_per 257          ;init to one past first period to try
  /block                     ;back here to try each new period
    /set tmr2_per [- tmr2_per 1] ;make period for this iteration
    /if [< tmr2_per 1] then  ;exhausted all periods without a solution ?
      /show "  Unable to achieve timer 2 interrupt period of " cycles " cycles"
         error   Timer 2 period
         end
      /stop
      /endif
    /set ii [div cycles tmr2_per] ;make remaining divisors product
    /if [<> [* ii tmr2_per] cycles] then ;period doesn't divide evenly ?
      /repeat                ;this period unusable, back and try next
      /endif
    /set tmr2_pre 1          ;init prescaler value to try next
    /block                   ;back here each new prescaler value
      /set tmr2_pos [div ii tmr2_pre] ;postscaler for this candidate
      /if [and [>= tmr2_pos 1] [<= tmr2_pos 16]] then ;valid postscaler ?
        /if [= [* tmr2_pre tmr2_per tmr2_pos] cycles] then ;found solution ?
tmr2_pre set     [v tmr2_pre] ;prescaler: 1, 4, 16
tmr2_per set     [v tmr2_per] ;period: 1 - 256
tmr2_pos set     [v tmr2_pos] ;postscaler: 1 - 16
          /quitmac           ;all done, solution set
          /endif
        /endif
      /set tmr2_pre [* tmr2_pre 4] ;advance to next prescaler
      /if [<= tmr2_pre 16] then ;still within range ?
        /repeat
        /endif
      /endblock              ;done trying all prescaler choices
    /repeat                  ;back to try next period
    /endblock
  /endmac

;*******************************************************************************
;
;   Macro TIMER2_CYCLES tmr2ncy
;
;   *** WARNING: Legacy, not for new code ***
;   Use TMR2CY_CLOSEPER or TMR2CY_MAXPER.
;
;   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 postscaler is set to the maximum possible value that results in the
;   specified period.  This minimizes the PWM period for the given overall
;   period.  Within this constraint, the smallest possible prescaler is used,
;   thereby maximizing the PWM frequency.  This results in the maximum possible
;   PWM resolution given the overall interrupt period constraint.
;
;   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.
;
timer2_cycles macro tmr2ncy

tmr2_pos set     16          ;init the postscaler to maximum
  while tmr2_pos >= 1        ;loop thru decreasing 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_CYCLES."
         endm

;*******************************************************************************
;
;   Macro TIMER2_USEC usec
;
;   *** WARNING: Legacy, not for new code ***
;   Use TMR2CY_CLOSEPER or TMR2CY_MAXPER.
;
;   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

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine TIMER2B_PER_SET
//
//   Set up a enhanced 16F152xx timer 2 to generate a periodic event.  The
//   timer is set up from the following preprocessor state:
//
//     TMR2_PRE  -  Prescaler, power of 2 from 1 to 128
//
//     TMR2_PER  -  Period divide value, 1 - 256
//
//     TMR2_POS  -  Postscaler, 1 - 16
//
/subroutine timer2b_per_set
  /var local ii integer      ;scratch integer
  /var local s string        ;scratch string
//
//   Validate the setup parameters.
//
  /set ii [rnd [log2 tmr2_pre]] //get the prescaler power of 2
  /set ii [max 0 [min 7 ii]] //clip to the valid range
  /set ii [shiftl 1 ii] //re-create the original power of 2
  /if [<> ii tmr2_pre] then //invalid prescaler value ?
    /show "  Timer 2 prescaler of " tmr2_pre " is out of range."
         errors  TMR2_PRE
         end
    /stop
    /endif

  /if [or [< tmr2_per 1] [> tmr2_per 256]] then
    /show "  Timer 2 period of " tmr2_per " is out of range."
         errors  TMR2_PER
         end
    /stop
    /endif

  /if [or [< tmr2_pos 1] [> tmr2_pos 16]] then
    /show "  Timer 2 postscaler of " tmr2_pos " is out of range."
         errors  TMR2_POS
         end
    /stop
    /endif
//
//   The setup parameters are valid.  Now set the timer registers.
//
  /set ii [shiftl [rnd [log2 tmr2_pre]] 4] //init value with prescaler field
  /set ii [or ii [- tmr2_pos 1]] //merge in postscaler field
  /set s ""
  /call tabopcode s
  /append s "setreg"
  /call taboperand s
  /append s ii ", t2con"
  /call startcomm s
  /append s "timer off, prescaler " tmr2_pre ", postscaler " tmr2_pos
  /write s

         setreg  0, t2tmr    ;init the timer counter to 0

  /set s ""
  /call tabopcode s
  /append s "setreg"
  /call taboperand s
  /append s [- tmr2_per 1] ", t2pr"
  /call startcomm s
  /append s "set timer 2 period to " tmr2_per
  /write s

         setreg  b'00000001', t2clkcon
                 ; XXXXX---  unused
                 ; -----001  clock source is instruction clock

         dbankif pir1
         bcf     pir1, tmr2if ;clear the clock tick flag
  /endsub

;*******************************************************************************
;
;   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 or its 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 TMR2CY_xxx macros 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.
;
;   For newer timer 2 types, the state must be supplied both as preprocessor
;   constants and assembly constants.  This is automatically done by the
;   TMR2CY_xxx macros.  The old TIMER2_xxx macros are not supported with the new
;   timer 2 type.
;
timer2_setup macro
         local   t2con_val
;
;   Handle traditional timer 2.
;
;   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

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine TIMER2_SETUP
//
//   Set up timer 2 to periodically set the timer 2 interrupt flag.  The timer
//   is only configured, not run.
//
//   The timer is set up according to the following preprocessor constants:
//
//     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 TMR2CY_xxx macros can be used to compute these values given a
//   desired period.
//
/subroutine timer2_setup

  /if [exist "_t2rst:const"] then ;16F152xx type ?
    /call timer2b_per_set
    /return
    /endif

         timer2_setup        ;use old macro for the old timer 2 type
  /endsub

;*******************************************************************************
;
;   Macro TIMER2_SETUP_INTR
;
;   Same as 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


;*******************************************************************************
;*******************************************************************************
;
;   Handling multi-byte data.
;

;*******************************************************************************
;
;   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 and SETF 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 and SETF 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 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 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 and SETF 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 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 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 SETF 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_16b || 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 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_16b
         lsrf    (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 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_16b
         asrf    (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 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_16b
         lslf    (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 direct register bank must be set for
;   access to all state.
;
add32    macro   dest, src, temp

;*****
;
;   16 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

;*****
;
;   Enhanced PIC 16 (enhanced 14 bit core).
;
  if fam_16b
         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

;*****
;
;   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

;*****
;
;   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 direct register bank must be set for
;   access to all state.
;
add24    macro   dest, src, temp

;*****
;
;   16 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

;*****
;
;   Enhanced PIC 16.
;
  if fam_16b
         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

;*****
;
;   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

;*****
;
;   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
;   value are stored with the low byte first.  The direct register bank must be
;   set for access to all state.
;
add16    macro   dest, src

;*****
;
;   For 16 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 enhanced PIC 16.
;
  if fam_16b
         movf    src+0, w
         addwf   dest+0      ;add byte 0

         movf    src+1, w
         addwfc  dest+1      ;add byte 1
         exitm
    endif

;*****
;
;   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 direct register bank must be set for access to all state.
;
sub32    macro   dest, src, temp

;*****
;
;   For 16 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

;*****
;
;   For enhanced PIC 16.
;
  if fam_16b
         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

;*****
;
;   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 direct register bank 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 enhanced PIC 16.
;
  if fam_16b
         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
;*****
;
;   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 direct register bank 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 enhanced PIC 16.
;
  if fam_16b
         movf    src+0, w
         subwf   dest+0      ;subtract byte 0

         movf    src+1, w
         subwfb  dest+1      ;subtract byte 1
         exitm
    endif

;*****
;
;   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, but 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 || fam_16b
         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 FP24MOVE src dest
;
;   Copy the 24 bit floating point value from SRC to DEST.
;
fp24move macro   src, dest

;*****
;
  if fam_16 || fam_16b
         movf    (src)+0, w
         movwf   (dest)+0
         movf    (src)+1, w
         movwf   (dest)+1
         movf    (src)+2, w
         movwf   (dest)+2
         exitm
    endif

;*****
;
  if fam_18
         movff   (src)+0, (dest)+0
         movff   (src)+1, (dest)+1
         movff   (src)+2, (dest)+2
         exitm
    endif

;*****
;
         error   "FP24MOVE macro in STD.INS.ASPIC not implemented for this processor"
         endm

;*******************************************************************************
;
;   Macro FP24LDK dest, kval
;
;   Load the floating point constant KVAL into the 3-byte floating point
;   variable at DEST.
;
/macro fp24ldk
  /if [exist -1 arg] then
    /show "Dumb place for a label, in front of FP24LDK macro."
         error   FP24LDK label
         end
    /stop
    /endif
  /var local kval real = [arg 2]
         movlw   low [fp24i kval] [chars ";load " [fp kval "sig 6"]]
         movwf   ([arg 1])+0
         movlw   high [fp24i kval]
         movwf   ([arg 1])+1
         movlw   upper [fp24i kval]
         movwf   ([arg 1])+2
  /write ""
  /endmac

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine FP48_MAKE val
//
//   Converts VAL to 48 bit floating point.  VAL must be convertable to a
//   preprocessor floating point value.  The integer preprocessor variables
//   FP48_EXP and FP48_MANT are set to the exponent word and mantissa,
//   respectively.
//
//   The exponent word is 16 bits wide with the high bit being the overall sign
//   bit.  0 is positive and 1 negative.  The low 15 bits of the exponent word
//   are the power of 2 exponent to apply to the mantissa value plus 16384.  For
//   example, if the low 15 bits have a value of 16385, then 2^1 is to be
//   applied to the mantissa value.  Likewise, 16381 specifies to apply 2^-3.
//
//   The mantissa is 32 bits wide.  Its value is as if the binary point and then
//   a 1 bit were immediately to its left.  For example, the mantissa value of
//   3456789Ah is to be interpreted as the hexadecimal fixed point value
//   1.3456789A, which has a decimal value of about 1.204444.  The mantissa
//   value is therefore always from 1 up to but not including 2.
//
//   The overal floating point value is the mantissa value times the power of 2
//   indicated by the exponent, and the sign indicated by the high bit of the
//   exponent word.
//
//   The special case of 0 is represented by all 48 bits 0.
//
/subroutine fp48_make
  /var exist fp48_exp integer
  /var exist fp48_mant integer

  /var local val real = [arg 1]
  /var local neg bool        ;overal value is negative
  /var local exp integer     ;power of 2 exponent
  /var local man1 integer    ;mantissa low 16 bits
  /var local man2 integer    ;mantissa high 16 bits

  /if [= val 0.0] then       ;special case of 0 ?
    /set fp48_exp 0
    /set fp48_mant 0
    /return
    /endif

  /set neg [< val 0.0]       ;negative ?
  /set val [abs val]         ;work with the positive value from now on

  /set exp 0                 ;init exponent of 2 (multiplier = 1)
  /block                     ;make smaller exponent to adjust value up
    /if [>= val 1.0] then    ;large enough
      /quit
      /endif
    /set val [* val 2.0]
    /set exp [- exp 1]
    /repeat
    /endblock
  /block                     ;make larger exponent to adjust value down
    /if [< val 2.0] then     ;small enough ?
      /quit
      /endif
    /set val [/ val 2.0]
    /set exp [+ exp 1]
    /repeat
    /endblock

  /set val [* val 65536.0]
  /set man2 [trunc val]      ;make high 16 mantissa bits
  /set val [- val man2]      ;remove the high 16 bits value
  /set val [* val 65536.0]
  /set man1 [rnd val]        ;make low 16 mantissa bits
  /if [> man1 16#FFFF] then  ;low 16 bits overflowed ?
    /set man2 [+ man2 [shiftr man1 16]] ;move excess to high word
    /set man1 [and man1 16#FFFF]
    /endif
  /if [> man2 16#1FFFF] then ;high 16 bits overflowed ?
    /set man1 [shiftr man1 1] ;shift mantissa right one bit
    /set man1 [or [shiftl [and man2 1] 15]]
    /set man2 [shiftr man2 1]
    /set exp [+ exp 1]       ;adjust exponent to account for the shift
    /endif
  /set man2 [and man2 16#FFFF] ;mask in only the bits to save
  /set fp48_mant [or [shiftl man2 16] man1] ;assemble the mantissa

  /set fp48_exp [+ exp 16384]
  /if neg then
    /set fp48_exp [or fp48_exp 16#8000] ;set the negative bit
    /endif
  /endsub


;*******************************************************************************
;*******************************************************************************
;
;   UART configuration.
;

;*******************************************************************************
;
;   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
;
;     SPBRGH  -  Baud rate generator register high byte (not all UARTs)
;
;     TXREG  -  Transmit data register
;
;     RCREG  -  Receive data register
;
;     BAUDCON  -  Baud rate control register (not all UARTs)
;
;     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 tx1if
      ifndef txif
         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                  ;end of TX1IF not defined case
;
;   Select UART 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
    ifdef spbrgh1
spbrgh   set     spbrgh1
      endif
    ifdef baudcon1
baudcon  set     baudcon1
      endif

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

txie_bit set     tx1ie
txie     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
rcif     set     rc1if
    ifdef rcif_flag
#undefine rcif_flag
      endif
#define rcif_flag rcif_reg, rcif_bit

rcie_bit set     rc1ie
rcie     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
    ifdef spbrgh2
spbrgh   set     spbrgh2
      endif
    ifdef baudcon2
baudcon  set     baudcon2
      endif

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.
;
;     VAL_BAUDCON  -  Value for the BAUDCON register for those processors that
;       have this.
;
;   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_baudcon set  b'00001000' ;set baud rate configuration
                 ; 0-------  clear auto-baud rollover status
                 ; -X------  read-only status bit
                 ; --0-----  normal receive polarity, idle is high
                 ; ---0----  normal transmit polarity, idsl is high
                 ; ----1---  use full 16 bit baud rate generator
                 ; -----X--  unused
                 ; ------X-  used only with auto-baud mode
                 ; -------0  auto-baud mode disabled

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, VAL_BAUDCTL, and VAL_BAUDCON.  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 baudcon
         dbankif baudcon
         movlw   val_baudcon
         movwf   baudcon
    else
    ifdef baudctl
         dbankif baudctl
         movlw   val_baudctl
         movwf   baudctl
      endif
    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


;*******************************************************************************
;*******************************************************************************
;
;   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

;*******************************************************************************
;
;   I/O port initialization.
;
;   Create and initialize the various assembler variables assumed by the /INBIT,
;   /INANA, and /OUTBIT commands.  These variables are later used in the PORT
;   module to initialize the I/O pins according to the /INxxx and /OUTxxx
;   commands.
;
analogused0 set  0
analogused1 set  0

/loop with ii from 0 to 25
  /var local c string
  /set c [char [+ [ccode "a"] ii]]

  /write
  ifdef port[chars c]
val_port[chars c] set 0
val_tris[chars c] set 0
val_pullup[chars c] set 0
val_analog[chars c] set 0
    ifndef pullups_port[chars c]
pullups_port[chars c] equ 0
      endif
    endif
  /endloop

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine PINNAME_PARSE name
//
//   Parse a composite pin name, like "RA1" or "RB5" into the port name and pin
//   number.  NAME is a string.
//
//   The following variables will be created if not already existing, then set:
//
//     PINNAME_PORT, string  -  Single upper case port name.
//
//     PINNAME_BIT, integer  -  0-N bit number within the port.
//
/subroutine pinname_parse
  /var local name string = [ucase [vnl [arg 1]]] //pin name, like "RA1" or "RB5"
  /var local bstr string //bit number string
  /var exist pinname_port string //upper case port name letter
  /var exist pinname_bit integer //0-N bit number of pin within its port

  /block //abort block on any error
    /if [< [slen name] 3] then //too short for minimum pin name ?
      /quit
      /endif
    /if [<> [sindx 1 name] "R"] then //not Rxx ?
      /quit
      /endif
    /set pinname_port [sindx 2 name] //extract the port name
    /set bstr [substr 3 [- [slen name] 2] name] //extract bit number string
    /if [not [isint [chars bstr]]] then //invalid bit number ?
      /quit
      /endif
    /set pinname_bit [chars bstr] //return bit number
    /return
    /endblock

  /show "  Invalid pin name " name " passed to subroutine PINNAME_PARSE."
         error   Pin Name
         end
  /stop
  /endsub

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine PINDATA name
//
//   Get data about the named pin NAME.  NAME is a string.
//
//   The following variables are set:
//
//     PINDATA_NAME, string
//
//       The pin name.  This is the same name as passed in by the NAME
//       parameter.
//
//     PINDATA_EXIST, bool
//
//       TRUE iff the named pin exists.  This means it was defined with a INBIT,
//       OUTBIT, etc command.  The values of the remaining variables are
//       undefined when this variable is FALSE.
//
//     PINDATA_PORT, string
//
//       Upper case port name single letter.
//
//     PINDATA_BIT, integer
//
//       0-N bit number within port and other I/O pin registers.
//
//     PINDATA_OUT, bool
//
//       TRUE iff the pin was defined as an output.
//
//     PINDATA_POS, bool
//
//       TRUE iff the pin was defined with positive logic.
//
//     PINDATA_DIG, bool
//
//       TRUE iff the pin was defined as digital, not analog.
//
//     PINDATA_AN, integer
//
//       ANx analog input number when the pin was defined as analog.  Set to the
//       special value of -1 when the pin is not analog.
//
//     These variables are created if not already existing.  When the pin name
//     is not defined (PINDATA_EXIST = FALSE), then the state of the remaining
//     variables is undefined.
//
/subroutine pindata
  /var local name string = [vnl [arg 1]] //pin name
  /var local vn string //scratch variable name
  /var local ind integer //parse index
  /var local tk string //parsed token

  /var exist pindata_name string
  /var exist pindata_exist bool
  /var exist pindata_port string
  /var exist pindata_bit integer
  /var exist pindata_out bool
  /var exist pindata_pos bool
  /var exist pindata_dig bool
  /var exist pindata_an integer

  /set pindata_name name //save name of pin other vars pertain to
  /set pindata_exist false //init to pin is not defined.
//
//   Get PORT, BIT, and OUT by looking at the Inbit/Outbit PORT and BIT
//   constants.
//
  /block
    /set vn [str "Outbit_" name "_port"] //try output pin
    /if [exist vn] then
      /set pindata_exist true
      /set pindata_port [chars vn]
      /set pindata_out true
      /set vn [str "Outbit_" name "_bit"]
      /quit
      /endif
    /set vn [str "Inbit_" name "_port"] //try input pin
    /if [exist vn] then
      /set pindata_exist true
      /set pindata_port [chars vn]
      /set pindata_out false
      /set vn [str "Inbit_" name "_bit"]
      /quit
      /endif
    /return //pin doesn't exist
    /endblock

  /set pindata_bit [chars vn]
//
//   Get POS, DIG, and AN by parsing Portdata_xxx for this pin.
//
  /set vn [str "Portdata_" [lcase pindata_port] pindata_bit]
  /set ind 1 //init parse index

  /call string_token [chars vn] ind tk //get name

  /call string_token [chars vn] ind tk //get IN/OUT

  /call string_token [chars vn] ind tk //get POS/NEG
  /set pindata_pos [= tk "POS"]

  /call string_token [chars vn] ind tk //get DIG/ANA
  /set pindata_dig [= tk "DIG"]

  /set pindata_an -1 //init to no analog pin
  /if [not pindata_dig] then //is analog pin ?
    /call string_token [chars vn] ind tk //get ANx
    /set tk [substr 3 [- [slen tk] 2] tk] //extract analog input number string
    /set pindata_an [chars tk]
    /endif
  /endsub

////////////////////////////////////////////////////////////////////////////////
//
//   Macro SETPIN name state
//
//   Set a pin to a particular state.
//
//   NAME is the pin name as defined with the INBIT, OUTBIT, INANA, etc,
//   command.  NAME is a token, not a string.
//
//   STATE is one of the keywords:
//
//     ON  -  The pin will be driven.
//
//     OFF  -  Set the pin to high impedance.
//
//     HIGH  -  Logic high.  This does not set the pin to driven, only the state
//       it will be driven to if driven.
//
//     LOW  -  Logic low.  This does not set the pint to driven, only the state
//       it will be driven to if driven.
//
//     TRUE  -  Like HIGH or LOW according to the logic polarity the pin is
//       defined with.
//
//     FALSE  -  Like HIGH or LOW according to the logic polarity the pin is
//       defined with.
//
/macro setpin
  /var local name string = [qstr [arg 1]] //pin name
  /var local s string //scratch string
  /var local port string //lower case port name letter

  /var local pindata_name string //PINDATA subroutine return values
  /var local pindata_exist bool
  /var local pindata_port string
  /var local pindata_bit integer
  /var local pindata_out bool
  /var local pindata_pos bool
  /var local pindata_dig bool
  /var local pindata_an integer

  /call pindata name //get info about this pin
  /if [not pindata_exist] then
    /show "  I/O pin " [pick] " is not defined."
         error   SETPIN name
         end
    /stop
    /endif
  /set port [lcase pindata_port] //save lower case port name letter

  /pick one by [ucase [qstr [arg 2]]]

  /option "ON"
         dbankif tris[chars port]
    /call tabopcode s
    /append s "bcf"
    /call taboperand s
    /append s "tris" port ", " pindata_bit
    /call startcomm s
    /append s "enable output drive for pin " [ucase name]
    /write s

  /option "OFF"
         dbankif tris[chars port]
    /call tabopcode s
    /append s "bsf"
    /call taboperand s
    /append s "tris" port ", " pindata_bit
    /call startcomm s
    /append s "set pin " [ucase name] " to high impedance"
    /write s

  /option "HIGH"
         dbankif lat[chars port]
    /call tabopcode s
    /append s "bsf"
    /call taboperand s
    /append s "lat" port ", " pindata_bit
    /call startcomm s
    /append s "set pin " [ucase name] " output value to HIGH"
    /write s

  /option "LOW"
         dbankif lat[chars port]
    /call tabopcode s
    /append s "bcf"
    /call taboperand s
    /append s "lat" port ", " pindata_bit
    /call startcomm s
    /append s "set pin " [ucase name] " output value to LOW"
    /write s

  /option "TRUE"
         dbankif lat[chars port]
    /call tabopcode s
    /if pindata_pos
      /then
        /append s "bsf"
      /else
        /append s "bcf"
      /endif
    /call taboperand s
    /append s "lat" port ", " pindata_bit
    /call startcomm s
    /append s "assert pin " [ucase name]
    /write s

  /option "FALSE"
         dbankif lat[chars port]
    /call tabopcode s
    /if pindata_pos
      /then
        /append s "bcf"
      /else
        /append s "bsf"
      /endif
    /call taboperand s
    /append s "lat" port ", " pindata_bit
    /call startcomm s
    /append s "de-assert pin " [ucase name]
    /write s

  /optionelse
    /show "  """ [pick] """ is not a valid option to SETPIN."
         error   SETPIN option
         end
    /stop

    /endpick
  /endmac


;*******************************************************************************
;*******************************************************************************
;
;   Bank and page switching, memory allocation.
;
;   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 indirect 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_16b
currdb   set     nobank      ;init current direct 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

;*******************************************************************************
;
;   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_16b
#define bankof(adr) (((adr) >> 7) & 63)
    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 || fam_16b
#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_16b
#define bankadr(bank) ((((bank) & 31) << 7) + h'20')
    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 || fam_16b
#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_16
#define inbanked(adr) (((((adr)&h'7F')<commregs_first)||(((adr)&h'7F')>commregs_last)))
    endif
  if fam_16b
#define inbanked(adr) (((((adr)&h'7F')<commregs_first)||(((adr)&h'7F')>commregs_last)))&&(((adr)&h'7F')>h'0B')
    endif
  if fam_18
#define inbanked(adr) (((adr) > (acclast)) && ((adr) <= (h'F00'+acclast)))
    endif

;*******************************************************************************
;
;   BANKnADR constants.
;
;   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 || fam_16b
         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 || fam_16b
         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.
;
;   On a PIC 16, 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.
;
;   On the enhanced 16 family, this macro sets BSR.  No code is emitted and the
;   bank setting not altered if ADR is in unbanked memory.  Generally, the first
;   12 locations of each bank are the unbanked SFRs, and the last 16 locations
;   unbanked general RAM.
;
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

;*****
;
;   Traditional 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

;*****
;
;   Enhanced 16 family devices.
;
  if fam_16b

    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 enhanced 16 family 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 || fam_16b
         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

;*****
;
;   Enhanced 16 family devices.
;
  if fam_16b
         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 data 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.
;
;   The details of how the stack works and is managed varies by processor type:
;
;   PIC 16 and 17
;
;     The stack grows from high to low addresses.  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
;     family.  STACKP is assumed to be accessible without requiring bank
;     switching.  It is defined in the STACK module when it is a software
;     register.
;
;   Enhanced PIC 16
;
;     FSR1 is reserved as the stack pointer, and the stack grows from low to
;     high addresses.  The stack pointer points to the next empty location.  A
;     push is therefore a post-increment and a pop a pre-decrement access.  The
;     processor can perform both these operations natively.
;
;   PIC 18
;
;     By default, FSR2 is used as the stack pointer, the stack grows from low to
;     high addresses, and the pointer points to the last-pushed byte.  A push is
;     therefore a pre-increment and a pop a post-decrement.  Both these can be
;     performed natively by the processor.
;
;     Unfortunately, the C18 compiler uses a different convention.  It reserves
;     FSR2 as a frame pointer and FSR1 as the stack point, thereby leaving only
;     a single FSR for application use.  Worse yet, it defines a stack
;     convention that can not be realized with the native addressing modes.  The
;     stack grows from low to high addresses, but the pointer points to the next
;     empty location.  This means a push requires a post-increment and a pop a
;     pre-decrement.  Since the PIC 18 has no native pre-decrement capability, a
;     pop operation takes multiple instructions.
;
;   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_16b || 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

;*****
;
;   Enhanced PIC 16 family.
;
  if fam_16b
         movlw   low (adr)
         movwf   fsr#v(fsrstack)l
         movlw   high (adr)
         movwf   fsr#v(fsrstack)h
         exitm
    endif

;*****
;
;   PIC 18 family.
;
  if fam_18
    if c18comp               ;C18 compatibility mode
         lfsr    1, adr
         lfsr    2, adr
      else                   ;normal Embed stack convention
         lfsr    fsrstack, (adr) - 1
      endif
         exitm
    endif

;*****
;
         error   "STACK_SET macro in STD.INS.ASPIC not implemented for this processor"
         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

;*****
;
;   Enhanced PIC 16.
;
  if fam_16b
    if ((n) <= 31)
         addfsr  fsr#v(fsrstack), n
         exitm
      else
         movlw   low (n)
         addwf   fsr#v(fsrstack)l, w
         movwf   (reg16)+0
         movlw   high (n)
         addwfc  fsr#v(fsrstack)h, w
         movlw   (reg16)+1

         intr_off
         movwf   fsr#v(fsrstack)h
         movf    reg16, w
         movwf   fsr#v(fsrstack)l
         intr_on
         exitm
      endif
    endif                    ;end of enhanced PIC 16 case

;*****
;
;   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(fsrstack)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.
;
;   Enhanced 16 family and 18 family  -  Efficient to push a single value.
;     Multiple PUSHREG just as efficient as a single PUSHREGS pushing the same
;     state.
;
pushreg  macro   reg
;
;   Enhanced 16 family.
;
  if fam_16b
         movf    reg, w
         movwi   fsr#v(fsrstack)++
w_trashed set    true
         exitm
    endif
;
;   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_16b
         movwi   fsr#v(fsrstack)++
         exitm
    endif

;*****
;
  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 PUSH_TBLPTR
;
;   Save TBLPTR on the stack.
;
push_tblptr macro
         pushreg tblptrl
         pushreg tblptrh
  if progadrb > 2
         pushreg tblptru
    endif
         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.
;
;   Enhanced 16 family  -  Multiple POPREG just as efficient as a single
;     POPREGS.  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

;*****
;
;   Enhanced 16 family.
;
  if fam_16b
         moviw   --fsr#v(fsrstack)
         movwf   reg
w_trashed set    true
         exitm
    endif

;*****
;
;   18 family.
;
  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_16b
         moviw   --fsr#v(fsrstack)
         exitm
    endif

;*****
;
  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 POP_TBLPTR
;
;   Pop TBLPTR on the stack.  This should have been pushed onto the stack with
;   the PUSH_TBLPTR macro.
;
pop_tblptr macro
  if progadrb > 2
         popreg  tblptru
    endif
         popreg  tblptrh
         popreg  tblptrl
         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, psjj

;*****
;
;   Enhanced PIC 16 family.
;
  if fam_16b
psii     set     n           ;init number of bytes left to pop

    while psii
psjj     set     psii        ;init to do all bytes this time
      if psjj > 32
psjj     set     32          ;clip to max for one ADDFSR instruction
        endif
         addfsr  fsr#v(fsrstack), -psjj
psii     set     psii - psjj ;update number of bytes left to pop
      endw

         exitm
    endif                    ;end of enhanced PIC 16 case

;*****
;
;   PIC 18 family.
;
  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

;*****
;
;   Enhanced 16 family processors.  The FSR indicated by FSRSTACK is reserved as
;   the stack pointer.
;
  if fam_16b

reg      set     0           ;init register number
    while reg < numregs      ;once for each possible register
      if f & regf#v(reg)     ;this register enabled ?
         movf    reg#v(reg), w ;get the value to push
         movwi   fsr#v(fsrstack)++ ;push it
n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack
w_trashed set    true
        endif
reg      set     reg + 1     ;advance to next register number
      endw                   ;back to process this new register number

    if f & regff             ;FLAGS register enabled ?
         movf    flags, w    ;get the value to push
         movwi   fsr#v(fsrstack)++ ;push it
n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack
w_trashed set    true
      endif

         exitm
    endif                    ;end of enhanced 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

;*****
;
;   Enhanced 16 family processors.  The FSR indicated by FSRSTACK is reserved as
;   the stack pointer.
;
  if fam_16b

    if f & regff             ;FLAGS register enabled ?
         moviw   --fsr#v(fsrstack) ;pop the value into W
         movwf   flags       ;save it in the target register
w_trashed set    true
      endif

reg      set     numregs - 1 ;init register number
    while reg >= 0           ;once for each possible register
      if f & regf#v(reg)     ;this register enabled ?
         moviw   --fsr#v(fsrstack) ;pop the value into W
         movwf   reg#v(reg)  ;save it in the target register
w_trashed set    true
        endif
reg      set     reg - 1     ;advance to next register number
      endw                   ;back to process this new register number

         exitm
    endif                    ;end of enhanced 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.
;
;   Unless otherwise documented, subroutines are assumed to preserve the general
;   registers REG0 - REGn and 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 in a particular linker section is 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 BREAKPOINT
;
;   Add NOPs to the code when debugging with a ICD.  These type of debuggers
;   skid after a breakoint is hit, so using ordinary instructions for
;   breakpoints is sometimes difficult.  This macro provides instructions to set
;   the breakpoint on, and include enough padding so that the next instruction
;   after the macro is not executed despite debugging skidding.
;
;   This macro only emits code when the DEBUG_ICD switch is set to TRUE.  It can
;   therefore safely be left in production code without harm.
;
/macro breakpoint
  /if debug_icd then
    /write
         nop                 ;for debugger breakpoint
    /write
    /endif
  /endmac

;*******************************************************************************
;
;   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

;*******************************************************************************
;
;   <name> GLBSUB [<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 or is omitted.
;
;   This macro was changed from a MPASM macro to a preprocessor macro in January
;   2012 and a different interface allowed as a result.  The old interface of
;
;          GLBSUB <name>, <regflags>
;
;   is still allowed.  Due to restrictions of MPASM macros, it was previously
;   not possible to have the subroutine name appear as a label before the macro,
;   and the REGFLAGS argument could not be defaulted.  Preprocessor macros do
;   not suffer from these restrictions, so the more visually clear interface was
;   adopted as described earlier.
;
;   For backwards compatibility with existing code, the old interface is still
;   supported.  If no label is supplied on the GLBSUB line, then it is assumed
;   the old interface is being used.  The first argument to the macro must be
;   the subroutine name.  The second is made optional and will default to NOREGS
;   (no registers automatically saved) when left off.
;
;   If a label is supplied then the new interface is assumed.  The new interface
;   is preferred and should be used in new code.
;
/macro glbsub
  /var local name string     ;subroutine name
  /var local regflags string = 0 ;<regflags> argument, init to no registers to save
//
//   Set NAME and REGFLAGS to the subroutine name and register list arguments,
//   respectively.  This is done differently depending on which interface is
//   used, as indicated by whether a leading label is present.
//
  /if [exist -1 arg]
    /then                    ;new interface with leading label
      /set name [qstr [arg -1]] ;get the subroutine name
      /if [exist 1 arg] then
        /set regflags [qstr [arg 1]] ;get the explicit regflags argument
        /endif
    /else                    ;old interface, label is first argument
      /set name [qstr [arg 1]] ;get subroutine name
      /if [exist 2 arg] then
        /set regflags [qstr [arg 2]] ;get the explicit regflags argument
        /endif
    /endif

  /write name                ;define the subroutine entry point
  /write "         global  " name ;define it global
  /write "         enter   " regflags ;perform standard subroutine entry
  /endmac

;*******************************************************************************
;
;   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

  if c18comp
nexthllarg set   1           ;init number of next HLL argument to fetch
         glbsub  name, regflags
nexthllarg set   nexthllarg + n_bytes_pushed
         exitm
    endif

         error   Compiler not defined or supported in HLLSUB macro.
         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

;*******************************************************************************
;
;   <name> LOCSUB [<regflags>]
;
;   Just like GLBSUB except that the subroutine will not be global.  It will
;   only be known within its module.
;
;   Like GLBSUB, the interface to this macro was changed in January 2012.  See
;   the GLBSUB description for details.
;
/macro locsub
  /var local name string     ;subroutine name
  /var local regflags string = 0 ;<regflags> argument, init to no registers to save
//
//   Set NAME and REGFLAGS to the subroutine name and register list arguments,
//   respectively.  This is done differently depending on which interface is
//   used, as indicated by whether a leading label is present.
//
  /if [exist -1 arg]
    /then                    ;new interface with leading label
      /set name [qstr [arg -1]] ;get the subroutine name
      /if [exist 1 arg] then
        /set regflags [qstr [arg 1]] ;get the explicit regflags argument
        /endif
    /else                    ;old interface, label is first argument
      /set name [qstr [arg 1]] ;get subroutine name
      /if [exist 2 arg] then
        /set regflags [qstr [arg 2]] ;get the explicit regflags argument
        /endif
    /endif

  /write name                ;define the subroutine entry point
  /write "         enter   " regflags ;perform standard subroutine entry
  /endmac

;*******************************************************************************
;
;   name     GLBENT
;            GLBENT    name
;
;   Declare a global entry point that can be jumped to from other modules.
;
/macro glbent
  /if [exist -1 arg]
    /then                    ;new interface with leading label
[arg -1]
         global  [arg -1]
    /else                    ;old interface, label is first argument
[arg 1]
         global  [arg 1]
    /endif
         unbank
  /endmac

;*******************************************************************************
;
;   name     LOCENT
;            LOCENT    name
;
;   Declare a local entry point that can only be jumped to from within the same
;   module.
;
/macro locent
  /if [exist -1 arg]
    /then                    ;new interface with leading label
[arg -1]
    /else                    ;old interface, label is first argument
[arg 1]
    /endif
         unbank
  /endmac

;*******************************************************************************
;
;   Macro SETPAGE <address>
;
;   Select the code page state so that a GOTO to the indicated address ends at
;   the right location.
;
setpage  macro   adr
  if ncodepages <= 1         ;only one code page exists, no selection required ?
         exitm
    endif

         pagesel adr
         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 may 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
    if debug_icd
         call    subroutine
      else
         rcall   subroutine  ;use short call within the module
      endif
    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
         call    subroutine  ;use long call
  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 fam_12 || fam_16c5 || fam_16
    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
    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 flags.
;
;   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

;*******************************************************************************
;
;   Macro SETFLAG flag
;
;   Set the flag defined by a /FLAG preprocessor command.
;
/macro setflag
         dbankif gbankadr
         bsf     flag_[arg 1]_reg, flag_[arg 1]_bit
  /endmac

;*******************************************************************************
;
;   Macro CRLFLAG flag
;
;   Clear the flag defined by a /FLAG preprocessor command.
;
/macro clrflag
         dbankif gbankadr
         bcf     flag_[arg 1]_reg, flag_[arg 1]_bit
  /endmac

;*******************************************************************************
;
;   Macro SKIP_FLAG flag
;
;   Skip the next instruction if the flag defined by a /FLAG preprocessor
;   directive is set.
;
/macro skip_flag
         dbankif gbankadr
         btfss   flag_[arg 1]_reg, flag_[arg 1]_bit
  /endmac

;*******************************************************************************
;
;   Macro SKIP_NFLAG flag
;
;   Skip the next instruction if the flag defined by a /FLAG preprocessor
;   directive is clear.
;
/macro skip_nflag
         dbankif gbankadr
         btfsc   flag_[arg 1]_reg, flag_[arg 1]_bit
  /endmac


;*******************************************************************************
;*******************************************************************************
;
;   FIFO (first in, first out) queues.
;
;   This section defines a set of macros and other facilities for creating and
;   using FIFOs.
;
;   Two types of FIFOs are available, old and new.  The original (old) FIFOs
;   were implemented with MPASM macros, requiring configuration information to
;   be passed separately to some macros.  The data structure also did not allow
;   writing and reading from a FIFO simultaneously.  When a FIFO was used in
;   interrupt and foreground code, interrupts had to be disabled around FIFO
;   accesses in the foreground code.
;
;   The new (2021) FIFOs are implemented with pre-processor macros.
;   Configuration state can be kept in known pre-processor constants, so does
;   not need to be passed to subsequent macros to use a FIFO.  Also, the data
;   structure of this type of FIFO allows for simultaneous reading and writing
;   by interrupt and foreground code.  Interrupts do not need to be disabled
;   around FIFO accesses in foreground code.
;
;   For backwards compatibility, the new FIFO type is only selected when the
;   pre-processor constant FIFOS_NEW exists and is set to TRUE before this file
;   is referenced.
;

;*******************************************************************************
;
;   Old FIFO type.
;
/if [not fifos_new] then
;
;   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.
;
;   WARNING
;
;     This current implementation on the PIC 18 family uses the PLUSW addressing
;     mode to access data in the FIFO buffer.  Due to the signed byte offset of
;     that addressing mode and how the buffer indicies are used, the maximum
;     usable FIFO size is 126 bytes.  This is the maximum SIZE parameter that
;     should be passed to FIFO_DEFINE.
;
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_INIT_P0
;
;   Initialize the FIFO pointed to by FSR0.  FSR0 is preserved.
;
fifo_init_p0 macro
         addfsr0 fifo_ofs_n  ;point to N field
         clrf    postinc0
         movlw   1
         addfsr0 fifo_ofs_put - (fifo_ofs_n + 1) ;point to PUT field
         movwf   postinc0
         addfsr0 fifo_ofs_get - (fifo_ofs_put + 1) ;point to GET field
         movwf   postdec0
         addfsr0 0 - (fifo_ofs_get - 1) ;point back to start of FIFO
         endm

;***********************************************************
;
;   Macro FIFO_GETN_P0
;
;   Get the number of bytes in the FIFO pointed to by FSR0 into W.  FSR0 is
;   preserved.
;
fifo_getn_p0 macro
         addfsr0 fifo_ofs_n  ;point to number of bytes in FIFO byte
         movf    indf0, w    ;get the number of bytes in the FIFO
         addfsr0 0 - fifo_ofs_n ;point back to start of FIFO
         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_12 || fam_16 || fam_16b
         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_12 || fam_16 || fam_16b || 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.
;
;   On a PIC 18, DATA may be in any bank.
;
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

;*****
;
;   Enhanced 16 family processors.
;
  if fam_16b
         movlw   low (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#v(fsrsc1)l ;set write pointer low byte
         movlw   high (name + (fifo_ofs_buf - 1))
         skip_ncarr
         addlw   1
         movwf   fsr#v(fsrsc1)h ;set write pointer high byte
         movf    data, w     ;get the data byte into W
         movwf   indf#v(fsrsc1) ;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 0-N 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_PUT_P0 size, data
;
;   Push the byte in DATA onto the FIFO at where FSR0 is pointing.  FSR0 will be
;   preserved.
;
fifo_put_p0 macro size, data

  if fam_18
         addfsr0 fifo_ofs_put ;point to PUT field
         movf    postinc0, w ;get offset into FIFO to write this byte to
         addfsr0 (fifo_ofs_buf - 1) - (fifo_ofs_put + 1) ;point to one before buffer
         movff   data, plusw0 ;stuff the byte into the buffer

         addfsr0 fifo_ofs_n - (fifo_ofs_buf - 1) ;point to N field
         incf    postinc0    ;indicate one more byte now in the FIFO

         addfsr0 fifo_ofs_put - (fifo_ofs_n + 1) ;point to PUT field
         movlw   size        ;get new PUT value for wrapping to end of FIFO
         dcfsnz  indf0       ;update PUT index, skip on not wrap
         movwf   indf0       ;wrap back to end of the buffer
         addfsr0 0 - fifo_ofs_put ;point FSR0 back to start of FIFO structure
         exitm
    endif

         error   "Macro FIFO_PUT_P0 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.
;
;   On a PIC 18, DATA may be in any bank.
;
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

;*****
;
;   Enhanced 16 family processors.
;
  if fam_16b
         movlw   low (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#v(fsrsc1)l ;set read pointer low byte
         movlw   high (name + (fifo_ofs_buf - 1))
         skip_ncarr
         addlw   1
         movwf   fsr#v(fsrsc1)h ;set read pointer high byte
         movf    indf#v(fsrsc1), 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

;***********************************************************
;
;   Macro FIFO_GET_P0 size, data
;
;   Get the next byte from the FIFO at where FSR0 is pointing into DATA.  FSR0
;   will be preserved.
;
fifo_get_p0 macro size, data
;
;   18 family processors.
;
  if fam_18
         addfsr0 fifo_ofs_get ;point to GET field
         movf    indf0, w    ;get the GET offset into W
         addfsr0 (fifo_ofs_buf - 1) - fifo_ofs_get ;point to base for GET offset
         movff   plusw0, data ;fetch the byte from the FIFO into DATA

         addfsr0 fifo_ofs_get - (fifo_ofs_buf - 1) ;point to GET offset
         movlw   size        ;get offset for last byte in buffer
         dcfsnz  indf0       ;update GET index, skip on not wrap
         movwf   indf0       ;wrap back to end of buffer

         addfsr0 fifo_ofs_n - fifo_ofs_get ;point to N field
         decf    indf0       ;count one less byte now in the FIFO

         addfsr0 0 - fifo_ofs_n ;restore FSR0 pointing to start of FIFO
         exitm
    endif

         error   "Macro FIFO_GET not implemented for this processor"
         endm

  /endif                     ;end of old FIFOs selected

;*******************************************************************************
;
;   New FIFO type.
;
/if fifos_new then
;
;   The FIFO macros are briefly listed here.  See their header comments, below,
;   for details.  The FIFO macros are:
;
;     name FIFO_DEFINE size
;
;       Allocates memory for the FIFO.  Leaves configuration info in
;       preprocessor constants derived from NAME for the other macros to use.
;
;     FIFO_INIT name
;
;       Initialize the FIFO at run time.  Will be empty.
;
;     FIFO_N_FULL name [, dest]
;
;       Get the number of data bytes in the FIFO.
;
;     FIFO_N_EMPTY name [, dest]
;
;       Get the number of empty slots in the FIFO.
;
;     FIFO_BR_FULL name, n, label [, temp]
;
;       Jumps to LABEL when the FIFO has N or less bytes of room left.
;
;     FIFO_BR_EMPTY name, n, label [, temp]
;
;       Jumps to LABEL when the FIFO has N or less data bytes in it.
;
;     FIFO_PUT name, data
;
;       Write the byte in DATA to the FIFO.
;
;     FIFO_GET name, data
;
;       Get the next byte from the FIFO into DATA.
;
;   The format of a FIFO in memory is:
;
;     PUT  -  Offset from the start of the buffer to write the next byte to.
;       This is a single byte when the FIFO was configured for 255 bytes or
;       less.  It is two bytes when the FIFO was configured longer.
;
;     GET  -  Offset from the start of the buffer to read the next byte from.
;       This is a single byte when the FIFO was configured for 255 bytes or
;       less.  It is two bytes when the FIFO was configured longer.
;
;     BUF  -  The data buffer.  This is 1 byte longer than the size the FIFO was
;       configured for.
;
;     Bytes are written and read from the buffer in ascending address order.
;     The buffer is circular, so the first byte of BUF immediately follows the
;     last byte of BUF.
;
;     The number of bytes in the FIFO is PUT-GET, after buffer wrapping is
;     accounted for.  The FIFO is full when PUT indicates the byte immediately
;     before that indicated by GET.  The FIFO is empty when PUT=GET.  This means
;     one byte of the buffer will always be unused.  The buffer is made 1 byte
;     larger to compensate.  Note that the alternative is to store the number
;     of bytes in the buffer, which would require additional state.  The method
;     used here is no less efficient of memory, but allows for interrupt and
;     foreground code to simulataneously read and write from/to the FIFO.  There
;     is no need to disabled interrupts in foreground code around FIFO accesses.
;
;   Preprocessor constants are created by FIFO_DEFINE so that configuration
;   information is implicitly available to other macros.  These preprocessor
;   constants are:
;
;     FIFO_name_SIZE, integer
;
;       The maximum number of data bytes the FIFO can hold.
;
;     FIFO_name_SMALL, bool
;
;       TRUE if PUT and GET are a single byte, FALSE if they are 2 bytes.
;
;     FIFO_name_BIG, bool
;
;       The opposite of FIFO_name_SMALL.
;
;     FIFO_name_BUFN, integer
;
;       The size in bytes of BUF.  This is the value that the PUT and GET
;       indicies are wrapped to.
;

;*******************************************************************************
;
;   name FIFO_DEFINE size
;
;   Allocate the memory for a FIFO that can hold up to SIZE data bytes.  SIZE
;   must be resolvable to a value by the preprocessor.
;
  /macro fifo_define
    /var local size integer = [vnl [arg 1]] //SIZE argument
    /var local name string = [qstr [arg -1]] //FIFO name
    /var local memsz integer //total memory size of the FIFO
    /var local s string //for assembling output line

    /if [= [slen name] 0] then
      /show "  Missing FIFO name label preceeding FIFO_DEFINE macro."
         error   "FIFO_DEFINE name"
      /stop
      /endif

    /const fifo_[chars name]_size integer = size //save user-visible FIFO size
    /const fifo_[chars name]_small bool = [<= size 255] //PUT, GET fit in one byte ?
    /const fifo_[chars name]_big bool = [not fifo_[chars name]_small]
    /const fifo_[chars name]_bufn integer = [+ size 1] //memory size of buffer

    /set memsz [+ fifo_[chars name]_bufn 2] //init size assuming small
    /if fifo_[chars name]_big then //actually using big indicies ?
      /set memsz [+ memsz 2] //update total mem needed for big indicies
      /endif

    /set s name //init output line with leading label
    /call tabopcode s
    /append s "res"
    /call taboperand s
    /append s memsz
    /call startcomm s
    /append s "FIFO, " size " max data bytes"
    /write s
    /endmac

;*******************************************************************************
;
;   Macro FIFO_INIT name
;
;   Initialize the FIFO to empty.  The previous state of the FIFO is irrelevant.
;
;   The start of the FIFO must be directly accessible with the current bank
;   setting.
;
  /macro fifo_init
    /var local name string = [qstr [arg 1]] ;FIFO name
    /var local size integer = fifo_[chars name]_size ;user-visible FIFO size
    /var local big bool = fifo_[chars name]_big ;PUT and GET are two bytes
    /var local bufn integer = fifo_[chars name]_bufn ;size of the data buffer
    /var local s string      ;for assembling output line
    /var local nint integer  ;number of bytes to initialize

    /write
    /set nint [if big 4 2]   ;get number of bytes to initialize
    /loop with ii from 0 n nint ;once for each byte
      /set s ""              ;clrf name+0
      /call tabopcode s
      /append s "clrf"
      /call taboperand s
      /append s name "+" ii
      /if [= ii 0] then      ;first CLRF instruction ?
        /call startcomm s
        /append s "initialize the FIFO " [ucase name] " to empty"
        /endif
      /write s
      /endloop
    /write
    /endmac

;*******************************************************************************
;
;   Macro FIFO_N_FULL name [, dest]
;
;   Get the number of data bytes in the FIFO NAME.  Without the DEST parameter,
;   the value is left in W.  In that case, the FIFO size must be 255 bytes or
;   less.
;
;   DEST is the memory where to write the result.  DEST is assumed to be two
;   bytes in size when the FIFO was configured for more than 255 bytes.  In that
;   case, the low byte of the result is written to DEST+0, and the high byte to
;   DEST+1.
;
;   The start of the FIFO and DEST (if used) must be directly accessible with
;   the current bank setting.
;
  /macro fifo_n_full
    /var local name string = [qstr [arg 1]] ;FIFO name
    /var local dest string = [qstr [arg 2]] ;destination variable
    /var local size integer = fifo_[chars name]_size ;user-visible FIFO size
    /var local big bool = fifo_[chars name]_big ;PUT and GET are two bytes
    /var local bufn integer = fifo_[chars name]_bufn ;size of the data buffer
    /var local s string      ;for assembling output line
    /var local dvar bool = [> [slen dest] 0] ;destination is variable, not W

    /if [and [> size 255] [not dvar]] then
      /show "  FIFO too large for FIFO_N_FULL macro without <dest>."
         error   "No dest"
         end
      /stop
      /endif

    /write
//
//   Handle case of the result is a single byte.
//
    /if [not big] then

      /set s ""              ;movf name+1, w
      /call tabopcode s
      /append s "movf"
      /call taboperand s
      /append s name "+1, w"
      /call startcomm s
      /append s "get FIFO GET index"
      /write s

      /set s ""              ;subwf name, w
      /call tabopcode s
      /append s "subwf"
      /call taboperand s
      /append s name "+0, w"
      /call startcomm s
      /append s "make PUT-GET, N bytes in FIFO without wrap"
      /write s

      /if [<> bufn 256] then
         skip_wle            ;no buffer wrapping ?

        /set s ""            ;addlw bufn
        /call tabopcode s
        /append s "addlw"
        /call taboperand s
        /append s bufn
        /call startcomm s
        /append s "account for buffer wrapping"
        /write s

        /endif

      /if dvar
        /then                ;write result to DEST
          /set s ""          ;movwf dest
          /call tabopcode s
          /append s "movwf"
          /call taboperand s
          /append s dest
          /call startcomm s
          /append s "number of data bytes in FIFO " name
          /write s
        /else                ;leave the result in W
          /write "                             ;W is number of data bytes in FIFO " name
        /endif
      /write
      /quitmac
      /endif                 ;end of single-byte result case
//
//   The result is two bytes, and is to be stored in DEST.
//

    //   Make DEST <-- PUT - GET
    //
    /set s ""                ;movf get+0, w
    /call tabopcode s
    /append s "movf"
    /call taboperand s
    /append s name "+2, w"
    /call startcomm s
    /append s "make PUT-GET low byte"
    /write s

    /set s ""                ;subwf put+0, w
    /call tabopcode s
    /append s "subwf"
    /call taboperand s
    /append s name "+0, w"
    /write s

    /set s ""                ;movwf dest+0
    /call tabopcode s
    /append s "movwf"
    /call taboperand s
    /append s dest "+0"
    /write s

    /set s ""                ;movf get+1, w
    /call tabopcode s
    /append s "movf"
    /call taboperand s
    /append s name "+3, w"
    /call startcomm s
    /append s "make PUT-GET high byte"
    /write s

    /set s ""                ;subwfb put+1, w
    /call tabopcode s
    /append s "subwfb"
    /call taboperand s
    /append s name "+1, w"
    /write s

    /set s ""                ;movwf dest+1
    /call tabopcode s
    /append s "movwf"
    /call taboperand s
    /append s dest "+1"
    /write s

    /set s ""                ;jmp_wle done
    /call tabopcode s
    /append s "jmp_wle"
    /call taboperand s
    /append s [qstr [lab fifn]]
    /call startcomm s
    /append s "no buffer wrap-around ?"
    /write s

    //   DEST constains PUT-GET, but there is a buffer wrap between PUT and GET.
    //   Add the buffer wrap size to make the actual distance from GET to PUT.
    //
    /set s ""                ;movlw low bufn
    /call tabopcode s
    /append s "movlw"
    /call taboperand s
    /append s [and bufn 16#FF]
    /call startcomm s
    /append s "add buffer length into low byte"
    /write s

    /set s ""                ;addwf dest+0
    /call tabopcode s
    /append s "addwf"
    /call taboperand s
    /append s dest "+0"
    /write s

    /set s ""                ;movlw high bufn
    /call tabopcode s
    /append s "movlw"
    /call taboperand s
    /append s [shiftr bufn 8]
    /call startcomm s
    /append s "add buffer length to high byte"
    /write s

    /set s ""                ;addwfc dest+1
    /call tabopcode s
    /append s "addwfc"
    /call taboperand s
    /append s dest "+1"
    /write s

    /set s [qstr [lab fifn]] ;final result in DEST
    /call startcomm s
    /append s "number of bytes in FIFO " name " is in 16 bit var " [ucase dest]
    /write s

    /endmac

;*******************************************************************************
;
;   Macro FIFO_N_EMPTY name [, dest]
;
;   Get the number of empty slots in the FIFO NAME.  Without the DEST parameter,
;   the value is left in W.  In that case, the FIFO size must be 255 bytes or
;   less.
;
;   DEST is the memory where to write the result.  DEST is assumed to be two
;   bytes in size when the FIFO was configured for more than 255 bytes.  In that
;   case, the low byte of the result is written to DEST+0, and the high byte to
;   DEST+1.
;
;   The start of the FIFO and DEST (if used) must be directly accessible with
;   the current bank setting.
;
  /macro fifo_n_empty
    /var local name string = [qstr [arg 1]] ;FIFO name
    /var local dest string = [qstr [arg 2]] ;destination variable
    /var local size integer = fifo_[chars name]_size ;user-visible FIFO size
    /var local big bool = fifo_[chars name]_big ;PUT and GET are two bytes
    /var local bufn integer = fifo_[chars name]_bufn ;size of the data buffer
    /var local s string      ;for assembling output line
    /var local dvar bool = [> [slen dest] 0] ;destination is variable, not W

    /if [and [> size 255] [not dvar]] then
      /show "  FIFO too large for FIFO_N_EMPTY macro without <dest>."
         error   "No dest"
         end
      /stop
      /endif

    /write
//
//   Handle case of the result is a single byte.
//
    /if [not big] then

      /set s ""              ;movf name+0, w
      /call tabopcode s
      /append s "movf"
      /call taboperand s
      /append s name "+0, w"
      /call startcomm s
      /append s "get FIFO PUT index + 1"
      /write s

      /set s ""              ;addlw 1
      /call tabopcode s
      /append s "addlw"
      /call taboperand s
      /append s 1
      /write s

      /set s ""              ;subwf name+1, w
      /call tabopcode s
      /append s "subwf"
      /call taboperand s
      /append s name "+1, w"
      /call startcomm s
      /append s "make empty slots without accounting for wrapping"
      /write s

      /if [<> bufn 256] then
         skip_wle            ;no buffer wrapping ?

        /set s ""            ;addlw bufn
        /call tabopcode s
        /append s "addlw"
        /call taboperand s
        /append s bufn
        /call startcomm s
        /append s "account for buffer wrapping"
        /write s

        /endif

      /if dvar
        /then                ;write result to DEST
          /set s ""          ;movwf dest
          /call tabopcode s
          /append s "movwf"
          /call taboperand s
          /append s dest
          /call startcomm s
          /append s "number of empty slots in FIFO " name
          /write s
        /else                ;leave the result in W
          /write "                             ;W is number of empty slots in FIFO " name
        /endif
      /write
      /quitmac
      /endif                 ;end of single-byte result case
//
//   The result is two bytes, and is to be stored in DEST.
//

    //   Make DEST <-- GET - PUT - 1
    //
    /set s ""                ;movf put+0, w
    /call tabopcode s
    /append s "movf"
    /call taboperand s
    /append s name "+0, w"
    /call startcomm s
    /append s "make GET-PUT low byte"
    /write s

    /set s ""                ;subwf get+0, w
    /call tabopcode s
    /append s "subwf"
    /call taboperand s
    /append s name "+2, w"
    /write s

    /set s ""                ;movwf dest+0
    /call tabopcode s
    /append s "movwf"
    /call taboperand s
    /append s dest "+0"
    /write s

    /set s ""                ;movf put+1, w
    /call tabopcode s
    /append s "movf"
    /call taboperand s
    /append s name "+1, w"
    /call startcomm s
    /append s "make GET-PUT high byte"
    /write s

    /set s ""                ;subwfb get+1, w
    /call tabopcode s
    /append s "subwfb"
    /call taboperand s
    /append s name "+3, w"
    /write s

    /set s ""                ;movwf dest+1
    /call tabopcode s
    /append s "movwf"
    /call taboperand s
    /append s dest "+1"
    /write s

    /set s ""                ;decf dest+0
    /call tabopcode s
    /append s "decf"
    /call taboperand s
    /append s dest "+0"
    /call startcomm s
    /append s "-1 to make empty slots, without wrapping"
    /write s

    /set s ""                ;movlw 0
    /call tabopcode s
    /append s "movlw"
    /call taboperand s
    /append s "0"
    /write s

    /set s ""                ;subwfb dest+1
    /call tabopcode s
    /append s "subwfb"
    /call taboperand s
    /append s dest "+1"
    /write s
    //
    //   DEST contains the number of empty slots without buffer wrapping taken
    //   into account.
    //
    /set s ""                ;bnn fifn
    /call tabopcode s
    /append s "bnn"
    /call taboperand s
    /append s [qstr [lab fifn]]
    /call startcomm s
    /append s "no buffer wrap to account for ?"
    /write s
    //
    //   DEST contains GET-PUT-1, but there is a buffer wrap between PUT and
    //   GET.  Add the buffer wrap size to make the actual number of empty
    //   slots in the FIFO.
    //
    /set s ""                ;movlw low bufn
    /call tabopcode s
    /append s "movlw"
    /call taboperand s
    /append s [and bufn 16#FF]
    /call startcomm s
    /append s "add buffer length into low byte"
    /write s

    /set s ""                ;addwf dest+0
    /call tabopcode s
    /append s "addwf"
    /call taboperand s
    /append s dest "+0"
    /write s

    /set s ""                ;movlw high bufn
    /call tabopcode s
    /append s "movlw"
    /call taboperand s
    /append s [shiftr bufn 8]
    /call startcomm s
    /append s "add buffer length to high byte"
    /write s

    /set s ""                ;addwfc dest+1
    /call tabopcode s
    /append s "addwfc"
    /call taboperand s
    /append s dest "+1"
    /write s

    /set s [qstr [lab fifn]] ;final result in DEST
    /call startcomm s
    /append s "empty slots in FIFO " name " is in 16 bit var " [ucase dest]
    /write s

    /endmac

;*******************************************************************************
;
;   Macro FIFO_BR_FULL name, n, label [, temp]
;
;   Branch to LABEL when the FIFO NAME has N or less bytes of room left.  TEMP
;   is temporary memory for the macro to use internally.  It is required when
;   the FIFO size exceeds 255 slots.  Up to two bytes at TEMP may be trashed.
;
;   The start of the FIFO and TEMP (if used) must be directly accessible with
;   the current bank setting.
;
  /macro fifo_br_full
    /var local name string = [qstr [arg 1]] ;FIFO name
    /var local n integer = [vnl [arg 2]] ;full if this many or less empty slots
    /var local lab string = [qstr [arg 3]] ;label to jump to on FIFO full
    /var local temp string = [qstr [arg 4]] ;start of temporary scratch vars
    /var local size integer = fifo_[chars name]_size ;user-visible FIFO size
    /var local big bool = fifo_[chars name]_big ;PUT and GET are two bytes
    /var local bufn integer = fifo_[chars name]_bufn ;size of the data buffer
    /var local s string      ;for assembling output line
//
//   Handle case where the number of empty FIFO slots fits into one unsigned
//   byte.
//
    /if [not big] then
         fifo_n_empty [chars name] ;get FIFO room into W

      /set s ""              ;sublw n
      /call tabopcode s
      /append s "sublw"
      /call taboperand s
      /append s n
      /call startcomm s
      /append s "compare to threshold"
      /write s

      /set s ""              ;jmp_wle lab
      /call tabopcode s
      /append s "jmp_wle"
      /call taboperand s
      /append s lab
      /call startcomm s
      /append s "too little room in the FIFO ?"
      /write s

      /write
      /quitmac
      /endif
//
//   Two bytes are required to express the number of FIFO slots.
//
    /if [= [slen temp] 0] then
      /show "  TEMP parameter to FIFO_BR_FULL required but not supplied."
         error   "No TEMP"
         end
      /stop
      /endif

         fifo_n_empty [chars name], [chars temp] ;get FIFO room into TEMP

    /set s ""                ;movlw low n+1
    /call tabopcode s
    /append s "movlw"
    /call taboperand s
    /append s [and [+ n 1] 16#FF]
    /call startcomm s
    /append s "compare to threshold low byte"
    /write s

    /set s ""                ;subwf temp+0
    /call tabopcode s
    /append s "subwf"
    /call taboperand s
    /append s temp "+0"
    /write s

    /set s ""                ;movlw high n+1
    /call tabopcode s
    /append s "movlw"
    /call taboperand s
    /append s [shiftr [+ n 1] 8]
    /call startcomm s
    /append s "compare to threshold high byte"
    /write s

    /set s ""                ;subwfb temp+1
    /call tabopcode s
    /append s "subwfb"
    /call taboperand s
    /append s temp "+1"
    /write s

    /set s ""                ;bn lab
    /call tabopcode s
    /append s "bn"
    /call taboperand s
    /append s lab
    /call startcomm s
    /append s "too little room in FIFO ?"
    /write s

    /write
    /endmac

;*******************************************************************************
;
;   Macro FIFO_BR_EMPTY name, n, label [, temp]
;
;   Branch to LABEL when the FIFO NAME has N or less data bytes in it.  TEMP is
;   temporary memory for the macro to use internally.  It is required when the
;   FIFO size exceeds 255 slots.  Up to two bytes at TEMP may be trashed.
;
;   The start of the FIFO and TEMP (if used) must be directly accessible with
;   the current bank setting.
;
  /macro fifo_br_empty
    /var local name string = [qstr [arg 1]] ;FIFO name
    /var local n integer = [vnl [arg 2]] ;empty if this many data bytes or less
    /var local lab string = [qstr [arg 3]] ;label to jump to on FIFO empty
    /var local temp string = [qstr [arg 4]] ;start of temporary scratch vars
    /var local size integer = fifo_[chars name]_size ;user-visible FIFO size
    /var local big bool = fifo_[chars name]_big ;PUT and GET are two bytes
    /var local bufn integer = fifo_[chars name]_bufn ;size of the data buffer
    /var local s string      ;for assembling output line
//
//   Handle case where the number of data bytes in the FIFO fits into one
//   unsigned byte.
//
    /if [not big] then
         fifo_n_full [chars name] ;get the number of data bytes into W

      /set s ""              ;sublw n
      /call tabopcode s
      /append s "sublw"
      /call taboperand s
      /append s n
      /call startcomm s
      /append s "compare to threshold"
      /write s

      /set s ""              ;jmp_wle lab
      /call tabopcode s
      /append s "jmp_wle"
      /call taboperand s
      /append s lab
      /call startcomm s
      /append s "too few bytes in the FIFO ?"
      /write s

      /write
      /quitmac
      /endif
//
//   Two bytes are required to express the number of bytes in the FIFO.
//
    /if [= [slen temp] 0] then
      /show "  TEMP parameter to FIFO_BR_EMPTY required but not supplied."
         error   "No TEMP"
         end
      /stop
      /endif

         fifo_n_full [chars name], [chars temp] ;get number of data bytes into TEMP

    /set s ""                ;movlw low n+1
    /call tabopcode s
    /append s "movlw"
    /call taboperand s
    /append s [and [+ n 1] 16#FF]
    /call startcomm s
    /append s "compare to threshold low byte"
    /write s

    /set s ""                ;subwf temp+0
    /call tabopcode s
    /append s "subwf"
    /call taboperand s
    /append s temp "+0"
    /write s

    /set s ""                ;movlw high n+1
    /call tabopcode s
    /append s "movlw"
    /call taboperand s
    /append s [shiftr [+ n 1] 8]
    /call startcomm s
    /append s "compare to threshold high byte"
    /write s

    /set s ""                ;subwfb temp+1
    /call tabopcode s
    /append s "subwfb"
    /call taboperand s
    /append s temp "+1"
    /write s

    /set s ""                ;bn lab
    /call tabopcode s
    /append s "bn"
    /call taboperand s
    /append s lab
    /call startcomm s
    /append s "too few bytes in the FIFO ?"
    /write s

    /write
    /endmac

;*******************************************************************************
;
;   Internal macro FIFO_INDEX_INC name, ofs
;
;   Increment a buffer index for FIFO NAME.  OFS is the offset of the first byte
;   of the index from the start of the FIFO data structure.  The index will be
;   incremented by 1, except if that would cause it to point past the end of the
;   buffer.  In that case, the index is wrapped back to 0.
;
;   Interrupts are temporarily disabled during the write to any multi-byte
;   index.
;
;   This macro is not intended for application use.  It is used internally in
;   other FIFO_xxx macros.
;
  /macro fifo_index_inc
    /var local name string = [qstr [arg 1]] ;FIFO name
    /var local ofs integer = [vnl [arg 2]] ;offset of the index from FIFO start
    /var local size integer = fifo_[chars name]_size ;user-visible FIFO size
    /var local big bool = fifo_[chars name]_big ;PUT and GET are two bytes
    /var local bufn integer = fifo_[chars name]_bufn ;size of the data buffer
    /var local s string      ;for assembling output line
//
//   Handle case of index is one byte.
//
    /if [not big] then
      /set s ""              ;incf name+ofs, w
      /call tabopcode s
      /append s "incf"
      /call taboperand s
      /append s name "+" ofs ", w"
      /call startcomm s
      /append s "make candidate incremented index in W"
      /write s

      /set s ""              ;sublw bufn-1
      /call tabopcode s
      /append s "sublw"
      /call taboperand s
      /append s [- bufn 1]
      /call startcomm s
      /append s "compare to last valid buffer index"
      /write s

      /set s ""              ;jmp_wle nwrap
      /call tabopcode s
      /append s "jmp_wle"
      /call taboperand s
      /append s [qstr [lab nwrap]]
      /call startcomm s
      /append s "still valid, don't wrap back to start"
      /write s

      /set s ""              ;clrf name+ofs
      /call tabopcode s
      /append s "clrf"
      /call taboperand s
      /append s name "+" ofs
      /call startcomm s
      /append s "wrap index back to start of buffer"
      /write s

      /set s ""              ;jump done
      /call tabopcode s
      /append s "jump"
      /call taboperand s
      /append s [qstr [lab done]]
      /write s

      /set s [qstr [lab nwrap]] ;nwrap:
      /call startcomm s
      /append s "don't wrap back to start of buffer"
      /write s

      /set s ""              ;incf name+ofs
      /call tabopcode s
      /append s "incf"
      /call taboperand s
      /append s name "+" ofs
      /call startcomm s
      /append s "update the buffer index"
      /write s

      /set s [qstr [lab done]] ;done:
      /call startcomm s
      /append s "done updating FIFO buffer index"
      /write s

      /quitmac
      /endif
//
//   The index is two bytes wide.
//
    //
    //   Compare the index to the last value that does not require wrapping.
    //
    /set s ""                ;movlw low bufn-1
    /call tabopcode s
    /append s "movlw"
    /call taboperand s
    /append s [and [- bufn 1] 16#FF]
    /call startcomm s
    /append s "compare to wrap level low byte"
    /write s

    /set s ""                ;subwf name+ofs+0, w
    /call tabopcode s
    /append s "subwf"
    /call taboperand s
    /append s name "+" ofs ", w"
    /write s

    /set s ""                ;movlw high bufn-1
    /call tabopcode s
    /append s "movlw"
    /call taboperand s
    /append s [shiftr [- bufn 1] 8]
    /call startcomm s
    /append s "compare to wrap level high byte"
    /write s

    /set s ""                ;subwfb name+ofs+1, w
    /call tabopcode s
    /append s "subwfb"
    /call taboperand s
    /append s name "+" [+ ofs 1] ", w"
    /write s

    /set s ""                ;jmp_wle wrap
    /call tabopcode s
    /append s "jmp_wle"
    /call taboperand s
    /append s [qstr [lab wrap]]
    /call startcomm s
    /append s "need to wrap back to start of buffer ?"
    /write s
    //
    //   Increment the index normally without wrapping.  The increment is done
    //   so that the two index bytes are changed on consecutive instructions.
    //   This minimizes the time that interrupts are disabled for.
    //
    /set s ""                ;incf name+ofs+0, w
    /call tabopcode s
    /append s "incf"
    /call taboperand s
    /append s name "+" ofs ", w"
    /call startcomm s
    /append s "set C bit from incrementing low byte"
    /write s

    /set s ""                ;movlw 0
    /call tabopcode s
    /append s "movlw"
    /call taboperand s
    /append s "0"
    /write s

    /if [not no_intr_disable] then
         intr_off            ;temp disable interrupts
      /endif

    /set s ""                ;addwfc name+ofs+1
    /call tabopcode s
    /append s "addwfc"
    /call taboperand s
    /append s name "+" [+ ofs 1]
    /call startcomm s
    /append s "propagate carry into high byte"
    /write s

    /set s ""                ;incf name+ofs+0
    /call tabopcode s
    /append s "incf"
    /call taboperand s
    /append s name "+" ofs
    /call startcomm s
    /append s "update the low byte"
    /write s

    /if [not no_intr_disable] then
         intr_on             ;re-enable interrupts
      /endif

    /set s ""                ;jump done
    /call tabopcode s
    /append s "jump"
    /call taboperand s
    /append s [qstr [lab done]]
    /write s
    //
    //   Wrap the index back to the start of the buffer.
    //
    /set s [qstr [lab wrap]] ;wrap:
    /call startcomm s
    /append s "wrap buffer index back to start of buffer"
    /write s

    /if [not no_intr_disable] then
         intr_off            ;temp disable interrupts
      /endif

    /set s ""                ;clrf name+ofs+0
    /call tabopcode s
    /append s "clrf"
    /call taboperand s
    /append s name "+" ofs
    /write s

    /set s ""                ;clrf name+ofs+1
    /call tabopcode s
    /append s "clrf"
    /call taboperand s
    /append s name "+" [+ ofs 1]
    /write s

    /if [not no_intr_disable] then
         intr_on             ;re-enable interrupts
      /endif
    //
    //   Back to common code after no-wrap and wrap cases.
    //
    /set s [qstr [lab done]] ;done:
    /call startcomm s
    /append s "done updating FIFO buffer index"
    /write s
    /endmac

;*******************************************************************************
;
;   Macro FIFO_PUT name, data
;
;   Write the byte in DATA to the FIFO NAME.  It is the caller's responsibility
;   to ensure that the FIFO has room for the new byte.  All manner of
;   destruction can result from writing to a full FIFO.
;
;   The bank must be set for access to the start of the FIFO.  DATA can be
;   anywhere in RAM.
;
;   FSR0 is trashed.
;
  /macro fifo_put
    /var local name string = [qstr [arg 1]] ;FIFO name
    /var local data string = [qstr [arg 2]] ;variable to take the data byte from
    /var local size integer = fifo_[chars name]_size ;user-visible FIFO size
    /var local big bool = fifo_[chars name]_big ;PUT and GET are two bytes
    /var local bufn integer = fifo_[chars name]_bufn ;size of the data buffer
    /var local s string      ;for assembling output line

    /write
//
//   Handle case where 8 bit indicies are used.
//
    //   Write the data byte into the buffer at the slot indexed by PUT.
    //
    /if [not big] then
      /set s ""              ;lfsr 0, buf
      /call tabopcode s
      /append s "lfsr"
      /call taboperand s
      /append s "0, " name "+2"
      /call startcomm s
      /append s "point to start of FIFO data buffer"
      /write s

      /set s ""              ;movf put, w
      /call tabopcode s
      /append s "movf"
      /call taboperand s
      /append s name "+0, w"
      /call startcomm s
      /append s "add buffer PUT index to the pointer"
      /write s

      /set s ""              ;addwf fsr0l
      /call tabopcode s
      /append s "addwf"
      /call taboperand s
      /append s "fsr0l"
      /write s

      /set s ""              ;movlw 0
      /call tabopcode s
      /append s "movlw"
      /call taboperand s
      /append s "0"
      /write s

      /set s ""              ;addwfc fsr0h
      /call tabopcode s
      /append s "addwfc"
      /call taboperand s
      /append s "fsr0h"
      /write s

      /set s ""              ;movff data, indf0
      /call tabopcode s
      /append s "movff"
      /call taboperand s
      /append s data ", indf0"
      /call startcomm s
      /append s "stuff the data byte into the buffer"
      /write s

         fifo_index_inc [chars name], 0 ;update the buffer PUT index

      /write
      /quitmac
      /endif
//
//   The buffer indicies are 16 bits wide.
//
    /set s ""                ;lfsr 0, buf
    /call tabopcode s
    /append s "lfsr"
    /call taboperand s
    /append s "0, " name "+4"
    /call startcomm s
    /append s "init pointer to start of data buffer"
    /write s

    /set s ""                ;movf put+0, w
    /call tabopcode s
    /append s "movf"
    /call taboperand s
    /append s name "+0, w"
    /call startcomm s
    /append s "add buffer PUT index to the pointer"
    /write s

    /set s ""                ;addwf fsr0l
    /call tabopcode s
    /append s "addwf"
    /call taboperand s
    /append s "fsr0l"
    /write s

    /set s ""                ;movf put+1, w
    /call tabopcode s
    /append s "movf"
    /call taboperand s
    /append s name "+1, w"
    /write s

    /set s ""                ;addwfc fsr0h
    /call tabopcode s
    /append s "addwfc"
    /call taboperand s
    /append s "fsr0h"
    /write s

    /set s ""                ;movff data, indf0
    /call tabopcode s
    /append s "movff"
    /call taboperand s
    /append s data ", indf0"
    /call startcomm s
    /append s "stuff the data byte into the buffer"
    /write s

         fifo_index_inc [chars name], 0 ;update the buffer PUT index
    /write
    /endmac

;*******************************************************************************
;
;   Macro FIFO_GET name, data
;
;   Get the next byte from the FIFO NAME into DATA.  It is the caller's
;   responsibility to ensure that the FIFO is not empty.  All manner of
;   destruction can result from reading from an empty FIFO.
;
;   The bank must be set for access to the start of the FIFO.  DATA can be
;   anywhere in RAM.
;
;   FSR0 is trashed.
;
  /macro fifo_get
    /var local name string = [qstr [arg 1]] ;FIFO name
    /var local data string = [qstr [arg 2]] ;variable to take the data byte from
    /var local size integer = fifo_[chars name]_size ;user-visible FIFO size
    /var local big bool = fifo_[chars name]_big ;PUT and GET are two bytes
    /var local bufn integer = fifo_[chars name]_bufn ;size of the data buffer
    /var local s string      ;for assembling output line

    /write
//
//   Handle case where 8 bit indicies are used.
//
    //   Read the from the FIFO slot indexed by GET.
    //
    /if [not big] then
      /set s ""              ;lfsr 0, buf
      /call tabopcode s
      /append s "lfsr"
      /call taboperand s
      /append s "0, " name "+2"
      /call startcomm s
      /append s "point to start of FIFO data buffer"
      /write s

      /set s ""              ;movf get, w
      /call tabopcode s
      /append s "movf"
      /call taboperand s
      /append s name "+1, w"
      /call startcomm s
      /append s "add buffer GET index to the pointer"
      /write s

      /set s ""              ;addwf fsr0l
      /call tabopcode s
      /append s "addwf"
      /call taboperand s
      /append s "fsr0l"
      /write s

      /set s ""              ;movlw 0
      /call tabopcode s
      /append s "movlw"
      /call taboperand s
      /append s "0"
      /write s

      /set s ""              ;addwfc fsr0h
      /call tabopcode s
      /append s "addwfc"
      /call taboperand s
      /append s "fsr0h"
      /write s

      /set s ""              ;movff indf0, data
      /call tabopcode s
      /append s "movff"
      /call taboperand s
      /append s "indf0, " data
      /call startcomm s
      /append s "read the byte from the FIFO buffer"
      /write s

         fifo_index_inc [chars name], 1 ;update the buffer GET index

      /write
      /quitmac
      /endif
//
//   The buffer indicies are 16 bits wide.
//
    /set s ""                ;lfsr 0, buf
    /call tabopcode s
    /append s "lfsr"
    /call taboperand s
    /append s "0, " name "+4"
    /call startcomm s
    /append s "init pointer to start of data buffer"
    /write s

    /set s ""                ;movf get+0, w
    /call tabopcode s
    /append s "movf"
    /call taboperand s
    /append s name "+2, w"
    /call startcomm s
    /append s "add buffer GET index to the pointer"
    /write s

    /set s ""                ;addwf fsr0l
    /call tabopcode s
    /append s "addwf"
    /call taboperand s
    /append s "fsr0l"
    /write s

    /set s ""                ;movf get+1, w
    /call tabopcode s
    /append s "movf"
    /call taboperand s
    /append s name "+3, w"
    /write s

    /set s ""                ;addwfc fsr0h
    /call tabopcode s
    /append s "addwfc"
    /call taboperand s
    /append s "fsr0h"
    /write s

    /set s ""                ;movff indf0, data
    /call tabopcode s
    /append s "movff"
    /call taboperand s
    /append s "indf0, " data
    /call startcomm s
    /append s "read the byte from the FIFO buffer"
    /write s

         fifo_index_inc [chars name], 2 ;update the buffer GET index
    /write
    /endmac

  /endif                     ;end of new FIFOs selected


;*******************************************************************************
;*******************************************************************************
;
;   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 the 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, fall 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_EXTRN index, adr
;
;   Like DSP_ENTRY, except that ADR is external to the module this macro is in.
;
dsp_extrn macro  index, adr
         extern  adr
         dsp_entry index, adr
         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

////////////////////////////////////////////////////////////////////////////////
//
//   Function CmdRef ent
//
//   Returns TRUE iff the entry point ENT is referenced as a command routine.
//   The ENT parameter is a sequence of characters, not a string.
//
//   Commands are defined by variables or constants named "cmd_xxx".  A
//   additional name can be inserted after "cmd_".  This is necessary, for
//   example, when there are multiple command processors with different command
//   sets.  The constant or variable SUFF denotes this additional part of the
//   CMD_ symbol names, if present and not set to the empty string.  In that
//   case, the variable or constants defining commands are named "cmd<suff>_xxx"
//   where <suff> indicates the contents of the SUFF variable or constant.
//
/function CmdRef
  /var local ent string = [qstr [arg 1]] //entry point name checking for
  /var local opc integer //command opcode
  /var local entpnt string //command routine entry point name
  /var local retval bool //function return value
  /var local ii integer //scratch integer
  /var local tk string //scratch token
  /if [not [exist "suff:vcon"]] then
    /var local suff string
    /endif

  /set tk [str "cmd" suff "_"] //starting fixed part of CMD_xxx names
  /set ii [slen tk] //length of fixed part of CMD_xxx names
  /loop symbols sym vcon
    /var local sy string
    /set sy [sym sym name]
    /if [<= [slen sy] ii] then
      /repeat
      /endif
    /if [<> [substr 1 ii sy] tk] then
      /repeat
      /endif
    /call parse_cmd [chars sym] [chars suff] //get entry point of this command
    /if [= entpnt ent] then
      /set retval True
      /quit
      /endif
    /endloop

  /funcval retval
  /endfunc

////////////////////////////////////////////////////////////////////////////////
//
//   Function Command entpnt
//
//   Returns TRUE if the entry point ENTPNT is referenced as a command routine,
//   otherwise FALSE.  When returning TRUE, the entry point of the command is
//   defined.  ENTPNT is a sequence of characters, not a string.
//
/function Command
  /if [CmdRef [arg 1]]
    /then
      /funcval TRUE
         glbent  [arg 1]
    /else
      /funcval FALSE
    /endif
  /endfunc

;*******************************************************************************
;*******************************************************************************
;
;   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

;*******************************************************************************
;
;   Macro SKIP_NYIELDNOW
;
;   Skip the next instruction if no condition exists such that the current task
;   in this multi-tasking system should yield now.  This macro is used by
;   CHECK_YIELD (below) to determine whether a yield must be performed now.
;   SKIP_NYIELDNOW is a separate preprocessor macro so that it can be easily
;   customized.
;
;   The default version defined here assumes the existance of the global
;   FLAG_YIELDNOW.  A yield must be performed when this flag is set.  This flag
;   would typically be set by the interrupt routine based on elapsed time or a
;   particular hardware event.
;
/macro skip_nyieldnow
  /if [exist -1 arg] then
    /show "  Dumb place for a label, moron."
         error   SKIP_NYIELDNOW label
         end
    /stop
    /endif

         extern_flags        ;declare global flag bits EXTERN
         dbankif gbankadr
         btfsc   flag_yieldnow
  /endmac

;*******************************************************************************
;
;   Macro CHECK_YIELD
;
;   Conditionally perform a task yield.  This check is short and fast, and can
;   therefore be performed often with little penalty.  For example, this check
;   can be performed each iteration of a inner loop that could possibly take
;   longer than a single task should run.  Calling TASK_YIELD would make the
;   loop much slower due to many unnecessary task swaps.
;
;   The existance of a condition that is set asynchronously to the current task
;   is assumed.  This could be set by the interrupt routine based on elapsed
;   time, or a hardware condition, for example.  The check for the condition is
;   isolated in macro SKIP_NYIELDNOW (above) to allow for easy customization of
;   the condition.  The default condition is FLAG_YIELDNOW set.
;
;   All the REGn general registers are preserved.  This requires enough data
;   stack space for all the general registers plus the little overhead required
;   by TASK_YIELD.
;
;   This is a preprocessor macro so that it can easily be redefined as needed.
;   The decision as to whether a yield is required now is abstracted into the
;   separate macro SKIP_NYIELDNOW (above), since most customization only use a
;   different condition and don't need to change the mechanics as encoded in
;   this macro.
;
/macro check_yield
  /if [exist -1 arg] then
    /show "  Dumb place for a label, moron."
         error   CHECK_YIELD label
         end
    /stop
    /endif

         extern  task_yield_save
         skip_nyieldnow      ;don't need to yield now ?
         call    task_yield_save ;perform the yield, perserve all registers
         unbank
  /endmac

;*******************************************************************************
;
;   Macro LEAVECHECK
;
;   Like LEAVEREST except that it runs CHECK_YIELD after restoring the registers
;   saved on entry to the subroutine.  If CHECK_YIELD does perform a yield, all
;   the registers will be saved on the stack.  It therefore takes less stack
;   space to perform the yield after removing anything this subroutine
;   temporarily pushed onto the stack.
;
/macro leavecheck
  /if [exist -1 arg] then
    /show "  Dumb place for a label, moron."
         error   LEAVECHECK label
         end
    /stop
    /endif

         popregs savedregs   ;restore registers saved on entry to routine
         check_yield         ;let other tasks run if needed
         return              ;return from the subroutine
         unbank              ;invalidate bank assumptions in following source code
  /endmac


;*******************************************************************************
;*******************************************************************************
;
;   Support for writing constants to program memory.
;

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine PBYTE_INIT
//
//   Initialize for writing a block of data to a CODE_PACK section of program
//   memory.  The state is set to these initial values to this at the beginning
//   of normal code and after PBYTE_FINISH.  This subroutine provides a positive
//   mechanism to reset to the initialized state at any time.
//
/subroutine pbyte_init
  /var exist pbyte_str string ;output line so far
  /var exist pbyte_nbytes integer ;number of data bytes on the current line
  /var exist pbyte_full bool ;output line is full
  /var exist pbyte_offset integer ;number of bytes written
  /var exist pbyte_global bool ;make labels global
  /var exist pbyte_memlab bool ;write labels as memory addresses
  /var exist pbyte_saveofs bool ;save label names and their offsets

  /set pbyte_str ""          ;reset the current output line to empty
  /set pbyte_nbytes 0
  /set pbyte_full false
  /set pbyte_offset 0        ;reset number of bytes written to prog memory
  /set pbyte_global false    ;don't make labels global
  /set pbyte_memlab true     ;label values will be the memory address
  /set pbyte_saveofs false   ;don't save label names and offsets

  /endsub

;*******************************************************************************
;
;   Macro [label] PBYTE val [, comment]
;
;   Write to the next program memory byte in a CODE_PACK section.  Multiple
;   bytes are written on a single MPASM output line.  This macro may buffer data
;   until the PBYTE_FINISH subroutine is called.  The byte value is in the low 8
;   bits of VAL.
;
;   The preprocessor integer PBYTE_OFFSET is incremented by 1.  This value is
;   not used by PBYTE otherwise.  It is intended to allow callers to track the
;   amount of memory that was written from some starting point.  PBYTE_OFFSET
;   can be set to a arbitrary value by the caller.  By default, PBYTE_OFFSET is
;   initialized to 0 before the first byte, then incremented for each byte
;   written.  It therefore provides a count of the number of bytes that have
;   been written with this mechanism.
;
/macro pbyte
  /var exist pbyte_str string = "" ;MPASM line so far
  /var exist pbyte_nbytes integer = 0 ;number of data bytes on the current line
  /var exist pbyte_full bool = false ;indicates when the output line is full
  /var exist pbyte_offset integer = 0 ;offset of next byte from start
  /var exist pbyte_global bool = false ;make labels global
  /var exist pbyte_memlab bool = true ;write labels as memory addresses
  /var exist pbyte_saveofs bool = false ;save label names and their nvol offsets
  /var exist pbyte_labeln integer = 0 ;number of labels names and offsets saved
  /var local val integer = [and [arg 1] 16#FF] ;get the byte value

  /set pbyte_global [and pbyte_global pbyte_memlab] ;can't global if not define

  /if [exist -1 arg] then    ;label provided ?
    /call pbyte_finish       ;ensure this byte starts on a new line
    /write
    /if pbyte_global then    ;label needs to be declared global ?
         global  [arg -1]
      /endif
    /if pbyte_saveofs then   ;save name and offset of this label ?
      /write ";   " [qstr [arg -1]] " = " pbyte_offset
      /set pbyte_labeln [+ pbyte_labeln 1] ;count one more label and offset saved
      /const pbyte_labofs[chars pbyte_labeln] string = [str [qstr [arg -1]] " " pbyte_offset]
      /endif
    /endif
  /if [exist 2 arg] then     ;comment supplied ?
    /call pbyte_finish       ;ensure this byte starts on a new line
    /endif
  /if pbyte_full then        ;existing line is full ?
    /call pbyte_finish       ;write the previously completed line
    /endif

  /if [= pbyte_nbytes 0]
    /then                    ;starting a newline
      /if pbyte_memlab
        /then                ;define labels in program memory
          /set pbyte_str [qstr [arg -1]] ;init the line with the label, if any
        /else                ;don't define labels in program memory
          /set pbyte_str ""
        /endif
      /call tabto pbyte_str 10 ;go to start of opcode column
      /set pbyte_str [str pbyte_str "db"]
      /call tabto pbyte_str 18 ;go to start of operand column
    /else                    ;adding to end of existing line
      /set pbyte_str [str pbyte_str ","]
    /endif
  /set pbyte_str [str pbyte_str "h'" [int val "fw 2 lz base 16 usin"] "'"]
  /set pbyte_nbytes [+ pbyte_nbytes 1]
  /set pbyte_offset [+ pbyte_offset 1]

  /if [exist 2 arg] then     ;comment supplied ?
    /call pbyte_finish [str [arg 2]]
    /endif
  /set pbyte_full [>= pbyte_nbytes 8] ;the output line is now full ?
  /endmac

////////////////////////////////////////////////////////////////////////////////
//
//   Subroutine PBYTE_FINISH [comment]
//
//   Write out any unwritten data that may have been buffered by PBYTE.  The
//   optional call parameter will be written as a comment to the line, if the
//   parameter is supplied.  If not, no comment will be written.  The COMMENT
//   parameter must be convertable to a preprocessor string.  It can therefore
//   be a numeric value, a string variable, or a literal string enclosed in
//   quotes.
//
/subroutine pbyte_finish
  /if [not [exist "pbyte_nbytes"]] then ;PBYTE never called ?
    /return
    /endif
  /if [<= pbyte_nbytes 0] then ;no buffered data to write ?
    /return
    /endif

  /if [exist 1 arg] then     ;comment supplied ?
    /call startcomm pbyte_str
    /set pbyte_str [str pbyte_str [arg 1]]
    /endif

  /write pbyte_str           ;write the MPASM line
  /set pbyte_nbytes 0        ;reset to no unwritten buffered bytes
  /set pbyte_str ""          ;reset the buffered output line to empty
  /endsub

;*******************************************************************************
;
;   Macro [label] PWORD val [, comment]
;
;   Write a 16 bit value as the next two bytes of program memory.  This macro
;   must be run in a CODE_PACK section.  VAL must be convertable to a integer
;   value by the preprocesor.  The low 16 bits of the integer will be written.
;
;   If the optional comment string is supplied, then the word value will be
;   written on a single line with that end of line comment.
;
/macro pword
  /var local val integer = [and [arg 1] 16#FFFF]

  /if [exist 2 arg] then     ;comment supplied ?
    /call pbyte_finish       ;write out any previous buffered data
    /endif

[arg -1] pbyte   [shiftr val 0] ;write the low byte
         pbyte   [shiftr val 8] ;write the high byte

  /if [exist 2 arg] then     ;comment supplied ?
    /call pbyte_finish [str [arg 2]]
    /endif
  /endmac

;*******************************************************************************
;
;   Macro [label] PWORD24 val [, comment]
;
;   Write a 24 bit value as the next three bytes of program memory.  This macro
;   must be run in a CODE_PACK section.  VAL must be convertable to a integer
;   value by the preprocesor.  The low 24 bits of the integer will be written.
;
;   If the optional comment string is supplied, then the word value will be
;   written on a single line with that end of line comment.
;
/macro pword24
  /var local val integer = [and [arg 1] 16#FFFFFF]

  /if [exist 2 arg] then     ;comment supplied ?
    /call pbyte_finish       ;write out any previous buffered data
    /endif

[arg -1] pbyte   [shiftr val 0] ;write the low byte
         pbyte   [shiftr val 8]
         pbyte   [shiftr val 16] ;write the high byte

  /if [exist 2 arg] then     ;comment supplied ?
    /call pbyte_finish [str [arg 2]]
    /endif
  /endmac

;*******************************************************************************
;
;   Macro [label] PWORD32 val [, comment]
;
;   Write a 32 bit value as the next four bytes of program memory.  This macro
;   must be run in a CODE_PACK section.  VAL must be convertable to a integer
;   value by the preprocesor.  The low 32 bits of the integer will be written.
;
;   If the optional comment string is supplied, then the word value will be
;   written on a single line with that end of line comment.
;
/macro pword32
  /var local val integer = [and [arg 1] 16#FFFFFFFF]

  /if [exist 2 arg] then     ;comment supplied ?
    /call pbyte_finish       ;write out any previous buffered data
    /endif

[arg -1] pbyte   [shiftr val 0] ;write the low byte
         pbyte   [shiftr val 8]
         pbyte   [shiftr val 16]
         pbyte   [shiftr val 24] ;write the high byte

  /if [exist 2 arg] then     ;comment supplied ?
    /call pbyte_finish [str [arg 2]]
    /endif
  /endmac

;*******************************************************************************
;
;   Macro [label] FP48P fpval
;
;   Write 6 consecutive bytes to program memory.  These will be the 48 bit
;   floating point representation of FPVAL in least to most significant byte
;   order.  The FPVAL parameter must be interpretable as a floating point value
;   by the preprocessor.
;
;   This macro must be used in a CODE_PACK section to produce the correct
;   result.
;
/macro fp48p
  /var local fpval real = [arg 1]
  /var local s string

  /call fp48_make [chars fpval] ;set FP48_EXP and FP48_MANT

  /call pbyte_finish         ;write out any previously buffered data
[arg -1] pbyte   [shiftr fp48_mant 0]
         pbyte   [shiftr fp48_mant 8]
         pbyte   [shiftr fp48_mant 16]
         pbyte   [shiftr fp48_mant 24]
         pbyte   [shiftr fp48_exp 0]
         pbyte   [shiftr fp48_exp 8]
  /call pbyte_finish [str "FP48 " [fp fpval "sig 7 mnr 1 mxr 8 eng"]]
  /endmac

;*******************************************************************************
;
;   Macro [label] PGRAWSTR "..."
;
;   Write a raw string constant into a CODE_PACK program memory section.  Only
;   the string bytes will be written.
;
;   If a label is supplied, it will be defined as the address of the first
;   program memory byte of the string.  The string value will be written as a
;   comment on the label line, which will be separate from the program memory
;   words containing the string data.
;
/macro pgrawstr
  /var exist pbyte_global bool = false ;makes labels global
  /var local s string = [arg 1]
  /var local ln string
  //
  //   Write the label line, if a label was provided.  If so, this will include
  //   a comment that shows the value of the string.
  //
  /if [exist -1 arg] then    ;label was provided ?
    /call pbyte_finish       ;make sure all previously buffered data is written
    /if pbyte_global then
         global  [arg -1]
      /endif
    /set ln [qstr [arg -1]]  ;init label line with label name
    /call startcomm ln       ;start the end of line comment
    /set ln [str ln '"']
    /loop with ind from 1 to [slen s] ;loop once for each string character
      /var local cc integer  ;character code of current char
      /set cc [ccode [sindx ind s]] ;get character code of this character
      /if [and [>= cc 32] [<> cc 127]] then ;this is a printable character ?
        /set ln [str ln [char cc]]
        /endif
      /endloop
    /set ln [str ln '"']
    /write ln                ;write the label line to the output file
    /endif
  //
  //   Write the string bytes.
  //
  /loop with ind from 1 to [slen s] ;once for each character in the string
         pbyte   [ccode [sindx ind s]] ;write this character
    /endloop
  /call pbyte_finish         ;write any partial program memory word
  /endmac

;*******************************************************************************
;
;   Macro [label] PGSTRING "..."
;
;   Write a counted string constant into a CODE_PACK program memory section.
;   The first byte is the length byte, which is then followed by exactly that
;   many string bytes.
;
;   If a label is supplied, it will be defined as the address of the first
;   program memory byte of the string.  The string value will be written as a
;   comment on the label line, which will be separate from the program memory
;   words containing the string data.
;
/macro pgstring
  /var exist pbyte_global bool = false ;makes labels global
  /var local s string = [arg 1]
  /var local ln string
  /var local ii integer
  /var local cc integer
  //
  //   Write the label line, if a label was provided.  If so, this will include
  //   a comment that shows the value of the string.
  //
  /if [exist -1 arg] then    ;label was provided ?
    /call pbyte_finish       ;make sure all previously buffered data is written
    /if pbyte_global then
         global  [arg -1]
      /endif
    /set ln [qstr [arg -1]]  ;init label line with label name
    /call startcomm ln       ;start the end of line comment
    /set ln [str ln '"']
    /set ii 1                ;init index of next character to write
    /block                   ;back here each character of the string
      /if [> ii [slen s]] then
        /quit
        /endif
      /set cc [ccode [sindx ii s]] ;get character code of this character
      /if [and [>= cc 32] [<> cc 127]] then ;this is a printable character ?
        /set ln [str ln [char cc]]
        /endif
      /set ii [+ ii 1]       ;advance to next character in the string
      /repeat
      /endblock
    /set ln [str ln '"']
    /write ln                ;write the label line to the output file
    /endif
  //
  //   Write the string bytes.
  //
         pbyte   [slen s]    ;write the string length byte

  /set ii 1                  ;init next string character to write
  /block                     ;back here each new character
    /if [> ii [slen s]] then
      /quit
      /endif
         pbyte   [ccode [sindx ii s]] ;write this character
    /set ii [+ ii 1]
    /repeat
    /endblock
  /call pbyte_finish         ;write any partial program memory word
  /endmac

;*******************************************************************************
;
;   Macro [label] NSTRING maxsize, "..." [, comment]
;
;   Write a counted string constant into a CODE_PACK program memory section.
;   The first byte is the length byte, which is then followed by exactly that
;   many string bytes.  MAXSIZE string bytes are reserved, for a total of
;   MAXSIZE+1 bytes (string plus the length byte).  The supplied string is
;   truncated to MAXSIZE characters if it is longer.  The length byte is set to
;   the actual length of the string as initialized, not the maximum possible
;   value.  Any unused string bytes are set to the erased value of FFh.
;
/macro nstring
  /var local maxsz integer = [arg 1] ;max allowed string length
  /var local s string = [arg 2] ;the initial string value
  /var local wcomm bool      ;still need to write comment
  /var local len integer     ;length of the string to write
  /var local ii integer      ;string index of next char to write
  /var local cc integer      ;character code of current character

  /set len [min maxsz [slen s]] ;make string length

[arg -1] pbyte   [v len]     ;write the length byte

  /set wcomm [exist 3 arg]   ;TRUE if comment still needs to be written
  /set ii 1                  ;init index of next string char to write
  /block                     ;back here each new character
    /if [> ii maxsz] then    ;done defining all the bytes ?
      /quit
      /endif
    /if [<= ii [slen s]]
      /then                  ;still within the supplied string
        /set cc [ccode [sindx ii s]] ;get this character from the string
      /else                  ;past the end of the supplied string
        /set cc 16#FF        ;get the memory erased value for this byte
      /endif

         pbyte   [v cc]      ;write this string byte

    /if [and wcomm pbyte_full] then ;write comment here ?
      /call pbyte_finish [arg 3]
      /set wcomm false       ;don't try to write the comment again
      /endif

    /set ii [+ ii 1]         ;advance to next string index
    /repeat                  ;back to do next string character
    /endblock

  /if wcomm
    /then                    ;still have unwritten comment
      /call pbyte_finish [arg 3]
    /else
      /call pbyte_finish
    /endif
  /endmac

;*******************************************************************************
;
;   Macro [label] MNSTRING maxsize, "..." [, comment]
;
;   Like NSTRING (above) except that the maximum string length is also written
;   to the memory.  The maximum string length byte is written first, then the
;   actual string length for the string the memory is initialized with, then the
;   string bytes.  This macro will define a total of MAXSIZE+2 bytes.
;
/macro mnstring
  /var local maxsz integer = [arg 1] ;max allowed string length
  /var local s string = [arg 2] ;the initial string value
  /var local wcomm bool      ;still need to write comment
  /var local len integer     ;length of the string to write
  /var local ii integer      ;string index of next char to write
  /var local cc integer      ;character code of current character

  /set len [min maxsz [slen s]] ;make string length
  /set wcomm [exist 3 arg]   ;TRUE if comment still needs to be written

[arg -1] pbyte   [v maxsz]   ;write the maximum length byte
         pbyte   [v len]     ;write the current length byte

  /set ii 1                  ;init index of next string char to write
  /block                     ;back here each new character
    /if [> ii maxsz] then    ;done defining all the bytes ?
      /quit
      /endif
    /if [<= ii [slen s]]
      /then                  ;still within the supplied string
        /set cc [ccode [sindx ii s]] ;get this character from the string
      /else                  ;past the end of the supplied string
        /set cc 16#FF        ;get the memory erased value for this byte
      /endif

         pbyte   [v cc]      ;write this string byte

    /if [and wcomm pbyte_full] then ;write comment here ?
      /call pbyte_finish [arg 3]
      /set wcomm false       ;don't try to write the comment again
      /endif

    /set ii [+ ii 1]         ;advance to next string index
    /repeat                  ;back to do next string character
    /endblock

  /if wcomm
    /then                    ;still have unwritten comment
      /call pbyte_finish [arg 3]
    /else
      /call pbyte_finish
    /endif
  /endmac


;*******************************************************************************
;*******************************************************************************
;
;   Mutual exclusion locks for use with the Embed multi-tasking system.
;
;   Simple exclusion locks.
;
;     These are either held by a specific task or not held at all.  Macros that
;     operate on these locks are named MUTEX_xxx:
;
;       MUTEX_INIT name
;
;         One-time initialization of the lock state.
;
;       MUTEX_SKIP_LOCK name
;
;         Skips the next instruction if the lock is being held by any task.
;
;       MUTEX_SKIP_LOCK_US name
;
;         Skips the next instruction if the lock is being held by this task.
;
;       MUTEX_SKIP_AVAIL name
;
;         Skips the next instruction if the lock is available (not held by any
;         task).
;
;       MUTEX_SKIP_NLOCK_US name
;
;         Skips the next instruction if the lock is not being held by this task.
;
;       MUTEX_LOCK name
;
;         Acquire the lock, wait as necessary until it is released first.
;
;       MUTEX_UNLOCK name
;
;         Release the lock, if held by this task.
;

////////////////////////////////////////////////////////////////////////////////
//
//   Macro MUTEX_INIT name
//
//   Initialize the simple exclusion lock NAME.  The variable LOCK_<name> must
//   be defined in the local register bank.  This macro must be run once before
//   this mutual exclusion lock is used.  The other MUTEX_xxx macros can operate
//   on this lock only after it has been initialized.
//
/macro mutex_init
         dbankif lbankadr
         setf    lock_[arg 1]
  /endmac

////////////////////////////////////////////////////////////////////////////////
//
//   Macro MUTEX_SKIP_LOCK name
//
//   Skip the next instruction after this macro if the simple exclusion lock
//   NAME is held by any task (lock is not available).  The bank will be set to
//   the local bank.
//
/macro mutex_skip_lock
         dbankif lbankadr
         btfsc   lock_[arg 1], 7 ;skip if locked
  /endmac

////////////////////////////////////////////////////////////////////////////////
//
//   Macro MUTEX_SKIP_LOCK_US name
//
//   Skip the next instruction after this macro if the simple exclusion lock
//   NAME is held by this task.  The bank will be set to the local bank.
//
/macro mutex_skip_lock_us
         extern  currtask    ;0-N ID of the currently running task
         dbankif gbankadr
         movf    currtask, w ;get the ID of this task
         dbankif lbankadr
         xorwf   lock_[arg 1], w ;compare to lock state
         skip_z              ;skip if we are holding the lock
  /endmac

////////////////////////////////////////////////////////////////////////////////
//
//   Macro MUTEX_SKIP_AVAIL name
//
//   Skip the next instruction after this macro if the simple exclusion lock
//   NAME is available (not held by any task).  The bank will be set to the
//   local bank.
//
/macro mutex_skip_avail
         dbankif lbankadr
         btfss   lock_[arg 1], 7 ;skip if not locked
  /endmac

////////////////////////////////////////////////////////////////////////////////
//
//   Macro MUTEX_SKIP_NLOCK_US name
//
//   Skip the next instruction after this macro if the simple exclusion lock
//   NAME is not held by this task.  The bank will be set to the local bank.
//
/macro mutex_skip_nlock_us
         extern  currtask    ;0-N ID of the currently running task
         dbankif gbankadr
         movf    currtask, w ;get the ID of this task
         dbankif lbankadr
         xorwf   lock_[arg 1], w ;compare to lock state
         skip_nz             ;skip if we are not holding the lock
  /endmac

////////////////////////////////////////////////////////////////////////////////
//
//   Macro MUTEX_LOCK name
//
//   Acquire the simple exclusion lock NAME.  If the lock is not in use, then
//   it is locked to this task.  If it is in use, then then TASK_YIELD_SAVE is
//   called until it is available.  Nothing is done if this task is already
//   holding the lock.
//
/macro mutex_lock
  /write
  /write "         ;acquire the " [ucase [qstr [arg 1]]] " lock."
         ;
         extern  currtask    ;0-N ID of the currently running task
[lab retry] unbank
         dbankif lbankadr
         btfsc   lock_[arg 1], 7 ;the lock is not currently available ?
         jump    [lab grab]  ;is available, go grab it
         movf    lock_[arg 1], w ;get the ID of the task holding the lock
         dbankif gbankadr
         xorwf   currtask, w ;compare to this task
         bz      [lab done]  ;we are already holding the lock, nothing to do ?
         gcall   task_yield_save ;give other tasks a chance to run
         jump    [lab retry] ;back to check the lock again
[lab grab] dbankis lbankadr  ;grab the lock
         movff   currtask, lock_[arg 1] ;indicate this task has the lock
[lab done] unbank
  /write
  /endmac

////////////////////////////////////////////////////////////////////////////////
//
//   Macro MUTEX_UNLOCK name
//
//   Release the simple exclusion lock NAME.  Nothing is done if it is not held
//   by this task.
//
/macro mutex_unlock
  /write
  /write "         ;release the " [ucase [qstr [arg 1]]] " lock."
         ;
         extern  currtask    ;0-N ID of the currently running task
         dbankif gbankadr
         movf    currtask, w ;get the ID of this task
         dbankif lbankadr
         xorwf   lock_[arg 1], w ;compare to ID of the holding task
         skip_nz             ;we aren't hoding the lock ?
         setf    lock_[arg 1] ;indicate the lock is now available
  /write
  /endmac