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

.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 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 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
;
;********************
;
;   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 STRUCT_START
;
;   Start the definition of a memory structure with named fields.  After
;   this macro is called, the FIELD macro can be called any number of
;   times to define successive fields in the structure.
;
.macro   struct_start
.set     struct_offset, 0    ;initialize the offset for the next field
.endm
;
;********************
;
;   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.
;   Macro STRUCT_START must be called to initialize assembler state
;   for that structure before the FIELD macro is used.
;
;   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   name, size=2, align=-1

.set     struct_align, \align
         .if     struct_align == -1 ;using default alignment ?
         .if     \size <= 1
.set     struct_align, 1     ;use byte alignment for bytes
         .else
.set     struct_align, 2     ;use word alignment for words or larger
         .endif
         .endif

.set     struct_offset, (struct_offset + struct_align - 1) / struct_align
.set     struct_offset, struct_offset * struct_align ;skip forward to alignment

.equiv   \name,  struct_offset ;define the new field here
.set     struct_offset, struct_offset + \size ;update next available offset
.endm
;
;********************
;
;   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
  /var exist can_timing_dbg bool = false ;enables extra debug messages

  /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 can_timing_dbg 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 can_timing_dbg then
      /show
      /endif
    /endblock

  /set s1 [fp [/ bitrate 1000] "sig 1 mxl 6 rit 3"] ;bit rate in KHz
  /set s2 [fp [/ fcanclk 1e6] "sig 1 mxl 6 rit 3"] ;CAN clock in MHz
  /if [> ferr 0.015] then    ;bit rate error too large to work ?
    /show "  ERROR: Bit rate of " s1 " KHz not possible with CAN clock of " s2 " MHz."
         .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 [fp [/ canrate 1000] "sig 1 mxl 6 rit 3"] ;actual bit frequency, KHz
  /set s2 [fp [/ fcanclk 1e6] "sig 1 mxl 6 rit 3"] ;CAN clock in MHz
  /set s3 [fp [* ferr 100] "sig 1 mxl 6 rit 2"] ;bit frequence error in percent
  /show "  CAN clock " s2 " MHz, bit freq " s1 " KHz (" 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
;
;*******************************************************************************
;
;   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 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
;
;*******************************************************************************
;
;   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:
;
         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
         and     #0xFF, w0   ;mask off upper byte which contains garbage
         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