; ***************************************************************
; * Copyright (C) 2007, 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 dsPIC 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.DSPIC is provided to set defaults for all
; the required assembly values. An application should include STD_DEF.INS.DSPIC,
; then set any values it knows and cares about, then include STD.INS.DSPIC.
; In this way, applications are protected from changes to this include
; file that may require additional values to be set.
;
; The following preprocessor symbols may be defined before this file.
;
; FREQ_OSC - Processor oscillator frequency in Hz. This is the
; effective oscillator frequency after the PLL, if enabled, is
; applied. Required.
;
; FREQ_INST - Instruction cycle frequency in Hz. The default is
; FREQ_OSC / 4.
;
; DEBUG_ICD - Boolean. TRUE means built for debugging with a in-circuit
; debugger. This may cause some system resources to be reserved for the
; debugger. NOPs are added to some macros to make it appear the debugger
; can stop on the macro when it actually skids a instruction or two. The
; default is FALSE.
;
; DEBUG - Boolean. TRUE means built for source level debugging. This
; does not imply support for the ICD or RealIce is required. DEBUG is
; forced to TRUE if DEBUG_ICD is TRUE.
;
; USING_C30 - Boolean. The firmware build includes C30 modules, not just
; ASM30 modules. This may cause additional C callable entry points to be
; defined for some standard subsystems. Defaults to FALSE.
;
/if [not [exist "freq_inst"]] then
/const freq_inst real = [/ freq_osc 4]
/endif
/if [not [exist "debug_icd"]] then
/const debug_icd bool = false
/endif
/if [not [exist "debug"]] then
/const debug bool = false
/endif
/var new dbg bool = debug
/del debug
/const debug bool = [or dbg debug_icd]
/del dbg
/if [not [exist "using_c30"]] then
/const using_c30 bool = false
/endif
.equiv debug_icd, [if debug_icd 1 0]
.set nflagb, 0 ;init to no GFLn flag registers allocated
;
; Derived constants.
;
.equiv freq_osc, [rnd freq_osc] ;final oscillator frequency in Hz
.equiv freq_inst, [rnd freq_inst] ;instruction cycle frequency in Hz
;*******************************************************************************
;
; General utility macros.
;
;********************
;
; Macro NOSKID
;
.macro noskid
.if debug_icd
nop
nop
.endif
.endm
;********************
;
; Macro SKIP_Z
;
; Skip the next instruction if the Z flag is set.
;
.macro skip_z
btss Sr, #Z
.endm
;********************
;
; Macro SKIP_NZ
;
; Skip the next instruction if the Z flag is not set.
;
.macro skip_nz
btsc Sr, #Z
.endm
;********************
;
; Macro BRA_BORR target
;
; Branch to TARGET if the last arthmetic operation resulted in a
; borrow (C flag not set).
;
.macro bra_borr target
bra nc, \target
.endm
;********************
;
; Macro BRA_NBORR target
;
; Branch to TARGET if the last arthmetic operation did not result in a
; borrow (C flag set).
;
.macro bra_nborr target
bra c, \target
.endm
;********************
;
; Macro SKIP_BORR
;
; Skip the next instruction if the last arithmetic operation resulted in a
; borrow.
;
.macro skip_borr
btsc Sr, #C
.endm
;********************
;
; Macro SKIP_NBORR
;
; Skip the next instruction if the last arithmetic operation did not result in
; a borrow.
;
.macro skip_nborr
btss Sr, #C
.endm
;********************
;
; Macro BRA_CARR target
;
; Branch to TARGET if the last arthmetic operation resulted in a
; carry (C flag set).
;
.macro bra_carr target
bra c, \target
.endm
;********************
;
; Macro BRA_NCARR target
;
; Branch to TARGET if the last arthmetic operation did not result in a
; carry (C flag not set).
;
.macro bra_ncarr target
bra nc, \target
.endm
;********************
;
; Macro SKIP_CARR
;
; Skip the next instruction if the last arithmetic operation resulted in a
; carry.
;
.macro skip_carr
btss Sr, #C
.endm
;********************
;
; Macro SKIP_NCARR
;
; Skip the next instruction if the last arithmetic operation did not result in
; a carry.
;
.macro skip_ncarr
btsc Sr, #C
.endm
;********************
;
; Macro SKIP_xxx
;
; Various macros that skip the next instruction on various conditions that can
; only be tested by BRA instructions. These execute a BRA to skip one
; instruction when the condition is true.
;
.macro skip_lt ;skip if less than, signed
bra lt, $+4
.endm
.macro skip_ltu ;skip if less than, unsigned
bra ltu, $+4
.endm
.macro skip_le ;skip if less than or equal to, signed
bra le, $+4
.endm
.macro skip_leu ;skip if less than or equal to, unsigned
bra leu, $+4
.endm
.macro skip_eq ;skip if equal to
bra z, $+4
.endm
.macro skip_ne ;skip if not equal to
bra nz, $+4
.endm
.macro skip_ge ;skip if greater than or equal to, signed
bra ge, $+4
.endm
.macro skip_geu ;skip if greater than or equal to, unsigned
bra geu, $+4
.endm
.macro skip_gt ;skip if greater than, signed
bra gt, $+4
.endm
.macro skip_gtu ;skip if greater than, unsigned
bra gtu, $+4
.endm
.macro skip_neg ;skip if negative
bra n, $+4
.endm
.macro skip_posz ;skip if positive or zero
bra nn, $+4
.endm
;********************
;
; Macro INTR_PRIORITY reg, bit, prio
;
; Set the interrupt priority field in register REG to PRIO. PRIO must be 0-7.
; BIT is the number of the LSB of the priority field within REG. Valid values
; for BIT are 0, 4, 8, and 12.
;
; W0 and W1 are trashed.
;
.macro intr_priority reg, bit, prio
mov \reg, w0 ;get the existing priority register value
mov #(0xF << \bit)^0xFFFF, w1 ;get inverse mask for priority field
and w0, w1, w0 ;mask off existing priority value
mov #((\prio & 0xF) << \bit), w1 ;get new priority in position
ior w0, w1, w0 ;merge in new priority field value
mov w0, \reg ;update priority register with the new value
.endm
;********************
;
; Subroutine FP48_MAKE val
;
; Converts VAL to 48 bit floating point. VAL must be convertable to a
; preprocessor floating point value. The integer preprocessor variables
; FP48_EXP and FP48_MANT are set to the exponent word and mantissa,
; respectively.
;
; The exponent word is 16 bits wide with the high bit being the overall sign
; bit. 0 is positive and 1 negative. The low 15 bits of the exponent word
; are the power of 2 exponent to apply to the mantissa value plus 16384. For
; example, if the low 15 bits have a value of 16385, then 2^1 is to be applied
; to the mantissa value. Likewise, 16381 specifies to apply 2^-3.
;
; The mantissa is 32 bits wide. Its value is as if the binary point and then
; a 1 bit were immediately to its left. For example, the mantissa value of
; 3456789Ah is to be interpreted as the hexadecimal fixed point value
; 1.3456789A, which has a decimal value of about 1.204444. The mantissa value
; is therefore always from 1 up to but not including 2.
;
; The overal floating point value is the mantissa value times the power of
; 2 indicated by the exponent, and the sign indicated by the high bit of the
; exponent word.
;
; The special case of 0 is represented by all 48 bits 0.
;
/subroutine fp48_make
/var exist fp48_exp integer
/var exist fp48_mant integer
/var local val real = [arg 1]
/var local neg bool ;overal value is negative
/var local exp integer ;power of 2 exponent
/var local man1 integer ;mantissa low 16 bits
/var local man2 integer ;mantissa high 16 bits
/if [= val 0.0] then ;special case of 0 ?
/set fp48_exp 0
/set fp48_mant 0
/return
/endif
/set neg [< val 0.0] ;negative ?
/set val [abs val] ;work with the positive value from now on
/set exp 0 ;init exponent of 2 (multiplier = 1)
/block ;make smaller exponent to adjust value up
/if [>= val 1.0] then ;large enough
/quit
/endif
/set val [* val 2.0]
/set exp [- exp 1]
/repeat
/endblock
/block ;make larger exponent to adjust value down
/if [< val 2.0] then ;small enough ?
/quit
/endif
/set val [/ val 2.0]
/set exp [+ exp 1]
/repeat
/endblock
/set val [* val 65536.0]
/set man2 [trunc val] ;make high 16 mantissa bits
/set val [- val man2] ;remove the high 16 bits value
/set val [* val 65536.0]
/set man1 [rnd val] ;make low 16 mantissa bits
/if [> man1 16#FFFF] then ;low 16 bits overflowed ?
/set man2 [+ man2 [shiftr man1 16]] ;move excess to high word
/set man1 [and man1 16#FFFF]
/endif
/if [> man2 16#1FFFF] then ;high 16 bits overflowed ?
/set man1 [shiftr man1 1] ;shift mantissa right one bit
/set man1 [or [shiftl [and man2 1] 15]]
/set man2 [shiftr man2 1]
/set exp [+ exp 1] ;adjust exponent to account for the shift
/endif
/set man2 [and man2 16#FFFF] ;mask in only the bits to save
/set fp48_mant [or [shiftl man2 16] man1] ;assemble the mantissa
/set fp48_exp [+ exp 16384]
/if neg then
/set fp48_exp [or fp48_exp 16#8000] ;set the negative bit
/endif
/endsub
;********************
;
; Macro FPLOAD Wn, fpval
;
; Load the floating point value FPVAL in 32 bit fast format into the register
; pair starting at Wn. N must be in the range of 0 to 13, which allows the
; register pairs from W1:W0 to W14:W13 to be loaded. This macro specifically
; refuses to load W15 (the stack pointer).
;
/macro fpload
/if [exist -1 arg] then
/show " Dumb place for a label, moron."
.error "Dumb label"
.end
/stop
/endif
/var local wok bool = false ;init to Wn argument not OK
/var local wn integer ;number of W register from Wn argument
/block
/var local s = [qstr [arg 1]] ;make target register Wn string
/if [< [slen s] 2] then ;Wn arg too short to be valid ?
/quit
/endif
/if [<> [ucase [sindx 1 s]] "W"] then ;check first char of Wn argument
/quit
/endif
/var local ns = [substr 2 [- [slen s] 1] s] ;extract W number string]
/set wn [chars ns] ;integer value of first W register number
/if [< wn 0] then ;below range ?
/quit
/endif
/if [> wn 13] then ;above range ?
/quit
/endif
/set wok true ;Wn argument OK, have W register number
/endblock
/if [not wok] then ;problem with Wn argument ?
/show " Invalid Wn argument to FPLOAD."
.error "Bad Wn"
.end
/stop
/endif
/var local fp real = [arg 2] ;get the floating point value
/var local s string ;scratch string for assembling line to write
/set s [str " mov #0x" [substr 7 4 [qstr [fp32f fp]]] ", w" wn]
/set s [str s " ;load " [fp fp "sig 6 mxl 6 mxr 6"]]
/set s [str s " into W" [+ wn 1] ":W" wn]
/write s
mov #0x[chars [substr 3 4 [qstr [fp32f fp]]]], w[+ wn 1]
/endmac
;********************
;
; Macro FPPUT Wn, var
;
; Write the floating point value starting in Wn into memory starting at VAR.
;
/macro fpput
/if [exist -1 arg] then
/show " Dumb place for a label, moron."
.error "Dumb label"
.end
/stop
/endif
/var local wok bool = false ;init to Wn argument not OK
/var local wn integer ;number of W register from Wn argument
/block
/var local s = [qstr [arg 1]] ;make target register Wn string
/if [< [slen s] 2] then ;Wn arg too short to be valid ?
/quit
/endif
/if [<> [ucase [sindx 1 s]] "W"] then ;check first char of Wn argument
/quit
/endif
/var local ns = [substr 2 [- [slen s] 1] s] ;extract W number string]
/set wn [chars ns] ;integer value of first W register number
/if [< wn 0] then ;below range ?
/quit
/endif
/if [> wn 14] then ;above range ?
/quit
/endif
/set wok true ;Wn argument OK, have W register number
/endblock
/if [not wok] then ;problem with Wn argument ?
/show " Invalid Wn argument to FPPUT."
.error "Bad Wn"
.end
/stop
/endif
mov w[v wn], [arg 2]+0
mov w[+ wn 1], [arg 2]+2
/endmac
;********************
;
; Macro FPGET var, Wn
;
; Get the floating point value from memory starting at VAR into the registers
; starting at Wn.
;
/macro fpget
/if [exist -1 arg] then
/show " Dumb place for a label, moron."
.error "Dumb label"
.end
/stop
/endif
/var local wok bool = false ;init to Wn argument not OK
/var local wn integer ;number of W register from Wn argument
/block
/var local s = [qstr [arg 2]] ;make target register Wn string
/if [< [slen s] 2] then ;Wn arg too short to be valid ?
/quit
/endif
/if [<> [ucase [sindx 1 s]] "W"] then ;check first char of Wn argument
/quit
/endif
/var local ns = [substr 2 [- [slen s] 1] s] ;extract W number string]
/set wn [chars ns] ;integer value of first W register number
/if [< wn 0] then ;below range ?
/quit
/endif
/if [> wn 14] then ;above range ?
/quit
/endif
/set wok true ;Wn argument OK, have W register number
/endblock
/if [not wok] then ;problem with Wn argument ?
/show " Invalid Wn argument to FPGET."
.error "Bad Wn"
.end
/stop
/endif
mov [arg 1]+0, w[v wn]
mov [arg 1]+2, w[+ wn 1]
/endmac
;********************
;
; Macro [label] EEWORD val, ... val
;
; Define one or more consecutive EEPROM words. If present, LABEL will be
; defined as the label for the first word. The preprocessor constants
; LASTEE and NEXTEE will be updated to be the word offset into the EEPROM of
; the last defined and next location, respectively. These offsets start at 0
; and increment by 1 each word (16 bits). This is different from the program
; memory addresses that will be assigned to the label, if present.
;
/var new nextee integer = 0 ;external word address of next EEPROM location
/var new lastee integer = [- nextee 1] ;external word adr of last defined EEPROM loc
/macro eeword
/var local s string = ""
/if [exist -1 arg] then
[arg -1]:
/show " EEPROM " [int nextee "fw 3 base 16 lz"] ": " [qstr [arg -1]]
/endif
/var local argn integer = 1 ;init number of next word value argument
/block
/if [not [exist argn arg]] then ;exhausted the word arguments ?
/quit
/endif
/if [> [slen s] 0] then
/set s [str s ", "]
/endif
/set s [str s [qstr [arg argn]]]
/set lastee nextee
/set nextee [+ nextee 1]
/set argn [+ argn 1]
/repeat
/endblock
.word [chars s]
/endmac
;********************
;
; Macro [label] FPWORDS fp
;
; Define two successive 16 bit words that have the floating point value FP
; when interpreted in 32 bit fast floating point format. This macro is
; intended for defining floating point constants in EEPROM. It expands to
; two EEWORD invocations in least to most significant word order.
;
/macro fpwords
/var local fp real = [arg 1] ;get the floating point value
/var local ifp integer = [fp32f_int fp] ;FP bits as 32 bit integer
/var local ifpl integer = [and ifp 16#FFFF] ;low word
/var local ifph integer = [shiftr ifp 16] ;high word
/var local s string = ""
/write ""
/set s [str ";FP " [fp fp "sig 6 mxl 6 mxr 6"]]
[chars s]
/if [exist -1 arg] then
/set s [str [qstr [arg -1]] " "]
/endif
/block
/if [>= [slen s] 9] then
/quit
/endif
/set s [str s " "]
/repeat
/endblock
/set s [str s "eeword "]
/set s [str s "0x" [int ifpl "fw 4 base 16 lz"]]
/set s [str s ", 0x" [int ifph "fw 4 base 16 lz"]]
[chars s]
/endmac
;********************
;
; Macro WAITNOP n
;
; Write instructions that do nothing for the next N instruction cycles.
; Nothing is done if N <= 0.
;
.macro waitnop n
.if \n >> 23
.exitm ;abort if N is negative
.endif
.rept \n
nop
.endr
.endm
;********************
;
; Macro ALLOC name [, size [, align]]
;
; Allocate space in the current section and define the label NAME as the first address
; of the allocated space. SIZE is the number of address increments to allocate. This
; is in bytes if allocating in a data section. The default SIZE is 2. ALIGN is the
; minimum required starting alignment multiple of the allocated space. The default
; ALIGN is 1 for SIZE of 1 or less and 2 for SIZE of 2 or more.
;
.macro alloc name, size=2, align=-1
.set align\@, \align
.if align\@ == -1 ;using default alignment ?
.if \size <= 1
.set align\@, 1 ;use byte alignment for bytes
.else
.set align\@, 2 ;use word alignment for words or larger
.endif
.endif
.align align\@
.if \size
\name: .skip \size
.else
\name:
.endif
.endm
;********************
;
; Macro ALLOCG name [, size [, align]]
;
; Like macro ALLOC (above) except that NAME is declared global. The
; C30-compatible version of the name is also created and exported. This
; allows C30 code to reference NAME directly as a externally defined global
; variable.
;
.macro allocg name, size=2, align=-1
.set align\@, \align
.if align\@ == -1 ;using default alignment ?
.if \size <= 1
.set align\@, 1 ;use byte alignment for bytes
.else
.set align\@, 2 ;use word alignment for words or larger
.endif
.endif
.align align\@
.if \size
_\name:
\name: .skip \size
.else
\name:
.endif
.global _\name, \name
.endm
;********************
;
; Subroutine ALIGN_ADR adr align var
;
; Align the address ADR to the alignment rule ALIGN and write the result to
; variable VAR. ADR will be increased as necessary so that it is a multiple
; of ALIGN. This means the resulting value written to VAR will be ADR plus
; 0 to ALIGN-1.
;
; ADR and ALIGN must be integer values, and VAR the bare name of a integer
; variable (not a string of the variable name). VAR will be created if it
; does not already exist.
;
/subroutine align_adr
/var local adr integer = [arg 1]
/var local align integer = [arg 2]
/var local mult integer
/var exist [arg 3] integer
/set mult [div [+ adr align -1] align]
/set [arg 3] [* align mult]
/endsub
;********************
;
; Subroutine STRUCT_START
;
; Start the definition of a memory structure with named fields. After
; this routine is called, the FIELD macro can be invoked any number of
; times to define successive fields in the structure.
;
; After this call and every FIELD macro the following state is updated:
;
; STRUCT_ALIGN - The minimum alignment byte multiple of the structure.
; This is initialized to 1, and updated to the largest ALIGN parameter
; each FIELD invocation.
;
; STRUCT_OFFSET - Offset from the beginning of the structure where the
; next field can start. The next field will start at this offset or
; later, depending on alignment requirements. Fields with alignment 1
; will always start exactly at the current STRUCT_OFFSET.
;
; STRUCT_SIZE - Aligned size of the structure. This is STRUCT_OFFSET
; padded to STRUCT_ALIGN alignment. Put another way, if a array of these
; structures were created, this is the amount of memory that would be
; reserved per array element. The total array size would be STRUCT_SIZE
; times the number of elements.
;
/subroutine struct_start
/var exist struct_align integer
/var exist struct_offset integer
/var exist struct_size integer
/set struct_align 0
/set struct_offset 0
/set struct_size 0
/endsub
;********************
;
; Macro FIELD name [, size [, align]]
;
; Define one more field in a structure. NAME will be defined as the
; offset from the start of the structure to the start of the new field.
; Subroutine STRUCT_START must be called once to initialize creating the
; structure, then this macro invoked to define each field.
;
; SIZE is the size of the field in bytes. It defaults to 2, meaning
; the default field is a 16 bit word.
;
; ALIGN is the minimum required address alignment of the field. Addresses
; will be skipped as necessary so that the offset of the field is a
; multiple of ALIGN. The default alignment is 2 (field starts on a
; 16 bit word boundary) for SIZE values of 2 or more. The default
; alignment is 1 for SIZE values of 1 or less.
;
/macro field
/var local name string = [qstr [arg 1]]
/var local size integer = 2
/if [exist 2 arg] then
/set size [arg 2]
/endif
/var local align integer = [if [<= size 1] 1 2]
/if [exist 3 arg] then
/set align [arg 3]
/endif
/set struct_align [max struct_align align] ;update alignment of whole structure
/call align_adr [v struct_offset] [v align] struct_offset
.equiv [chars name], [v struct_offset]
/set struct_offset [+ struct_offset size]
/call align_adr [v struct_offset] [v struct_align] struct_size
.set struct_offset, [v struct_offset]
/endmac
;********************
;
; Subroutine TIMER_SEC time
;
; Computes the setup of a timer to achive TIME seconds period. The following
; preprocessor variables are set, or created if they do not exist:
;
; TIMER_PER (real) - Actual resulting period in seconds.
;
; TIMER_TICK (real) - Seconds for one count of the timer.
;
; TIMER_CNT (integer) - Period in timer counts, will be 0-65536.
;
; TIMER_PRE (integer) - Prescaler divide value. This 1, 8, 64, or 256.
;
; TIMER_TCKPS (integer) - The prescaler selection field value in the
; position it is in in the TxCON timer control register. All other bits
; are 0.
;
/subroutine timer_sec
/var local time real = [arg 1] ;desired timer period, seconds
/var exist timer_per real
/var exist timer_tick real
/var exist timer_cnt integer
/var exist timer_pre integer
/var exist timer_tckps integer
/block
/set timer_pre 1 ;try with prescaler of 1
/set timer_tckps 0
/set timer_cnt [rnd [/ [* freq_inst time] timer_pre]]
/if [<= timer_cnt 65536] then
/quit
/endif
/set timer_pre 8 ;try with prescaler of 8
/set timer_tckps 1
/set timer_cnt [rnd [/ [* freq_inst time] timer_pre]]
/if [<= timer_cnt 65536] then
/quit
/endif
/set timer_pre 64 ;try with prescaler of 64
/set timer_tckps 2
/set timer_cnt [rnd [/ [* freq_inst time] timer_pre]]
/if [<= timer_cnt 65536] then
/quit
/endif
/set timer_pre 256 ;try with prescaler of 256
/set timer_tckps 3
/set timer_cnt [rnd [/ [* freq_inst time] timer_pre]]
/if [<= timer_cnt 65536] then
/quit
/endif
/show " Unable to achive the timer period of " time " seconds."
.error " Timer period too long."
.end
/stop
/endblock
/set timer_tckps [shiftl timer_tckps 4] ;move prescaler selection field into place
/set timer_tick [/ timer_pre freq_inst] ;seconds for one timer count
/set timer_per [* timer_cnt timer_tick] ;final resulting period in seconds
/endsub
;********************
;
; Macro TIMER_PERIOD timer, cycles
;
; Compute the period setup parameters for timer TIMER so that its period comes as
; close as possible to CYCLES instruction cycles. This macro only computes values
; and does not emit any executable code. The parameter TIMER must be an integer
; indicating the number of the timer to compute the setup values for. An error
; is generated if the period is out of range for the hardware to achieve.
;
; The following assembly variables are set:
;
; VAL_PR - PRn register value to achieve the desired period.
;
; VAL_TCKPS - TCKPS field value in the TnCON control register for the timer.
; This field controls the prescaler divide value.
;
; VAL_PRESCALE - Actual presscaler divide value selected by VAL_TCKPS.
;
; VAL_PERIOD - Actual period resulting from the computed setup in units of
; instruction cycles. This may not be exactly the same as CYCLES if the
; prescaler divide value is greater than 1.
;
; VAL_TnCON - Timer control register value for periodic ticks as specified.
;
; The lowest possible prescaler divide value is used such that the VAL_PR is within
; the range of the period register.
;
.macro timer_period timer, cycles
.set val_prescale, 1 ;init prescaler divide value to smallest possible
.set val_tckps, 0 ;init prescaler select field to match divide value
.set val_pr, ((((\cycles * 2) / val_prescale) + 1) >> 1) - 1
.if val_pr & ~0xFFFF ;need larger prescaler ?
.set val_prescale, 8
.set val_tckps, 1
.set val_pr, ((((\cycles * 2) / val_prescale) + 1) >> 1) - 1
.endif
.if val_pr & ~0xFFFF ;need larger prescaler ?
.set val_prescale, 64
.set val_tckps, 2
.set val_pr, ((((\cycles * 2) / val_prescale) + 1) >> 1) - 1
.endif
.if val_pr & ~0xFFFF ;need larger prescaler ?
.set val_prescale, 256
.set val_tckps, 3
.set val_pr, ((((\cycles * 2) / val_prescale) + 1) >> 1) - 1
.endif
.if val_pr & ~0xFFFF ;need larger prescaler ?
.print "Timer period can not be achieved"
.fail 0
.endif
.set val_period, (val_pr + 1) * val_prescale ;compute final instructions per period
.set val_t&timer&con, 0b1010000000000000 | (val_tckps << 4)
; -X-XXXXXX------X unused bits
; 1--------------- enable the timer
; --1------------- turn off timer in idle mode
; ---------0------ disable timer gate
; ----------00---- prescaler select, VAL_TCKPS will be merged in
; ------------0--- do not make part of 32 bit timer (type B only)
; -------------0-- do not sync external clock (type A only)
; --------------0- clock source is intruction clock
.endm
;********************
;
; Subroutine BAUD_SETUP30 baud
;
; Compute the baud rate setup of a old style 30F UART. These do not have the
; high speed mode that the newer enhanced UARTs do.
;
; The following preprocessor variables are set:
;
; UART_BRG - UART baud rate divisor register.
;
; UART_BAUD - Actual resulting baud rate.
;
; BAUD_ERR - Error fraction of actual baud rate compared to ideal.
;
; These variables are created if not previously defined.
;
/subroutine baud_setup30
/var local baudr real = [arg 1]
/var exist uart_brg integer
/var exist uart_baud real
/var exist baud_err real
/var exist baud_time real
/var local bdiv integer ;baud rate divisor depending on low/high speed mode
/var local errp real ;baud rate error in percent
/set bdiv 16 ;this UART only has a single speed mode
/set uart_brg [- [rnd [/ freq_inst [* baudr bdiv]]] 1]
/set uart_brg [if [> uart_brg 65535] 65535 uart_brg] ;clip to max value
/set uart_baud [/ freq_inst [* [+ uart_brg 1] bdiv]] ;actual baud rate
/set baud_time [/ 1 uart_baud] ;time per bit, seconds
/set baud_err [/ [- uart_baud baudr] baudr] ;baud rate error fraction
/set errp [* baud_err 100] ;baud rate error percent
/show " " [eng uart_baud 4] "baud, " [eng baud_time] "s/bit, " [fp errp "sig 0 rit 2 pl zb"] "% error"
/endsub
;********************
;
; Subroutine BAUD_SETUP baud
;
; Compute the baud rate setup of a enhanced UART. These have a high speed
; mode that the original 30F UART did not.
;
; The following preprocessor variables are set:
;
; UART_BRG - UART baud rate divisor register.
;
; BAUD_MODE - Baud rate control bits for the UART MODE register. Only
; bits relevant to baud rate generation are set with the remainder 0.
; This value is intended to be ORed with the other control bits to form
; the value written to the MODE register.
;
; UART_BAUD - Actual resulting baud rate.
;
; BAUD_ERR - Error fraction of actual baud rate compared to ideal.
;
; These variables are created if not previously defined.
;
/subroutine baud_setup
/var local baudr real = [arg 1]
/var exist uart_brg integer
/var exist baud_mode integer
/var exist uart_baud real
/var exist baud_err real
/var local bdiv integer ;baud rate divisor depending on low/high speed mode
/var local bfast bool ;using high speed mode
/var local errp real ;baud rate error in percent
/set bdiv 4 ;first try high speed mode
/set bfast True
/set uart_brg [- [rnd [/ freq_inst [* baudr bdiv]]] 1]
/if [> uart_brg 65535] then ;try low speed mode ?
/set bdiv 16 ;divisor for low speed mode
/set bfast False
/set uart_brg [- [rnd [/ freq_inst [* baudr bdiv]]] 1]
/set uart_brg [if [> uart_brg 65535] 65535 uart_brg] ;clip to max value
/endif
/set uart_baud [/ freq_inst [* [+ uart_brg 1] bdiv]] ;actual baud rate
/set baud_err [/ [- uart_baud baudr] baudr] ;baud rate error fraction
/set errp [* baud_err 100] ;baud rate error percent
/if bfast
/then
/set baud_mode 2#0000000000001000 ;set bit for high speed baud rate mode
/else
/set baud_mode 2#0000000000000000 ;set bit for low speed baud rate mode
/endif
/show " Baud rate " [rnd uart_baud] ", error " [fp errp "sig 0 rit 2 pl zb"] "%"
/endsub
;********************
;
; Macro SELECT_OUTPIN n, id
;
; Configure the RPn pin to the peripheral output identified by ID. N must be
; the 0-63 remappable pin number, and ID must be the ID of the perpheral
; output to map to that pin. Only the selection for the indicated pin is
; changed. Other selections in the same register are preserved.
;
; WARNING: W0, W1 are trashed.
;
/macro select_outpin
/var local rpn integer = [arg 1] ;0-N remappable pin number
/var local regn integer = [div rpn 2] ;0-N RPORn register number
/var local low bool = [= 0 [and rpn 1]] ;field is in low half of RPORn register
/if low
/then ;field is in low byte of RPORn register
mov #Rpor[v regn]+0, w1 ;point W1 to byte to modify
/else ;field is in high byte of RPORn register
mov #Rpor[v regn]+1, w1 ;point W1 to byte to modify
/endif
mov #[arg 2], w0 ;get peripheral ID
mov.b w0, [w1] [chars ";select output peripheral for RP" rpn " pin"]
/endmac
;********************
;
; Subroutine CAN_TIMING bitrate
;
; Determine the CAN bit timing. CAN bits are divided into time segments, each
; defined in terms of the number of time quanta. The length of time quanta is
; determined by the CAN input clock and the baud rate divider setup. We
; require a minimum of 9 time quanta per bit. The maximum allowed is 25 time
; quanta per bit. The time quanta budget for a bit is allocated between the
; various bit segments as follows:
;
; Sync - Always 1.
;
; Propagation - 1-8, we require at least 2.
;
; Phase1 - 1-8, we require at least 3.
;
; Phase2 - 2-8, we require at least 3.
;
; The desired CAN bit rate in Hz is passed as argument 1, which must be
; convertable to floating point. The input frequency in Hz to the CAN baud
; rate generator must be previously set in FCANCLK.
;
; This subroutine sets the following preprocessor variables:
;
; TQBIT - Number of time quanta per whole bit, 9-25
;
; TQPROP - Time quanta per propagation segment, 2-8
;
; TQPH1 - Time quanta per phase 1 segment, 3-8
;
; TQPH2 - Time quanta per phase 2 segment, 3-8
;
; BDIV - Baud rate divisor to make time quanta rate, 1-64
;
; FERR - CAN bit frequency error fraction
;
/subroutine can_timing
/var local bitrate real = [arg 1] ;desired bit rate, Hz
/if [not [exist "debug_cancfg"]] then
/const debug_cancfg bool = false
/endif
/var exist tqbit integer ;time quanta per whole bit, 9-25
/var exist tqprop integer ;time quanta per propagation segment, 2-8
/var exist tqph1 integer ;time quanta per phase 1 segment, 3-8
/var exist tqph2 integer ;time quanta per phase 2 segment, 3-8
/var exist bdiv integer ;Fosc/2 divider to make time quanta rate, 1-64
/var exist canrate real ;actual CAN bit frequency, Hz
/var exist ferr real ;CAN bit frequency error fraction
/var local ii integer ;scratch integers
/var local jj integer
/var local r real ;scratch floating point
/var local r2 real
/var local r3 real
/var local s1 string ;scratch strings
/var local s2 string
/var local s3 string
//
// Determine the bit rate setup. The TQ frequency is (FCANCLK/2)/BDIV, with
// BDIV constrained to 1-64. The BDIV value resulting in the smallest
// frequency error will be chosen, within the constraint that there must be
// 9 to 25 time quanta per bit.
//
/set bdiv 0 ;init to no usable BDIV value found
/set ii 1 ;init trial BDIV value
/set ferr 1.0 ;init to large frequency error so far
/block ;back here to try each new possible BDIV value
/set r [/ fcanclk [* 2 ii]] ;TQ frequency for the divisor value in II
/set jj [rnd [/ r bitrate]] ;best whole time quanta per bit for this divisor
/set jj [if [<= jj 25] jj 25] ;clip to max usable value
/set jj [if [>= jj 9] jj 9] ;clip to min usable value
/set r2 [/ r jj] ;resulting actual bit frequency
/set r3 [/ [abs [- bitrate r2]] bitrate] ;make error fraction
/if [< r3 ferr] then ;this is lower error than previous best ?
/set tqbit jj ;save time quanta per bit
/set bdiv ii ;save this baud rate divisor value
/set canrate r2 ;save actual CAN bit rate of this config
/set ferr r3 ;save error fraction of this configuration
/endif
/if debug_cancfg then ;show results from individual BDIV choices ?
/show " BDIV " [int ii "fw 2"] " TQBIT " [int jj "fw 2"] " err " [fp [* r3 100] "fw 6 zb mxl 9 rit 2"] "%"
/endif
/set ii [+ ii 1]
/if [<= ii 64] then
/repeat
/endif
/if debug_cancfg then
/show
/endif
/endblock
/set s1 [str [eng bitrate 4] "Hz"] ;bit rate string
/set s2 [str [eng fcanclk 4] "Hz"] ;CAN clock frequency string
/if [> ferr 0.015] then ;bit rate error too large to work ?
/show " ERROR: Bit rate of " s1 " not possible with CAN clock of " s2 "."
.error "CAN bit rate"
.end
/stop
/endif
/if [> ferr 0.0085] then ;error more than half allotted total of 1.7%
/show " WARNING: High CAN bit rate error from desired."
/endif
/set s1 [str [eng canrate 4] "Hz"] ;actual CAN bit rate string
/set s2 [str [eng fcanclk 4] "Hz"] ;CAN clock frequency string
/set s3 [fp [* ferr 100] "sig 1 mxl 6 rit 2"] ;bit frequence error in percent
/show " CAN clock " s2 ", bit freq " s1 " (" s3 "% err), " tqbit " TQ/bit"
//
// The bit rate setup has been determined. There are TQBIT time quanta per
// bit, which is guaranteed to be in the range of 9 to 25.
//
// Now divvy up the time quanta to the various segements of the bit time.
//
/set tqprop 2 ;set the configurable segments to their minimum durations
/set tqph1 3
/set tqph2 3
/set ii [- tqbit [+ 1 tqprop tqph1 tqph2]] ;left over availabe TQs.
/block ;back here until all TQs are assigned
/if [< tqprop 8] then
/set tqprop [+ tqprop 1] ;one more TQ for propagation segment
/set ii [- ii 1]
/if [<= ii 0] then
/quit
/endif
/endif
/if [< tqph1 8] then
/set tqph1 [+ tqph1 1] ;one more TQ for phase 1 segment
/set ii [- ii 1]
/if [<= ii 0] then
/quit
/endif
/endif
/if [< tqph2 8] then
/set tqph2 [+ tqph2 1] ;one more TQ for phase 2 segment
/set ii [- ii 1]
/if [<= ii 0] then
/quit
/endif
/endif
/repeat
/endblock
/show " Total TQ " tqbit ": Sync 1, Prop " tqprop ", Phase1 " tqph1 ", Phase2 " tqph2
/endsub
;*******************************************************************************
;
; Support for the global 1-bit flags defined with the preprocessor /FLAG
; command.
;
;********************
;
; Macro SETFLAG flag
;
; Set the flag defined by a /FLAG preprocessor directive.
;
.macro setflag flag
bset flag_&flag&_reg, #flag_&flag&_bit
.endm
;********************
;
; Macro CLRFLAG flag
;
; Clear the flag defined by a /FLAG preprocessor directive.
;
.macro clrflag flag
bclr flag_&flag&_reg, #flag_&flag&_bit
.endm
;********************
;
; Macro SKIP_FLAG flag
;
; Skip the next instruction if the flag defined by a /FLAG preprocessor
; directive is set.
;
.macro skip_flag flag
btss flag_&flag&_reg, #flag_&flag&_bit
.endm
;********************
;
; Macro SKIP_NFLAG flag
;
; Skip the next instruction if the flag defined by a /FLAG preprocessor
; directive is clear.
;
.macro skip_nflag flag
btsc flag_&flag&_reg, #flag_&flag&_bit
.endm
/////////////////////
//
// Macro FLAGS_DEFINE
//
// Defines the storage for all the flag bits. These go in words named GFL0 to
// GFLn, according to how many flags are defined. The flags are placed in
// their own linker section in near memory. The preprocessor creates the
// constant Flagdata_nwords to indicate how many words are required to hold
// all the flags.
//
/macro flags_define
/var local word integer ;0-N GFLn word number
/if [not [exist "Flagdata_nwords"]] then
/const Flagdata_nwords integer = 0;
/endif
/write
.section .flags, bss, near
/if using_c30 then
.align 2
/endif
/set word -1 ;init to before first word
/block ;back here each new word
/set word [+ word 1] ;make 0-N number of this word
/if [>= word Flagdata_nwords] then
/quit
/endif
/if using_c30 then
_gfl[v word]:
/endif
alloc gfl[v word], 2, 2
/if using_c30
/then
.global _gfl[v word], gfl[v word]
/else
.global gfl[v word]
/endif
/repeat
/endblock
/write
/endmac
/////////////////////
//
// Macro FLAGS_CLEAR
//
// Write executable code to clear all the /FLAG flag bits to 0.
//
/macro flags_clear
/var local word integer ;0-N GFLn word number
/if [not [exist "Flagdata_nwords"]] then
/const Flagdata_nwords integer = 0;
/endif
/set word -1 ;init to before first word
/block ;back here each new word
/set word [+ word 1] ;make 0-N number of this word
/if [>= word Flagdata_nwords] then
/quit
/endif
clr gfl[v word]
/repeat
/endblock
/write
/endmac
/////////////////////
//
// Subroutine GET_FLAG_DATA name gflN bitN
//
// The call arugments are the expansion of a Flagdata_flagN constant. The
// value of the fields will be written to FLAG_NAME, FLAG_WORD, and FLAG_BIT.
//
/subroutine get_flag_data
/var exist flag_name string
/var exist flag_word integer
/var exist flag_bit integer
/set flag_name [qstr [arg 1]]
/set flag_word [arg 2]
/set flag_bit [arg 3]
/endsub
/////////////////////
//
// Subroutine WRITE_C_FLAGS
//
// Writes all the global one-bit flags defined with /FLAG to a H file so that
// they are accessible from C code. The flag definitions in C format will be
// written to file <fwname>_flags.h. FWNAME must be a preprocessor string
// constant or variable containing the name of this firmware.
//
// Local subroutine CLOSE_FLAG_DEF
//
// Closes a partially written flags word data type definition. BITS is the
// number of bits defined in the word being closed.
//
/subroutine close_flag_def
/block ;define all the unused bits
/if [>= bits 16] then ;all bits defined ?
/quit
/endif
/write " unsigned : 1;"
/set bits [+ bits 1] ;count one more bit defined
/repeat
/endblock
/write " } flags_gfl" word "_t;"
/set wclosed True
/endsub
// Start of code for WRITE_C_FLAGS
//
/subroutine write_c_flags
/var local flag integer ;1-N flag number
/var local word integer ;0-N GFLn word number
/var local bits integer ;scratch bits counter
/var local wclosed bool ;last GFLn word definition has been closed
/var local flag_name string
/var local flag_word integer
/var local flag_bit integer
/writeto [str fwname "_flags.h"]
//
// Write the struct data types for each flags word.
//
/set flag 0 ;init to before first flag
/set word -1 ;init to before first flags word
/set wclosed true ;init to previous GFLn data type closed
/block ;back here each new flag
/set flag [+ flag 1] ;make 1-N number of this flag
/if [> flag Flagdata_nflags] then
/quit
/endif
/call get_flag_data [chars Flagdata_flag[chars flag]]
/if [<> flag_word word] then ;this flag starts a new word ?
/if [not wclosed] then ;not finished definition of previous word ?
/call close_flag_def ;close the partially written definition
/endif
/set word flag_word ;switch to new flags word number
/write
/write "typedef struct {"
/set bits 0 ;init number of bits written in this new word
/endif
/write " unsigned " flag_name ": 1;"
/set wclosed False ;definition of this word not closed yet
/set bits [+ bits 1] ;count one more bit defined in this word
/repeat
/endblock
/if [not wclosed] then ;not finished definition of previous word ?
/call close_flag_def ;close the partially written definition
/endif
//
// Declare all the GFLn variables. The data type for each was defined above.
//
/write ;blank line before this section
/set word -1 ;init to before first flags word
/block ;back here each new flags word
/set word [+ word 1] ;make 0-N number of this word}
/if [>= word Flagdata_nwords] then
/quit
/endif
/write "extern volatile flags_gfl" word "_t gfl" word ";"
/repeat
/endblock
//
// Write the string substitution macros for accessing each flag just by its
// name.
//
/write ;blank line before this section
/set flag 0 ;init to before first flag
/block ;back here each new flag
/set flag [+ flag 1] ;make 1-N number of this flag
/if [> flag Flagdata_nflags] then
/quit
/endif
/call get_flag_data [chars Flagdata_flag[chars flag]]
/write "#define flag_" flag_name " gfl" flag_word "." flag_name
/repeat
/endblock
/writeend
/endsub
;*******************************************************************************
;
; Subroutine and other entry point linkage.
;
; By convention, subroutines preserve the general registers W0 - W14
; and trash status bits and other related state unless explicitly documented
; to the contrary.
;
; Create the REGF0 - REGF14 flag bits. These can be ORed together to
; indicate an arbitrary set of W0 - W14 registers.
;
.irp ii, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
.equiv regf\ii, 1 << \ii
.endr
; Mask of all registers trashed by C subroutines:
;
.equiv regf_ctrash, regf0 | regf1 | regf2 | regf3 | regf4 | regf5 | regf6 | regf7
;********************
;
; Macro PUSHREGS regflags
;
; Push the indicated registers W0-W14 onto the stack. REGFLAGS is the
; mask of the registers to push. Bit 0 set indicates to push W0, bit 1
; set to push W1, etc. Registers are pushed in W0 to W14 order.
;
.macro pushregs regflags
.irp ii, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
.if (\regflags) & (1 << \ii)
push w\ii
.endif
.endr
.endm
;********************
;
; Macro POPREGS regflags
;
; Pop the indicated registers off the stack. REGFLAGS is the mask of the
; selected registers in the same format as for macro PUSHREGS, above. This
; macro undoes what PUSHREGS did if the same REGFLAGS value is passed to
; both. The registers are popped in W14 to W0 order.
;
.macro popregs regflags
.irp ii, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
.if (\regflags) & (1 << \ii)
pop w\ii
.endif
.endr
.endm
;********************
;
; Macro ENTER [regflags]
;
; Perform standard subroutine entry.
;
; REGFLAGS is the logical OR of any combination of REGF0 - REGF14 to
; indicate which of the general registers W0 - W14 are to be
; automatically saved on the stack. The appropriate PUSH instructions
; will be emitted, and the mask of pushed registers is saved in the
; assembler variable SAVEDREGS. The registers will be pushed in low to
; high register number order.
;
; The default value for REGFLAGS is zero, meaning no registers are pushed
; onto the stack.
;
.macro enter regflags = 0
pushregs \regflags
.set savedregs, \regflags
.endm
;********************
;
; Macro POPSAVED
;
; Pop saved registers from the stack.
;
; The registers indicated by SAVEDREGS are popped. SAVEDREGS is usually
; set by the GLBSUB macro at the beginning of a subroutine. The POPSAVED
; macro allows those registers to be popped without having to know the
; explicit list of registers.
;
.macro popsaved
popregs savedregs
.endm
;********************
;
; Macro LEAVE [regflags]
;
; Perform standard subroutine exit. This macro is intended to be used
; together with ENTER at the beginning of the subroutine.
;
; REGFLAGS is the logical OR of any combination of REGF0 - REGF14 to
; indicate which of the general registers W0 - W14 are to be automatically
; restored from the stack. The appropriate POP instructions will be
; emitted in high to low register number order.
;
; The default value for REGFLAGS is zero, meaning no registers are popped
; from the stack.
;
.macro leave regflags = 0
popregs \regflags
return
.endm
;********************
;
; Macro LEAVEREST
;
; Just like LEAVE except that the registers indicated by SAVEDREGS are
; automatically restored. SAVEDREGS should have been set by the ENTER
; macro to the routine being left.
;
.macro leaverest
leave savedregs
.endm
;********************
;
; Macro GLBSUB name, [regflags]
;
; Define the entry point of a globally callable subroutine. NAME is the
; name the subroutine will be called by. REGFLAGS indicates which
; registers to automatically save on the stack. See the ENTER macro
; description for details.
;
;
.macro glbsub name, regflags = 0
\name: ;define the entry point label
.global \name ;declare the label global
enter \regflags ;perform standard subroutine entry
noskid
.endm
;********************
;
; Macro GLBSUBC name, [regflags]
;
; Like GLBSUB except that the entry point name will be decorated so that it
; can be called from C30. This macro should be used when the subroutine is
; only intended for C30, with possibly a different version (defined with
; GLBSUB) for use by assembly code.
;
.macro glbsubc name, regflags = 0
_\name: ;define the C30 entry point label
.global _\name ;declare the label global
enter \regflags ;perform standard subroutine entry
noskid
.endm
;********************
;
; Macro GLBSUBD name, [regflags]
;
; Like GLBSUB and GLBSUBC put together (the D stands for "dual"). Entry point
; names will be defined so that NAME can be used directly from assembler and
; C30 to reference this subroutine. This macro can only be used if the
; routine is directly compatible with C30, adhering to all C30 calling
; conventions.
;
.macro glbsubd name, regflags = 0
\name: ;define the assembler entry point label
_\name: ;define the C30 entry point label
.global \name, _\name ;declare the labels global
enter \regflags ;perform standard subroutine entry
noskid
.endm
;********************
;
; Macro LOCSUB name, regflags
;
; Define the entry point of a local subroutine. NAME is the
; name the subroutine will be called by. REGFLAGS indicates which
; registers to automatically save on the stack. See the ENTER macro
; description for details
;
.macro locsub name, regflags = 0
\name: ;define the entry point label
enter \regflags
noskid
.endm
;********************
;
; Macro GLBENT name
;
; Define a generic global entry point. NAME is the name of the entry
; point.
;
.macro glbent name
\name: ;define the entry point label
.global \name ;declare the label global
noskid
.endm
;********************
;
; Macro GLBLAB name
;
; Define a global label. This label is not intended to be a code entry
; point, so NOPs for debugging are not automatically added after the label
; as they are by the GLBENT macro.
;
.macro glblab name
\name:
.global \name
.endm
;********************
;
; Macro GCALL target
;
; Call subroutine TARGET, which is assumed to be outside the current
; module and can therefore be anywhere in program memory.
;
.macro gcall target
noskid
call \target
noskid
.endm
;********************
;
; Macro GJUMP target
;
; Jump to global label TARGET, which is assumed to be outside the current
; module and can therefore be anywhere in program memory.
;
.macro gjump target
noskid
goto \target
.endm
;********************
;
; Macro MCALL target
;
; Call subroutine TARGET, which is assumed to be in the same module and
; the same program memory linker section as the call.
;
.macro mcall target
noskid
rcall \target
noskid
.endm
;********************
;
; Macro JUMP target
;
; Jump to label TARGET, which is assumed to be in the same module and
; the same program memory linker section as this macro.
;
.macro jump target
bra \target
.endm
;*******************************************************************************
;
; I/O port initialization.
;
; Create and initialize the VAL_xxxx variables so that all I/O pins
; default to outputs driven low.
;
.ifdef Porta
.set val_porta, 0
.set val_trisa, 0
.set val_pullupa, 0
.endif
.ifdef Portb
.set val_portb, 0
.set val_trisb, 0
.set val_pullupb, 0
.endif
.ifdef Portc
.set val_portc, 0
.set val_trisc, 0
.set val_pullupc, 0
.endif
.ifdef Portd
.set val_portd, 0
.set val_trisd, 0
.set val_pullupd, 0
.endif
.ifdef Porte
.set val_porte, 0
.set val_trise, 0
.set val_pullupe, 0
.endif
.ifdef Portf
.set val_portf, 0
.set val_trisf, 0
.set val_pullupf, 0
.endif
.ifdef Portg
.set val_portg, 0
.set val_trisg, 0
.set val_pullupg, 0
.endif
.ifdef Porth
.set val_porth, 0
.set val_trish, 0
.set val_pulluph, 0
.endif
.ifdef Porti
.set val_porti, 0
.set val_trisi, 0
.set val_pullupi, 0
.endif
.ifdef Portj
.set val_portj, 0
.set val_trisj, 0
.set val_pullupj, 0
.endif
.ifdef Portk
.set val_portk, 0
.set val_trisk, 0
.set val_pullupk, 0
.endif
.ifdef Portl
.set val_portl, 0
.set val_trisl, 0
.set val_pullupl, 0
.endif
.ifdef Portm
.set val_portm, 0
.set val_trism, 0
.set val_pullupm, 0
.endif
.ifdef Portn
.set val_portn, 0
.set val_trisn, 0
.set val_pullupn, 0
.endif
.ifdef Porto
.set val_porto, 0
.set val_triso, 0
.set val_pullupo, 0
.endif
.ifdef Portp
.set val_portp, 0
.set val_trisp, 0
.set val_pullupp, 0
.endif
.ifdef Portq
.set val_portq, 0
.set val_trisq, 0
.set val_pullupq, 0
.endif
.ifdef Portr
.set val_portr, 0
.set val_trisr, 0
.set val_pullupr, 0
.endif
.ifdef Ports
.set val_ports, 0
.set val_triss, 0
.set val_pullups, 0
.endif
.ifdef Portt
.set val_portt, 0
.set val_trist, 0
.set val_pullupt, 0
.endif
.ifdef Portu
.set val_portu, 0
.set val_trisu, 0
.set val_pullupu, 0
.endif
.ifdef Portv
.set val_portv, 0
.set val_trisv, 0
.set val_pullupv, 0
.endif
.ifdef Portw
.set val_portw, 0
.set val_trisw, 0
.set val_pullupw, 0
.endif
.ifdef Portx
.set val_portx, 0
.set val_trisx, 0
.set val_pullupx, 0
.endif
.ifdef Porty
.set val_porty, 0
.set val_trisy, 0
.set val_pullupy, 0
.endif
.ifdef Portz
.set val_portz, 0
.set val_trisz, 0
.set val_pullupz, 0
.endif
/////////////////////
//
// Subroutine GET_PORT_DATA name dir pol
//
// The call arguments are intended to be the expansion of one of the
// Portdata_<port><bit> constants. The following variables will be set:
//
// IOBIT_NAME - User name of this I/O bit.
//
// IOBIT_OUT - Boolean, TRUE for output bit, FALSE for input bit.
//
// IOBIT_POS - Boolean, TRUE for positive logic, FALSE for negative.
//
/subroutine get_port_data
/var exist iobit_name string
/var exist iobit_out bool
/var exist iobit_pos bool
/set iobit_name [qstr [arg 1]]
/set iobit_out [= [ucase [qstr [arg 2]]] "OUT"]
/set iobit_pos [= [ucase [qstr [arg 3]]] "POS"]
/endsub
/////////////////////
//
// Subroutine WRITE_C_IOPINS
//
// Exports the symbolic I/O pin definitions to C by writing the
// <fwname>_iopins.h file. For example, this will contain C macros
// set_<name>_on and set_<name>_off for each output pin, where NAME is the
// name assigned to the output pin with a /OUTBIT preprocessor command.
//
/subroutine write_c_iopins
/var local port string ;lower case a-z port name
/var local portu string ;lower case A-Z port name
/var local portn integer ;1-26 number of port name letter
/var local bit integer ;0-15 bit number within port
/var local def bool ;at least one I/O bit defined in current port
/var local pdata string ;name of Portdata_<port><bit> variable
/var local ii integer ;scratch integer
/writeto [str fwname "_iopins.h"]
/set portn 0 ;init to before first port letter
/block ;back here each new port
/set portn [+ portn 1] ;make 1-26 number of letter for this port
/if [> portn 26] then
/quit
/endif
/set port [char [+ [ccode "a"] portn -1]] ;make lower case port name letter
/set portu [ucase port] ;make upper case port name letter
//
// Check that anything is defined for this port. If not, skip it and go on
// to the next port.
//
/set def false ;init to no I/O bits in this port
/set bit 0 ;init to first bit within port
/block ;back here each new bit in this port
/set pdata [str "Portdata_" port bit] ;make full name of Portdata constant
/if [exist pdata] then ;this I/O pin has been given a name ?
/set def true
/quit
/endif
/set bit [+ bit 1] ;advance to next bit in this port
/if [> bit 15] then ;done with whole port ?
/quit
/endif
/repeat ;back and do next bit in this port
/endblock
/if [not def] then ;no named bits in this port ?
/repeat ;skip this port, on to next
/endif
//
// Write the data type definition for this port.
//
/write "typedef struct {"
/set bit 0 ;init to first bit within port
/block ;back here each new bit in this port
/set pdata [str "Portdata_" port bit] ;make full name of Portdata constant
/if [exist pdata]
/then ;this I/O pin has been given a name
/call get_port_data [chars [chars pdata]]
/write " unsigned " iobit_name ": 1;"
/else ;this I/O pin has not been given a explicit name
/write " unsigned : 1;"
/endif
/set bit [+ bit 1] ;advance to next bit in this port
/if [> bit 15] then ;done with whole port ?
/quit
/endif
/repeat ;back and do next bit in this port
/endblock
/write " } ioport_" port "_t;"
/write "extern volatile ioport_" port "_t PORT" portu ";"
/write "extern volatile ioport_" port "_t LAT" portu ";"
/write
//
// Write the macros for each named pin in this port. These allow manipulation
// of the I/O pin without having to know which port it is in.
//
/set bit -1 ;init to before first bit number
/block ;back here each new bit in this port
/set bit [+ bit 1] ;make 0-N number of this bit
/if [> bit 15] then ;done all bits
/quit
/endif
/set pdata [str "Portdata_" port bit] ;make full name of Portdata constant
/if [not [exist pdata]] then
/repeat ;this bit does not have a symbolic name, skip it
/endif
/call get_port_data [chars [chars pdata]] ;get info about this bit
/if iobit_out
/then ;this is a output pin
/set ii [if iobit_pos 1 0]
/write "#define set_" iobit_name "_on() do {LAT" portu "." iobit_name "=" ii ";} while(0)"
/set ii [if iobit_pos 0 1]
/write "#define set_" iobit_name "_off() do {LAT" portu "." iobit_name "=" ii ";} while(0)"
/else ;this is a input pin
/write "#define " iobit_name "_pin PORT" portu "." iobit_name
/endif
/repeat ;back and do next bit in this port
/endblock
/write
/repeat ;back to do next I/O port
/endblock ;done with all I/O ports
/writeend ;done writing to fwname_IOPINS.H file
/endsub
;*******************************************************************************
;
; FIFOs
;
; FIFOs that contain 8 bit bytes have the following format:
;
; name + FIFOB_OFS_N - Number of data bytes currently in the FIFO (byte).
;
; name + FIFOB_OFS_PUT - Offset into the buffer where to write next
; byte (byte).
;
; name + FIFOB_OFS_GET - Offset into the buffer where to read the
; next byte from (byte).
;
; name + FIFOB_OFS_BUF - Start of the data buffer.
;
; Each of these fields can have arbitrary byte alignment.
;
; Define a byte FIFO data structure:
;
/call struct_start
field fifob_ofs_n, 1 ;number of bytes currently in the FIFO
field fifob_ofs_put, 1 ;buffer offset where to write next data byte
field fifob_ofs_get, 1 ;buffer offset where to read next data byte from
.equiv fifob_ofs_buf, struct_offset ;start of data buffer
.equiv fifob_size, struct_offset ;size of FIFO without the data buffer
;********************
;
; Macro FIFOB_DEFINE name, size
;
; Define a byte FIFO. NAME will be defined as the starting address of the
; FIFO data structure. SIZE is the maximum number of bytes the FIFO must
; be able to hold, and must not exceed 255. The constant <name>_SZ will
; be defined to be the size of the FIFO unless it already exists. It is
; an error if <name>_SZ is previously defined but is not equal to SIZE.
;
.macro fifob_define name, size
.ifdef &name&_sz
.if (&name&_sz - (\size))
.error "Pre-existing FIFO size constant not equal to new FIFO size."
.endif
.endif
.ifndef &name&_sz
.equiv &name&_sz, \size
.endif
alloc \name, (\size) + fifob_size, 1
.endm
;********************
;
; Macro FIFOB_INIT name
;
; Initialize the indicated byte FIFO.
;
; W0 is trashed.
;
.macro fifob_init name
mov #\name, w0 ;point to start of FIFO data structure
clr.b [w0++] ;init buffer to empty
clr.b [w0++] ;init write index to start of buffer
clr.b [w0++] ;init read index to start of buffer
.endm
;********************
;
; Macro FIFOB_Z_EMPTY name
;
; Set the Z flag if the indicated FIFO is completely empty.
;
; W0 is trashed.
;
.macro fifob_z_empty name
mov #\name, w0 ;point to start of FIFO data structure
mov.b [w0 + fifob_ofs_n], w0 ;get number of bytes in the FIFO
and #0xFF, w0 ;mask off upper byte, set Z if FIFO is empty
.endm
;********************
;
; Macro FIFOB_EMPTY_N name
;
; Set W0 to the number of empty slots in the FIFO.
;
.macro fifob_empty_n name
mov #\name, w0 ;point to start of FIFO data structure
mov.b [w0 + fifob_ofs_n], w0 ;get number of bytes in the FIFO
ze w0, w0
neg w0, w0
add #&name&_sz, w0 ;make number of empty FIFO slots
.endm
;********************
;
; Macro FIFOB_Z_FULL name
;
; Set the Z flag if the indicated FIFO is completely full.
;
; W0 and W1 are trashed.
;
.macro fifob_z_full name
mov #\name, w0 ;point to start of FIFO data structure
mov.b [w0 + fifob_ofs_n], w0 ;get number of bytes in the FIFO
and #0xFF, w0 ;mask off upper byte which contains garbage
mov #&name&_sz, w1
cp w0, w1
.endm
;********************
;
; Macro FIFOB_FULL_N name
;
; Set W0 to the number of full slots in the FIFO.
;
.macro fifob_full_n name
mov #\name, w0 ;point to start of FIFO data structure
mov.b [w0 + fifob_ofs_n], w0 ;get number of bytes in the FIFO
and #0xFF, w0 ;mask off upper byte which contains garbage
.endm
;********************
;
; Macro FIFOB_PUT name
;
; Write the byte in the low 8 bits of W0 to the indicated FIFO. The
; FIFO must not be full, although this is not checked.
;
; Trashes W1, W2, W3.
;
.macro fifob_put name
mov #\name, w1 ;point W1 to FIFO
mov.b [w1 + fifob_ofs_put], w2 ;get PUT index in W2
and #0xFF, w2
add w2, w1, w3
mov.b w0, [w3 + fifob_ofs_buf] ;write the data byte into the FIFO buffer
add w2, #1, w2 ;increment local copy of PUT index
mov #&name&_sz, w3 ;get buffer size
cp w2, w3 ;compare new PUT index to buffer size
bra ltu, m\@_1 ;still within buffer ?
mov #0, w2 ;no, wrap back to buffer start
m\@_1: ;W2 contains new PUT index
mov.b w2, [w1 + fifob_ofs_put] ;update PUT index in FIFO structure
mov.b [w1 + fifob_ofs_n], w2 ;update number of bytes in the FIFO
inc w2, w2
mov.b w2, [w1 + fifob_ofs_n]
.endm
;********************
;
; Macro FIFOB_GET name
;
; Get the next byte from the indicated FIFO into W0. The high byte of W0
; will be zero. The FIFO must not be empty, although this is not checked.
;
; Trashes W1, W2.
;
.macro fifob_get name
mov #\name, w1 ;point W1 to the FIFO structure
mov.b [w1 + fifob_ofs_n], w0 ;count one less byte in the FIFO
dec w0, w0
mov.b w0, [w1 + fifob_ofs_n]
mov.b [w1 + fifob_ofs_get], w2 ;get the GET index into W2
and #0xFF, w2
inc w2, w2 ;advance the index
mov #&name&_sz, w0 ;get the buffer size
cp w2, w0
bra ltu, m\@_1 ;still within the buffer ?
mov #0, w2 ;no, wrap back to buffer start
m\@_1: ;W2 contains new GET index
mov.b [w1 + fifob_ofs_get], w0 ;get the old GET index into W0
and #0xFF, w0
add w0, w1, w0
mov.b [w0 + fifob_ofs_buf], w0 ;get the data byte into W0
and #0xFF, w0 ;mask in only the data byte in all of W0
mov.b w2, [w1 + fifob_ofs_get] ;update GET index in FIFO structure
.endm
////////////////////////////////////////////////////////////////////////////////
//
// General preprocessor string manipulation.
//
////////////////////////////////////////
//
// Subroutine TABTO varname column
//
// Add blanks to the end of the string VARNAME so that the next next character
// appended to its end will be at column COLUMN or later.
//
// The Embed conventions for assembler code are:
//
// 1 2 3 4
//3456789_123456789_123456789_123456789_
// opcode operand ;comment
//
// which means the tabto columns are:
//
// opcode 10
// operand 18
// comment 30
//
/subroutine tabto
/block ;back here until at the right column
/if [>= [slen [arg 1]] [- [arg 2] 1]] then
/quit
/endif
/set [arg 1] [str [arg 1] " "]
/repeat
/endblock
/endsub
////////////////////////////////////////
//
// Subroutine TABOPCODE varname
//
// Append spaces as necessary to the end of the string in VARNAME so that the
// next character will be at or after the opcode start column. VARNAME is
// always returned ending in a blank.
//
/subroutine tabopcode
/if [<> [sindx [slen [arg 1]] [arg 1]] " "] then ;not already ending in blank ?
/set [arg 1] [str [arg 1] " "] ;add one blank at end
/endif
/call tabto [arg 1] 10
/endsub
////////////////////////////////////////
//
// Subroutine TABOPERAND varname
//
// Append spaces as necessary to the end of the string in VARNAME so that the
// next character will be at or after the operand start column. VARNAME is
// always returned ending in a blank.
//
/subroutine taboperand
/if [<> [sindx [slen [arg 1]] [arg 1]] " "] then ;not already ending in blank ?
/set [arg 1] [str [arg 1] " "] ;add one blank at end
/endif
/call tabto [arg 1] 18
/endsub
////////////////////////////////////////
//
// Subroutine STARTCOMM varname
//
// Add the start of a MPASM comment to the end of the string in the variable
// VARNAME. The string will end in the comment delimeter character, which
// will be in the usual comment column or later. There will always be at
// least one blank before the comment start character. The caller can
// directly append the text of the comment to the string.
//
/subroutine startcomm
/if [<> [sindx [slen [arg 1]] [arg 1]] " "] then ;not already ending in blank ?
/set [arg 1] [str [arg 1] " "] ;add one blank at end
/endif
/call tabto [arg 1] 30
/set [arg 1] [str [arg 1] ";"]
/endsub
////////////////////////////////////////
//
// Subroutine STRING_TOKEN str ind tok
//
// Parses the next token from the string STR. IND must be the name of a
// integer variable that is the string index to start parsing at. IND is
// updated to after the token. TOK must be a string variable into which the
// parsed token is returned. IND should be started at 1 in a sequence to get
// all tokens from the string. IND is returned past the end of the string
// when the input string has been exhausted.
//
/subroutine string_token
/set [arg 3] "" ;init the token to the empty string
/if [< [arg 2] 1] then ;invalid IND ?
/return
/endif
//
// Skip over leading blanks.
//
/block
/if [> [arg 2] [slen [arg 1]]] then ;past end of string ?
/return
/endif
/if [= [sindx [arg 2] [arg 1]] " "] then ;another blank ?
/set [arg 2] [+ [arg 2] 1] ;advance the parse index
/repeat
/endif
/endblock
//
// Grab string characters up to the first blank or end of input string.
//
/block
/if [= [sindx [arg 2] [arg 1]] " "] then ;hit a blank ?
/set [arg 2] [+ [arg 2] 1] ;start at next character next time
/return
/endif
/set [arg 3] [str [arg 3] [sindx [arg 2] [arg 1]]] ;add this char to token
/set [arg 2] [+ [arg 2] 1] ;advance to next input string index
/if [> [arg 2] [slen [arg 1]]] then ;past end of string ?
/return
/endif
/repeat
/endblock
/endsub
;*******************************************************************************
;
; Facilities for writing various types of data to program memory.
;
////////////////////////////////////////
//
// Macro PWORD val [, comment]
//
// Write one program memory word and set it the value in the low 24 bits of
// VAL. A .PWORD directive will be written, and the value will always be a
// 6 digit hexadecimal constant. The argument must be resolvable to a integer
// value by the preprocessor.
//
// If the optional COMMENT parameter is supplied, its string representation
// will be writtten as the end of line comment.
//
/macro pword
/var local val integer = [and [arg 1] 16#FFFFFF]
/var local s string
/var local comm string
/if [exist 2 arg] then
/set comm [str [arg 2]]
/endif
/set s [str " .pword 0x" [int val "fw 6 lz base 16 usin"]]
/if [<> comm ""] then ;there is a comment ?
/block
/if [>= [slen s] 28] then
/quit
/endif
/set s [str s " "]
/repeat
/endblock
/set s [str s " ;" comm]
/endif
/write s ;write the output file line
/endmac
////////////////////////////////////////
//
// Macro PBYTE val
//
// Write to the next program memory byte in the current program memory word.
// If the program memory word fills up, then write it with a .PWORD directive.
// The byte value will be the low 8 bits of the argument. The argument must
// be resolvable to a integer value by the preprocessor.
//
// Byte are written in least to most significant order in the program memory
// word.
//
// Data will be buffered until a complete program memory word can be written,
// which requires 3 bytes. Use subroutine PBYTE_FINISH to force any buffered
// data to be written. Unused high bytes of a program memory word will be set
// to the erased value of FFh.
//
/macro pbyte
/var exist pbyte_word integer = 16#FFFFFF ;make sure persistant state exists
/var exist pbyte_nbytes integer = 0
/var local val integer = [and [arg 1] 16#FF] ;get the byte value
/if [= pbyte_nbytes 0] then ;insert into byte 0 ?
/set pbyte_word 16#FFFF00
/set pbyte_word [or pbyte_word val]
/set pbyte_nbytes [+ pbyte_nbytes 1]
/quitmac
/endif
/if [= pbyte_nbytes 1] then ;insert into byte 1 ?
/set pbyte_word [and pbyte_word 16#FF00FF]
/set pbyte_word [or pbyte_word [shiftl val 8]]
/set pbyte_nbytes [+ pbyte_nbytes 1]
/quitmac
/endif
/if [= pbyte_nbytes 2] then ;insert into byte 2 ?
/set pbyte_word [and pbyte_word 16#00FFFF]
/set pbyte_word [or pbyte_word [shiftl val 16]]
//
// The word is full, write it to program memory.
//
pword [v pbyte_word]
/set pbyte_nbytes 0 ;reset to no pending unwritten bytes
/quitmac
/endif
/endmac
////////////////////////////////////////
//
// Subroutine PBYTE_FINISH
//
// Write out any last partially defined program memory word. Since program
// memory words are 3 bytes in size, one is only written every 3 PBYTE
// invocations.
//
/subroutine pbyte_finish
/if [not [exist "pbyte_nbytes"]] then ;PBYTE never called ?
/return
/endif
/block
/if [= pbyte_nbytes 0] then
/quit
/endif
pbyte 16#FF
/repeat
/endblock
/endsub
////////////////////////////////////////
//
// Macro [label] PGSTRING "..."
//
// Write a counted string constant into program memory. The string will be a
// sequence of bytes, with the byte order low to high within each program
// memory word. The first byte is the length byte, which is then followed by
// exactly that many string bytes.
//
// If a label is supplied, it will be defined as the address of the first
// program memory word of the string. In this case, the string is started at
// the beginning of a new program memory word, with any partially defined
// previous word written before this new string. The string value will be
// written as a comment on the label line, which will be separate from the
// program memory words containing the string data.
//
/macro pgstring
/var local s string = [arg 1]
/var local ln string
/var local ii integer
/var local cc integer
//
// Write the label line, if a label was provided. If so, this will include
// a comment that shows the value of the string.
//
/if [exist -1 arg] then ;label was provided ?
/call pbyte_finish ;flush any previously defined partial prog mem word
/set ln [qstr [arg -1]] ;init label line with label name
/set ln [str ln ": "]
/if [> [slen s] 0] then
/block ;tab out to comment start column
/if [>= [slen ln] 29] then
/quit
/endif
/set ln [str ln " "]
/repeat
/endblock
/set ln [str ln ";"] ;write comment start character
/endif
/set ii 1 ;init index of next character to write
/block ;back here each character of the string
/if [> ii [slen s]] then
/quit
/endif
/set cc [ccode [sindx ii s]] ;get character code of this character
/if [and [>= cc 32] [<> cc 127]] then ;this is a printable character ?
/set ln [str ln [char cc]]
/endif
/set ii [+ ii 1] ;advance to next character in the string
/repeat
/endblock
/write ln ;write the label line to the output file
/endif
//
// Write the string bytes.
//
pbyte [slen s] ;write the string length byte
/set ii 1 ;init next string character to write
/block ;back here each new character
/if [> ii [slen s]] then
/quit
/endif
pbyte [ccode [sindx ii s]] ;write this character
/set ii [+ ii 1]
/repeat
/endblock
/call pbyte_finish ;write any partial program memory word
/endmac
////////////////////////////////////////
//
// Macro FP48P fpval
//
// Write 6 consecutive bytes to program memory. These will be the 48 bit
// floating point representation of FPVAL in least to most significant byte
// order. The FPVAL parameter must be interpretable as a floating point value
// by the preprocessor.
//
/macro fp48p
/var local fpval real = [arg 1]
/call fp48_make [v fpval] ;set FP48_EXP and FP48_MANT
pbyte [v fp48_mant]
pbyte [shiftr fp48_mant 8]
pbyte [shiftr fp48_mant 16]
pbyte [shiftr fp48_mant 24]
pbyte [v fp48_exp]
pbyte [shiftr fp48_exp 8]
/endmac