; ***************************************************************
; * Copyright (C) 2006, Embed Inc (http://www.embedinc.com) *
; * *
; * Permission to copy this file is granted as long as this *
; * copyright notice is included in its entirety at the *
; * beginning of the file, whether the file is copied in whole *
; * or in part and regardless of whether other information is *
; * added to the copy. *
; * *
; * The contents of this file may be used in any way, *
; * commercial or otherwise. This file is provided "as is", *
; * and Embed Inc makes no claims of suitability for a *
; * particular purpose nor assumes any liability resulting from *
; * its use. *
; ***************************************************************
;
; Standard include file assumed by most PIC source modules. The specifics
; must be configured to the particular processor and application. This
; is done by setting assembly values before this file is included. The
; include file STD_DEF.INS.ASPIC is provided to set defaults for all
; the required assembly values. An application should include STD_DEF.INS.ASPIC,
; then set any values it knows and cares about, then include STD.INS.ASPIC.
; In this way, applications are protected from changes to this include
; file that may require additional values to be set.
;
; The following assembly values are assumed by this file, some of these
; are set to default values in STD_DEF.INS.ASPIC.
;
; NREGBANKS - Number of register banks the processor has. This
; configures macros that set the direct and indirect register banks.
;
; NCODEPAGES - Number of code pages the processor has. A
; value of 1 disables code page selection code.
;
; STACKLAST - The address of the last (highest) software stack byte.
; This is where the first pushed byte is written. On the 18 family,
; this is actually lowest address of the stack since the stack grows
; towards higher addresses on that family.
;
; STACKSIZE - Number of bytes to allocate for the stack.
;
; FREQ_OSC - Processor oscillator frequency in Hz. This is the
; effective oscillator frequency on the 18 family, after the PLL,
; if enabled, is applied. The instruction clock is FREQ_OSC / 4.
;
; FAM_12 - 1 if the processor is a 12xxx, meaning 12 bti core,
; 0 otherwise. Some PICs, like the 12F629, are really 16 family
; devices using the 14 bit core. These should have FAM_16, not
; FAM_12 set to 1.
;
; FAM_16c5 - 1 if the processor is a 16C5x, 0 otherwise.
;
; FAM_16 - 1 if the processor is a 16xxx except a 16C5x, 0 otherwise.
; This is meant to indicate the 14 bit "midrange" core devices, which
; includes some parts called "12F", like the 12F629.
;
; FAM_17 - 1 if the processor is a 17xxx, 0 otherwise.
;
; FAM_18 - 1 if the processor is a 18xxx, 0 otherwise.
;
; REGSTART - Starting address of general registers (REG0 address).
; The remaining registers immediately follow REG0 at higher addresses.
;
; NUMREGS - Number of general registers.
;
; COMMREGS_FIRST, COMMREGS_LAST - These indicate the range of
; address offsets within each bank that are mapped to the same
; memory. For example, many 16 family processors have 70h - 7Fh
; within each bank mapped to the same physical registers.
; COMMREGS_FIRST > COMMREGS_LAST indicates this processor has
; no such common mapped registers.
;
; N_IREGS - The number of IREG0 - IREGn register to allocate. These
; registers are for the exclusive use by interrupt code, since it
; would be impractical for that code to use the general registers
; REG0 - REGn. This constant will be created and set to 0 if it
; does not already exist.
;
; INTR_OFF_BUG - Set to TRUE if the processor has the interrupt
; disable bug where the interrupt bit must be checked in a loop
; to make sure interrupts really got disabled. FALSE indicates
; interrupt bit can be altered without checking.
;
; UART_TYPE - Indicates the type of USART this part has. The
; possible values are:
;
; 0 - This part has no UART.
;
; 1 - Normal 17 family USART, like 17C756A.
;
; 2 - Normal 16 family USART, like as 16F876.
;
; 3 - Advanced USART on some 18 family, like 18F1320.
;
; ADR_WORD - Number of address increments per program memory word.
; This is 1 for 12, 16, and 17 family PICs, and 2 for 18 family.
;
; PULLUPS_PORTx - Mask of which bits of port X (PORTA, PORTB, etc)
; have passive pullups that can be enabled. A 1 bit indicates that
; the I/O pin at that bit position can have an internal passive
; pullup. If the high bit (80000000h) is set, then all pullups
; of this port can only be enabled or disabled together. If a
; symbol of this name does not exist for a port, then it is assumed
; that the port has not passive pullups at all.
;
; EE_START - Starting address of the EEPROM mapped into the program
; memory space in the HEX file. This is the starting address to use
; for the CODE section containing EEPROM initial values. Note that
; linker files are set up so that a code section called ".EEDATA"
; will automatically get mapped to the EEPROM. If it is permissable
; to allow the linker to place data in EEPROM, then the code section
; need not be given the explicit address of EE_START.
;
; ACCLAST - Last location in the bank 0 access bank region of 18
; family PICs. This is 7Fh for most 18 family PICs, but some of the
; larger ones have more SFRs and decrease the access bank region
; in bank 0. The existance of ACCLAST indicates the existance of
; an access bank.
;
; C18COMP - A value of 1 indicates that this code must be compatible
; with modules compiled with the MPASM C18 compiler. This code
; must be able to call routines compiled with C18 and must also be
; callable from C18 code. This forces the following differences
; from the normal (and more efficient) Embed Inc assembler build
; environment:
;
; 1 - FSR1 is reserved as the stack pointer and FSR2 as the data
; stack frame pointer. The default Embed Inc conventions only
; reserve FSR2 as the data stack pointer and the application is
; free to use FSR0 and FSR1 in any way it wants.
;
; 2 - The stack pointer always points to the next empty byte
; instead of the byte on the top of the stack. In both cases
; the stack grows towards higher addresses. Leaving the stack
; pointer pointing to the next byte after the top of stack is
; less efficient because a PUSH would require a postincrement
; and a POP a predecrement. However the PIC 18 architecture
; has no predecrement, only preincrement. This requires an
; extra instruction for each group of bytes popped.
;
; 3 - Interrupt code advances the stack pointer by one before
; saving context. The C18 convention is that the stack pointer
; points to the next free entry past the top of the stack (where
; the next pushed byte would go). However, to deal with the lack
; of a predecrement addressing mode, the compiler emits code that
; performs a POP by decrementing the stack pointer then
; using it to read the top stack byte in two separate operations.
; An interrupt could occur between these two operations, so an
; interrupt routine must preserve the byte being pointed to by
; the stack pointer. It does this by leaving one untouched byte
; on the stack before saving any context, then removing the
; unused byte before returning from the interrupt.
;
; A value of 0 indicates this code need not be compatible with
; C18 code. All other values are illegal. If C18COMP does not
; exist, it will be created and set to 0 (defaulting to Embed Inc
; normal conventions).
;
; FSRSTACK - Indicate the 0-2 number of the FSR to be used as the
; data stack pointer. For 18 family only. Will be created and
; set to 2 if not previously existing unless C18COMP is 1. When
; C18COMP is 1 FSR1 will always be used for the stack, and it is
; an error if FSRSTACK is defined but not set to 1.
;
; FSRSC1 - The 0-2 number of the first FSR that can be used as a
; scratch pointer. By convention, this FSR may be trashed by
; subroutines. This is never the same FSR as the stack pointer.
;
; FSRSC2 - The 0-2 number of the second FSR that can be used as a
; scratch pointer. This FSR must be preserved by subroutines when
; FSRSC2_SAVE is 1. This is never the same FSR as the stack pointer.
;
; FSRSC2_SAVE - Indicates whether the scratch FSR indicated by FSRSC2
; must be preserved by subroutines. 0 means it does not need to be
; preserved, and 1 means it does.
;
; PROGADRB - Number of bytes required to store a full program
; memory address for this processor. Only required for 18 family.
; The 18 family architecture allows for 21 bit program memory
; addresses, which require 3 bytes to store. However many PICs
; of this family have 64Kb or less of program memory, requiring
; only 2 bytes to store. Optimizations that avoid storing or
; otherwise manipulating the unused upper address byte on these
; PICs can be made when PROGADRB is 2 instead of 3. (No PIC18
; currently has 256 bytes or less of program memory, but if that
; were the case PROGADRB would be 1 on those machines and
; manipulation of the upper two bytes could be optimized out).
;
; PROGADRB can also be artifically set lower on PICs with more
; than 64Kb of program memory if it is absolutely known that no
; program memory above 64Kb is used. The default will be set
; according to the total program memory available. Don't mess
; with the default unless you are absolutely sure you know
; what you're doing.
;
; CREATE_FLAGS - Create the global FLAGS variable. This is used
; by various library arithmetic routines to indicate information
; about the operation or result. CREATE_FLAGS is initialized to
; 1 in STD_DEF.INS.ASPIC so that FLAGS is created by default.
; CREATE_FLAGS can be set to 0 to save 1 RAM location by not
; creating the FLAGS variable.
;
; CREATE_TEMPW - Create the TEMPW global variable in bank 0 when
; this symbol is 1. TEMPW will not be created when it is 0.
; CREATE_TEMPW is initialized to 1 in STD_DEF.INS.ASPIC so that
; TEMPW is created by default. TEMPW is a local temporary save
; area for W used by some of the macros in this file.
;
; USING_INTERRUPTS - Always TRUE (1) or FALSE (0). The default is
; TRUE, meaning the code might be using interrupts. In this case
; the INTR_ON and INTR_OFF macros actually emit code to turn
; interrupts on and off. If interrupts are not in use, then a
; section of code that tries to temporarily turn off interrupts
; will have the effect of enabling interrupts at the end. Setting
; this switch to FALSE prevents re-using code that temporarily
; disables interrupts when interrupts are not in use at all.
;
; Check for required constants and abort on error if any is missing.
;
/if [exist "freq_osc"] then
ifndef freq_osc
freq_osc equ [rnd freq_osc]
endif
/const freq_inst real = [/ freq_osc 4]
/endif
ifndef freq_osc
error "Assembly constant FREQ_OSC not set before STD include file called."
endif
if fam_18
ifndef progadrb
error PROGADRB not define. Should be defined in STD_DEF.INS.ASPIC.
endif
endif
ifndef n_iregs
n_iregs equ 0 ;assume no private registers required by interrupt code
endif
;
; Derived constants.
;
freq_inst equ freq_osc / 4 ;instruction clock frequency in Hz
nsec_inst equ 1000000000 / freq_inst ;instruction time in nanseconds
;
; Global assembly time state.
;
w_trashed set false ;used by some macros to indicate W got trashed
;
; Other symbols.
;
; Define JUMP adr
;
; This inline substitution macro expands to the opcode for doing a simple
; jump local to the module.
;
if fam_18 ;18 family processor
#define jump bra
else ;all other processors
#define jump goto
endif
;
; Fix bug in some of the 18F include files where the INT2IP bit in
; INTCON3 is named INT2P instead.
;
ifdef intcon3 ;this part has INTCON3 register ?
ifndef int2ip ;INT2IP is not defined ?
ifdef int2p ;but INT2P is ?
#define int2ip 7 ;define the correct bit name
endif
endif
endif
;
; Make sure C18COMP is defined.
;
ifndef c18comp
c18comp equ 0 ;default to don't need to be compatible with C18 code
endif
;
; Define the FSR numbers if this is a 18 family PIC.
;
if fam_18
ifndef fsrstack
if c18comp
fsrstack set 1 ;C18 compiler uses FSR1 for stack pointer
else
fsrstack set 2 ;default to FSR2 as stack pointer
endif
endif
if (fsrstack < 0) || (fsrstack > 2)
error Out of range value for FSRSTACK found in STD.INS.ASPIC.
endif
if c18comp && (fsrstack != 1)
error FSRSTACK set to #v(fsrstack), must be 1 for C18 compatibility.
endif
if c18comp
fsrsc1 equ 0
fsrsc2 equ 2
fsrsc2_save equ 1
else
fsrsc1 equ 0
fsrsc2 equ 1
fsrsc2_save equ 0
endif
endif
;
;***********************************************************************
;
; General registers
;
; Several registers are declared in global (not banked) memory that are
; intended to act like general registers of other processors.
; Subroutines are expected to preserve these registers except as
; they are explicitly used to pass return values as documented
; individually for each subroutine. It is assumed that subroutines
; trash other state, such as W and FSR unless otherwise documented.
;
; The general registers (REG0 - REGn) are guaranteed to be mapped to
; memory in that order.
;
; The register addresses are declared here as constants so that their
; addresses are know at assembly time. The registers are defined in
; module REGS.ASPIC. The main routine must have an external reference
; to REGS to insure that the memory space for the registers is actually
; allocated.
;
; Create one REGn and REGFn symbol for each register. The REGn symbols
; are the memory addresses of the registers. The REGFn symbols are flags
; used to identify a set of registers. Each REGFn symbol has one
; unique bit set. These symbols can be ORed together to identify an
; arbitrary set of registers.
;
tempasm1 set 0 ;init symbol number
while tempasm1 < numregs
reg#v(tempasm1) equ regstart + tempasm1 ;define REGn register address symbol
regf#v(tempasm1) equ 1 << tempasm1 ;define REGFn register flag symbol
tempasm1 set tempasm1 + 1 ;advance to next symbol number
endw
noregs equ 0 ;special flag value to indicate no registers
;
; Declare the 32 bit registers A thru D and their flags if general registers
; are declared to cover them.
;
if numregs >= 4
rega equ regstart
regfa equ b'1111'
endif
if numregs >= 8
regb equ regstart + 4
regfb equ b'1111' << 4
endif
if numregs >= 12
regc equ regstart + 8
regfc equ b'1111' << 8
endif
if numregs >= 16
regd equ regstart + 12
regfd equ b'1111' << 12
endif
regf_allregs equ ~(-1 << numregs) ;mask for all REG0-REGn general registers
;
; Create FLAGS register. This register receives the result of compare
; and other operations. It is always located immediately following the
; general registers.
;
if create_flags
flags equ regstart + numregs ;declare FLAGS register address
regff equ 1 << numregs ;declare flag for pushing/popping FLAGS register
else
regff equ 0 ;there is no FLAGS register to push/pop
endif
regf_all equ regf_allregs | regff ;all possible push/pop flags
;
; Identify particular bits in the FLAGS register. These constants represent
; bit numbers so that they can be used directly with bit manipulation
; instructions.
;
if create_flags
flagb_lt equ 0 ;comparison result was "less than"
flagb_eq equ 1 ;comparison result was "equal"
flagb_gt equ 2 ;comparison result was "greater than"
flagb_err equ 3 ;error, failed to perform action
flagb_quoa equ 4 ;store quotient in 32 bit A register
flagb_quoc equ 5 ;store quotient in 32 bit C register
flagb_rema equ 6 ;store remainder in 32 bit A register
flagb_remc equ 7 ;store remainder in 32 bit C register
endif
;
; Bit masks for the flags defined above
;
if create_flags
flag_lt equ 1 << flagb_lt
flag_eq equ 1 << flagb_eq
flag_gt equ 1 << flagb_gt
flag_err equ 1 << flagb_err
flag_quoa equ 1 << flagb_quoa
flag_quoc equ 1 << flagb_quoc
flag_rema equ 1 << flagb_rema
flag_remc equ 1 << flagb_remc
;
; Mask for all flags that are set as a result of arithmetic
; operations.
;
flag_ar equ flag_lt | flag_eq | flag_gt
endif
;
; Macros for skipping the next instruction depending on some of the flags
; in FLAGS.
;
if create_flags
skip_flt macro ;skip on less than
dbankif flags
btfss flags, flagb_lt
endm
skip_fle macro ;skip on less than or equal to
dbankif flags
btfsc flags, flagb_gt
endm
skip_feq macro ;skip on equal
dbankif flags
btfss flags, flagb_eq
endm
skip_fgt macro ;skip on greater than
dbankif flags
btfss flags, flagb_gt
endm
skip_fge macro ;skip on greater than or equal to
dbankif flags
btfsc flags, flagb_lt
endm
skip_fne macro ;skip on not equal
dbankif flags
btfsc flags, flagb_eq
endm
skip_err macro ;skip on error flag bit set
dbankif flags
btfss flags, flagb_err
endm
skip_nerr macro ;skip on error flag bit not set
dbankif flags
btfsc flags, flagb_err
endm
endif
;
; Define the CONFIGnx constants that are the addresses of the
; configuration words for the 18 family. These are listed in the
; manual and mentioned in the standard include file comments, but
; not defined in the include file for some strange reason.
;
if fam_18
config_word_first equ h'300000' ;address of first configuration word
config_word_last equ h'30000D' ;address of last configuration word
ii set config_word_first ;init address of next config word to define
variable jj
while ii <= config_word_last ;once for each config word
jj set ((ii - config_word_first) >> 1) + 1 ;1-N config word number
if (ii & 1) == 0 ;at low word of low/high pair ?
CONFIG#V(jj)L equ ii ;define LOW config word of this number
else ;at high word of low/high pair
CONFIG#V(jj)H equ ii ;define HIGH config word of this number
endif
ii set ii + 1 ;advance to next config word address
endw ;back to do next pair of config words
endif ;end of 18 family case
;
;***********************************************************************
;
; General utility macros.
;
;********************
;
; Macro SET_TBLPTR adr
;
; Set the full TBLPTR to the indicated address. This macro is only
; defined on machines that have TBLPTR. PROGADRB must be defined
; correctly to indicate the number of bytes required to store a
; program memory address on this machine.
;
ifdef tblptrl
set_tblptr macro adr
movlw low (adr)
movwf tblptrl
if progadrb < 2
clrf tblptrh
else
movlw high (adr)
movwf tblptrh
endif
if progadrb < 3
clrf tblptru
else
movlw upper (adr)
movwf tblptru
endif
endm
endif
;
;********************
;
; Macro IREGS_DEFINE
;
; Define all the IREG0 - IREGn interrupt routine registers. The number
; of these registers is set by the constant N_IREGS.
;
iregs_define macro
local ii
ii set 0 ;init loop counter
while ii < n_iregs ;once for each IREG to define
ireg#v(ii) res 1 ;define this IREG
global ireg#v(ii) ;declare it global
ii set ii + 1
endw
endm
;
;********************
;
; Macro EXTERN_IREGS
;
; Declare all the IREGn register external to this module.
;
extern_iregs macro
local ii
ii set 0 ;init loop counter
while ii < n_iregs ;once for each IREG
extern ireg#v(ii) ;declare it external
ii set ii + 1
endw
endm
;
;********************
;
; Macro GETF <adrf>
;
; Move the contents of the file register ADRF into W. The Z flag is
; trashed.
;
getf macro adrf
if fam_16 ;16C devices
movf (adrf), w
exitm
endif
if fam_17 ;17C devices
movfp (adrf), wreg
exitm
endif
if fam_18 ;18 family devices
movf (adrf), w
exitm
endif
error "GETF macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro GETFZ <adrf>
;
; Move the contents of the file register ADRF into W. The Z flag is
; set according to the value moved.
;
getfz macro adrf
if fam_16 ;16C devices
movf (adrf), w
exitm
endif
if fam_17 ;17C devices
movfp (adrf), wreg
iorlw 0
exitm
endif
if fam_18 ;18 family devices
movf (adrf), w
exitm
endif
error "GETFZ macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro TESTFZ <adrf>
;
; Set the Z flag according to the contents of the file register ADRF.
; W is preserved.
;
testfz macro adrf
if fam_16 ;16C devices
movf (adrf), f
exitm
endif
if fam_17 ;17C devices
comf (adrf), f
comf (adrf), f
exitm
endif
if fam_18 ;18 family devices
movf (adrf), f
exitm
endif
error "GETF macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro WAITCY NCY
;
; Generates code that does nothing for the next NCY cycles. Note that this
; is not accurate for timing unless interrupts are disabled. This macro
; generates no code if NCY is zero or less. This macro causes no changes
; to status bits, banking, or other state other than some number of
; instructions are executed. The number of instructions emitted is not
; guaranteed, only that they will take NCY instruction cycles to execute.
;
; This macro replaces the old WAITNOP macro for most cases. See the
; WAITNOP comments below for a discussion of this.
;
waitcy macro ncy
local n
n set (ncy) ;init number of NOPs left to write
if fam_12 | fam_16c5 | fam_16
while n >= 2
goto $+1
n set n - 2
endw
endif
if fam_18
while n >= 2
bra $+2
n set n - 2
endw
endif
while n > 0
nop
n set n - 1
endw
endm
;
;********************
;
; Macro WAITNOP NNOP
;
; This macro now causes an error so that all uses of it can be examined
; to determine the reason for its use, then replaced by the specific
; macro for that use.
;
; WAITNOP originally just wrote the indicated number of sequential
; NOP instructions although it was intended for instruction timing.
; Some time later it was modified to use GOTO $+1 on PIC 16 and BRA $+2
; on PIC 18 to act as NOPs that took 2 cycles but only one instruction
; word. The was beneficial for the intended use.
;
; However, WAITNOP was also used to create a table of sequential NOP
; instructions that could be indexed into to exactly synchronize with
; a timer. Using 2 cycle NOP instructions defeated this purpose and
; caused bugs.
;
; Two separate macros now exist to serve the two purposes, WAITCY (above)
; and NNOPS (below). WAITNOP now causes an assembly error so that
; the purpose can be examined and the appropriate replacement macro
; chosen. Note that the newer WAITUS and WAITNS macros may be a
; better choice than WAITCY in some cases since they automatically
; adjust to the processor clock speed.
;
waitnop macro nnop
error WAITNOP has been depricated. See comments in STD.INS.ASPIC.
endm
;
;********************
;
; Macro NNOPS nnop
;
; Write NNOP consecutive NOP instructions. Nothing is done if NNOP
; is zero or negative. The WAITCY macro should be used if the intent
; is to waste a specific number of instruction cycles, since WAITCY
; does that and possibly takes fewer instruction words. Use NNOPS
; only when a number of consecutive NOP instructions are needed. This
; could be the case, for example, when indexing into a list of NOPs
; to exactly synchronize to a timer value.
;
nnops macro nnop
local n
n set nnop ;init number of NOPs left to write
while n > 0
nop
n set n - 1
endw
endm
;
;********************
;
; Macro WAITNS ns, cy
;
; Generates the minimum necessary inline instructions to wait at least
; NS nanoseconds minus CY instruction cycles. This macro is only meant
; for short delays since inline instructions are generated and the
; the processor is consumed executing them. Timing is not accurate unless
; interrupts are disabled.
;
; The purpose of this macro is to allow for accurate timing between two
; instructions with other fixed instructions also between the two events
; and without having to know the instruction rate. For example, if a
; minimum of 1uS is required between two instructions and 3 other instructions
; are always executed between the two outside this macro, then
;
; WAITNS 1000, 4
;
; will add the minimum necessary wait. Note that the CY parameter is 4
; and not 3 since there is 1 instruction cycle between consecutive
; instructions even with no additional instructions in between.
;
; This macro produces no code if the minimum wait time is already met
; by the CY instructions.
;
waitns macro ns, cy
local waitns_ii
waitns_ii set 2000000000 / (freq_inst / 5) ;instruction time in 100fs units
waitns_ii set (((ns) * 10) + waitns_ii - 1) / waitns_ii ;total instr wait needed
waitns_ii set waitns_ii - (cy) ;subtract off instructions already waited
if waitns_ii & h'80000000' ;remaining instructions to wait is negative ?
exitm
endif
waitcy waitns_ii ;add the wait instructions
endm
;
;********************
;
; Macro WAITUS us, cy
;
; Just like WAITNS except that the wait time parameter US is in microseconds
; instead of nanoseconds.
;
waitus macro us, cy
waitns (us * 1000), cy
endm
;
;********************
;
; Macro SKIP_WLE
;
; Skip the next instruction if W was less than or equal to the value
; it was subtracted from. This assumes that the carry flag has been
; preserved from the last SUBWF or SUBLW instruction.
;
skip_wle macro
if fam_12 || fam_16 || fam_18
btfss status, c ;skip if no borrow occurred
exitm
endif
if fam_17
btfss alusta, c
exitm
endif
error "SKIP_WLE macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro SKIP_WGT
;
; Skip the next instruction if W was greater than the value
; it was subtracted from. This assumes that the carry flag has been
; preserved from the last SUBWF or SUBLW instruction.
;
skip_wgt macro
if fam_12 || fam_16 || fam_18
btfsc status, c ;skip if a borrow occurred
exitm
endif
if fam_17
btfsc alusta, c
exitm
endif
error "INTR_WGT macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro SKIP_Z
;
; Skip the next instruction if the zero flag is set.
;
skip_z macro
if fam_12 || fam_16 || fam_18
btfss status, z
exitm
endif
if fam_17
btfss alusta, z
exitm
endif
error "SKIP_Z macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro SKIP_NZ
;
; Skip the next instruction if the zero flag is not set.
;
skip_nz macro
if fam_12 || fam_16 || fam_18
btfsc status, z
exitm
endif
if fam_17
btfsc alusta, z
exitm
endif
error "SKIP_NZ macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro SKIP_CARR
;
; Skip the next instruction if a carry occurred.
;
skip_carr macro
if fam_12 || fam_16 || fam_18
btfss status, c
exitm
endif
if fam_17
btfss alusta, c
exitm
endif
error "SKIP_CARR macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro SKIP_NCARR
;
; Skip the next instruction if no carry occurred.
;
skip_ncarr macro
if fam_12 || fam_16 || fam_18
btfsc status, c
exitm
endif
if fam_17
btfsc alusta, c
exitm
endif
error "SKIP_NCARR macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro SKIP_BORR
;
; Skip the next instruction if a borrow occurred.
;
skip_borr macro
if fam_12 || fam_16 || fam_18
btfsc status, c
exitm
endif
if fam_17
btfsc alusta, c
exitm
endif
error "SKIP_BORR macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro SKIP_NBORR
;
; Skip the next instruction if no borrow occurred.
;
skip_nborr macro
if fam_12 || fam_16 || fam_18
btfss status, c
exitm
endif
if fam_17
btfss alusta, c
exitm
endif
error "SKIP_NBORR macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro JMP_WEQ adr
;
; Jump to ADR if W was equal to the value it was subtracted from.
; This assumes the Z flag has been preserved from the last SUBWF or
; SUBLW instruction.
;
jmp_weq macro adr
if fam_18
bz adr
exitm
endif
error "JMP_WEQ macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro JMP_WNE adr
;
; Jump to ADR if W was not equal to the value it was subtracted from.
; This assumes the Z flag has been preserved from the last SUBWF or
; SUBLW instruction.
;
jmp_wne macro adr
if fam_18
bnz adr
exitm
endif
error "JMP_WNE macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro JMP_WLE
;
; Jump to ADR if W was less than or equal to the value it was
; subtracted from. This assumes the C flag has been preserved from the
; last SUBWF or SUBLW instruction.
;
jmp_wle macro adr
if fam_18
bc adr
exitm
endif
error "JMP_WLE macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro JMP_WGT
;
; Jump to ADR if W was greater than to the value it was subtracted
; from. This assumes the C flag has been preserved from the last SUBWF
; or SUBLW instruction.
;
jmp_wgt macro adr
if fam_18
bnc adr
exitm
endif
error "JMP_WGT macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro INTR_OFF
;
; Globally disable interrupts without changing which interrupts are
; individually disabled. This macro together with INTR_ON can be used
; around small sections of code that need to run with interrupts off.
;
; This macro works around the interrupt off bug on some processors where
; an interrupt can occur immediately after interrupts are disabled. This
; causes interrupts to be re-enabled by the RETFIE in the interrupt service
; routine. The work around is to verify that interrupts were indeed
; disabled on the next instruction and loop back if they weren't.
; The assembler switch INTR_OFF_BUG is set to TRUE if this processor
; has the bug.
;
intr_off macro
if ! using_interrupts
exitm
endif
local retry
if fam_16
retry
bcf intcon, gie
if intr_off_bug
btfsc intcon, gie
jump retry
endif
exitm
endif
if fam_17
retry
bsf cpusta, glintd
if intr_off_bug
btfss cpusta, glintd
jump retry
endif
exitm
endif
if fam_18
bcf intcon, gieh
exitm
endif
error "INTR_OFF macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro INTR_ON
;
; Globally enable interrupts without changing which interrupts are
; individually enabled. This macro together with INTR_OFF can be used
; around small sections of code that need to run with interrupts off.
;
intr_on macro
if ! using_interrupts
exitm
endif
if fam_16
bsf intcon, gie
exitm
endif
if fam_17
bcf cpusta, glintd
exitm
endif
if fam_18
bsf intcon, gieh
exitm
endif
error "INTR_ON macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro INTR_OFF_LOW
;
; Disable low priority interrupts.
;
; Operation is undefined unless separate high and low interrupt
; priorities are enabled. This is not checked.
;
; It is an error to call this macro on a processor that is not
; capable of high and low priority interrupts.
;
intr_off_low macro
if ! using_interrupts
exitm
endif
if fam_18
bcf intcon, giel
exitm
endif
error "INTR_OFF_LOW macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro INTR_ON_LOW
;
; Re-enable low priority interrupts.
;
; Operation is undefined unless separate high and low interrupt
; priorities are enabled. This is not checked.
;
; It is an error to call this macro on a processor that is not
; capable of high and low priority interrupts.
;
intr_on_low macro
if ! using_interrupts
exitm
endif
if fam_18
bsf intcon, giel
exitm
endif
error "INTR_ON_LOW macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro DTWORD val16
;
; Define a 16 bit value in a table. The low byte is stored first, the
; high byte second.
;
dtword macro val16
if fam_16
retlw low (val16)
retlw high (val16)
exitm
endif
if fam_17 || fam_18
db low (val16), high (val16)
exitm
endif
error "DTWORD macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro DT32I val32
;
; Define a 32 bit integer value in a table. The low byte is stored first,
; the high byte last.
;
dt32i macro val32
if fam_16
retlw (val32) & h'FF'
retlw ((val32) >> 8) & h'FF'
retlw ((val32) >> 16) & h'FF'
retlw ((val32) >> 24) & h'FF'
exitm
endif
if fam_17 || fam_18
data (val32) & h'FFFF'
data ((val32) >> 16)) & h'FFFF'
exitm
endif
error "DT32I macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro LOADK32 <adr>, <32 bit constant>
;
; Load the 32 bit constant into the memory locations starting at ADR.
; ADR is the address of the least significant byte. The remaining
; bytes follow at increasing memory addresses. The direct register
; bank must already be set for access to the four data bytes.
;
; This macro uses CLRF instructions when possible and avoids re-loading
; W with the same value.
;
loadk32 macro adr, kk
local currw, newval, ii
currw set 256 ;init current W to invalid value to force next load
ii set 0 ;init offset of next byte to write into
while ii < 4 ;once for the offset of each byte to write into
newval set ((kk) >> (ii * 8)) & 255 ;make value to write into this byte
if newval == 0
clrf (adr) + ii ;value is 0, use single instruction
else
if fam_18 && (newval == 255)
setf (adr) + ii ;value is all bits on, use single instruction
else
if currw != newval ;W not already set to the value for this byte ?
movlw newval ;load W with the value for this byte
currw set newval ;remember how W is loaded
endif
movwf (adr) + ii ;write W into this byte
endif
endif
ii set ii + 1 ;make offset for next byte
endw ;back to do the next byte
endm
;
;********************
;
; Macro LOADK24 <adr>, <24 bit constant>
;
; Load the 24 bit constant into the memory locations starting at ADR.
; ADR is the address of the least significant byte. The remaining
; bytes follow at increasing memory addresses. The direct register
; bank must already be set for access to the data bytes at ADR.
;
; This macro uses CLRF instructions when possible and avoids re-loading
; W with the same value.
;
loadk24 macro adr, kk
local currw, newval, ii
currw set 256 ;init current W to invalid value to force next load
ii set 0 ;init offset of next byte to write into
while ii < 3 ;once for the offset of each byte to write into
newval set ((kk) >> (ii * 8)) & 255 ;make value to write into this byte
if newval == 0
clrf (adr) + ii ;value is 0, use single instruction
else
if currw != newval ;W not already set to the value for this byte ?
movlw newval ;load W with the value for this byte
currw set newval ;remember how W is loaded
endif
movwf (adr) + ii ;write W into this byte
endif
ii set ii + 1 ;make offset for next byte
endw ;back to do the next byte
endm
;
;********************
;
; Macro LOADK16 <adr>, <16 bit constant>
;
; Load the 16 bit constant into the memory locations starting at ADR.
; ADR is the address of the least significant byte. The remaining
; bytes follow at increasing memory addresses. The direct register
; bank must already be set for access to the data bytes at ADR.
;
; This macro uses CLRF instructions when possible and avoids re-loading
; W with the same value.
;
loadk16 macro adr, kk
local currw, newval, ii
currw set 256 ;init current W to invalid value to force next load
ii set 0 ;init offset of next byte to write into
while ii < 2 ;once for the offset of each byte to write into
newval set ((kk) >> (ii * 8)) & 255 ;make value to write into this byte
if newval == 0
clrf (adr) + ii ;value is 0, use single instruction
else
if currw != newval ;W not already set to the value for this byte ?
movlw newval ;load W with the value for this byte
currw set newval ;remember how W is loaded
endif
movwf (adr) + ii ;write W into this byte
endif
ii set ii + 1 ;make offset for next byte
endw ;back to do the next byte
endm
;
;********************
;
; Macro LOADK8 <adr>, <8 bit constant>
;
; Load the 8 bit constant into the memory location at ADR. The direct register
; bank must already be set for access ADR.
;
; This macro uses CLRF and other instructions to load the value into ADR when
; possible.
;
loadk8 macro adr, kk
local newval
newval set low (kk) ;make the 8 bit value to store in NEWVAL
if newval == 0
clrf (adr) ;set to 0 in single cycle
exitm
endif
if fam_18 && (newval == h'FF')
setf (adr) ;set to FFh in single cycle
exitm
endif
movlw newval ;get the data value into W
movwf (adr) ;write it to the target
endm
;
;********************
;
; Macro LOADADR16 dest, adr
;
; Load the 16 bit address ADR into the two bytes starting at DEST. The direct register
; bank state must be set up for access to DEST. The low byte will be stored at DEST and
; the high byte at DEST+1. ADR may be a label or resolvable relocatable expressions
; that is not known at assembly time.
;
loadadr16 macro dest, adr
movlw low (adr)
movwf dest
movlw high (adr)
movwf (dest)+1
endm
;
;********************
;
; Macro LOADADR24 dest, adr
;
; Load the 24 bit address ADR into the three bytes starting at DEST. The direct register
; bank state must be set up for access to DEST. The low byte will be stored at DEST.
; ADR may be a label or resolvable relocatable expressions that is not known at assembly
; time.
;
loadadr24 macro dest, adr
movlw low (adr)
movwf dest
movlw high (adr)
movwf (dest)+1
movlw upper (adr)
movwf (dest)+2
endm
;
;********************
;
; Macro COPYN <dest>, <src>, <n>
;
; Copy the N bytes starting at SRC into the N bytes starting at DEST.
; The direct register bank must be set for access to all bytes of SRC
; and DEST. The results are undefined if SRC and DEST overlap. The
; copy instructions are written successively instead of a runtime
; loop. This macros is therefore intended for copying "short" values
; only.
;
copyn macro dest, src, n
local ii, didit
ii set 0 ;init offset from start of SRC and DEST
didit set false
while ii < n ;once for each byte to copy
if fam_16 | fam_16c5 | fam_12
movf src+ii, w ;get the source byte
movwf dest+ii ;write it into the destination
didit set true
endif
if fam_17
movfp src+ii, wreg ;get the source byte
movwf dest+ii ;write it into the destination
didit set true
endif
if fam_18
movff src+ii, dest+ii ;copy this byte
didit set true
endif
if ! didit
error "COPYN in STD.INS.ASPIC not implemented for this processor family"
endif
ii set ii + 1 ;increment byte offset for next iteration
endw
endm
;
;********************
;
; Macro COPY32 <dest>, <src>
;
; Copy the 32 bit value at SRC into DEST. The direct register bank
; must be set for access to all bytes of SRC and DEST. The results
; are undefined if SRC and DEST overlap.
;
copy32 macro dest, src
copyn dest, src, 4
endm
;
;********************
;
; Macro COPY24 <dest>, <src>
;
; Copy the 24 bit value at SRC into DEST. The direct register bank
; must be set for access to all bytes of SRC and DEST. The results
; are undefined if SRC and DEST overlap.
;
copy24 macro dest, src
copyn dest, src, 3
endm
;
;********************
;
; Macro COPY16 <dest>, <src>
;
; Copy the 16 bit value at SRC into DEST. The direct register bank
; must be set for access to all bytes of SRC and DEST. The results
; are undefined if SRC and DEST overlap.
;
copy16 macro dest, src
copyn dest, src, 2
endm
;
;********************
;
; Macro SHIFT32RL1 <adr>
;
; Perform a logical right shift of 1 bit on a 32 bit value. ADR is the
; address of the least significant byte. The remaining bytes follow
; at increasing memory addresses. The direct register bank must already
; be set for access to the four data bytes.
;
shift32rl1 macro adr
if fam_16
bcf status, c ;set value to shift into high bit
rrf (adr) + 3
rrf (adr) + 2
rrf (adr) + 1
rrf (adr) + 0
exitm
endif
if fam_17
bcf alusta, c
rrcf (adr) + 3
rrcf (adr) + 2
rrcf (adr) + 1
rrcf (adr) + 0
exitm
endif
if fam_18
bcf status, c
rrcf (adr) + 3
rrcf (adr) + 2
rrcf (adr) + 1
rrcf (adr) + 0
exitm
endif
error "SHIFT32RL1 macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro SHIFT32RA1 <adr>
;
; Perform an arithmetic right shift of 1 bit on a 32 bit value. ADR is the
; address of the least significant byte. The remaining bytes follow
; at increasing memory addresses. The direct register bank must already
; be set for access to the four data bytes.
;
; W is trashed.
;
shift32ra1 macro adr
if fam_16
rlf (adr) + 3, w ;set carry flag to bit value to shift in from left
rrf (adr) + 3
rrf (adr) + 2
rrf (adr) + 1
rrf (adr) + 0
exitm
endif
if fam_17
rlcf (adr) + 3, w ;set carry flag to bit value to shift in from left
rrcf (adr) + 3
rrcf (adr) + 2
rrcf (adr) + 1
rrcf (adr) + 0
exitm
endif
if fam_18
rlcf (adr) + 3, w ;set carry flag to bit value to shift in from left
rrcf (adr) + 3
rrcf (adr) + 2
rrcf (adr) + 1
rrcf (adr) + 0
exitm
endif
error "SHIFT32RA1 macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro SHIFT32L1 <adr>
;
; Perform a left shift of 1 bit on a 32 bit value. ADR is the
; address of the least significant byte. The remaining bytes follow
; at increasing memory addresses. The direct register bank must already
; be set for access to the four data bytes.
;
shift32l1 macro adr
if fam_16
bcf status, c ;set value to shift into low bit
rlf (adr) + 0
rlf (adr) + 1
rlf (adr) + 2
rlf (adr) + 3
exitm
endif
if fam_17
bcf alusta, c
rlcf (adr) + 0
rlcf (adr) + 1
rlcf (adr) + 2
rlcf (adr) + 3
exitm
endif
if fam_18
bcf status, c
rlcf (adr) + 0
rlcf (adr) + 1
rlcf (adr) + 2
rlcf (adr) + 3
exitm
endif
error "SHIFT32L1 macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro ADD32 <dest>, <src>, <temp>
;
; Add the 32 bit source value into the 32 bit destination value. TEMP
; will be used for temporary storage, and will be trashed. The 32 bit
; values are stored with the low byte first. The current direct access
; page must be set for access to all state.
;
add32 macro dest, src, temp
;**********
;
; For 16C family processors.
;
if fam_16
movf src, w
addwf dest ;do add for byte 0
clrw ;init to no carry
skip_ncarr
movlw 1
addwf dest+1 ;propagate carry
clrf temp ;init to no carry
skip_ncarr
incf temp ;carry happened on propagate carry
movf src+1, w
addwf dest+1 ;do the add for this byte
skip_ncarr
incf temp
movf temp, w
addwf dest+2 ;propagate carry
clrf temp ;init to no carry
skip_ncarr
incf temp ;carry happened on propagate carry
movf src+2, w
addwf dest+2 ;do the add for this byte
skip_ncarr
incf temp
movf temp, w
addwf dest+3 ;propagate carry
movf src+3, w
addwf dest+3 ;do the add for this byte
exitm
endif ;end of 16C processor case
;**********
;
; For 17C family processors.
;
if fam_17
movfp src+0, wreg
addwf dest+0 ;add byte 0
movfp src+1, wreg
addwfc dest+1 ;add byte 1
movfp src+2, wreg
addwfc dest+2 ;add byte 2
movfp src+3, wreg
addwfc dest+3 ;add byte 3
exitm
endif ;end of 17C processor case
;**********
;
; For 18 family processors.
;
if fam_18
movf (src)+0, w
addwf (dest)+0 ;add byte 0
movf (src)+1, w
addwfc (dest)+1 ;add byte 1
movf (src)+2, w
addwfc (dest)+2 ;add byte 2
movf (src)+3, w
addwfc (dest)+3 ;add byte 3
exitm
endif ;end of 18 family case
error "ADD32 macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro ADD24 <dest>, <src>, <temp>
;
; Add the 24 bit source value into the 24 bit destination value. TEMP
; will be used for temporary storage, and will be trashed. The 24 bit
; values are stored with the low byte first. The current direct access
; page must be set for access to all state.
;
add24 macro dest, src, temp
;**********
;
; For 16C family processors.
;
if fam_16
movf src, w
addwf dest ;do add for byte 0
clrw ;init to no carry
skip_ncarr
movlw 1
addwf dest+1 ;propagate carry
clrf temp ;init to no carry
skip_ncarr
incf temp ;carry happened on propagate carry
movf src+1, w
addwf dest+1 ;do the add for this byte
skip_ncarr
incf temp
movf temp, w
addwf dest+2 ;propagate carry
movf src+2, w
addwf dest+2 ;do the add for this byte
exitm
endif ;end of 16C processor case
;**********
;
; For 17C family processors.
;
if fam_17
movfp src+0, wreg
addwf dest+0 ;add byte 0
movfp src+1, wreg
addwfc dest+1 ;add byte 1
movfp src+2, wreg
addwfc dest+2 ;add byte 2
exitm
endif ;end of 17C processor case
;**********
;
; For 18 family processors.
;
if fam_18
movf (src)+0, w
addwf (dest)+0 ;add byte 0
movf (src)+1, w
addwfc (dest)+1 ;add byte 1
movf (src)+2, w
addwfc (dest)+2 ;add byte 2
exitm
endif ;end of 18 family case
error "ADD24 macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro ADD16 <dest>, <src>
;
; Add the 16 bit source value into the 16 bit destination value. The
; 16 bit values are stored with the low byte first. The current direct
; access page must be set for access to all state.
;
add16 macro dest, src
;**********
;
; For 16C family processors.
;
if fam_16
movf src+0, w
addwf dest+0 ;do add for byte 0
skip_ncarr ;no carry into upper byte ?
incf dest+1 ;propagate the carry
movf src+1, w
addwf dest+1 ;do the add for byte 1
exitm
endif ;end of 16C processor case
;**********
;
; For 17C family processors.
;
if fam_17
movfp src+0, wreg
addwf dest+0 ;add byte 0
movfp src+1, wreg
addwfc dest+1 ;add byte 1
exitm
endif ;end of 17C processor case
;**********
;
; For 18 family processors.
;
if fam_18
movf (src)+0, w
addwf (dest)+0 ;add byte 0
movf (src)+1, w
addwfc (dest)+1 ;add byte 1
exitm
endif ;end of 18 family case
error "ADD16 macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro SUB32 <dest>, <src>, <temp>
;
; Subtract the 32 bit source value from the 32 bit destination value
; and put the result into the destination. TEMP will be used for temporary
; storage, and will be trashed. The 32 bit values are stored with the
; low byte first. The current direct access page must be set for access
; to all state.
;
sub32 macro dest, src, temp
;**********
;
; For 16C family processors.
;
if fam_16
movf src+0, w
subwf dest+0 ;do subtract for byte 0
clrw ;init to no borrow
skip_nborr
movlw 1
subwf dest+1 ;propagate borrow
clrf temp ;init borrow from this byte
skip_nborr
incf temp ;borrow happened on propagate borrow
movf src+1, w
subwf dest+1 ;do the subtract for this byte
skip_nborr
incf temp ;set borrow from this byte
movf temp, w ;get borrow value from last byte
subwf dest+2 ;propagate borrow
clrf temp ;init borrow from this byte
skip_nborr
incf temp ;borrow happened on propagate borrow
movf src+2, w
subwf dest+2 ;do the subtract for this byte
skip_nborr
incf temp ;set borrow from this byte
movf temp, w ;get borrow value from last byte
subwf dest+3 ;propagate borrow
movf src+3, w
subwf dest+3
exitm
endif ;end of 16C processor case
;**********
;
; For 17C family processors.
;
if fam_17
movfp src+0, wreg
subwf dest+0 ;subtract byte 0
movfp src+1, wreg
subwfb dest+1 ;subtract byte 1
movfp src+2, wreg
subwfb dest+2 ;subtract byte 2
movfp src+3, wreg
subwfb dest+3 ;subtract byte 3
exitm
endif ;end of 17C processor case
;**********
;
; For 18 family processors.
;
if fam_18
movf (src)+0, w
subwf (dest)+0 ;subtract byte 0
movf (src)+1, w
subwfb (dest)+1 ;subtract byte 1
movf (src)+2, w
subwfb (dest)+2 ;subtract byte 2
movf (src)+3, w
subwfb (dest)+3 ;subtract byte 3
exitm
endif ;end of 18 family case
error "SUB32 macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro SUB24 <dest>, <src>
;
; Subtract the 24 bit source value from the 24 bit destination value
; and put the result into the destination. The 24 bit values are stored
; with the low byte first. The current direct access page must be set
; for access to all state.
;
sub24 macro dest, src
;**********
;
; For 16C family processors.
;
if fam_16
local nborr
movf src+0, w
subwf dest+0 ;do subtract for byte 0
skip_borr ;borrow from byte 1 ?
jump nborr ;no borrow
movlw 1
subwf dest+1 ;propagate the borrow to byte 1
skip_nborr ;no borrow from byte 2 ?
decf dest+2 ;propagate the borrow from byte 1 to byte 2
nborr ;skip to here on no borrow from byte 0
movf src+1, w
subwf dest+1 ;do the subtract for byte 1
skip_nborr ;no borrow from next higher byte ?
decf dest+2 ;propagate the borrow
movf src+2, w
subwf dest+2 ;do the subtract for byte 2
exitm
endif ;end of 16C processor case
;**********
;
; For 17C family processors.
;
if fam_17
sub24 macro dest, src
movfp src+0, wreg
subwf dest+0 ;subtract byte 0
movfp src+1, wreg
subwfb dest+1 ;subtract byte 1
movfp src+2, wreg
subwfb dest+2 ;subtract byte 2
exitm
endif ;end of 17C processor case
;**********
;
; For 18 family processors.
;
if fam_18
movf (src)+0, w
subwf (dest)+0 ;subtract byte 0
movf (src)+1, w
subwfb (dest)+1 ;subtract byte 1
movf (src)+2, w
subwfb (dest)+2 ;subtract byte 2
exitm
endif ;end of 18 family case
error "SUB24 macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro SUB16 <dest>, <src>
;
; Subtract the 16 bit source value from the 16 bit destination value
; and put the result into the destination. The 16 bit values are
; stored with the low byte first. The current direct access page must
; be set for access to all state.
;
sub16 macro dest, src
;**********
;
; For 16C family processors.
;
if fam_16
movf src+0, w
subwf dest+0 ;do subtract for byte 0
skip_nborr ;no borrow from high byte ?
decf dest+1 ;propagate the borrow
movf src+1, w
subwf dest+1 ;do subtract for byte 1
exitm
endif ;end of 16C processor case
;**********
;
; For 17C family processors.
;
if fam_17
movfp src+0, wreg
subwf dest+0 ;subtract byte 0
movfp src+1, wreg
subwfb dest+1 ;subtract byte 1
exitm
endif ;end of 17C processor case
;**********
;
; For 18 family processors.
;
if fam_18
movf (src)+0, w
subwf (dest)+0 ;subtract byte 0
movf (src)+1, w
subwfb (dest)+1 ;subtract byte 1
exitm
endif ;end of 18 family case
error "SUB16 macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro NEGATE <dest>, <n>
;
; Negate the twos complement value starting at address DEST. DEST is
; N bytes long and is stored with the least significant byte first.
; N must be greater than zero, and all bytes of DEST must be accessible
; with the current direct register bank selection.
;
negate macro dest, n
local ii
if n <= 0
error "N parameter to macro NEGATE must be at least 1."
endif
ii set 0 ;init byte offset into DEST
while ii < n ;once for each byte in DEST
comf dest+ii ;ones complement each byte
ii set ii + 1 ;make byte offset for next loop
endw
incf dest+0 ;increment the least significant byte
ii set 1 ;init byte offset into DEST
while ii < n ;once for each byte in DEST
btfsc status, z ;no carry from previous byte (wrapped to 0 on inc) ?
incf dest+ii ;propagate the carry to this byte
ii set ii + 1 ;make byte offset for next loop
endw
endm
;
;********************
;
; Macro FP24 <name>
;
; Declare memory for a 24 bit floating point number. The first (least
; significant) byte will have the label NAME. This macro simply reserves
; 3 bytes, documents that these bytes are intended to be one floating
; point value.
;
fp24 macro name
name res 3 ;least to most significant byte order
endm
;
;********************
;
; Macro FP24NEG <dest>
;
; Negate the 24 bit floating point number at DEST. The register bank setting
; must be set for direct access to DEST.
;
fp24neg macro dest
if fam_16
movlw h'80' ;get mask for bits to flip
movf (dest) + 2 ;set Z flag on the sign and exponent byte
skip_z ;FP value is zero, don't change anything ?
xorwf (dest) + 2 ;flip the sign bit
exitm
endif
if fam_18
movf (dest)+2 ;set Z flag if FP value is zero
skip_z ;FP value is zero ?
btg (dest)+2, 7 ;flip the sign bit
exitm
endif
error "FP24NEG macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro FP24ABS <dest>
;
; Set the 24 bit floating point number at DEST to its absolute value.
;
fp24abs macro dest
bcf (dest) + 2, 7 ;make sure the sign bit indicates zero or positive
endm
;
;********************
;
; Macro UART_SELECT <n>
;
; Select which UART will be accessed when UART registers and other symbols
; without a qualifier are used.
;
; For example, on machines with two UARTs, there is no TXREG, but TXREG1
; and TXREG2 instead. This macro would define TXREG to reference whichever
; is selected by the N argument.
;
; This macro allows code to be written that uses generic symbols, but that
; can be reused to drive all UARTs on machines that have multiple UARTs.
;
; Specifically, the following symbols will exist when the processor has
; the associated features:
;
; RCSTA - Receive status register
;
; TXSTA - Transmit status register
;
; SPBRG - Baud rate generator register
;
; TXREG - Transmit data register
;
; RCREG - Receive data register
;
; TXIF_REG - Register containing TXIF transmitter ready flag bit
; TXIF_BIT - TXIF bit number within its register
; TXIF_FLAG - String substitution macro for TXIF register and bit number
;
; TXIE_REG - Register containing TXIE transmitter ready flag bit
; TXIE_BIT - TXIE bit number within its register
; TXIE_FLAG - String substitution macro for TXIE register and bit number
;
; TXIP_REG - Register containing TXIP transmitter ready flag bit
; TXIP_BIT - TXIP bit number within its register
; TXIP_FLAG - String substitution macro for TXIP register and bit number
;
; RCIF_REG - Register containing RCIF transmitter ready flag bit
; RCIF_BIT - RCIF bit number within its register
; RCIF_FLAG - String substitution macro for RCIF register and bit number
;
; RCIE_REG - Register containing RCIE transmitter ready flag bit
; RCIE_BIT - RCIE bit number within its register
; RCIE_FLAG - String substitution macro for RCIE register and bit number
;
; RCIP_REG - Register containing RCIP transmitter ready flag bit
; RCIP_BIT - RCIP bit number within its register
; RCIP_FLAG - String substitution macro for RCIP register and bit number
;
; N must be greater than zero. It is an error if the processor does not
; have at least N UARTs when N is greater than 1. Nothing is done when
; N is 1 and the processor has no UARTS. N must be 1 on processors
; with only 1 UART.
;
uart_select macro n
if n < 1
error "Invalid parameter to UART_SELECT macro. Must be 1 or more."
exitm
endif
;
;***
;
; Handle first and only UART is being selected. In this case the
; normal symbols are already defined in the include file. Only the
; special symbols relating to the interrupt bits will be defined.
;
if n == 1
ifndef txreg1
ii set 0
ifdef txreg
ii set 1
endif
ifdef txreg1
ii set 1
endif
if ii == 0
exitm ;this processor has no UARTs
endif
txif_reg set pir1
txif_bit set txif
ifdef txif_flag
#undefine txif_flag
endif
#define txif_flag txif_reg, txif_bit
txie_reg set pie1
txie_bit set txie
ifdef txie_flag
#undefine txie_flag
endif
#define txie_flag txie_reg, txie_bit
ifdef txip
txip_reg set ipr1
txip_bit set txip
ifdef txip_flag
#undefine txip_flag
endif
#define txip_flag txip_reg, txip_bit
endif
rcif_reg set pir1
rcif_bit set rcif
ifdef rcif_flag
#undefine rcif_flag
endif
#define rcif_flag rcif_reg, rcif_bit
rcie_reg set pie1
rcie_bit set rcie
ifdef rcie_flag
#undefine rcie_flag
endif
#define rcie_flag rcie_reg, rcie_bit
ifdef rcip
rcip_reg set ipr1
rcip_bit set rcip
ifdef rcip_flag
#undefine rcip_flag
endif
#define rcip_flag rcip_reg, rcip_bit
endif
exitm
endif
endif
;
;***
;
; Select UART 1.
;
if n == 1
ifndef txreg1
error "Attempt to select non-existent UART with UART_SELECT macro."
exitm
endif
txif_reg set pir1
txie_reg set pie1
ifdef tx1ip
txip_reg set ipr1
endif
rcif_reg set pir1
rcie_reg set pie1
ifdef rc1ip
rcip_reg set ipr1
endif
rcsta set rcsta1
txsta set txsta1
spbrg set spbrg1
txreg set txreg1
rcreg set rcreg1
txif_bit set tx1if
ifdef txif_flag
#undefine txif_flag
endif
#define txif_flag txif_reg, txif_bit
txie_bit set tx1ie
ifdef txie_flag
#undefine txie_flag
endif
#define txie_flag txie_reg, txie_bit
ifdef tx1ip
txip_bit set tx1ip
ifdef txip_flag
#undefine txip_flag
endif
#define txip_flag txip_reg, txip_bit
endif
rcif_bit set rc1if
ifdef rcif_flag
#undefine rcif_flag
endif
#define rcif_flag rcif_reg, rcif_bit
rcie_bit set rc1ie
ifdef rcie_flag
#undefine rcie_flag
endif
#define rcie_flag rcie_reg, rcie_bit
ifdef rc1ip
rcip_bit set rc1ip
ifdef rcip_flag
#undefine rcip_flag
endif
#define rcip_flag rcip_reg, rcip_bit
endif
exitm
endif ;end of UART 1 case
;
;***
;
; Select UART 2.
;
if n == 2
txif_reg set pir3
txie_reg set pie3
ifdef tx2ip
txip_reg set ipr3
endif
rcif_reg set pir3
rcie_reg set pie3
ifdef rc2ip
rcip_reg set ipr3
endif
rcsta set rcsta2
txsta set txsta2
spbrg set spbrg2
txreg set txreg2
rcreg set rcreg2
txif_bit set tx2if
ifdef txif_flag
#undefine txif_flag
endif
#define txif_flag txif_reg, txif_bit
txie_bit set tx2ie
ifdef txie_flag
#undefine txie_flag
endif
#define txie_flag txie_reg, txie_bit
ifdef tx2ip
txip_bit set tx2ip
ifdef txip_flag
#undefine txip_flag
endif
#define txip_flag txip_reg, txip_bit
endif
rcif_bit set rc2if
ifdef rcif_flag
#undefine rcif_flag
endif
#define rcif_flag rcif_reg, rcif_bit
rcie_bit set rc2ie
ifdef rcie_flag
#undefine rcie_flag
endif
#define rcie_flag rcie_reg, rcie_bit
ifdef rc2ip
rcip_bit set rc2ip
ifdef rcip_flag
#undefine rcip_flag
endif
#define rcip_flag rcip_reg, rcip_bit
endif
exitm
endif ;end of UART 2 case
error "Selected UART does not exist or not implemented in UART_SELECT."
endm
;
;********************
;
; Make sure unqualified UART symbols exist for the first or only UART.
;
uart_select 1 ;select first UART, if any present
;
;********************
;
; Macro UART_BAUD <baud>
;
; This macro sets assembler variables to indicate the best baud rate
; generator configuration for the USART in asynchronous mode, given the
; desired baud rate and the oscillator frequency. No code is generated.
; The following assembler variables are set:
;
; VAL_SPBRG - Value for the SPBRG baud rate generator register.
; For UARTs with 16 bit baud rate generators this will be the
; full 16 bit value. The low byte is then intended for SPBRG,
; and the high byte for SPBRGH.
;
; BAUD_REAL - Real (not desired) baud rate resulting from the
; selected settings.
;
; VAL_TXSTA - Value for the TXSTA register. In addition to
; selecting the proper baud rate generator configuration, this
; value also selects the following:
;
; Asynchronous mode
; 8 bits per character
; Transmitter enabled
;
; VAL_RCSTA - Value for the RCSTA register. The value will
; select the following setup:
;
; Serial port enabled
; 8 bits per character
; Reception enabled
; Address detection disabled
;
; VAL_BAUDCTL - Value for the BAUDCTL register for those processors
; that have this. This register is part of the enhanced USART of
; parts like the 18F1320.
;
; This macro produces an assembly time warning if the closest available
; baud rate is more then 2.9% from the desired baud rate (off by 25% of
; a bit time in the middle of the last bit). It produces an assembly
; error if the baud rate error is 5.8% or higher.
;
uart_baud macro baud
local err ;baud rate error in parts per 1000
if uart_type == 0
error "UART_BAUD macro called, but this part has no UART."
exitm
endif
;
;**********
;
; Handle 18F1320 style advanced USART.
;
if uart_type == 3 ;18F1320 type advanced USART ?
val_txsta set b'00100100' ;set transmitter configuration
; X------- not used in asynchronous mode
; -0------ select 8 bits (not 9 bits) per char
; --1----- enable the transmitter
; ---0---- select asynchronous mode
; ----0--- do not send BREAK next char
; -----1-- init to using high speed baud rate mode
; ------X- read-only status bit
; -------X 9th bit of transmit data, not used
val_rcsta set b'10010000' ;set receiver configuration
; 1------- enable the serial port hardware
; -0------ select 8 bits per received character
; --X----- unused in asynchronous mode
; ---1---- enable the receiver
; ----0--- disable address detection
; -----XXX read-only status bits
val_baudctl set b'00001000' ;set baud rate configuration
; X-X--X-- unused bits
; -X------ read-only status bit
; ---X---- unused in asynchronous mode
; ----1--- use full 16 bit buad rate generator
; ------0- no wakeup on next falling edge
; -------0 disable auto baud rate detection on next char
val_spbrg set (freq_osc / baud) - 4 ;baud rate value, 2 fraction bits
val_spbrg set (val_spbrg + 2) >> 2 ;round and scale to SPBRG value
if val_spbrg > 65535
val_spbrg set 65535 ;clip at largest allowable value
endif
baud_real set freq_osc / (4 * (val_spbrg + 1)) ;find actual baud rate
err set (1000 * (baud_real - baud)) / baud ;baud rate err in parts/1000
if err < 0
err set -err ;make absolute value of error
endif
if err > 58
error "Baud rate error exceeds 5.8%"
endif
if err > 29
messg "WARNING: Baud rate error exceeds 2.9%"
endif
exitm
endif ;end of 18F1320 type advanced USART
;
;**********
;
; Assume normal 16 or 17 family USART.
;
; Init the non-baud rate bits of TXSTA.
;
val_txsta set b'00100000'
; X------- not used in asynchronous mode
; -0------ select 8 bits (not 9 bits) per char
; --1----- enable the transmitter
; ---0---- select asynchronous mode
; ----X--- unused
; -----0-- high/low baud rate select, will be set later
; ------X- read-only status bit
; -------X 9th bit of transmit data, not used
val_rcsta set b'10010000' ;set receiver configuration
; 1------- enable the serial port hardware
; -0------ select 8 bits per received character
; --X----- unused in asynchronous mode
; ---1---- enable the receiver
; ----0--- disable address detection
; -----XXX read-only status bits
;
; Init values assuming will use high speed mode.
;
if fam_16 || fam_18 ;init to high speed mode if supported
val_txsta set val_txsta | (1 << brgh)
endif
val_spbrg set freq_osc * 16 / baud - 256 ;find divider value, 8 fraction bits
val_spbrg set (val_spbrg + 128) >> 8 ;round and scale to final SPBRG value
baud_real set freq_osc / (16 * (val_spbrg + 1)) ;find actual selected baud rate
;
; Switch to low speed mode if the baud rate generator value would require
; more than 8 bits to represent, or this chip has no high speed mode.
;
if (val_spbrg > 255) || fam_17 ;too slow for high speed mode or no HS ?
if ! fam_17
val_txsta set val_txsta & ~(1 << brgh) ;disable high speed mode
endif
val_spbrg set freq_osc * 4 / baud - 256 ;find divider value, 8 fraction bits
val_spbrg set (val_spbrg + 128) >> 8 ;round and scale to final SPBRG value
if val_spbrg > 255 ;clip at largest allowable baud rate generator value
val_spbrg set 255
endif
baud_real set freq_osc / (64 * (val_spbrg + 1)) ;find actual selected baud rate
endif
err set (1000 * (baud_real - baud)) / baud ;baud rate err in parts/1000
if err < 0
err set -err ;make absolute value of error
endif
if err > 58
error "Baud rate error exceeds 5.8%"
endif
if err > 29
messg "WARNING: Baud rate error exceeds 2.9%"
endif
endm
;
;********************
;
; Macro UART_SETUP
;
; Set up the UART according to the assembler variables VAL_SPBRG,
; VAL_TXSTA, VAL_RCSTA, and VAL_BAUDCTL. See the documentation for
; the UART_BAUD macro for a description of these variables.
;
; The values do not need to be set by the UART_BAUD macro, although
; that may be a convenient way to do so.
;
uart_setup macro
dbankif rcsta
clrf rcsta ;disable UART and clear any error conditions
dbankif txsta
movlw val_txsta ;set transmitter configuration
movwf txsta
ifdef baudctl ;this processor has BAUDCTL register ?
dbankif baudctl
movlw val_baudctl
movwf baudctl
endif
dbankif spbrg
movlw low val_spbrg
movwf spbrg ;set baud rate generator period low byte
ifdef spbrgh
dbankif spbrgh
movlw high val_spbrg
movwf spbrgh ;set baud rate generator period high byte
endif
dbankif rcsta
movlw val_rcsta
movwf rcsta ;set receiver configuration and enable UART
endm
;
;********************
;
; Macro SETREG val, reg
;
; Set the register REG to the value VAL. Both REG and VAL must be
; constants. The instructions are optimized. For example, CLRF is
; used if the value is zero.
;
setreg macro val, reg
dbankif (reg)
if (val) == 0 ;value is exactly zero
clrf (reg)
else ;value is not zero
movlw (val)
movwf (reg)
endif
endm
;
;********************
;
; Macro TIMER0_PER cy
;
; Update timer 0 so that it next wraps CY cycles from the previous wrap. This
; can be useful in a timer 0 interrupt routine to set the exact number of
; cycles until the next timer 0 interrupt. Timer 0 is assumed to be running
; from the instruction clock. The appropriate value is added into timer 0,
; so this macro does not need to be invoked a fixed delay after the last
; timer 0 wrap. CY must be a constant.
;
; The timer sets its interrupt flag when counting from 255, which wraps back
; to 0. If left alone, the timer therefore has a period of 256 instruction
; cycles. When adding a value into the timer, the increment is lost during
; the add instruction, and the timer is not incremented for two additional
; cycles when the TMR0 register is written to. This effectively adds 3 more
; cycles to the timer 0 wrap period. These additional cycles are taken
; into account in computing the value to add to TMR0.
;
timer0_per macro cy
dbankif tmr0
movlw 256 + 3 - (cy)
addwf tmr0
endm
;
;********************
;
; Macro TIMER2_CYCLES tmr2ncy
;
; Calculate a timer 2 setup to achieve the interrupt period of TMR2NCY
; instruction cycles. This macro generates no code, but sets the following
; assembler variables:
;
; TMR2_PRE - timer 2 prescaler value: 1, 4, or 16
;
; TMR2_PER - timer 2 period divide value: 1 - 256
;
; TMR2_POS - timer 2 postscaler value: 1 - 16
;
; The hardware PWM period, if used, is defined by the prescaler and period
; values only. The number of instructions per PWM period is therefore
; TMR2_PRE * TMR2_PER, whereas the number of instructions per timer 2
; interrupt (if enabled) is TMR2_PRE * TMR2_PER * TMR2_POS.
;
; An error is generated if the indicated period can not be attained exactly
; within the constraints of the timer 2 hardware. The postscaler is
; set to the minimum possible value that results in the specified
; period.
;
timer2_cycles macro tmr2ncy
tmr2_pos set 1 ;init the postscaler to minimum
while tmr2_pos <= 16 ;loop thru increasing postscaler values
tmr2_pre set 1 ;init the prescaler to minimum
while tmr2_pre <= 16 ;loop thru increasing prescaler values
tmr2_per set tmr2ncy / (tmr2_pre * tmr2_pos) ;make period value with this pre/post
if (tmr2_per >= 1) && (tmr2_per <= 256) ;period within range ?
if (tmr2_pre * tmr2_per * tmr2_pos) == tmr2ncy ;result achieved exactly ?
exitm ;found exact result, all done
endif
endif
tmr2_pre set tmr2_pre * 4 ;advance to next prescaler value to try
endw ;back to try with this new prescaler value
tmr2_pos set tmr2_pos + 1 ;advance to next postscaler value to try
endw ;back to try with this new postscaler value
error "Requested timer 2 period can not be achieved in TIMER2_USEC."
endm
;
;********************
;
; Macro TIMER2_USEC usec
;
; Calculate a timer 2 setup to achieve an interrupt period of USEC
; microseconds. This macro is a wrapper around macro TIMER2_CYCLES where the
; period is expressed in microseconds instead of instruction cycles. See the
; TIMER2_CYCLES documentation (above) for details.
;
timer2_usec macro usec
local nsec ;period in nanoseconds
local ninstr ;number of instruction cycles in period
ninstr set ((freq_inst / 16) * usec) / 62500 ;instructions in period
if ((62500 * ninstr) / (freq_inst / 16)) != usec
error "Requested period not multiple of instruction time in TIMER2_USEC"
exitm
endif
timer2_cycles ninstr
endm
;
;********************
;
; Macro TIMER2_SETUP
;
; Set up timer 2 to periodically set the timer 2 interrupt flag.
; This macro only sets up the timer hardware and initializes the flag
; to reset. It does not enable the timer 2 interrupt.
;
; Timer 2 divides the instruction clock by a prescaler, period, and
; postscaler to make the interrupt period. The following assembler
; variables must be previously set to define the timer 2 divider chain:
;
; TMR2_PRE - timer 2 prescaler value: 1, 4, or 16
;
; TMR2_PER - timer 2 period divide value: 1 - 256
;
; TMR2_POS - timer 2 postscaler value: 1 - 16
;
; Note that the TIMER2_USEC macro can be used to compute these values
; given a desired interrupt period. It is an error if any of these
; assembler variables don't exist or are set to invalid values.
;
timer2_setup macro
local t2con_val
;
; Verify prescaler value is valid.
;
ifndef tmr2_pre
error "TMR2_PRE variable not defined before TIMER2_SETUP_INTR called."
exitm
endif
if !((tmr2_pre == 1) || (tmr2_pre == 4) || (tmr2_pre == 16))
error "Illegal TMR2_PRE value found in TIMER2_SETUP_INTR macro."
exitm
endif
;
; Verify period value is valid.
;
ifndef tmr2_per
error "TMR2_PER variable not defined before TIMER2_SETUP_INTR called."
exitm
endif
if (tmr2_per < 1) || (tmr2_per > 256)
error "Illegal TMR2_PER value found in TIMER2_SETUP_INTR macro."
exitm
endif
;
; Verify postscaler value is valid.
;
ifndef tmr2_pos
error "TMR2_POS variable not defined before TIMER2_SETUP_INTR called."
exitm
endif
if (tmr2_pos < 1) || (tmr2_pos > 16)
error "Illegal TMR2_POS value found in TIMER2_SETUP_INTR macro."
exitm
endif
;
; All configuration value are within range. Now set up the timer 2
; state and the associated interrupt.
;
dbankif pr2
movlw tmr2_per - 1 ;set the period register
movwf pr2
dbankif t2con
t2con_val set b'00000100' ;set static timer 2 control bits
; X------- unused
; -0000--- postscaler selection, set below
; -----1-- enable timer 2
; ------00 init prescaler to 1, altered below if different
t2con_val set t2con_val | ((tmr2_pos - 1) << 3) ;merge in postscaler field
if tmr2_pre == 4 ;prescaler divide value is 4 ?
t2con_val set t2con_val | 1 ;merge in value for prescaler divide by 4
endif
if tmr2_pre == 16 ;prescaler divide value is 16 ?
t2con_val set t2con_val | 2 ;merge in value for prescaler divide by 16
endif
movlw t2con_val ;set timer 2 control register
movwf t2con
dbankif tmr2
clrf tmr2 ;leave max time before first interrupt
endm
;
;********************
;
; Macro TIMER2_SETUP_INTR
;
; Same ast TIMER2_SETUP (above), but also enables the timer 2
; interrupt.
;
timer2_setup_intr macro
timer2_setup
dbankif pie1
bsf pie1, tmr2ie ;enable timer 2 interrupt
endm
;
;***********************************************************************
;
; I/O bits and ports handling.
;
; Create aliases for the data direction registers of the 17C family.
; Names will be created called TRISp to be compatible with the 16C
; naming convention.
;
; Create the aliases PORTA and TRISA on processors that have a single
; port called GPIO and an associated TRIS register called TRISIO.
; All the subsequent port handling code assumes that ports and their
; tristate registers are called PORTx and TRISx, with "X" starting at
; "A" for the first and continuing with consecutive letters for any
; additional ports.
;
ifdef gpio
porta equ gpio
endif
ifdef trisio
trisa equ trisio
endif
if fam_17 ;special handling for PIC 17 family
ifdef ddra
trisa equ ddra
endif
ifdef ddrb
trisb equ ddrb
endif
ifdef ddrc
trisc equ ddrc
endif
ifdef ddrd
trisd equ ddrd
endif
ifdef ddre
trise equ ddre
endif
ifdef ddrf
trisf equ ddrf
endif
ifdef ddrg
trisg equ ddrg
endif
ifdef ddrh
trish equ ddrh
endif
ifdef ddrj
trisj equ ddrj
endif
endif ;end of 17C family case
;
; Set up the following assembler variables:
;
; VAL_TRISp - Used to track the desired TRISp register value for
; each port, where P is the port name, as the "A" and "B" in PORTA,
; PORTB, etc. The values are initialized to 0, meaning all bits of
; the port are assumed to be outputs. In general, unused bits are
; left unconnected, and should be driven to avoid picking up spurious
; noise and causing unnecessary current flow and other potential
; problems. Bits in these registers are explicitly set by the
; /INBIT and /OUTBIT preprocessor directives. See the PREPIC program
; documentation file for a description of preprocessor directives.
;
; VAL_PORTp - Used to track the desired initial value for port
; registers. These symbols are all initialized to 0. Individual
; bits can be set with the /OUTBIT preprocessor directive if an
; initial bit value is supplied.
;
; VAL_PULLUPp - Used to track which bits for each port have a
; passive pullup enabled with the /INBIT preprocessor directive.
;
; PULLUPS_PORTp - Indicates which bits for each port can have
; passive pullups enabled. See comments at top of file for
; details. Any of these symbols not already defined for an
; existing port are defined here and set to 0, indicating the
; port has not passive pullups.
;
ifdef porta
val_trisa set 0
val_porta set 0
val_pullupa set 0
ifndef pullups_porta
pullups_porta equ 0
endif
endif
ifdef portb
val_trisb set 0
val_portb set 0
val_pullupb set 0
ifndef pullups_portb
pullups_portb equ 0
endif
endif
ifdef portc
val_trisc set 0
val_portc set 0
val_pullupc set 0
ifndef pullups_portc
pullups_portc equ 0
endif
endif
ifdef portd
val_trisd set 0
val_portd set 0
val_pullupd set 0
ifndef pullups_portd
pullups_portd equ 0
endif
endif
ifdef porte
val_trise set 0
val_porte set 0
val_pullupe set 0
ifndef pullups_porte
pullups_porte equ 0
endif
endif
ifdef portf
val_trisf set 0
val_portf set 0
val_pullupf set 0
ifndef pullups_portf
pullups_portf equ 0
endif
endif
ifdef portg
val_trisg set 0
val_portg set 0
val_pullupg set 0
ifndef pullups_portg
pullups_portg equ 0
endif
endif
ifdef porth
val_trish set 0
val_porth set 0
val_pulluph set 0
ifndef pullups_porth
pullups_porth equ 0
endif
endif
ifdef portj
val_trisj set 0
val_portj set 0
val_pullupj set 0
ifndef pullups_portj
pullups_portj equ 0
endif
endif
;
;***********************************************************************
;
; Bank and page switching.
;
; The assembler variables CURRIB and CURRDB are used to keep track of the
; current indirect and direct register bank settings. Note that these
; are only assembly time, not run time variables. They reflect which
; bank is currently ASSUMED to be selected. The programmer must therefore
; play an active role in making sure these variables are set correctly
; if they are used. Proper use can avoid unneccessary bank switching
; instructions.
;
; On the 17Cxxx series, similar assembler variables CURRRB and CURRGB
; are used. CURRRB is the current special function register bank set
; by the low nibble of BSR. CURRGB is the current general register
; bank set by the high nibble of BSR.
;
; On the 18 family, CURRIB is not used because each FSR contains the
; full target address. There are no RAM banks on the 18 family.
;
nobank equ -1 ;value to indicate current bank is unknown
if fam_16 ;16Cxxx except 16C5xx ?
currdb set nobank ;init current direct register bank assumption
currib set nobank ;init current indirect register bank assumption
endif
if fam_17 ;17Cxxx ?
currrb set nobank ;init current special function register bank
currgb set nobank ;init current general register bank
endif
if fam_18 ;18 family
currdb set nobank ;init current direct register bank assumption
endif
;
;********************
;
; Inline macro functions related to register banks, code pages, and
; other addressing issues.
;
;
; BANKOF (ADR)
;
; Returns the direct register bank number containing the RAM address ADR.
;
if fam_12
#define bankof(adr) (((adr) >> 5) & 3)
endif
if fam_16
#define bankof(adr) (((adr) >> 7) & 3)
endif
if fam_17
#define bankof(adr) (((adr) >> 8) & 15)
endif
if fam_18
#define bankof(adr) (((adr) >> 8) & 15)
endif
;
; IBANKOF (ADR)
;
; Returns the indirect register bank number containing the RAM address ADR.
;
if fam_12
#define ibankof(adr) (((adr) >> 5) & 3)
endif
if fam_16
#define ibankof(adr) (((adr) >> 8) & 1)
endif
if fam_17
#define ibankof(adr) (((adr) >> 8) & 15)
endif
if fam_18
#define ibankof(adr) (0) ;this processor has no indirect banks
endif
;
; BANKADRR (BANK)
;
; Returns an address within the special function register bank BANK.
;
if fam_17
#define bankadrr(bank) ((((bank) & 15) << 8) + h'10')
endif
;
; BANKADRG (BANK)
;
; Returns an address within the general RAM register bank BANK.
;
if fam_17
#define bankadrg(bank) ((((bank) & 15) << 8) + h'20')
endif
;
; BANKADR (BANK)
;
; Returns an address within the direct register bank BANK. An arbitrary
; address within the bank is returned, except that DBANKIF will not
; recognize it as an unbanked location.
;
if fam_12
#define bankadr(bank) (((bank) & 3) << 5)
endif
if fam_16
#define bankadr(bank) (((bank) & 3) << 7)
endif
if fam_17
#define bankadr(bank) ((((bank) & 15) << 8) + h'20')
endif
if fam_18
#define bankadr(bank) ((((bank) & 15) << 8) + h'FF' - (((bank) & 15) << 4))
endif
;
; IBANKADR (BANK)
;
; Returns an address within the indirect register bank BANK.
;
if fam_12
#define ibankadr(bank) (((bank) & 3) << 5)
endif
if fam_16
#define ibankadr(bank) (((bank) & 1) << 8)
endif
if fam_17
#define ibankadr(bank) ((((bank) & 15) << 8) + h'20')
endif
if fam_18
#define ibankadr(bank) (0) ;this processor has not indirect banks
endif
;
; INACCESSBANK (ADR)
;
; Returns 1 if ADR is within the access bank, 0 otherwise. Always
; returns 0 on processors that don't have an access bank.
;
if fam_18
#define inaccessbank(adr) (((adr) <= acclast) || ((adr) > (h'F00'+acclast)))
else
#define inaccessbank(adr) (0)
endif
;
; INBANKED (ADR)
;
; Returns 1 if ADR is in banked memory, 0 otherwise. Banked memory
; means some sort of bank settings must be properly set to access
; the memory.
;
if fam_18
#define inbanked(adr) (((adr) > (acclast)) && ((adr) <= (h'F00'+acclast)))
endif
if fam_16
#define inbanked(adr) (((adr) >= commregs_first) && ((adr) <= commregs_last)))
endif
;
; These constants provide addresses that are guaranteed to be within
; particular register banks. Note that the bank switching macros below
; take addresses, not bank numbers. These constants can be used to
; convert from bank numbers to addresses within the banks by using the
; #v(n) assembler syntax. For example, if "b" is a bank number, then:
;
; bank#v(b)adr
;
; is an address within bank b, which can be passed to the DBANKIF macro,
; for example.
;
ii set 0 ;init loop counter
while ii < nregbanks ;once for each possible direct register bank
bank#v(ii)adr equ bankadr(ii)
ii set ii + 1 ;advance to number of next register bank
endw
;
;********************
;
; Macro DBANK?
;
; Invalidate the current direct register data bank assumption.
; This macro produces no code, only sets assembly time state.
;
; On 17Cxxx processors, this sets both the special and general register
; bank assumptions to unknown. These processors make no distinction
; between a direct or indirect bank selection.
;
dbank? macro
if fam_17
currrb set nobank
currgb set nobank
exitm
endif
currdb set nobank
endm
;
;********************
;
; Macro IBANK?
;
; Invalidate the current indirect register data bank assumption.
; This macro produces no code, only sets assembly time state.
;
; On 17Cxxx processors, this sets both the special and general register
; bank assumptions to unknown. These processors make no distinction
; between a direct or indirect bank selection.
;
ibank? macro
if fam_18
exitm ;this processor has no indirect banks
endif
if fam_17
currrb set nobank
currgb set nobank
exitm
endif
currib set nobank
endm
;
;********************
;
; Macro UNBANK
;
; Invalidates all register bank assumptions.
;
unbank macro
dbank?
ibank?
endm
;
;********************
;
; Macro DBANKIS <adr>
;
; Set the current direct register bank assumption to the bank containing
; ADR. This macro generates no code to switch the register bank. It only
; updates assembly time state.
;
dbankis macro adr
if fam_17
currrb set bankof(adr)
currgb set bankof(adr)
exitm
endif
if fam_18
if inaccessbank(adr)
currdb set nobank
exitm
endif
endif
currdb set bankof(adr) ;set assumed direct register bank number
endm
;
;********************
;
; Macro IBANKIS <adr>
;
; Set the current indirect register bank assumption to the bank containing
; ADR. This macro generates no code to switch the register bank. It only
; updates assembly time state.
;
ibankis macro adr
if fam_18
exitm ;this processor has no indirect banks
endif
if fam_17
currrb set bankof(adr)
currgb set bankof(adr)
exitm
endif
currib set ibankof(adr) ;set assumed indirect register bank number
endm
;
;********************
;
; Macro RBANKIS <adr>
;
; Set the current special function register bank assumption to the bank
; containing ADR.
;
if fam_17
rbankis macro adr
currrb set bankof(adr)
endm
endif
;
;********************
;
; Macro GBANKIS <adr>
;
; Set the current general RAM register bank assumption to the bank
; containing ADR.
;
if fam_17
gbankis macro adr
currgb set bankof(adr)
endm
endif
;
;********************
;
; Macro RBANK?
;
; Invalidates the current special function register bank assumption.
;
if fam_17
rbank? macro
currrb set nobank
endm
endif
;
;********************
;
; Macro GBANK?
;
; Invalidates the current general RAM register bank assumption.
;
if fam_17
gbank? macro
currgb set nobank
endm
endif
;
;********************
;
; Macro DBANKIF <adr>
;
; Set the register bank for direct access to address ADR. This macro
; sets the RP0, RP1 bits of STATUS appropriately. The bank bits are
; not set if they are assumed to already be set correctly as indicated
; by the assembler variable CURRDB. CURRDB is updated to the new
; setting.
;
; On 18 family processors, this macro sets BSR. No code is emitted
; and the bank setting is not altered if ADR is within the access bank,
; and can therefore be accessed regardless of the BSR setting.
;
dbankif macro adr
local newbank, ii
newbank set bankof(adr) ;find 0-N desired bank number
;*****
;
; 17 family devices.
;
if fam_17
if nregbanks <= 1 ;this processor only has one register bank ?
exitm
endif
ii set (adr) & h'FF' ;make address offset within bank
;
; Avoid switching the bank if the target address is in an unbanked
; region. Note that both bank selects are always switched for
; offset 0 even though this address is unbanked. This is because
; BANKADR always returns the first address within a bank.
;
if ii == 0 ;special case to set both bank selects ?
if currrb != newbank
movlb newbank ;select new special function registers bank
currrb set newbank
endif
if currgb != newbank
movlr newbank ;select new general registers bank
currgb set newbank
endif
endif
if (ii >= h'10') && (ii <= h'17') ;adr is in banked special func regs region ?
if currrb != newbank
movlb newbank ;select new special function registers bank
currrb set newbank
endif
endif
if (ii >= h'20') && (ii <= h'FF') ;adr is in banked general regs region ?
if currgb != newbank
movlr newbank ;select new general registers bank
currgb set newbank
endif
endif
exitm
endif ;end of 17Cxxx case
;*****
;
; 16 family devices.
;
if fam_16
if nregbanks <= 1 ;this processor only has one register bank ?
exitm
endif
ii set (adr) & h'7F' ;make address offset within bank
if (ii >= commregs_first) && (ii <= commregs_last) ;in unbanked region ?
exitm
endif
if nregbanks > 1 ;bank bit 0 is meaningful ?
if (currdb < 0) || (currdb > 3) || ((currdb & 1) != (newbank & 1)) ;need update ?
if newbank & 1
bsf status, rp0
else
bcf status, rp0
endif
endif
endif
if nregbanks > 2 ;bank bit 1 is meaningful ?
if (currdb < 0) || (currdb > 3) || ((currdb & 2) != (newbank & 2)) ;need update ?
if newbank & 2
bsf status, rp1
else
bcf status, rp1
endif
endif
endif
currdb set newbank ;update current bank assumption
exitm
endif ;end of 16Cxxx case
;*****
;
; 18 family devices.
;
if fam_18
if inbanked(adr) ;address is in banked memory ?
if currdb != newbank ;not already in this bank ?
movlb newbank ;switch to the new bank
currdb set newbank ;update the current bank setting assumption
endif
endif
exitm
endif ;end of 18 family case
error "DBANKIF macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro DBANK <adr>
;
; Unconditionally set the direct access register bank for access to address
; ADR. The assembler state is updated.
;
dbank macro adr
dbank? ;invalidate current register bank assumption
dbankif adr ;set the new register bank
endm
;
;********************
;
; Macro IBANKIF <adr>
;
; Set the register bank for indirect access to address ADR, if needed.
; The assembler variable CURRIB is assumed to indicate the current
; indirect register bank setting. CURRIB is updated. This macro
; sets the IRP bit of STATUS appropriately.
;
ibankif macro adr
local newbank, ii
if fam_18
exitm ;this processor has no indirect banks
endif
newbank set ibankof(adr) ;find 0-N desired bank number
;*****
;
; 17Cxxx devices.
;
if fam_17
dbankif adr ;no direct/indirect distinction on these devices
exitm
endif ;end of 17Cxxx case
;*****
;
; 16Cxxx devices.
;
if fam_16
if nregbanks <= 2 ;this processor only has one indirect register bank ?
exitm
endif
ii set (adr) & h'7F' ;make address offset within direct bank
if (ii >= commregs_first) && (ii <= commregs_last) ;in unbanked region ?
exitm
endif
if nregbanks > 2 ;there is more than one indirect bank ?
if (currib < 0) || (currib > 1) || (currib != newbank) ;need update ?
if newbank & 1
bsf status, irp
else
bcf status, irp
endif
endif
endif
currib set newbank ;update current bank assumption
exitm
endif ;end of 16Cxxx case
error "IBANKIF macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro IBANK <adr>
;
; Unconditionally set the indirect access register bank for access to
; address ADR. CURRIB is updated.
;
ibank macro adr
ibank? ;invalidate current register bank assumption
ibankif adr ;set the new register bank
endm
;
;********************
;
; Macro RBANKIF <adr>
;
; This macro is only defined for processors that have different bank settings
; for the built in special function registers and the general RAM registers.
;
; Set the special function register bank for access to ADR. Nothing is done
; if ADR is not within the special banked special function register range.
;
if fam_17
rbankif macro adr
local newbank, ii
newbank set bankof(adr) ;find 0-N desired bank number
ii set (adr) & h'FF' ;make address offset within bank
if (ii >= h'10') && (ii <= h'17') ;adr is in banked special func regs region ?
if currrb != newbank
movlb newbank ;select new special function registers bank
currrb set newbank
endif
endif
endm
endif ;end of FAM_17 case
;
;********************
;
; Macro GBANKIF <adr>
;
; This macro is only defined for processors that have different bank settings
; for the built in special function registers and the general RAM registers.
;
; Set the general RAM register bank for access to ADR. Nothing is done
; if ADR is not within the general RAM register range.
;
if fam_17
gbankif macro adr
local newbank, ii
newbank set bankof(adr) ;find 0-N desired bank number
ii set (adr) & h'FF' ;make address offset within bank
if (ii >= h'20') && (ii <= h'FF') ;adr is in banked general regs region ?
if currgb != newbank
movlr newbank ;select new general registers bank
currgb set newbank
endif
endif
endm
endif ;end of FAM_17 case
;
;********************
;
; Macro DEFRAM <address>
;
; Set up for allocating RAM with subsequent RES directives. ADDRESS
; is the address that will be passed to DBANKIF to access any of the
; RAM defined in this section. If ADDRESS indicates normal banked
; memory, then the appropriate .BANKn linker section will be started
; with a UDATA directive. If ADDRESS is in common RAM, then the
; RAM will be in the .UDATA_SHR section. If ADDRESS is in the access
; bank of an 18 family, then the RAM will be allocated in the
; .UDATA_ACS section.
;
; If the new linker section would be the same as the linker section
; defined by the previous DEFRAM, then no new directives are written
; since only one occurrence of a linker section is allowed per source
; module.
;
; The assembler symbol DEFRAM_LAST is used to track linker section of
; the last DEFRAM invocation. This symbol has the following values:
;
; -1 - No previous DEFRAM section
;
; -2 - Last linker section was .UDATA_SHR
;
; -3 - Last linker section was .UDATA_ACS
;
; 0-N - Last linker section was .BANKn
;
defram_last set -1 ;init to DEFRAM not previously invoked
defram macro address
local badr
badr set address
;*****
;
; 16 family devices.
;
if fam_16
local ii
ii set badr & h'7F' ;make address offset within bank
if (ii >= commregs_first) && (ii <= commregs_last) ;in unbanked region ?
if defram_last == -2 ;already in this region ?
exitm
endif
udata_shr ;start the new RAM linker section
defram_last set -2 ;remember which linker section now in
exitm
endif
ii set bankof(badr) ;make 0-N register bank number
if defram_last == ii ;already in this linker section ?
exitm
endif
.bank#v(ii) udata ;start the new RAM linker section
defram_last set ii ;remember which linker section now in
exitm
endif ;end of 16 family case
;*****
;
; 18 family devices.
;
if fam_18
local ii
if ((badr >= 0) && (badr <= h'7F')) || ((badr >= h'F80') && (badr <= h'FFF'))
if defram_last == -3 ;already in access RAM ?
exitm
endif
udata_acs ;start the new RAM linker section
defram_last set -3 ;remember which linker section now in
exitm
endif ;end of access RAM case
ii set bankof(badr) ;make 0-N register bank number
if defram_last == ii ;already in this linker section ?
exitm
endif
.bank#v(ii) udata ;start the new RAM linker section
defram_last set ii ;remember which linker section now in
exitm
endif ;end of 18 family case
error "DEFRAM macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;***********************************************************************
;
; Software stack.
;
; The processor has a limited stack which is only used for subroutine
; return addresses. It can not be accessed directly as a general
; data stack. This section implements a general data stack.
;
; Subroutines are normally expected to preserve the general registers
; REG0 - REGn. This can be done by using the software stack.
;
; On the 16 and 17 family the stack grows from high to low addresses,
; and from low to high addresses on the 18 family due to the available
; auto increment and decrement operations available for the FSR
; registers.
;
; The global register STACKP contains the address of the last byte
; pushed onto the stack. This is a software register on the 16 family,
; and one of the FSR registers on the 17 and 18 families. STACKP
; is assumed to be accessible without requiring bank switching. It is
; defined in the STACK module when it is a software register.
;
; The stack is actually defined in module STACK.ASPIC. Routine
; STACK_INIT must be called to initialize the stack if it is used at
; all. The external reference to STACK_INIT also forces the STACK
; module to be loaded, thereby defining the stack. Otherwise, the
; stack is not loaded and it does not consume any memory.
;
stackbank equ bankof(stacklast) ;direct register bank containing the stack
if fam_17
stackp equ fsr1 ;second pointer reg will be used as stack pointer
endif
if fam_18
stackp equ fsr#v(fsrstack) ;FSR will be reserved as stack pointer
endif
;
;********************
;
; Macro STACK_SET adr
;
; Set the data stack so that ADR will be the next byte pushed. The
; stack is handled and initialized differently depending on whether
; C18 compatibility is enabled.
;
stack_set macro adr
if fam_18 == 0
error Macro STACK_SET only defined for PIC 18 family.
endif
if c18comp
;
; C18 compatibility mode.
;
lfsr 1, adr
lfsr 2, adr
else
;
; Normal Embed Inc environment.
;
lfsr fsrstack, (adr) - 1
endif
endm
;
;********************
;
; Macro PUSHREG reg
;
; Push the contents of the register REG onto the stack. The direct bank
; must already be set for access to REG. On some processors, it is more
; efficient to push a set of general registers onto the stack with one
; PUSHREGS macro than to use the PUSHREG macro several times. This macro
; also trashes W on some processors. In that case the assembler variable
; W_TRASHED is set to TRUE, otherwise it is left unaltered. Some of
; the specifics per processor are:
;
; 16 family - Relatively inefficient to push a single value. W
; trashed.
;
; 18 family - Efficient to push a single value. Multiple PUSHREG
; just as efficient as a single PUSHREGS pushing the same state.
;
pushreg macro reg
;
; 18 family processor. The FSR indicated by FSRSTACK is reserved as the
; software stack pointer. Auto increment/decrement allows a push or pop
; in a single instruction.
;
if fam_18
if c18comp
movff reg, postinc#v(fsrstack) ;use C18 stack convention
else
movff reg, preinc#v(fsrstack) ;use normal stack convention
endif
exitm
endif
error "PUSHREG macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro PUSHW
;
; Push the contents of W onto the data stack.
;
pushw macro
if fam_18
if c18comp
movwf postinc#v(fsrstack)
else
movwf preinc#v(fsrstack)
endif
exitm
endif
error "PUSHW macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro POPREG reg
;
; Pop the top of the software stack into REG. The direct bank
; must already be set for access to REG. On some processors, it is more
; efficient to pop a set of general registers onto the stack with one
; POPREGS macro than to use the POPREG macro several times. This macro
; also trashes W on some processors. In that case the assembler variable
; W_TRASHED is set to TRUE, otherwise it is left unaltered. Some of
; the specifics per processor are:
;
; 16 family - Relatively inefficient to pop a single value. W
; trashed.
;
; 18 family - Efficient to pop a single value if normal Embed Inc conventions
; used. Multiple POPREG just as efficient as a single POPREGS. If C18
; compiler compatibility enabled, then popping multiple registers with
; one POPREGS invocation is more efficient than popping the same registers
; with individual POPREG invocations.
;
popreg macro reg
;
; 18 family processor.
;
if fam_18
if c18comp
movf postdec#v(fsrstack) ;C18 stack convention
movff indf#v(fsrstack), reg
else
movff postdec#v(fsrstack), reg ;normal stack convention
endif
exitm
endif
error "POPREG macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro POPW
;
; Pop the top of the data stack into W.
;
popw macro
if fam_18
if c18comp
movf postdec#v(fsrstack)
movf indf#v(fsrstack), w
else
movf postdec#v(fsrstack), w
endif
exitm
endif
error "POPW macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro POPSTACK n
;
; Pop N bytes off the top of the data stack. The byte values are lost.
; W is trashed.
;
; WARNING: This macro assumes that interrupts are enabled. It must not be used
; if interrupts are disabled because it could enable them.
;
popstack macro n
local psii
if fam_18
if (n) <= 4
psii set 0
while psii < (n)
movf postdec#v(fsrstack)
psii set psii + 1
endw
exitm
endif
movlw low n
intr_off ;temp disable interrupts while stack pointer inconsistent
subwf fsr#v(fsrstack)l
movlw high n
subwfb fsr#v(fsrstack)h
intr_on ;re-enable interrupts after stack pointer consistent again
exitm
endif ;end of FAM_18 case
error "POPSTACK macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Stack utility macros that are private to the other macros below.
; These are not intended to be used directly in source code.
;
if fam_17
dopush macro adr ;push an individual register onto the stack
if (bankof(adr) == bankof(stacklast)) || ((adr & h'FF') < h'20') ;same bank ?
dbankif stacklast
movfp adr, indf1 ;move value directly to the stack
else ;variable and stack not in same bank
dbankif adr
movfp adr, wreg ;temp get value to push into WREG
dbankif stacklast
movwf indf1 ;move the WREG value onto the stack
endif
endm
dopop macro adr ;pop an individual register from the stack
if (bankof(adr) == bankof(stacklast)) || ((adr & h'FF') < h'20') ;same bank ?
dbankif stacklast
movpf indf1, adr ;get the value directly from the stack
else ;variable and stack not in same bank
dbankif stacklast
movpf indf1, wreg ;temp save the value from the stack in WREG
dbankif adr
movwf adr ;move the WREG value to its final destination
endif
endm
endif ;end of 17 family private stack macros
;
;********************
;
; Macro PUSHREGS <regflags>
;
; Push the indicated general registers onto the stack. The REGFLAGS
; argument is the OR of the REGFn flags for all the registers that
; are to be pushed. The registers are pushed in low to high order.
;
; It may be more efficient to call this macro once with all the registers
; to push than to call it separately for each register. See the
; PUSHREG macro description for details.
;
; The assembler variable N_BYTES_PUSHED is set to the number of bytes that
; were pushed onto the stack.
;
; NOTE: On PICs with indirect banks, the current indirect bank assumption
; must be correct. If the current indirect bank setting is not known,
; then the IBANK? macro should be invoked before this macro.
;
; Trashed:
;
; W, FSR, indirect register bank with CURRIB updated.
;
pushregs macro regflags ;push the indicated general registers onto SW stack
local f, reg
n_bytes_pushed set 0 ;init to no bytes pushed
f set (regflags) & regf_all ;make local sanitized flags value
if f == 0 ;nothing to push ?
exitm
endif
;*****
;
; 16 family processors. These use a software stack pointer.
;
if fam_16
ifndef stackp
extern stackp
endif
ibankif stacklast ;make sure indirect reg bank set for stack access
movf stackp, w ;set indirect pointer to current stack top
movwf fsr
;
; There is at least one register to push and indirect addressing has been
; set up for the current top of the stack.
;
; Now push all the registers that have been selected.
;
reg set 0 ;init register number
while reg < numregs ;once for each possible register
if f & regf#v(reg) ;this register enabled ?
decf fsr ;point to new stack location
movf reg#v(reg), w ;get the register value in W
movwf indf ;write it to the new stack location
n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack
endif
reg set reg + 1 ;advance to next register number
endw ;back to process this new register number
if f & regff ;FLAGS register enabled ?
decf fsr ;point to new stack location
movf flags, w ;get the register value in W
movwf indf ;write it to the new stack location
n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack
endif
;
; Done pushing individual registers.
;
movf fsr, w ;get new top of stack address
movwf stackp ;update stack pointer register
exitm
endif ;end of 16 family case
;*****
;
; For the 17 family processors. FSR1 is reserved as the stack pointer.
;
if fam_17
;
; There is at least one register to push. FSR1 is pointing to the last
; byte pushed onto the stack.
;
; Set up FSR1 for post decrement, then decrement it once to be ready to
; write the first byte to the stack.
;
bcf alusta, fs3 ;set FSR1 mode to post decrement
bcf alusta, fs2
decf fsr1 ;set up for first write to the stack
;
; Now push all the registers that have been selected.
;
reg set 0 ;init register number
while reg < numregs ;once for each possible register
if f & regf#v(reg) ;this register enabled ?
dopush reg#v(reg)
n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack
endif
reg set reg + 1 ;advance to next register number
endw ;back to process this new register number
if f & regff ;FLAGS register enabled ?
dopush flags
n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack
endif
;
; Done pushing individual registers.
;
incf fsr1 ;leave stack pointer pointing to last byte pushed
exitm
endif ;end of 17 family case
;*****
;
; 18 Family processors. The FSR indicated by FSRSTACK is reserved as the
; stack pointer.
;
; The normal Embed Inc convention is that the stack pointer points to the
; top byte on the stack. The C18 convention is that it points to the next
; byte after the top on the stack (where the next pushed byte would go).
; In both cases the stack grows towards higher addresses.
;
if fam_18
reg set 0 ;init register number
while reg < numregs ;once for each possible register
if f & regf#v(reg) ;this register enabled ?
if c18comp
movff reg#v(reg), postinc#v(fsrstack) ;C18 stack convention
else
movff reg#v(reg), preinc#v(fsrstack) ;normal stack convention
endif
n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack
endif
reg set reg + 1 ;advance to next register number
endw ;back to process this new register number
if f & regff ;FLAGS register enabled ?
if c18comp
movff flags, postinc#v(fsrstack) ;C18 stack convention
else
movff flags, preinc#v(fsrstack) ;normal stack convention
endif
n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack
endif
exitm
endif ;end of 18 family case
;*****
;
error "PUSHREGS macro in STD.INS.ASPIC not implemented for this processor"
endm
;
;********************
;
; Macro POPREGS <regflags>
;
; This macro performs the reverse operation to the PUSHREGS macro. The
; REGFLAGS argument is the OR of a set of REGFn flags to indicate the
; registers to be popped from the stack. The registers are popped in
; high to low order.
;
; It may be more efficient to call this macro once with all the registers
; to pop than to call it separately for each register. See the
; POPREG macro description for details.
;
; NOTE: On PICs with indirect banks, the current indirect bank assumption
; must be correct. If the current indirect bank setting is not known,
; then the IBANK? macro should be invoked before this macro.
;
; Trashed:
;
; W, FSR, indirect register bank, CURRIB updated
;
popregs macro regflags ;pop the indicated general registers from the SW stack
local f, reg
f set (regflags) & regf_all ;make local sanitized flags value
if f == 0 ;nothing to pop ?
exitm
endif
;*****
;
; 16 family processors. These use a software stack pointer.
;
if fam_16
ifndef stackp
extern stackp
endif
ibankif stacklast ;make sure indirect reg bank set for stack access
movf stackp, w ;set indirect pointer to current stack top
movwf fsr
;
; There is at least one register to pop and indirect addressing has been
; set up for the current top of the stack.
;
; Now pop all the registers that have been selected.
;
if f & regff ;FLAGS register enabled ?
movf indf, w ;get the top stack value
movwf flags ;update the register value
incf fsr ;point to new top of stack location
endif
reg set numregs - 1 ;init register number
while reg >= 0 ;once for each possible register
if f & regf#v(reg) ;this register selected ?
movf indf, w ;get the top stack value
movwf reg#v(reg) ;update the register value
incf fsr ;point to new top of stack location
endif
reg set reg - 1 ;advance to next register number
endw ;back to process this new register number
;
; Done poping poping individual registers.
;
movf fsr, w ;get new top of stack address
movwf stackp ;update stack pointer register
exitm
endif ;end of 16 family case
;*****
;
; For the 17 family processors. FSR1 is reserved as the stack pointer.
;
if fam_17
local f, reg
f set (regflags) & regf_all ;make local sanitized flags value
if f == 0 ;nothing to push ?
exitm
endif
;
; There is at least one register to pop. FSR1 is pointing to the last
; byte pushed onto the stack, which is the first byte to pop.
;
; Set up FSR1 for post increment.
;
bcf alusta, fs3 ;set FSR1 mode to post decrement
bsf alusta, fs2
;
; Now pop all the registers that have been selected.
;
if f & regff ;FLAGS register enabled ?
dopop flags
endif
reg set numregs - 1 ;init register number
while reg >= 0 ;once for each possible register
if f & regf#v(reg) ;this register selected ?
dopop reg#v(reg)
endif
reg set reg - 1 ;advance to next register number
endw ;back to process this new register number
exitm
endif ;end of 17 family case
;*****
;
; 18 family processors. The FSR indicated by FSRSTACK is reserved as the
; stack pointer.
;
; The normal Embed Inc convention is that the stack pointer points to the
; top byte on the stack. The C18 convention is that it points to the next
; byte after the top on the stack (where the next pushed byte would go).
; In both cases the stack grows towards higher addresses.
;
if fam_18
if c18comp ;using C18 stack convention ?
movf postdec#v(fsrstack) ;point to the top byte on the stack
endif
if f & regff ;FLAGS register enabled ?
f set f & ~regff ;disable this register flag
if c18comp
if f
movff postdec#v(fsrstack), flags ;at least one more to pop after this
else
movff indf#v(fsrstack), flags ;this is last pop this macro invocation
endif
else
movff postdec#v(fsrstack), flags ;pop using normal stack convention
endif
endif
reg set numregs - 1 ;init register number
while reg >= 0 ;once for each possible register
if f & regf#v(reg) ;this register selected ?
f set f & ~regf#v(reg) ;disable this register flag
if c18comp
if