; *************************************************************** ; * Copyright (C) 2008, Embed Inc (http://www.embedinc.com) * ; * * ; * Permission to copy this file is granted as long as this * ; * copyright notice is included in its entirety at the * ; * beginning of the file, whether the file is copied in whole * ; * or in part and regardless of whether other information is * ; * added to the copy. * ; * * ; * The contents of this file may be used in any way, * ; * commercial or otherwise. This file is provided "as is", * ; * and Embed Inc makes no claims of suitability for a * ; * particular purpose nor assumes any liability resulting from * ; * its use. * ; *************************************************************** ; ; Standard include file assumed by most PIC source modules. The specifics ; must be configured to the particular processor and application. This is ; done by setting assembly values before this file is included. The include ; file STD_DEF.INS.ASPIC is provided to set defaults for all the required ; assembly values. An application should include STD_DEF.INS.ASPIC, then set ; any values it knows and cares about, then include STD.INS.ASPIC. In this ; way, applications are protected from changes to this include file that may ; require additional values to be set. ; ; The following assembly values are assumed by this file. Some of these are ; set to default values in STD_DEF.INS.ASPIC. ; ; NREGBANKS - Number of register banks the processor has. This configures ; macros that set the direct and indirect register banks. ; ; NCODEPAGES - Number of code pages the processor has. A value of 1 ; disables code page selection code. ; ; STACKLAST - The address of the last (highest) software stack byte. This ; is where the first pushed byte is written. On the 18 family, this is ; actually lowest address of the stack since the stack grows towards ; higher addresses on that family. ; ; STACKSIZE - Number of bytes to allocate for the stack. ; ; FREQ_OSC - Processor oscillator frequency in Hz. This is the effective ; oscillator frequency on the 18 family, after the PLL, if enabled, is ; applied. The instruction clock is FREQ_OSC / 4. ; ; FAM_12 - 1 if the processor is a 12xxx, meaning 12 bit core, 0 ; otherwise. Some PICs, like the 12F629, are really 16 family devices ; using the 14 bit core. These should have FAM_16, not FAM_12 set to 1. ; ; FAM_16c5 - 1 if the processor is a 16C5x, 0 otherwise. ; ; FAM_16 - 1 if the processor is a 16xxx except a 16C5x, 0 otherwise. ; This is meant to indicate the 14 bit "midrange" core devices, which ; includes some parts called "12F", like the 12F629. 0 for the enhanced ; 14 bit core. ; ; FAM_16B - 1 if the processor is a enhanced 14 bit core part. These are ; usually called PIC 16 with a 4-digit part number. The original PIC 16 ; have 3 digit part numbers. These parts, for example, have 32 register ; banks and a bank select register. ; ; FAM_17 - 1 if the processor is a 17xxx, 0 otherwise. ; ; FAM_18 - 1 if the processor is a 18xxx, 0 otherwise. ; ; REGSTART - Starting address of general registers (REG0 address). The ; remaining registers immediately follow REG0 at successive higher ; addresses. ; ; NUMREGS - Number of general registers. ; ; COMMREGS_FIRST, COMMREGS_LAST - These indicate the range of address ; offsets within each bank that are mapped to the same memory. For ; example, many 16 family processors have 70h - 7Fh within each bank ; mapped to the same physical registers. COMMREGS_FIRST > COMMREGS_LAST ; indicates this processor has no such common mapped registers. ; ; N_IREGS - The number of IREG0 - IREGn register to allocate. These ; registers are for the exclusive use by interrupt code, since it would be ; impractical for that code to use the general registers REG0 - REGn. ; This constant will be created and set to 0 if it does not already exist. ; ; INTR_OFF_BUG - Set to TRUE if the processor has the interrupt disable ; bug where the interrupt bit must be checked in a loop to make sure ; interrupts really got disabled. FALSE indicates interrupt bit can be ; altered without checking. ; ; UART_TYPE - Indicates the type of USART this part has. The possible ; values are: ; ; 0 - This part has no UART. ; ; 1 - Normal 17 family USART, like 17C756A. ; ; 2 - Normal 16 family USART, like as 16F876. ; ; 3 - Advanced USART on some 18 family, like 18F1320. ; ; ADR_WORD - Number of address increments per program memory word. This ; is 1 for 12, 16, and 17 family PICs, and 2 for 18 family. ; ; PULLUPS_PORTx - Mask of which bits of port X (PORTA, PORTB, etc) have ; passive pullups that can be enabled. A 1 bit indicates that the I/O pin ; at that bit position can have an internal passive pullup. If the high ; bit (80000000h) is set, then all pullups of this port can only be ; enabled or disabled together. If a symbol of this name does not exist ; for a port, then it is assumed that the port has no passive pullups at ; all. ; ; EE_START - Starting address of the EEPROM mapped into the program memory ; space in the HEX file. This is the starting address to use for the CODE ; section containing EEPROM initial values. Note that linker files are ; set up so that a code section called ".EEDATA" will automatically get ; mapped to the EEPROM. If it is permissable to allow the linker to place ; data in EEPROM, then the code section need not be given the explicit ; address of EE_START. ; ; ACCLAST - Last location in the bank 0 access bank region of 18 family ; PICs. This is 7Fh for most 18 family PICs, but some of the larger ones ; have more SFRs and decrease the access bank region in bank 0. The ; existance of ACCLAST indicates the existance of an access bank. ; ; C18COMP - A value of 1 indicates that this code must be compatible with ; modules compiled with the C18 compiler. This code must be able to call ; routines compiled with C18 and must also be callable from C18 code. ; This forces the following differences from the normal (and more ; efficient) Embed Inc assembler build environment: ; ; 1 - FSR1 is reserved as the stack pointer and FSR2 as the data stack ; frame pointer. The default Embed Inc conventions only reserve FSR2 ; as the data stack pointer and the application is free to use FSR0 ; and FSR1 in any way it wants. ; ; 2 - The stack pointer always points to the next empty byte instead ; of the byte on the top of the stack. In both cases the stack grows ; towards higher addresses. Leaving the stack pointer pointing to the ; next byte after the top of stack is less efficient because a PUSH ; would require a postincrement and a POP a predecrement. However the ; PIC 18 architecture has no predecrement, only preincrement. This ; requires an extra instruction for each group of bytes popped. ; ; 3 - Interrupt code advances the stack pointer by one before saving ; context. The C18 convention is that the stack pointer points to the ; next free entry past the top of the stack (where the next pushed ; byte would go). However, to deal with the lack of a predecrement ; addressing mode, the compiler emits code that performs a POP by ; decrementing the stack pointer then using it to read the top stack ; byte in two separate operations. An interrupt could occur between ; these two operations, so any interrupt routine must preserve the ; byte being pointed to by the stack pointer. It does this by leaving ; one untouched byte on the stack before saving any context, then ; removing the unused byte before returning from the interrupt. ; ; A value of 0 indicates this code need not be compatible with C18 code. ; All other values are illegal. If C18COMP does not exist, it will be ; created and set to 0 (defaulting to Embed Inc normal conventions). ; ; FSRSTACK - Indicate the number of the FSR to be used as the data stack ; pointer. This symbol is only used on processors that have multiple FSRs ; and one is dedicated as the data stack pointer. ; ; On a PIC 18, FSRSTACK will be created and set to 2 if not previously ; existing unless C18COMP is 1. When C18COMP is 1, FSR1 will always be ; used for the stack, and it is an error if FSRSTACK is defined but not ; set to 1. ; ; On a enhanced PIC 16, FSRSTACK will default to 1. ; ; FSRSC1 - The 0-2 number of the first FSR that can be used as a scratch ; pointer. By convention, this FSR may be trashed by subroutines. This ; is never the same FSR as the stack pointer. ; ; FSRSC2 - The 0-2 number of the second FSR that can be used as a scratch ; pointer. This FSR must be preserved by subroutines when FSRSC2_SAVE is ; 1. This is never the same FSR as the stack pointer. ; ; FSRSC2_SAVE - Indicates whether the scratch FSR indicated by FSRSC2 must ; be preserved by subroutines. 0 means it does not need to be preserved, ; and 1 means it does. ; ; PROGADRB - Number of bytes required to store a full program memory ; address for this processor. Only required for 18 family. The 18 family ; architecture allows for 21 bit program memory addresses, which require 3 ; bytes to store. However many PICs of this family have 64Kb or less of ; program memory, requiring only 2 bytes to store. Optimizations that ; avoid storing or otherwise manipulating the unused upper address byte on ; these PICs can be made when PROGADRB is 2 instead of 3. (No PIC18 ; currently has 256 bytes or less of program memory, but if that were the ; case PROGADRB would be 1 on those machines and manipulation of the upper ; two bytes could be optimized out). ; ; PROGADRB can also be artifically set lower on PICs with more than 64Kb ; of program memory if it is absolutely known that no program memory above ; 64Kb is used. The default will be set according to the total program ; memory available. Don't mess with the default unless you are absolutely ; sure you know what you're doing. ; ; Automatically created and set to the default of 2 for enhanced PIC 16 ; (fam_16b) devices. ; ; CREATE_FLAGS - Create the global FLAGS variable. This is used by ; various library arithmetic routines to indicate information about the ; operation or result. CREATE_FLAGS is initialized to 1 in ; STD_DEF.INS.ASPIC so that FLAGS is created by default. CREATE_FLAGS can ; be set to 0 to save 1 RAM location by not creating the FLAGS variable. ; ; CREATE_TEMPW - Create the TEMPW global variable in bank 0 when this ; symbol is 1. TEMPW will not be created when it is 0. CREATE_TEMPW is ; initialized in STD_DEF.INS.ASPIC as appropriate for the processor. ; TEMPW is a local temporary save area for W used by some of the macros in ; this file. ; ; USING_INTERRUPTS - Always TRUE (1) or FALSE (0). The default is TRUE, ; meaning the code might be using interrupts. In this case the INTR_ON ; and INTR_OFF macros actually emit code to turn interrupts on and off. ; If interrupts are not in use, then a section of code that tries to ; temporarily turn off interrupts will have the effect of enabling ; interrupts at the end. Setting this switch to FALSE prevents re-using ; code that temporarily disables interrupts when interrupts are not in use ; at all. ; ; DEBUG_ICD - Indicate debugging with a in-circuit debugger. These have ; the annoying habit of skidding at breakpoints. Some macros add NOPs to ; give the appearance of breakpoints and single stepping stopping as ; expected. ; ; DEBUG - Being built for debugging, not production. This is forced on ; when DEBUG_ICD is TRUE. ; ; FIFOS_NEW (preprocessor constant) ; ; Indicates whether to use the "new" FIFOs implemented with the ; preprocessor, instead of the old FIFOs implemented with MPASM macros. ; TRUE selects the new FIFOs, and FALSE selects the old. For ; compatibility with existing code, the default is FALSE when FIFOS_NEW is ; not defined. See the discussion at the start of the FIFO section of ; this file for details of the old versus new FIFOs. ; ; This file is divided into sections of related features. Each section starts ; with two lines of stars. Briefly, the sections, in order in this file, are: ; ; Configuration constants. ; ; General preprocessor string manipulation. ; ; General registers, REG0 ... REGn. ; ; Skip and branch macros. ; ; General utility macros and preprocessor subroutines. ; ; Timing and cycle counting. ; ; Handling multi-byte data. ; ; UART configuration. ; ; I/O bits and ports handling. ; ; Bank and page switching, memory allocation. ; ; Software data stack. ; ; Subroutine linkage. ; ; Global flags. ; ; FIFO (first in, first out) queues. ; ; Dispatch tables. ; ; Support for the multi-tasking system. ; ; Support for writing constants to program memory. ; ; Mutual exclusion locks for use with the Embed multi-tasking system. ; ;******************************************************************************* ;******************************************************************************* ; ; Configuration constants. ; //////////////////////////////////////////////////////////////////////////////// // // Set up the debugging environment. // // If any DEBUG_xxx constants exist, then it is assumed that the old system // for setting debug switches is in use. Otherwise, the MAKE_DEBUG program is // run to create the debug switches. Either way, the new constant DEBUGGING // is always created. // /block /var local dbg bool = false //found at least one DEBUG_xxx constant /var local dbgon bool = false //OR of all DEBUG_xxx constants /var local sy string //scratch symbol name /var local fnam string //scratch file name /loop symbols sym const /set sy [sym sym name] /if [< [slen sy] 7] then /repeat /endif /if [<> [substr 1 6 sy] "debug_"] then /repeat /endif /if [<> [sym sym dtype] "BOOL"] then /repeat /endif /set dbg True /set dbgon [or dbgon [chars sym]] /endloop /if dbg then //old style debug switches in use ? /if [<> [evar "debug"] ""] then /show " *** ERROR ***" /show " Using the DEBUG environment variable is incompatible with debug switches" /show " set in the project include file." .error "Debug" .end /stop /endif /if [not [exist "debugging:const"]] then /const debugging bool = dbgon /endif /if [not [exist "debug:const"]] then /const debug bool = debugging /endif /if [not [exist "debug_icd:const"]] then /const debug_icd bool = false /endif /if [exist "debug_icdram:vcon"] then /if [and debug_icd [not debug_icdram]] then /del debug_icdram /endif /endif /if [not [exist "debug_icdram:vcon"]] then /const debug_icdram bool = debug_icd /endif /quit /endif // // The debug switches are defined via the DEBUG environment variable. // /set fnam [str "(cog)src/" srcdir "/debug_" buildname ".ins.aspic"] /run "make_debug """ fnam """ icd" /include fnam /const debug bool = debugging //for compatibility with old code /endblock debug set [if debugging 1 0] debug_icd set [if debug_icd 1 0] ; Check for required constants and abort on error if any is missing. ; /if [exist "freq_osc"] then ifndef freq_osc freq_osc equ [rnd freq_osc] endif /const freq_inst real = [/ freq_osc 4] /endif /var exist oscdig integer = 4 ifndef freq_osc error "Assembly constant FREQ_OSC not set before STD include file called." endif if fam_18 ifndef progadrb error PROGADRB not defined. Should be defined in STD_DEF.INS.ASPIC. endif endif ifndef c18comp c18comp equ 0 ;default to don't need to be compatible with C18 code endif if fam_16b ifndef progadrb progadrb equ 2 ;default to 2 bytes required to store prog mem address endif endif ifndef n_iregs n_iregs equ 0 ;assume no private registers required by interrupt code endif ; ; Derived constants. ; freq_inst equ freq_osc / 4 ;instruction clock frequency in Hz nsec_inst equ 1000000000 / freq_inst ;instruction time in nanoseconds ; ; Global assembly time state. ; w_trashed set false ;used by some macros to indicate W got trashed ; ; Other symbols. ; ; Define JUMP adr ; ; This inline substitution macro expands to the opcode for doing a simple jump ; local to the module. ; if fam_18 || fam_16b if bra_bug #define jump goto else #define jump bra endif else ;all other processors #define jump goto endif ; ; Fix bug in some of the 18F include files where the INT2IP bit in INTCON3 is ; named INT2P instead. ; ifdef intcon3 ;this part has INTCON3 register ? ifndef int2ip ;INT2IP is not defined ? ifdef int2p ;but INT2P is ? #define int2ip 7 ;define the correct bit name endif endif endif ; ; Define which FSRs are used for specific purposes if this is a PIC 18. These ; have three FRSs (0-2). By default, the Embed environment uses FSR2 as the ; data stack pointer with the other two available for application use. The ; C18 compiler uses FSR1 as the stack pointer and FSR2 as a frame pointer with ; only FSR0 left for the application. ; if fam_18 ifndef fsrstack if c18comp fsrstack set 1 ;C18 compiler uses FSR1 for stack pointer else fsrstack set 2 ;default to FSR2 as stack pointer endif endif if (fsrstack < 0) || (fsrstack > 2) error Out of range value for FSRSTACK found in STD.INS.ASPIC. endif if c18comp && (fsrstack != 1) error FSRSTACK set to #v(fsrstack), must be 1 for C18 compatibility. endif if c18comp fsrsc1 equ 0 fsrsc2 equ 2 fsrsc2_save equ 1 else fsrsc1 equ 0 fsrsc2 equ 1 fsrsc2_save equ 0 endif endif ; ; Define the FSR numbers if this is a enhanced PIC 16. These have two FSRs. ; By default, the Embed environment reserves FSR1 as the data stack pointer ; and leaves FSR0 for application use. ; if fam_16b ifndef fsrstack fsrstack set 1 ;default to FSR1 as data stack pointer endif if (fsrstack < 0) || (fsrstack > 1) error FSRSTACK found at illegal values of #v(fsrstack) in STD.INS.ASPIC endif endif ifdef fsrstack if fsrstack fsrsc1 equ 0 else fsrsc1 equ 1 endif endif ; ; Define the CONFIGnx constants that are the addresses of the configuration ; words for the 18 family. These are listed in the manual and mentioned in ; the standard include file comments, but not defined in the include file for ; some strange reason. ; if fam_18 config_word_first equ h'300000' ;address of first configuration word config_word_last equ h'30000D' ;address of last configuration word ii set config_word_first ;init address of next config word to define variable jj while ii <= config_word_last ;once for each config word jj set ((ii - config_word_first) >> 1) + 1 ;1-N config word number if (ii & 1) == 0 ;at low word of low/high pair ? CONFIG#V(jj)L equ ii ;define LOW config word of this number else ;at high word of low/high pair CONFIG#V(jj)H equ ii ;define HIGH config word of this number endif ii set ii + 1 ;advance to next config word address endw ;back to do next pair of config words endif ;end of 18 family case /if [not [exist "fifos_new"]] then /const fifos_new bool = false /endif // // Make sure the NO_INTR_DISABLE variable exists, and initialize it to the // default value of FALSE if not. This switch is used by some macros that // need to perform atomic operations. With this switch off, interrupts are // disabled around such atomic operations. When this switch is on, // interrupts are not disabled and re-enabled. // // Reasons to set this switch to TRUE include: // // 1 - In an interrupt routine. Interrupts are already off, and will be // re-enabled by the RETFIE instruction. If this switch isn't off, then // nested macros would effectively enable interrupts early. // // 2 - Interrupts are not used in the project. Nested macros would then // effectively turn on interrupts if this switch is not set. // // 3 - The data structure that needs to be atomically updated is never // accessed from interrupt code. There is therefore no need to prevent // interrupts from running part way thru what should be an atomic // operation. // /var exist no_intr_disable bool = false //////////////////////////////////////////////////////////////////////////////// // // Subroutine DEBUGSW name // // Make sure the debug switch DEBUG_name exists. If it does not exist, then // it is created as a BOOL constant set to FALSE. NAME is a token, not a // string. // /subroutine debugsw /var local sym string = [str "debug_" [qstr [arg 1]]] /if [not [exist [str sym ":vcon"]]] then /const [chars sym] bool = false /endif /endsub ;******************************************************************************* ;******************************************************************************* ; ; General preprocessor string manipulation. ; //////////////////////////////////////////////////////////////////////////////// // // Subroutine PARSE_CMD var [suff] // // Parse the contents of the constant or variable VAR. VAR must be of data // type STRING. The contents of VAR is: // // n [entpnt] // // N is a 0 to 255 decimal integer indicating the opcode value of a command. // ENTPNT is the name of the entry point to the command routine. When ENTPNT // is not present, the entry point name is derived from the variable name. In // that case the name of VAR must be "cmd_xxx". The entry point name is // interpreted to be "cm_xxx". // // The optional SUFF parameter is a unique string added to the fixed part of // the variable name. The fixed part of the variable name is cmd_<suff>_, // which is removed from the variable name to make the default entry point // name. With a variable name "cmd_xyz_abc" and SUFF "_xyz", the default // entry point name is "cm_abc", not "cm_xyz_abc". // // This subroutine sets two variables: // // OPC - Integer. Opcode value. // // ENTPNT - String. Command routine entry point name. // // OPC and ENTPNT are created if they do not already exist. // // For exampe, if this routine is called with constant "cmd_abcd" containing // "5", then OPC will be 5 and ENTPNT "cm_abcd". If the constant contains // "13 send_blork", then OPC will be 13 and ENTPNT "send_blork". // /subroutine parse_cmd /var exist opc integer //make sure return values exist /var exist entpnt string /var local vname string = [qstr [arg 1]] //get variable or constant name /var local vnam string = [sym vname name] //make bare var or const name /var local vstr string = [vnl [chars vname]] //get input string /var local pref string = [str "cmd" [qstr [arg 2]] "_"] //fixed var name prefix /var local p integer = 1 //parse index /var local tk string //token parsed from input string /call string_token [v vstr] p tk //get opcode token /set opc [chars tk] /if [> p [slen vstr]] then //no ENTPNT token ? /block //block to abort out of on error /set tk [substr 1 [slen pref] vnam] /if [<> tk pref] then //name doesn't start with "cmd_" ? /quit /endif /set tk [substr [+ [slen pref] 1] 99 vnam] /if [< [slen tk] 1] then //nothing after "cmd_" ? /quit /endif /set entpnt [str "cm_" tk] //make full default entry point name /return /endblock /show " Bad name """ vname """ passed to PARSE_CMD." .error "Bad var/const name" /stop /endif /call string_token [v vstr] p tk //get ENTPNT token /if [<= p [slen vstr]] then //extra tokens in string ? /show " Extra token in variable or constant """ vname """". /show " String is """ vstr """". .error "Extra token" /stop /endif /set entpnt tk /endsub //////////////////////////////////////////////////////////////////////////////// // // Subroutine TABTO varname column // // Add blanks to the end of the string VARNAME so that the next next character // appended to its end will be at column COLUMN or later. // // The Embed conventions for assembler code are: // // 1 2 3 4 //3456789_123456789_123456789_123456789_ // opcode operand ;comment // // which means the tabto columns are: // // opcode 10 // operand 18 // comment 30 // /subroutine tabto /block ;back here until at the right column /if [>= [slen [arg 1]] [- [arg 2] 1]] then /quit /endif /append [arg 1] " " /repeat /endblock /endsub //////////////////////////////////////////////////////////////////////////////// // // Subroutine TABOPCODE varname // // Append spaces as necessary to the end of the string in VARNAME so that the // next character will be at or after the opcode start column. VARNAME is // always returned ending in a blank. // /subroutine tabopcode /if [<> [sindx [slen [arg 1]] [arg 1]] " "] then ;not already ending in blank ? /set [arg 1] [str [arg 1] " "] ;add one blank at end /endif /call tabto [arg 1] 10 /endsub //////////////////////////////////////////////////////////////////////////////// // // Subroutine TABOPERAND varname // // Append spaces as necessary to the end of the string in VARNAME so that the // next character will be at or after the operand start column. VARNAME is // always returned ending in a blank. // /subroutine taboperand /if [<> [sindx [slen [arg 1]] [arg 1]] " "] then ;not already ending in blank ? /set [arg 1] [str [arg 1] " "] ;add one blank at end /endif /call tabto [arg 1] 18 /endsub //////////////////////////////////////////////////////////////////////////////// // // Subroutine STARTCOMM varname // // Add the start of a MPASM comment to the end of the string in the variable // VARNAME. The string will end in the comment delimeter character, which // will be in the usual comment column or later. There will always be at // least one blank before the comment start character. The caller can // directly append the text of the comment to the string. // /subroutine startcomm /if [<> [sindx [slen [arg 1]] [arg 1]] " "] then ;not already ending in blank ? /set [arg 1] [str [arg 1] " "] ;add one blank at end /endif /call tabto [arg 1] 30 /set [arg 1] [str [arg 1] ";"] /endsub //////////////////////////////////////////////////////////////////////////////// // // Subroutine STRING_TOKEN str ind tok // // Parses the next token from the string STR. IND must be the name of a // integer variable that is the string index to start parsing at. IND is // updated to after the token. TOK must be a string variable into which the // parsed token is returned. IND should be started at 1 in a sequence to get // all tokens from the string. IND is returned past the end of the string // when the input string has been exhausted. // /subroutine string_token /set [arg 3] "" ;init the token to the empty string /if [< [arg 2] 1] then ;invalid IND ? /return /endif // // Skip over leading blanks. // /block /if [> [arg 2] [slen [arg 1]]] then ;past end of string ? /return /endif /if [= [sindx [arg 2] [arg 1]] " "] then ;another blank ? /set [arg 2] [+ [arg 2] 1] ;advance the parse index /repeat /endif /endblock // // Grab string characters up to the first blank or end of input string. // /block /if [= [sindx [arg 2] [arg 1]] " "] then ;hit a blank ? /set [arg 2] [+ [arg 2] 1] ;start at next character next time /return /endif /set [arg 3] [str [arg 3] [sindx [arg 2] [arg 1]]] ;add this char to token /set [arg 2] [+ [arg 2] 1] ;advance to next input string index /if [> [arg 2] [slen [arg 1]]] then ;past end of string ? /return /endif /repeat /endblock /endsub //////////////////////////////////////////////////////////////////////////////// // // Subroutine SHOWVAL name [description] // // Show the value of the preprocessor symbol NAME. When no DESCRIPTION // parameter is present, the following will be written to standard output: // // NAME <value> // // When DESCRIPTION is present, this is followed by ", <description>". The // description argument must be a single argument. It will generally be a // string of characters enclosed in quotes. // /subroutine showval /if [exist 2 arg] /then ;DESCRIPTION exists /var local desc string = [str ", " [arg 2]] /else ;DESCRIPTION not supplied /var local desc string = "" /endif /var local name string = [qstr [arg 1]] /var local val string = [vnl [chars name]] /if [= [sym name dtype] "STRING"] then /set val [str '"' val '"'] /endif /show " " name " " val desc /endsub //////////////////////////////////////////////////////////////////////////////// // // Subroutine SHOWHEX name [description] // // Like SHOWVAL, except that the value is shown in hexadecimal if the data // type of NAME is integer. // /subroutine showhex /if [exist 2 arg] /then ;DESCRIPTION exists /var local desc string = [str ", " [arg 2]] /else ;DESCRIPTION not supplied /var local desc string = "" /endif /var local name string = [qstr [arg 1]] /var local val string /block /if [= [sym name dtype] "REAL"] then /set val [eng [chars name] 4 ""] /quit /endif /if [= [sym name dtype] "STRING"] then /set val [str '"' [chars name] '"'] /quit /endif /if [= [sym name dtype] "INTEGER"] then /set val [str [int [chars name] "base 16 usin"] "h"] /quit /endif /set val [chars name] /endblock /show " " name " " val desc /endsub //////////////////////////////////////////////////////////////////////////////// // // Subroutine SHOWBIN name [description] // // Like SHOWVAL, except that the value is shown in binary if the data type of // NAME is integer. // /subroutine showbin /if [exist 2 arg] /then ;DESCRIPTION exists /var local desc string = [str ", " [arg 2]] /else ;DESCRIPTION not supplied /var local desc string = "" /endif /var local name string = [qstr [arg 1]] /var local val string /block /if [= [sym name dtype] "REAL"] then /set val [eng [chars name] 4 ""] /quit /endif /if [= [sym name dtype] "STRING"] then /set val [str '"' [chars name] '"'] /quit /endif /if [= [sym name dtype] "INTEGER"] then /set val [str [int [chars name] "base 2 usin"] "b"] /quit /endif /set val [chars name] /endblock /show " " name " " val desc /endsub ;******************************************************************************* ;******************************************************************************* ; ; General registers, REG0 ... REGn ; ; Several registers are declared in global (not banked) memory that are ; intended to act like general registers of other processors. Subroutines are ; expected to preserve these registers except as they are explicitly used to ; pass return values as documented individually for each subroutine. It is ; assumed that subroutines trash other state, such as W and FSR unless ; otherwise documented. ; ; The general registers (REG0 - REGn) are guaranteed to be mapped to memory in ; that order. ; ; The register addresses are declared here as constants so that their ; addresses are know at assembly time. The registers are defined in module ; REGS.ASPIC. The main routine must have an external reference to REGS to ; ensure that the memory space for the registers is actually allocated. ; ; Create one REGn and REGFn symbol for each register. The REGn symbols are ; the memory addresses of the registers. The REGFn symbols are flags used to ; identify a set of registers. Each REGFn value has one unique bit set. ; These symbols can be ORed together to identify an arbitrary set of ; registers. ; tempasm1 set 0 ;init symbol number while tempasm1 < numregs reg#v(tempasm1) equ regstart + tempasm1 ;define REGn register address symbol regf#v(tempasm1) equ 1 << tempasm1 ;define REGFn register flag symbol tempasm1 set tempasm1 + 1 ;advance to next symbol number endw noregs equ 0 ;special flag value to indicate no registers ; ; Declare the 32 bit registers A thru D and their flags if general registers ; are declared to cover them. REGA overlays REG3:REG2:REG1:REG0, REGB the ; next 4 general registers, etc. The REGA-REGD registers are used to hold ; operands of 32 bit operations. ; if numregs >= 4 rega equ regstart regfa equ b'1111' endif if numregs >= 8 regb equ regstart + 4 regfb equ b'1111' << 4 endif if numregs >= 12 regc equ regstart + 8 regfc equ b'1111' << 8 endif if numregs >= 16 regd equ regstart + 12 regfd equ b'1111' << 12 endif regf_allregs equ ~(-1 << numregs) ;mask for all REG0-REGn general registers ; ; Create FLAGS register. This register receives the result of compare and ; other operations. It is always located immediately following the general ; registers. ; if create_flags flags equ regstart + numregs ;declare FLAGS register address regff equ 1 << numregs ;declare flag for pushing/popping FLAGS register else regff equ 0 ;there is no FLAGS register to push/pop endif regf_all equ regf_allregs | regff ;all possible push/pop flags ; ; Identify particular bits in the FLAGS register. These constants represent ; bit numbers so that they can be used directly with bit manipulation ; instructions. ; if create_flags flagb_lt equ 0 ;comparison result was "less than" flagb_eq equ 1 ;comparison result was "equal" flagb_gt equ 2 ;comparison result was "greater than" flagb_err equ 3 ;error, failed to perform action flagb_quoa equ 4 ;store quotient in 32 bit A register flagb_quoc equ 5 ;store quotient in 32 bit C register flagb_rema equ 6 ;store remainder in 32 bit A register flagb_remc equ 7 ;store remainder in 32 bit C register endif ; ; Bit masks for the flags defined above ; if create_flags flag_lt equ 1 << flagb_lt flag_eq equ 1 << flagb_eq flag_gt equ 1 << flagb_gt flag_err equ 1 << flagb_err flag_quoa equ 1 << flagb_quoa flag_quoc equ 1 << flagb_quoc flag_rema equ 1 << flagb_rema flag_remc equ 1 << flagb_remc ; ; Mask for all flags that are set as a result of arithmetic ; operations. ; flag_ar equ flag_lt | flag_eq | flag_gt endif ;******************************************************************************* ;******************************************************************************* ; ; Skip and branch macros. ; ; Macros for skipping the next instruction depending on some of the flags in ; FLAGS. ; if create_flags skip_flt macro ;skip on less than dbankif flags btfss flags, flagb_lt endm skip_fle macro ;skip on less than or equal to dbankif flags btfsc flags, flagb_gt endm skip_feq macro ;skip on equal dbankif flags btfss flags, flagb_eq endm skip_fgt macro ;skip on greater than dbankif flags btfss flags, flagb_gt endm skip_fge macro ;skip on greater than or equal to dbankif flags btfsc flags, flagb_lt endm skip_fne macro ;skip on not equal dbankif flags btfsc flags, flagb_eq endm skip_err macro ;skip on error flag bit set dbankif flags btfss flags, flagb_err endm skip_nerr macro ;skip on error flag bit not set dbankif flags btfsc flags, flagb_err endm endif ;******************************************************************************* ; ; Macro SKIP_WLE ; ; Skip the next instruction if W was less than or equal to the value it was ; subtracted from. This assumes that the carry flag has been preserved from ; the last SUBWF or SUBLW instruction. ; skip_wle macro if fam_12 || fam_16 || fam_16b || fam_18 btfss status, c ;skip if no borrow occurred exitm endif if fam_17 btfss alusta, c exitm endif error "SKIP_WLE macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro SKIP_WGT ; ; Skip the next instruction if W was greater than the value it was subtracted ; from. This assumes that the carry flag has been preserved from the last ; SUBWF or SUBLW instruction. ; skip_wgt macro if fam_12 || fam_16 || fam_16b || fam_18 btfsc status, c ;skip if a borrow occurred exitm endif if fam_17 btfsc alusta, c exitm endif error "SKIP_WGT macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro SKIP_Z ; ; Skip the next instruction if the zero flag is set. ; skip_z macro if fam_12 || fam_16 || fam_16b || fam_18 btfss status, z exitm endif if fam_17 btfss alusta, z exitm endif error "SKIP_Z macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro SKIP_NZ ; ; Skip the next instruction if the zero flag is not set. ; skip_nz macro if fam_12 || fam_16 || fam_16b || fam_18 btfsc status, z exitm endif if fam_17 btfsc alusta, z exitm endif error "SKIP_NZ macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro SKIP_CARR ; ; Skip the next instruction if a carry occurred. ; skip_carr macro if fam_12 || fam_16 || fam_16b || fam_18 btfss status, c exitm endif if fam_17 btfss alusta, c exitm endif error "SKIP_CARR macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro SKIP_NCARR ; ; Skip the next instruction if no carry occurred. ; skip_ncarr macro if fam_12 || fam_16 || fam_16b || fam_18 btfsc status, c exitm endif if fam_17 btfsc alusta, c exitm endif error "SKIP_NCARR macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro SKIP_BORR ; ; Skip the next instruction if a borrow occurred. ; skip_borr macro if fam_12 || fam_16 || fam_16b || fam_18 btfsc status, c exitm endif if fam_17 btfsc alusta, c exitm endif error "SKIP_BORR macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro SKIP_NBORR ; ; Skip the next instruction if no borrow occurred. ; skip_nborr macro if fam_12 || fam_16 || fam_16b || fam_18 btfss status, c exitm endif if fam_17 btfss alusta, c exitm endif error "SKIP_NBORR macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro JMP_WEQ adr ; ; Jump to ADR if W was equal to the value it was subtracted from. This ; assumes the Z flag has been preserved from the last SUBWF or SUBLW ; instruction. ; jmp_weq macro adr if fam_18 bz adr exitm endif error "JMP_WEQ macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro JMP_WNE adr ; ; Jump to ADR if W was not equal to the value it was subtracted from. This ; assumes the Z flag has been preserved from the last SUBWF or SUBLW ; instruction. ; jmp_wne macro adr if fam_18 bnz adr exitm endif error "JMP_WNE macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro JMP_WLE adr ; ; Jump to ADR if W was less than or equal to the value it was subtracted from. ; This assumes the C flag has been preserved from the last SUBWF or SUBLW ; instruction. ; jmp_wle macro adr if fam_18 bc adr exitm endif error "JMP_WLE macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro JMP_WGT adr ; ; Jump to ADR if W was greater than to the value it was subtracted from. This ; assumes the C flag has been preserved from the last SUBWF or SUBLW ; instruction. ; jmp_wgt macro adr if fam_18 bnc adr exitm endif error "JMP_WGT macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro JMP_BORR adr ; ; Jump to ADR if a borrow occurred. ; jmp_borr macro adr if fam_12 || fam_16 || fam_16b skip_nborr jump adr exitm endif if fam_18 bnc adr exitm endif error "JMP_BORR macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro JMP_NBORR adr ; ; Jump to ADR if a borrow did not occurr. ; jmp_nborr macro adr if fam_12 || fam_16 || fam_16b skip_borr jump adr exitm endif if fam_18 bc adr exitm endif error "JMP_NBORR macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro JMP_CARR adr ; ; Jump to ADR if a carry occurred. ; jmp_carr macro adr if fam_12 || fam_16 || fam_16b skip_ncarr jump adr exitm endif if fam_18 bc adr exitm endif error "JMP_CARR macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro JMP_NCARR adr ; ; Jump to ADR if a carry did not occurr. ; jmp_ncarr macro adr if fam_12 || fam_16 || fam_16b skip_carr jump adr exitm endif if fam_18 bnc adr exitm endif error "JMP_NCARR macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ;******************************************************************************* ; ; General utility macros and preprocessor subroutines. ; //////////////////////////////////////////////////////////////////////////////// // // Subroutine SHOW_DEBUG // // Show the state of all the preprocessor debug switches. // /subroutine show_debug /var local sym string ;generic name of variable or constant symbol /loop symbols qsym vcon ;loop over all variables and constants /set sym [sym qsym name] ;get symbol generic name /if [< [slen sym] 7] then ;not long enough for DEBUG_x /repeat /endif /if [<> [substr 1 6 sym] "debug_"] then /repeat /endif /show " " [ucase sym] " " [chars qsym] /endloop /endsub //////////////////////////////////////////////////////////////////////////////// // // Subroutine DEBUG_EXIST name // // Make sure that the debug switch DEBUG_name exists. Name is a token, not a // string. Nothing is done if the constant DEBUG_name already exists. if not // then it is created as a bool and set to FALSE. // /subroutine debug_exist /var local name string = [str "debug_" [qstr [arg 1]]] /if [not [exist [str name ":vcon"]]] then /const [chars name] bool = false /endif /endsub ;******************************************************************************* ; ; Macro SET_TBLPTR adr ; ; Set the full TBLPTR to the indicated address. This macro is only defined on ; machines that have TBLPTR. PROGADRB must be defined correctly to indicate ; the number of bytes required to store a program memory address on this ; machine. ; ifdef tblptrl set_tblptr macro adr movlw low (adr) movwf tblptrl if progadrb < 2 clrf tblptrh else movlw high (adr) movwf tblptrh endif if progadrb < 3 clrf tblptru else movlw upper (adr) movwf tblptru endif endm endif ;******************************************************************************* ; ; Macro ADDW_TBLPTR ; ; Add the 0-255 value in W to the full TBLPTR. W is trashed. ; ifdef tblptrl addw_tblptr macro addwf tblptrl movlw 0 addwfc tblptrh if progadrb < 3 addwfc tblptru endif endm endif ;******************************************************************************* ; ; Macro IREGS_DEFINE ; ; Define all the IREG0 - IREGn interrupt routine registers. The number of ; these registers is set by the constant N_IREGS. ; iregs_define macro local ii ii set 0 ;init loop counter while ii < n_iregs ;once for each IREG to define ireg#v(ii) res 1 ;define this IREG global ireg#v(ii) ;declare it global ii set ii + 1 endw endm ;******************************************************************************* ; ; Macro EXTERN_IREGS ; ; Declare all the IREGn register external to this module. ; extern_iregs macro local ii ii set 0 ;init loop counter while ii < n_iregs ;once for each IREG extern ireg#v(ii) ;declare it external ii set ii + 1 endw endm ;******************************************************************************* ; ; Macro GETF <adrf> ; ; Move the contents of the file register ADRF into W. The Z flag is trashed. ; getf macro adrf ;***** ; ; 16 and enhanced 16 families. ; if fam_16 || fam_16b movf (adrf), w exitm endif ;***** ; ; 17 family. ; if fam_17 ;17C devices movfp (adrf), wreg exitm endif ;***** ; ; 18 family. ; if fam_18 ;18 family devices movf (adrf), w exitm endif ;***** ; error "GETF macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro GETFZ <adrf> ; ; Move the contents of the file register ADRF into W. The Z flag is set ; according to the value moved. ; getfz macro adrf ;***** ; if fam_16 ;16C devices movf (adrf), w exitm endif ;***** ; if fam_17 ;17C devices movfp (adrf), wreg iorlw 0 exitm endif ;***** ; if fam_18 ;18 family devices movf (adrf), w exitm endif ;***** ; error "GETFZ macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro TESTFZ <adrf> ; ; Set the Z flag according to the contents of the file register ADRF. W is ; preserved. ; testfz macro adrf ;***** ; ; 16 and enhanced 16 families. ; if fam_16 || fam_16b ;14 bit core movf (adrf), f exitm endif ;***** ; ; 17 family. ; if fam_17 comf (adrf), f comf (adrf), f exitm endif ;***** ; ; 18 family. ; if fam_18 ;18 family devices movf (adrf), f exitm endif ;***** ; error "TESTFZ macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro INTR_OFF ; ; Globally disable interrupts without changing which interrupts are ; individually disabled. This macro together with INTR_ON can be used around ; small sections of code that need to run with interrupts off. ; ; This macro works around the interrupt off bug on some processors where an ; interrupt can occur immediately after interrupts are disabled. This causes ; interrupts to be re-enabled by the RETFIE in the interrupt service routine. ; The work around is to verify that interrupts were indeed disabled on the ; next instruction and loop back if they weren't. The assembler switch ; INTR_OFF_BUG is set to TRUE if this processor has the bug. ; intr_off macro if ! using_interrupts exitm endif local retry ;***** ; if fam_16 || fam_16b retry bcf intcon, gie if intr_off_bug btfsc intcon, gie jump retry endif exitm endif ;***** ; if fam_17 retry bsf cpusta, glintd if intr_off_bug btfss cpusta, glintd jump retry endif exitm endif ;***** ; if fam_18 bcf intcon, gieh exitm endif ;***** ; error "INTR_OFF macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro INTR_ON ; ; Globally enable interrupts without changing which interrupts are ; individually enabled. This macro together with INTR_OFF can be used around ; small sections of code that need to run with interrupts off. ; intr_on macro if ! using_interrupts exitm endif ;***** ; if fam_16 || fam_16b bsf intcon, gie exitm endif ;***** ; if fam_17 bcf cpusta, glintd exitm endif ;***** ; if fam_18 bsf intcon, gieh exitm endif ;***** ; error "INTR_ON macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro INTR_OFF_LOW ; ; Disable low priority interrupts. ; ; Operation is undefined unless separate high and low interrupt priorities are ; enabled. This is not checked. ; ; It is an error to call this macro on a processor that is not capable of high ; and low priority interrupts. ; intr_off_low macro if ! using_interrupts exitm endif ;***** ; if fam_18 bcf intcon, giel exitm endif ;***** ; error "INTR_OFF_LOW macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro INTR_ON_LOW ; ; Re-enable low priority interrupts. ; ; Operation is undefined unless separate high and low interrupt priorities are ; enabled. This is not checked. ; ; It is an error to call this macro on a processor that is not capable of high ; and low priority interrupts. ; intr_on_low macro if ! using_interrupts exitm endif ;***** ; if fam_18 bsf intcon, giel exitm endif ;***** ; error "INTR_ON_LOW macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro SETREG val, reg ; ; Set the register REG to the value VAL. Both REG and VAL must be constants. ; The instructions are optimized. For example, CLRF is used if the value is ; zero. ; ; The direct bank is set to REG as needed. ; setreg macro val, reg dbankif (reg) if (val) == 0 ;value is exactly zero clrf (reg) exitm endif if ((val)==255) && fam_18 setf (reg) exitm endif movlw (val) movwf (reg) endm ;******************************************************************************* ; ; Macro ADDFSR0 ofs ; ; Add the fixed offset OFS into FSR0. This macro points FSR0 OFS addresses ; forwards. Depending on the value of OFS, the constant value is either added ; into FSR0 or auto inc/dec instructions are used, whichever takes fewer ; cycles. ; addfsr0 macro ofs ;***** ; ; PIC 18 ; if fam_18 local ii ; ; Handle offset of 0. ; if (ofs)==0 exitm endif ; ; Handle small positive offset. ; if ((ofs) > 0) && ((ofs) < 4) ;incrementing by 1-3 ? ii set (ofs) while ii movf postinc0 ii set ii - 1 endw exitm endif ; ; Handle small negative offset. ; if ((ofs) < 0) && ((ofs) > -4) ;decrementing by 1-3 ? ii set -(ofs) while ii movf postdec0 ii set ii - 1 endw exitm endif ; ; The offset is not a special case that can be done in less than 4 ; instructions. Do the explicit add using 4 instructions. ; movlw low (ofs) addwf fsr0l movlw high (ofs) addwfc fsr0h exitm endif ;end of PIC 18 family case ;***** ; ; Enhanced PIC 16. ; if fam_16b if ((ofs) < 32) && ((ofs) > -33)) addfsr fsr0, ofs exitm else movlw low (ofs) addwf fsr0l movlw high (ofs) addwfc fsr0h exitm endif endif ;end of enhanced 14 bit core case ;***** ; error "ADDFSR0 macro not implemented for this processor" endm //////////////////////////////////////////////////////////////////////////////// // // Preprocessor subroutine PARSE_IPADR ipdot ipname // // Parse a IP address string in dot notation format into the four integer byte // values. // // IPDOT is the IP address string. This must be a preprocessor string. // // IPNAME is the name prefix of the preprocessor variables that will be // assigned the 0-255 values of each of the 4 bytes in the IP address. Each // variable name will start with the IPNAME argument, followed by a single // digit to indicate which byte of the four bytes it represents. The least // significant byte is number 0, and the most significant number 3. The // IPNAME argument is just characters, not a string. If the IPNAMEn variables // do not previously exist, they will be created. // // Example: // // /call parse_ipadr "192.168.0.1" myip // // This will set MYIP0 to 1, MYIP3 to 192, etc. // /subroutine parse_ipadr /var exist [arg 2]0 integer ;make sure the output variables exist /var exist [arg 2]1 integer /var exist [arg 2]2 integer /var exist [arg 2]3 integer /var local ipstr string = [arg 1] ;get IP address string /var local c string ;single character string /var local s string ;byte value string /var local ib integer = 3 ;0-3 byte number /var local p integer = 1 ;input string parse index /block ;back here each new byte /set s "" ;init this byte string to empty /block ;back here each new character this byte /set c [substr p 1 ipstr] ;get next character from the input string /set p [+ p 1] ;advance the parse index /if [not [or [= c ""] [= c "."]]] then /set s [str s c] ;append this character to end of byte string /repeat ;back to get next character /endif /endblock /set [arg 2][chars ib] [chars s] ;set this byte variable /set ib [- ib 1] ;make 0-3 number of next byte /if [>= ib 0] then ;still a valid byte number ? /repeat ;back to get next byte /endif /endblock /endsub ;******************************************************************************* ;******************************************************************************* ; ; Timing and cycle counting. ; ;******************************************************************************* ; ; Macro WAITNOP NNOP (deprecated) ; ; This macro now causes an error so that all uses of it can be examined to ; determine the reason for its use, then replaced by the specific macro for ; that use. ; ; WAITNOP originally just wrote the indicated number of sequential NOP ; instructions although it was intended for instruction timing. Some time ; later it was modified to use GOTO $+1 on PIC 16 and BRA $+2 on PIC 18 to act ; as NOPs that took 2 cycles but only one instruction word. The was ; beneficial for the intended use. ; ; However, WAITNOP was also used to create a table of sequential NOP ; instructions that could be indexed into to exactly synchronize with a timer. ; Using 2 cycle NOP instructions defeated this purpose. ; ; Two separate macros now exist to serve the two purposes, WAITCY and NNOPS. ; WAITNOP now causes an assembly error so that the purpose can be examined and ; the appropriate replacement macro chosen. Note that the newer WAITUS and ; WAITNS macros may be a better choice than WAITCY in some cases since they ; automatically adjust to the processor clock speed. ; waitnop macro nnop error WAITNOP has been depricated. See comments in STD.INS.ASPIC. endm ;******************************************************************************* ; ; Macro WAITCY NCY ; ; Generate code that does nothing for the next NCY cycles. Note that this is ; not accurate for timing unless interrupts are disabled. This macro ; generates no code if NCY is zero or less. This macro causes no changes to ; status bits, banking, or other state other than some number of instructions ; are executed. The number of instructions emitted is not guaranteed, only ; that they will take NCY instruction cycles to execute. ; ; This macro replaces the old WAITNOP macro for most cases. See the WAITNOP ; comments below for a discussion of this. ; waitcy macro ncy local n n set (ncy) ;init number of NOPs left to write if fam_12 | fam_16c5 | fam_16 while n >= 2 goto $+1 n set n - 2 endw endif if fam_16b while n >= 2 bra $+1 n set n - 2 endw endif if fam_18 while n >= 2 bra $+2 n set n - 2 endw endif while n > 0 nop n set n - 1 endw endm ;******************************************************************************* ; ; Macro NNOPS nnop ; ; Write NNOP consecutive NOP instructions. Nothing is done if NNOP is zero or ; negative. The WAITCY macro should be used if the intent is to waste a ; specific number of instruction cycles, since WAITCY does that and possibly ; takes fewer instruction words. Use NNOPS only when a number of consecutive ; NOP instructions are needed. This could be the case, for example, when ; indexing into a list of NOPs to exactly synchronize to a timer value. ; nnops macro nnop local n n set nnop ;init number of NOPs left to write while n > 0 nop n set n - 1 endw endm ;******************************************************************************* ; ; Macro WAITNS ns, cy ; ; Generates the minimum necessary inline instructions to wait at least NS ; nanoseconds minus CY instruction cycles. This macro is only meant for short ; delays since inline instructions are generated and the the processor is ; consumed executing them. Timing is not accurate unless interrupts are ; disabled. ; ; The purpose of this macro is to allow for accurate timing between two ; instructions with other fixed instructions also between the two events and ; without having to know the instruction rate. For example, if a minimum of ; 1 us is required between two instructions and 3 other instructions are ; always executed between the two outside this macro, then ; ; WAITNS 1000, 4 ; ; will add the minimum necessary wait. Note that the CY parameter is 4 and ; not 3 since there is 1 instruction cycle between consecutive instructions ; even with no additional instructions in between. ; ; This macro produces no code if the minimum wait time is already met by the ; CY instructions. ; waitns macro ns, cy local waitns_ii waitns_ii set 2000000000 / (freq_inst / 5) ;instruction time in 100 fs units waitns_ii set (((ns) * 10) + waitns_ii - 1) / waitns_ii ;total instr wait needed waitns_ii set waitns_ii - (cy) ;subtract off instructions already waited if waitns_ii & h'80000000' ;remaining instructions to wait is negative ? exitm endif waitcy waitns_ii ;add the wait instructions endm ;******************************************************************************* ; ; Macro WAITUS us, cy ; ; Just like WAITNS except that the wait time parameter US is in microseconds ; instead of nanoseconds. ; waitus macro us, cy waitns ((us) * 1000), cy endm //////////////////////////////////////////////////////////////////////////////// // // Macro WAITSEC seconds // // Wait the indicated number of seconds, which can be a floating point value. // // This macro relies on the following subroutines to exist: // // WAITMS - Wait the number of milliseconds in REG0. // // WAITMS16 - Wait the number of milliseconds in REG1:REG0. // // The appropriate wait routine is chosen, depending on the length of the // wait. Only REG0 is trashed for up to 255 ms. REG1 and REG0 are trashed // for longer waits. // /macro waitsec /var local ms integer = [rnd [* 1000 [vnl [arg 1]]]] ;num of ms to wait /if [> ms 65535] then /show " ERROR: Wait time too long, must be 65.535 seconds or less" .error "Wait time" .end /stop /endif /if [<= ms 255] /then ;use single byte wait loadk8 reg0, [v ms] gcall waitms /else ;use double byte wait loadk16 reg0, [v ms] gcall waitms16 /endif /endmac ;******************************************************************************* ; ; Macro TIMER0_PER cy ; ; Update timer 0 so that it next wraps CY cycles from the previous wrap. This ; can be useful in a timer 0 interrupt routine to set the exact number of ; cycles until the next timer 0 interrupt. Timer 0 is assumed to be running ; from the instruction clock. The appropriate value is added into timer 0, so ; this macro does not need to be invoked a fixed delay after the last timer 0 ; wrap. CY must be a constant. ; ; The timer sets its interrupt flag when counting from 255, which wraps back ; to 0. If left alone, the timer therefore has a period of 256 instruction ; cycles. When adding a value into the timer, the increment is lost during ; the add instruction, and the timer is not incremented for two additional ; cycles when the TMR0 register is written to. This effectively adds 3 more ; cycles to the timer 0 wrap period. These additional cycles are taken into ; account in computing the value to add to TMR0. ; timer0_per macro cy dbankif tmr0 movlw 256 + 3 - (cy) addwf tmr0 endm //////////////////////////////////////////////////////////////////////////////// // // Subroutine TIMER2_CONFIG_SET pre per pos [desper] // // Determine the timer 2 configuration constants according to the call // parameters. No executable code is generated, only preprocessor and // assembly state is created and/or updated. The call parameters are: // // PRE - Prescaler: 1, 4, 16 // // PER - period after prescaler: 1 - 256 // // POS - Postscaler: 1 - 16 // // DESPER - Desired overall period, seconds. This parameter is optional. // The constant TMR2_ERROR (see below) is created if DESPER is given. // // The arguments will be checked, and assembly bombed with error on any // invalid values. // // The following preprocessor constants will be set: // // TMR2_PRE, integer - timer 2 prescaler value: 1, 4, or 16 // // TMR2_PER, integer - timer 2 period divide value: 1 - 256 // // TMR2_POS, integer - timer 2 postscaler value: 1 - 16 // // TMR2_PERIOD, real - Actual timer 2 period in seconds. // // TMR2_ERROR - Relative error of actual period with respect to the // desired period. This value times 100 would be the percent error. This // constant is only created when the DESPER call argument is provided. // // TMR2_PERPWM, real - Period delivered to CCP module for PWM, seconds. // This is the period without the postscaler, taking into account only the // prescaler and period settings. // // TMR2_CYCLES, integer - Actual timer 2 period in instruction cycles. // // TMR2_FREQ, real - Actual timer 2 frequency, Hz. // // Assembly constants will also be created: TMR2_PRE, TMR2_PER, TMR2_POS. // /subroutine timer2_config_set /var local pre integer = [vnl [arg 1]] /var local per integer = [vnl [arg 2]] /var local pos integer = [vnl [arg 3]] /var local desper_given bool = [exist 4 arg] /if desper_given then /var local desper real = [vnl [arg 4]] /endif // // Validate the call arguments. // /pick first by pre /option 1 4 16 /optionelse /show " Invalid timer 2 prescaler of " pre error "Timer 2 prescaler" end /stop /endpick /if [or [< per 1] [> per 256]] then /show " Invalid timer 2 period value of " per error "Timer 2 period" end /stop /endif /if [or [< pos 1] [> pos 16]] then /show " Invalid timer 2 postscaler of " pos error "Timer 2 postscaler" end /stop /endif // // Create the basic timer 2 configuration constants. // /if [exist "tmr2_pre:vcon"] then /show " TMR2_PRE previously created" error TMR2_PRE end /stop /endif /const tmr2_pre integer = pre /if [exist "tmr2_per:vcon"] then /show " TMR2_PER previously created" error TMR2_PER end /stop /endif /const tmr2_per integer = per /if [exist "tmr2_pos:vcon"] then /show " TMR2_POS previously created" error TMR2_POS end /stop /endif /const tmr2_pos integer = pos tmr2_pre equ [v tmr2_pre] ;make assembly constants of the same names tmr2_per equ [v tmr2_per] tmr2_pos equ [v tmr2_pos] // // Compute the derived values. // /const tmr2_cypwm integer = [* tmr2_pre tmr2_per] ;PWM period, cycles /const tmr2_perpwm real = [/ tmr2_cypwm freq_inst] ;PWM period, seconds /const tmr2_cycles integer = [* tmr2_cypwm tmr2_pos] ;full period, cycles /const tmr2_period real = [/ tmr2_cycles freq_inst] ;full period, seconds /const tmr2_freq real = [/ 1 tmr2_period] ;timer 2 frequency, Hz /if desper_given then //desired period is known ? /const tmr2_error real = [/ [- tmr2_period desper] desper] ;period error /endif /endsub //////////////////////////////////////////////////////////////////////////////// // // Macro TMR2CY_CLOSEPER period // // Computes the timer 2 setup to result in the desired period as close as // possible. PERIOD is the desired timer 2 period in floating point seconds. // // This macro produces no code. It only creates preprocessor and assembler // constants. See subroutine TIMER2_CONFIG_SET for a list of the constants. // // The configuration resulting in the closest period to PERIOD is always // chosen. When multiple configurations result in the same lowest error, then // the PWM period is minimized (the postscaler maximized). Otherwise, the PWM // resolution is maximized by minimizing the prescaler. // /macro tmr2cy_closeper /var local dper real = [vnl [arg 1]] ;desired period, seconds /var local pre integer ;current prescaler: 1, 4, 16 /var local percy integer ;current period, cycles /var local period real ;current period, seconds /var local err real ;current error /var local best_err real ;best error so far /var local best_pre integer ;best prescaler so far /var local best_per integer ;best period divider so far /var local best_pos integer ;best postscaler so far /set best_err 1e35 ;init to out of range error so far /loop with pos from 16 to 1 by -1 ;try the various postscalers /loop with pren from 0 to 2 ;try prescalers in ascending order /set pre [exp 4 pren] ;actual prescaler, power of 4 /loop with per from 1 to 256 /set percy [* pre per pos] ;total period, cycles /set period [/ percy freq_inst] ;total period, seconds /set err [abs [/ [- period dper] dper]] ;error at this setting /if [< err best_err] then ;found better config than previous ? /set best_pre pre ;update best config found so far /set best_per per /set best_pos pos /set best_err err /endif /endloop ;back for next higher period divider /endloop ;back for next higher prescaler /endloop ;back for next lower postscaler /call timer2_config_set best_pre best_per best_pos dper /endmac //////////////////////////////////////////////////////////////////////////////// // // // Macro TMR2CY_MAXPER cycles // // Like TIMER2_CYCLES except that the period value is maximized as the highest // priority and the postscaler maximized second. This results in the highest // possible PWM resolution within the total period constraint, and the highest // PWM frequency for the chosen PWM period. // /macro tmr2cy_maxper /var local cycles integer = [arg 1] ;instruction cycles in whole period /var exist tmr2_pre integer ;prescaler value: 1, 4, or 16 /var exist tmr2_per integer ;period value: 1 - 256 /var exist tmr2_pos integer ;postscaler value: 1 - 16 /var local ii integer /set tmr2_per 257 ;init to one past first period to try /block ;back here to try each new period /set tmr2_per [- tmr2_per 1] ;make period for this iteration /if [< tmr2_per 1] then ;exhausted all periods without a solution ? /show " Unable to achieve timer 2 interrupt period of " cycles " cycles" error Timer 2 period end /stop /endif /set ii [div cycles tmr2_per] ;make remaining divisors product /if [<> [* ii tmr2_per] cycles] then ;period doesn't divide evenly ? /repeat ;this period unusable, back and try next /endif /set tmr2_pre 1 ;init prescaler value to try next /block ;back here each new prescaler value /set tmr2_pos [div ii tmr2_pre] ;postscaler for this candidate /if [and [>= tmr2_pos 1] [<= tmr2_pos 16]] then ;valid postscaler ? /if [= [* tmr2_pre tmr2_per tmr2_pos] cycles] then ;found solution ? tmr2_pre set [v tmr2_pre] ;prescaler: 1, 4, 16 tmr2_per set [v tmr2_per] ;period: 1 - 256 tmr2_pos set [v tmr2_pos] ;postscaler: 1 - 16 /quitmac ;all done, solution set /endif /endif /set tmr2_pre [* tmr2_pre 4] ;advance to next prescaler /if [<= tmr2_pre 16] then ;still within range ? /repeat /endif /endblock ;done trying all prescaler choices /repeat ;back to try next period /endblock /endmac ;******************************************************************************* ; ; Macro TIMER2_CYCLES tmr2ncy ; ; *** WARNING: Legacy, not for new code *** ; Use TMR2CY_CLOSEPER or TMR2CY_MAXPER. ; ; Calculate a timer 2 setup to achieve the interrupt period of TMR2NCY ; instruction cycles. This macro generates no code, but sets the following ; assembler variables: ; ; TMR2_PRE - timer 2 prescaler value: 1, 4, or 16 ; ; TMR2_PER - timer 2 period divide value: 1 - 256 ; ; TMR2_POS - timer 2 postscaler value: 1 - 16 ; ; The postscaler is set to the maximum possible value that results in the ; specified period. This minimizes the PWM period for the given overall ; period. Within this constraint, the smallest possible prescaler is used, ; thereby maximizing the PWM frequency. This results in the maximum possible ; PWM resolution given the overall interrupt period constraint. ; ; The hardware PWM period, if used, is defined by the prescaler and period ; values only. The number of instructions per PWM period is therefore ; TMR2_PRE * TMR2_PER, whereas the number of instructions per timer 2 ; interrupt (if enabled) is TMR2_PRE * TMR2_PER * TMR2_POS. ; ; An error is generated if the indicated period can not be attained exactly ; within the constraints of the timer 2 hardware. ; timer2_cycles macro tmr2ncy tmr2_pos set 16 ;init the postscaler to maximum while tmr2_pos >= 1 ;loop thru decreasing postscaler values tmr2_pre set 1 ;init the prescaler to minimum while tmr2_pre <= 16 ;loop thru increasing prescaler values tmr2_per set tmr2ncy / (tmr2_pre * tmr2_pos) ;make period value with this pre/post if (tmr2_per >= 1) && (tmr2_per <= 256) ;period within range ? if (tmr2_pre * tmr2_per * tmr2_pos) == tmr2ncy ;result achieved exactly ? exitm ;found exact result, all done endif endif tmr2_pre set tmr2_pre * 4 ;advance to next prescaler value to try endw ;back to try with this new prescaler value tmr2_pos set tmr2_pos - 1 ;advance to next postscaler value to try endw ;back to try with this new postscaler value error "Requested timer 2 period can not be achieved in TIMER2_CYCLES." endm ;******************************************************************************* ; ; Macro TIMER2_USEC usec ; ; *** WARNING: Legacy, not for new code *** ; Use TMR2CY_CLOSEPER or TMR2CY_MAXPER. ; ; Calculate a timer 2 setup to achieve an interrupt period of USEC ; microseconds. This macro is a wrapper around macro TIMER2_CYCLES where the ; period is expressed in microseconds instead of instruction cycles. See the ; TIMER2_CYCLES documentation (above) for details. ; timer2_usec macro usec local nsec ;period in nanoseconds local ninstr ;number of instruction cycles in period ninstr set ((freq_inst / 16) * usec) / 62500 ;instructions in period if ((62500 * ninstr) / (freq_inst / 16)) != usec error "Requested period not multiple of instruction time in TIMER2_USEC" exitm endif timer2_cycles ninstr endm //////////////////////////////////////////////////////////////////////////////// // // Subroutine TIMER2B_PER_SET // // Set up a enhanced 16F152xx timer 2 to generate a periodic event. The // timer is set up from the following preprocessor state: // // TMR2_PRE - Prescaler, power of 2 from 1 to 128 // // TMR2_PER - Period divide value, 1 - 256 // // TMR2_POS - Postscaler, 1 - 16 // /subroutine timer2b_per_set /var local ii integer ;scratch integer /var local s string ;scratch string // // Validate the setup parameters. // /set ii [rnd [log2 tmr2_pre]] //get the prescaler power of 2 /set ii [max 0 [min 7 ii]] //clip to the valid range /set ii [shiftl 1 ii] //re-create the original power of 2 /if [<> ii tmr2_pre] then //invalid prescaler value ? /show " Timer 2 prescaler of " tmr2_pre " is out of range." errors TMR2_PRE end /stop /endif /if [or [< tmr2_per 1] [> tmr2_per 256]] then /show " Timer 2 period of " tmr2_per " is out of range." errors TMR2_PER end /stop /endif /if [or [< tmr2_pos 1] [> tmr2_pos 16]] then /show " Timer 2 postscaler of " tmr2_pos " is out of range." errors TMR2_POS end /stop /endif // // The setup parameters are valid. Now set the timer registers. // /set ii [shiftl [rnd [log2 tmr2_pre]] 4] //init value with prescaler field /set ii [or ii [- tmr2_pos 1]] //merge in postscaler field /set s "" /call tabopcode s /append s "setreg" /call taboperand s /append s ii ", t2con" /call startcomm s /append s "timer off, prescaler " tmr2_pre ", postscaler " tmr2_pos /write s setreg 0, t2tmr ;init the timer counter to 0 /set s "" /call tabopcode s /append s "setreg" /call taboperand s /append s [- tmr2_per 1] ", t2pr" /call startcomm s /append s "set timer 2 period to " tmr2_per /write s setreg b'00000001', t2clkcon ; XXXXX--- unused ; -----001 clock source is instruction clock dbankif pir1 bcf pir1, tmr2if ;clear the clock tick flag /endsub ;******************************************************************************* ; ; Macro TIMER2_SETUP ; ; Set up timer 2 to periodically set the timer 2 interrupt flag. This macro ; only sets up the timer hardware and initializes the flag to reset. It does ; not enable the timer or its interrupt. ; ; Timer 2 divides the instruction clock by a prescaler, period, and postscaler ; to make the interrupt period. The following assembler variables must be ; previously set to define the timer 2 divider chain: ; ; TMR2_PRE - timer 2 prescaler value: 1, 4, or 16 ; ; TMR2_PER - timer 2 period divide value: 1 - 256 ; ; TMR2_POS - timer 2 postscaler value: 1 - 16 ; ; Note that the TMR2CY_xxx macros can be used to compute these values given a ; desired interrupt period. It is an error if any of these assembler ; variables don't exist or are set to invalid values. ; ; For newer timer 2 types, the state must be supplied both as preprocessor ; constants and assembly constants. This is automatically done by the ; TMR2CY_xxx macros. The old TIMER2_xxx macros are not supported with the new ; timer 2 type. ; timer2_setup macro local t2con_val ; ; Handle traditional timer 2. ; ; Verify prescaler value is valid. ; ifndef tmr2_pre error "TMR2_PRE variable not defined before TIMER2_SETUP_INTR called." exitm endif if !((tmr2_pre == 1) || (tmr2_pre == 4) || (tmr2_pre == 16)) error "Illegal TMR2_PRE value found in TIMER2_SETUP_INTR macro." exitm endif ; ; Verify period value is valid. ; ifndef tmr2_per error "TMR2_PER variable not defined before TIMER2_SETUP_INTR called." exitm endif if (tmr2_per < 1) || (tmr2_per > 256) error "Illegal TMR2_PER value found in TIMER2_SETUP_INTR macro." exitm endif ; ; Verify postscaler value is valid. ; ifndef tmr2_pos error "TMR2_POS variable not defined before TIMER2_SETUP_INTR called." exitm endif if (tmr2_pos < 1) || (tmr2_pos > 16) error "Illegal TMR2_POS value found in TIMER2_SETUP_INTR macro." exitm endif ; ; All configuration value are within range. Now set up the timer 2 state and ; the associated interrupt. ; dbankif pr2 movlw tmr2_per - 1 ;set the period register movwf pr2 dbankif t2con t2con_val set b'00000100' ;set static timer 2 control bits ; X------- unused ; -0000--- postscaler selection, set below ; -----1-- enable timer 2 ; ------00 init prescaler to 1, altered below if different t2con_val set t2con_val | ((tmr2_pos - 1) << 3) ;merge in postscaler field if tmr2_pre == 4 ;prescaler divide value is 4 ? t2con_val set t2con_val | 1 ;merge in value for prescaler divide by 4 endif if tmr2_pre == 16 ;prescaler divide value is 16 ? t2con_val set t2con_val | 2 ;merge in value for prescaler divide by 16 endif movlw t2con_val ;set timer 2 control register movwf t2con dbankif tmr2 clrf tmr2 ;leave max time before first interrupt endm //////////////////////////////////////////////////////////////////////////////// // // Subroutine TIMER2_SETUP // // Set up timer 2 to periodically set the timer 2 interrupt flag. The timer // is only configured, not run. // // The timer is set up according to the following preprocessor constants: // // TMR2_PRE - timer 2 prescaler value: 1, 4, or 16 // // TMR2_PER - timer 2 period divide value: 1 - 256 // // TMR2_POS - timer 2 postscaler value: 1 - 16 // // Note that the TMR2CY_xxx macros can be used to compute these values given a // desired period. // /subroutine timer2_setup /if [exist "_t2rst:const"] then ;16F152xx type ? /call timer2b_per_set /return /endif timer2_setup ;use old macro for the old timer 2 type /endsub ;******************************************************************************* ; ; Macro TIMER2_SETUP_INTR ; ; Same as TIMER2_SETUP (above), but also enables the timer 2 ; interrupt. ; timer2_setup_intr macro timer2_setup dbankif pir1 bcf pir1, tmr2if ;clear any existing interrupt condition dbankif pie1 bsf pie1, tmr2ie ;enable timer 2 interrupt endm ;******************************************************************************* ;******************************************************************************* ; ; Handling multi-byte data. ; ;******************************************************************************* ; ; Macro DTWORD val16 ; ; Define a 16 bit value in a table. The low byte is stored first, the high ; byte second. ; dtword macro val16 ;***** ; if fam_16 retlw low (val16) retlw high (val16) exitm endif ;***** ; if fam_17 || fam_18 db low (val16), high (val16) exitm endif ;***** ; error "DTWORD macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro DT32I val32 ; ; Define a 32 bit integer value in a table. The low byte is stored first, the ; high byte last. ; dt32i macro val32 ;***** ; if fam_16 retlw (val32) & h'FF' retlw ((val32) >> 8) & h'FF' retlw ((val32) >> 16) & h'FF' retlw ((val32) >> 24) & h'FF' exitm endif ;***** ; if fam_17 || fam_18 data (val32) & h'FFFF' data ((val32) >> 16)) & h'FFFF' exitm endif ;***** ; error "DT32I macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro LOADK32 <adr>, <32 bit constant> ; ; Load the 32 bit constant into the memory locations starting at ADR. ADR is ; the address of the least significant byte. The remaining bytes follow at ; increasing memory addresses. The direct register bank must already be set ; for access to the four data bytes. ; ; This macro uses CLRF and SETF instructions when possible, and avoids ; re-loading W with the same value. ; loadk32 macro adr, kk local currw, newval, ii currw set 256 ;init current W to invalid value to force next load ii set 0 ;init offset of next byte to write into while ii < 4 ;once for the offset of each byte to write into newval set ((kk) >> (ii * 8)) & 255 ;make value to write into this byte if newval == 0 clrf (adr) + ii ;value is 0, use single instruction else if fam_18 && (newval == 255) setf (adr) + ii ;value is all bits on, use single instruction else if currw != newval ;W not already set to the value for this byte ? movlw newval ;load W with the value for this byte currw set newval ;remember how W is loaded endif movwf (adr) + ii ;write W into this byte endif endif ii set ii + 1 ;make offset for next byte endw ;back to do the next byte endm ;******************************************************************************* ; ; Macro LOADK24 <adr>, <24 bit constant> ; ; Load the 24 bit constant into the memory locations starting at ADR. ADR is ; the address of the least significant byte. The remaining bytes follow at ; increasing memory addresses. The direct register bank must already be set ; for access to the data bytes at ADR. ; ; This macro uses CLRF and SETF instructions when possible, and avoids ; re-loading W with the same value. ; loadk24 macro adr, kk local currw, newval, ii currw set 256 ;init current W to invalid value to force next load ii set 0 ;init offset of next byte to write into while ii < 3 ;once for the offset of each byte to write into newval set ((kk) >> (ii * 8)) & 255 ;make value to write into this byte if newval == 0 clrf (adr) + ii ;value is 0, use single instruction else if fam_18 && (newval == 255) setf (adr) + ii ;value is all bits on, use single instruction else if currw != newval ;W not already set to the value for this byte ? movlw newval ;load W with the value for this byte currw set newval ;remember how W is loaded endif movwf (adr) + ii ;write W into this byte endif endif ii set ii + 1 ;make offset for next byte endw ;back to do the next byte endm ;******************************************************************************* ; ; Macro LOADK16 <adr>, <16 bit constant> ; ; Load the 16 bit constant into the memory locations starting at ADR. ADR is ; the address of the least significant byte. The remaining bytes follow at ; increasing memory addresses. The direct register bank must already be set ; for access to the data bytes at ADR. ; ; This macro uses CLRF and SETF instructions when possible, and avoids ; re-loading W with the same value. ; loadk16 macro adr, kk local currw, newval, ii currw set 256 ;init current W to invalid value to force next load ii set 0 ;init offset of next byte to write into while ii < 2 ;once for the offset of each byte to write into newval set ((kk) >> (ii * 8)) & 255 ;make value to write into this byte if newval == 0 clrf (adr) + ii ;value is 0, use single instruction else if fam_18 && (newval == 255) setf (adr) + ii ;value is all bits on, use single instruction else if currw != newval ;W not already set to the value for this byte ? movlw newval ;load W with the value for this byte currw set newval ;remember how W is loaded endif movwf (adr) + ii ;write W into this byte endif endif ii set ii + 1 ;make offset for next byte endw ;back to do the next byte endm ;******************************************************************************* ; ; Macro LOADK8 <adr>, <8 bit constant> ; ; Load the 8 bit constant into the memory location at ADR. The direct ; register bank must already be set for access ADR. ; ; This macro uses CLRF and SETF instructions to load the value into ADR when ; possible. ; loadk8 macro adr, kk local newval newval set low (kk) ;make the 8 bit value to store in NEWVAL if newval == 0 clrf (adr) ;set to 0 in single cycle exitm endif if fam_18 && (newval == h'FF') setf (adr) ;set to FFh in single cycle exitm endif movlw newval ;get the data value into W movwf (adr) ;write it to the target endm ;******************************************************************************* ; ; Macro LOADADR16 dest, adr ; ; Load the 16 bit address ADR into the two bytes starting at DEST. The direct ; register bank state must be set up for access to DEST. The low byte will be ; stored at DEST and the high byte at DEST+1. ADR may be a label or ; resolvable relocatable expressions that is not known at assembly time. ; loadadr16 macro dest, adr movlw low (adr) movwf dest movlw high (adr) movwf (dest)+1 endm ;******************************************************************************* ; ; Macro LOADADR24 dest, adr ; ; Load the 24 bit address ADR into the three bytes starting at DEST. The ; direct register bank state must be set up for access to DEST. The low byte ; will be stored at DEST. ADR may be a label or resolvable relocatable ; expressions that is not known at assembly time. ; loadadr24 macro dest, adr movlw low (adr) movwf dest movlw high (adr) movwf (dest)+1 movlw upper (adr) movwf (dest)+2 endm ;******************************************************************************* ; ; Macro COPYN <dest>, <src>, <n> ; ; Copy the N bytes starting at SRC into the N bytes starting at DEST. The ; direct register bank must be set for access to all bytes of SRC and DEST. ; The results are undefined if SRC and DEST overlap. The copy instructions ; are written successively instead of a runtime loop. This macros is ; therefore intended for copying "short" values only. ; copyn macro dest, src, n local ii, didit ii set 0 ;init offset from start of SRC and DEST didit set false while ii < n ;once for each byte to copy ;***** ; if fam_16 || fam_16b || fam_16c5 || fam_12 movf src+ii, w ;get the source byte movwf dest+ii ;write it into the destination didit set true endif ;***** ; if fam_17 movfp src+ii, wreg ;get the source byte movwf dest+ii ;write it into the destination didit set true endif ;***** ; if fam_18 movff src+ii, dest+ii ;copy this byte didit set true endif ;***** ; if ! didit error "COPYN in STD.INS.ASPIC not implemented for this processor family" endif ii set ii + 1 ;increment byte offset for next iteration endw endm ;******************************************************************************* ; ; Macro COPY32 <dest>, <src> ; ; Copy the 32 bit value at SRC into DEST. The direct register bank must be ; set for access to all bytes of SRC and DEST. The results are undefined if ; SRC and DEST overlap. ; copy32 macro dest, src copyn dest, src, 4 endm ;******************************************************************************* ; ; Macro COPY24 <dest>, <src> ; ; Copy the 24 bit value at SRC into DEST. The direct register bank must be ; set for access to all bytes of SRC and DEST. The results are undefined if ; SRC and DEST overlap. ; copy24 macro dest, src copyn dest, src, 3 endm ;******************************************************************************* ; ; Macro COPY16 <dest>, <src> ; ; Copy the 16 bit value at SRC into DEST. The direct register bank must be ; set for access to all bytes of SRC and DEST. The results are undefined if ; SRC and DEST overlap. ; copy16 macro dest, src copyn dest, src, 2 endm ;******************************************************************************* ; ; Macro SHIFT32RL1 <adr> ; ; Perform a logical right shift of 1 bit on a 32 bit value. ADR is the ; address of the least significant byte. The remaining bytes follow at ; increasing memory addresses. The direct register bank must be set for ; access to the four data bytes. ; shift32rl1 macro adr ;***** ; if fam_16 bcf status, c ;set value to shift into high bit rrf (adr) + 3 rrf (adr) + 2 rrf (adr) + 1 rrf (adr) + 0 exitm endif ;***** ; if fam_16b lsrf (adr) + 3 rrf (adr) + 2 rrf (adr) + 1 rrf (adr) + 0 exitm endif ;***** ; if fam_17 bcf alusta, c rrcf (adr) + 3 rrcf (adr) + 2 rrcf (adr) + 1 rrcf (adr) + 0 exitm endif ;***** ; if fam_18 bcf status, c rrcf (adr) + 3 rrcf (adr) + 2 rrcf (adr) + 1 rrcf (adr) + 0 exitm endif ;***** ; error "SHIFT32RL1 macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro SHIFT32RA1 <adr> ; ; Perform an arithmetic right shift of 1 bit on a 32 bit value. ADR is the ; address of the least significant byte. The remaining bytes follow at ; increasing memory addresses. The direct register bank must be set for ; access to the four data bytes. ; ; W is trashed. ; shift32ra1 macro adr ;***** ; if fam_16 rlf (adr) + 3, w ;set carry flag to bit value to shift in from left rrf (adr) + 3 rrf (adr) + 2 rrf (adr) + 1 rrf (adr) + 0 exitm endif ;***** ; if fam_16b asrf (adr) + 3 rrf (adr) + 2 rrf (adr) + 1 rrf (adr) + 0 exitm endif ;***** ; if fam_17 rlcf (adr) + 3, w ;set carry flag to bit value to shift in from left rrcf (adr) + 3 rrcf (adr) + 2 rrcf (adr) + 1 rrcf (adr) + 0 exitm endif ;***** ; if fam_18 rlcf (adr) + 3, w ;set carry flag to bit value to shift in from left rrcf (adr) + 3 rrcf (adr) + 2 rrcf (adr) + 1 rrcf (adr) + 0 exitm endif ;***** ; error "SHIFT32RA1 macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro SHIFT32L1 <adr> ; ; Perform a left shift of 1 bit on a 32 bit value. ADR is the address of the ; least significant byte. The remaining bytes follow at increasing memory ; addresses. The direct register bank must be set for access to the four data ; bytes. ; shift32l1 macro adr ;***** ; if fam_16 bcf status, c ;set value to shift into low bit rlf (adr) + 0 rlf (adr) + 1 rlf (adr) + 2 rlf (adr) + 3 exitm endif ;***** ; if fam_16b lslf (adr) + 0 rlf (adr) + 1 rlf (adr) + 2 rlf (adr) + 3 exitm endif ;***** ; if fam_17 bcf alusta, c rlcf (adr) + 0 rlcf (adr) + 1 rlcf (adr) + 2 rlcf (adr) + 3 exitm endif ;***** ; if fam_18 bcf status, c rlcf (adr) + 0 rlcf (adr) + 1 rlcf (adr) + 2 rlcf (adr) + 3 exitm endif ;***** ; error "SHIFT32L1 macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro ADD32 <dest>, <src>, <temp> ; ; Add the 32 bit source value into the 32 bit destination value. TEMP will be ; used for temporary storage, and will be trashed. The 32 bit values are ; stored with the low byte first. The direct register bank must be set for ; access to all state. ; add32 macro dest, src, temp ;***** ; ; 16 family processors. ; if fam_16 movf src, w addwf dest ;do add for byte 0 clrw ;init to no carry skip_ncarr movlw 1 addwf dest+1 ;propagate carry clrf temp ;init to no carry skip_ncarr incf temp ;carry happened on propagate carry movf src+1, w addwf dest+1 ;do the add for this byte skip_ncarr incf temp movf temp, w addwf dest+2 ;propagate carry clrf temp ;init to no carry skip_ncarr incf temp ;carry happened on propagate carry movf src+2, w addwf dest+2 ;do the add for this byte skip_ncarr incf temp movf temp, w addwf dest+3 ;propagate carry movf src+3, w addwf dest+3 ;do the add for this byte exitm endif ;***** ; ; Enhanced PIC 16 (enhanced 14 bit core). ; if fam_16b movf src+0, w addwf dest+0 ;add byte 0 movf src+1, w addwfc dest+1 ;add byte 1 movf src+2, w addwfc dest+2 ;add byte 2 movf src+3, w addwfc dest+3 ;add byte 3 exitm endif ;***** ; ; 17C family processors. ; if fam_17 movfp src+0, wreg addwf dest+0 ;add byte 0 movfp src+1, wreg addwfc dest+1 ;add byte 1 movfp src+2, wreg addwfc dest+2 ;add byte 2 movfp src+3, wreg addwfc dest+3 ;add byte 3 exitm endif ;end of 17C processor case ;***** ; ; 18 family processors. ; if fam_18 movf (src)+0, w addwf (dest)+0 ;add byte 0 movf (src)+1, w addwfc (dest)+1 ;add byte 1 movf (src)+2, w addwfc (dest)+2 ;add byte 2 movf (src)+3, w addwfc (dest)+3 ;add byte 3 exitm endif ;end of 18 family case ;***** ; error "ADD32 macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro ADD24 <dest>, <src>, <temp> ; ; Add the 24 bit source value into the 24 bit destination value. TEMP will be ; used for temporary storage, and will be trashed. The 24 bit values are ; stored with the low byte first. The direct register bank must be set for ; access to all state. ; add24 macro dest, src, temp ;***** ; ; 16 family processors. ; if fam_16 movf src, w addwf dest ;do add for byte 0 clrw ;init to no carry skip_ncarr movlw 1 addwf dest+1 ;propagate carry clrf temp ;init to no carry skip_ncarr incf temp ;carry happened on propagate carry movf src+1, w addwf dest+1 ;do the add for this byte skip_ncarr incf temp movf temp, w addwf dest+2 ;propagate carry movf src+2, w addwf dest+2 ;do the add for this byte exitm endif ;***** ; ; Enhanced PIC 16. ; if fam_16b movf src+0, w addwf dest+0 ;add byte 0 movf src+1, w addwfc dest+1 ;add byte 1 movf src+2, w addwfc dest+2 ;add byte 2 exitm endif ;***** ; ; 17C family processors. ; if fam_17 movfp src+0, wreg addwf dest+0 ;add byte 0 movfp src+1, wreg addwfc dest+1 ;add byte 1 movfp src+2, wreg addwfc dest+2 ;add byte 2 exitm endif ;end of 17C processor case ;***** ; ; 18 family processors. ; if fam_18 movf (src)+0, w addwf (dest)+0 ;add byte 0 movf (src)+1, w addwfc (dest)+1 ;add byte 1 movf (src)+2, w addwfc (dest)+2 ;add byte 2 exitm endif ;end of 18 family case ;***** ; error "ADD24 macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro ADD16 <dest>, <src> ; ; Add the 16 bit source value into the 16 bit destination value. The 16 bit ; value are stored with the low byte first. The direct register bank must be ; set for access to all state. ; add16 macro dest, src ;***** ; ; For 16 family processors. ; if fam_16 movf src+0, w addwf dest+0 ;do add for byte 0 skip_ncarr ;no carry into upper byte ? incf dest+1 ;propagate the carry movf src+1, w addwf dest+1 ;do the add for byte 1 exitm endif ;end of 16C processor case ;***** ; ; For enhanced PIC 16. ; if fam_16b movf src+0, w addwf dest+0 ;add byte 0 movf src+1, w addwfc dest+1 ;add byte 1 exitm endif ;***** ; ; For 17C family processors. ; if fam_17 movfp src+0, wreg addwf dest+0 ;add byte 0 movfp src+1, wreg addwfc dest+1 ;add byte 1 exitm endif ;end of 17C processor case ;***** ; ; For 18 family processors. ; if fam_18 movf (src)+0, w addwf (dest)+0 ;add byte 0 movf (src)+1, w addwfc (dest)+1 ;add byte 1 exitm endif ;end of 18 family case ;***** ; error "ADD16 macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro SUB32 <dest>, <src>, <temp> ; ; Subtract the 32 bit source value from the 32 bit destination value and put ; the result into the destination. TEMP will be used for temporary storage, ; and will be trashed. The 32 bit values are stored with the low byte first. ; The direct register bank must be set for access to all state. ; sub32 macro dest, src, temp ;***** ; ; For 16 family processors. ; if fam_16 movf src+0, w subwf dest+0 ;do subtract for byte 0 clrw ;init to no borrow skip_nborr movlw 1 subwf dest+1 ;propagate borrow clrf temp ;init borrow from this byte skip_nborr incf temp ;borrow happened on propagate borrow movf src+1, w subwf dest+1 ;do the subtract for this byte skip_nborr incf temp ;set borrow from this byte movf temp, w ;get borrow value from last byte subwf dest+2 ;propagate borrow clrf temp ;init borrow from this byte skip_nborr incf temp ;borrow happened on propagate borrow movf src+2, w subwf dest+2 ;do the subtract for this byte skip_nborr incf temp ;set borrow from this byte movf temp, w ;get borrow value from last byte subwf dest+3 ;propagate borrow movf src+3, w subwf dest+3 exitm endif ;***** ; ; For enhanced PIC 16. ; if fam_16b movf src+0, w subwf dest+0 ;subtract byte 0 movf src+1, w subwfb dest+1 ;subtract byte 1 movf src+2, w subwfb dest+2 ;subtract byte 2 movf src+3, w subwfb dest+3 ;subtract byte 3 exitm endif ;***** ; ; For 17C family processors. ; if fam_17 movfp src+0, wreg subwf dest+0 ;subtract byte 0 movfp src+1, wreg subwfb dest+1 ;subtract byte 1 movfp src+2, wreg subwfb dest+2 ;subtract byte 2 movfp src+3, wreg subwfb dest+3 ;subtract byte 3 exitm endif ;end of 17C processor case ;***** ; ; For 18 family processors. ; if fam_18 movf (src)+0, w subwf (dest)+0 ;subtract byte 0 movf (src)+1, w subwfb (dest)+1 ;subtract byte 1 movf (src)+2, w subwfb (dest)+2 ;subtract byte 2 movf (src)+3, w subwfb (dest)+3 ;subtract byte 3 exitm endif ;end of 18 family case ;***** ; error "SUB32 macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro SUB24 <dest>, <src> ; ; Subtract the 24 bit source value from the 24 bit destination value and put ; the result into the destination. The 24 bit values are stored with the low ; byte first. The direct register bank must be set for access to all state. ; sub24 macro dest, src ;***** ; ; For 16C family processors. ; if fam_16 local nborr movf src+0, w subwf dest+0 ;do subtract for byte 0 skip_borr ;borrow from byte 1 ? jump nborr ;no borrow movlw 1 subwf dest+1 ;propagate the borrow to byte 1 skip_nborr ;no borrow from byte 2 ? decf dest+2 ;propagate the borrow from byte 1 to byte 2 nborr ;skip to here on no borrow from byte 0 movf src+1, w subwf dest+1 ;do the subtract for byte 1 skip_nborr ;no borrow from next higher byte ? decf dest+2 ;propagate the borrow movf src+2, w subwf dest+2 ;do the subtract for byte 2 exitm endif ;end of 16C processor case ;***** ; ; For enhanced PIC 16. ; if fam_16b movf src+0, w subwf dest+0 ;subtract byte 0 movf src+1, w subwfb dest+1 ;subtract byte 1 movf src+2, w subwfb dest+2 ;subtract byte 2 exitm endif ;***** ; ; For 17C family processors. ; if fam_17 sub24 macro dest, src movfp src+0, wreg subwf dest+0 ;subtract byte 0 movfp src+1, wreg subwfb dest+1 ;subtract byte 1 movfp src+2, wreg subwfb dest+2 ;subtract byte 2 exitm endif ;end of 17C processor case ;***** ; ; For 18 family processors. ; if fam_18 movf (src)+0, w subwf (dest)+0 ;subtract byte 0 movf (src)+1, w subwfb (dest)+1 ;subtract byte 1 movf (src)+2, w subwfb (dest)+2 ;subtract byte 2 exitm endif ;end of 18 family case ;***** ; error "SUB24 macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro SUB16 <dest>, <src> ; ; Subtract the 16 bit source value from the 16 bit destination value and put ; the result into the destination. The 16 bit values are stored with the low ; byte first. The direct register bank must be set for access to all state. ; sub16 macro dest, src ;***** ; ; For 16C family processors. ; if fam_16 movf src+0, w subwf dest+0 ;do subtract for byte 0 skip_nborr ;no borrow from high byte ? decf dest+1 ;propagate the borrow movf src+1, w subwf dest+1 ;do subtract for byte 1 exitm endif ;end of 16C processor case ;***** ; ; For enhanced PIC 16. ; if fam_16b movf src+0, w subwf dest+0 ;subtract byte 0 movf src+1, w subwfb dest+1 ;subtract byte 1 exitm endif ;***** ; ; For 17C family processors. ; if fam_17 movfp src+0, wreg subwf dest+0 ;subtract byte 0 movfp src+1, wreg subwfb dest+1 ;subtract byte 1 exitm endif ;end of 17C processor case ;***** ; ; For 18 family processors. ; if fam_18 movf (src)+0, w subwf (dest)+0 ;subtract byte 0 movf (src)+1, w subwfb (dest)+1 ;subtract byte 1 exitm endif ;end of 18 family case ;***** ; error "SUB16 macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro NEGATE <dest>, <n> ; ; Negate the twos complement value starting at address DEST. DEST is N bytes ; long and is stored with the least significant byte first. N must be greater ; than zero, and all bytes of DEST must be accessible with the current direct ; register bank selection. ; negate macro dest, n local ii if n <= 0 error "N parameter to macro NEGATE must be at least 1." endif ii set 0 ;init byte offset into DEST while ii < n ;once for each byte in DEST comf dest+ii ;ones complement each byte ii set ii + 1 ;make byte offset for next loop endw incf dest+0 ;increment the least significant byte ii set 1 ;init byte offset into DEST while ii < n ;once for each byte in DEST btfsc status, z ;no carry from previous byte (wrapped to 0 on inc) ? incf dest+ii ;propagate the carry to this byte ii set ii + 1 ;make byte offset for next loop endw endm ;******************************************************************************* ; ; Macro FP24 <name> ; ; Declare memory for a 24 bit floating point number. The first (least ; significant) byte will have the label NAME. This macro simply reserves 3 ; bytes, but documents that these bytes are intended to be one floating point ; value. ; fp24 macro name name res 3 ;least to most significant byte order endm ;******************************************************************************* ; ; Macro FP24NEG <dest> ; ; Negate the 24 bit floating point number at DEST. The register bank setting ; must be set for direct access to DEST. ; fp24neg macro dest ;***** ; if fam_16 || fam_16b movlw h'80' ;get mask for bits to flip movf (dest) + 2 ;set Z flag on the sign and exponent byte skip_z ;FP value is zero, don't change anything ? xorwf (dest) + 2 ;flip the sign bit exitm endif ;***** ; if fam_18 movf (dest)+2 ;set Z flag if FP value is zero skip_z ;FP value is zero ? btg (dest)+2, 7 ;flip the sign bit exitm endif ;***** ; error "FP24NEG macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro FP24ABS <dest> ; ; Set the 24 bit floating point number at DEST to its absolute value. ; fp24abs macro dest bcf (dest) + 2, 7 ;make sure the sign bit indicates zero or positive endm ;******************************************************************************* ; ; Macro FP24MOVE src dest ; ; Copy the 24 bit floating point value from SRC to DEST. ; fp24move macro src, dest ;***** ; if fam_16 || fam_16b movf (src)+0, w movwf (dest)+0 movf (src)+1, w movwf (dest)+1 movf (src)+2, w movwf (dest)+2 exitm endif ;***** ; if fam_18 movff (src)+0, (dest)+0 movff (src)+1, (dest)+1 movff (src)+2, (dest)+2 exitm endif ;***** ; error "FP24MOVE macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro FP24LDK dest, kval ; ; Load the floating point constant KVAL into the 3-byte floating point ; variable at DEST. ; /macro fp24ldk /if [exist -1 arg] then /show "Dumb place for a label, in front of FP24LDK macro." error FP24LDK label end /stop /endif /var local kval real = [arg 2] movlw low [fp24i kval] [chars ";load " [fp kval "sig 6"]] movwf ([arg 1])+0 movlw high [fp24i kval] movwf ([arg 1])+1 movlw upper [fp24i kval] movwf ([arg 1])+2 /write "" /endmac //////////////////////////////////////////////////////////////////////////////// // // Subroutine FP48_MAKE val // // Converts VAL to 48 bit floating point. VAL must be convertable to a // preprocessor floating point value. The integer preprocessor variables // FP48_EXP and FP48_MANT are set to the exponent word and mantissa, // respectively. // // The exponent word is 16 bits wide with the high bit being the overall sign // bit. 0 is positive and 1 negative. The low 15 bits of the exponent word // are the power of 2 exponent to apply to the mantissa value plus 16384. For // example, if the low 15 bits have a value of 16385, then 2^1 is to be // applied to the mantissa value. Likewise, 16381 specifies to apply 2^-3. // // The mantissa is 32 bits wide. Its value is as if the binary point and then // a 1 bit were immediately to its left. For example, the mantissa value of // 3456789Ah is to be interpreted as the hexadecimal fixed point value // 1.3456789A, which has a decimal value of about 1.204444. The mantissa // value is therefore always from 1 up to but not including 2. // // The overal floating point value is the mantissa value times the power of 2 // indicated by the exponent, and the sign indicated by the high bit of the // exponent word. // // The special case of 0 is represented by all 48 bits 0. // /subroutine fp48_make /var exist fp48_exp integer /var exist fp48_mant integer /var local val real = [arg 1] /var local neg bool ;overal value is negative /var local exp integer ;power of 2 exponent /var local man1 integer ;mantissa low 16 bits /var local man2 integer ;mantissa high 16 bits /if [= val 0.0] then ;special case of 0 ? /set fp48_exp 0 /set fp48_mant 0 /return /endif /set neg [< val 0.0] ;negative ? /set val [abs val] ;work with the positive value from now on /set exp 0 ;init exponent of 2 (multiplier = 1) /block ;make smaller exponent to adjust value up /if [>= val 1.0] then ;large enough /quit /endif /set val [* val 2.0] /set exp [- exp 1] /repeat /endblock /block ;make larger exponent to adjust value down /if [< val 2.0] then ;small enough ? /quit /endif /set val [/ val 2.0] /set exp [+ exp 1] /repeat /endblock /set val [* val 65536.0] /set man2 [trunc val] ;make high 16 mantissa bits /set val [- val man2] ;remove the high 16 bits value /set val [* val 65536.0] /set man1 [rnd val] ;make low 16 mantissa bits /if [> man1 16#FFFF] then ;low 16 bits overflowed ? /set man2 [+ man2 [shiftr man1 16]] ;move excess to high word /set man1 [and man1 16#FFFF] /endif /if [> man2 16#1FFFF] then ;high 16 bits overflowed ? /set man1 [shiftr man1 1] ;shift mantissa right one bit /set man1 [or [shiftl [and man2 1] 15]] /set man2 [shiftr man2 1] /set exp [+ exp 1] ;adjust exponent to account for the shift /endif /set man2 [and man2 16#FFFF] ;mask in only the bits to save /set fp48_mant [or [shiftl man2 16] man1] ;assemble the mantissa /set fp48_exp [+ exp 16384] /if neg then /set fp48_exp [or fp48_exp 16#8000] ;set the negative bit /endif /endsub ;******************************************************************************* ;******************************************************************************* ; ; UART configuration. ; ;******************************************************************************* ; ; Macro UART_SELECT <n> ; ; Select which UART will be accessed when UART registers and other symbols ; without a qualifier are used. ; ; For example, on machines with two UARTs, there is no TXREG, but TXREG1 and ; TXREG2 instead. This macro would define TXREG to reference whichever is ; selected by the N argument. ; ; This macro allows code to be written that uses generic symbols, but that can ; be reused to drive all UARTs on machines that have multiple UARTs. ; ; Specifically, the following symbols will exist when the processor has the ; associated features: ; ; RCSTA - Receive status register ; ; TXSTA - Transmit status register ; ; SPBRG - Baud rate generator register ; ; SPBRGH - Baud rate generator register high byte (not all UARTs) ; ; TXREG - Transmit data register ; ; RCREG - Receive data register ; ; BAUDCON - Baud rate control register (not all UARTs) ; ; TXIF_REG - Register containing TXIF transmitter ready flag bit ; TXIF_BIT - TXIF bit number within its register ; TXIF_FLAG - String substitution macro for TXIF register and bit number ; ; TXIE_REG - Register containing TXIE transmitter ready flag bit ; TXIE_BIT - TXIE bit number within its register ; TXIE_FLAG - String substitution macro for TXIE register and bit number ; ; TXIP_REG - Register containing TXIP transmitter ready flag bit ; TXIP_BIT - TXIP bit number within its register ; TXIP_FLAG - String substitution macro for TXIP register and bit number ; ; RCIF_REG - Register containing RCIF transmitter ready flag bit ; RCIF_BIT - RCIF bit number within its register ; RCIF_FLAG - String substitution macro for RCIF register and bit number ; ; RCIE_REG - Register containing RCIE transmitter ready flag bit ; RCIE_BIT - RCIE bit number within its register ; RCIE_FLAG - String substitution macro for RCIE register and bit number ; ; RCIP_REG - Register containing RCIP transmitter ready flag bit ; RCIP_BIT - RCIP bit number within its register ; RCIP_FLAG - String substitution macro for RCIP register and bit number ; ; N must be greater than zero. It is an error if the processor does not ; have at least N UARTs when N is greater than 1. Nothing is done when ; N is 1 and the processor has no UARTS. N must be 1 on processors ; with only 1 UART. ; uart_select macro n if n < 1 error "Invalid parameter to UART_SELECT macro. Must be 1 or more." exitm endif ; ; Handle first and only UART is being selected. In this case the normal ; symbols are already defined in the include file. Only the special symbols ; relating to the interrupt bits will be defined. ; if n == 1 ifndef tx1if ifndef txif exitm ;this processor has no UARTs endif txif_reg set pir1 txif_bit set txif ifdef txif_flag #undefine txif_flag endif #define txif_flag txif_reg, txif_bit txie_reg set pie1 txie_bit set txie ifdef txie_flag #undefine txie_flag endif #define txie_flag txie_reg, txie_bit ifdef txip txip_reg set ipr1 txip_bit set txip ifdef txip_flag #undefine txip_flag endif #define txip_flag txip_reg, txip_bit endif rcif_reg set pir1 rcif_bit set rcif ifdef rcif_flag #undefine rcif_flag endif #define rcif_flag rcif_reg, rcif_bit rcie_reg set pie1 rcie_bit set rcie ifdef rcie_flag #undefine rcie_flag endif #define rcie_flag rcie_reg, rcie_bit ifdef rcip rcip_reg set ipr1 rcip_bit set rcip ifdef rcip_flag #undefine rcip_flag endif #define rcip_flag rcip_reg, rcip_bit endif exitm endif ;end of TX1IF not defined case ; ; Select UART 1. ; ifndef txreg1 error "Attempt to select non-existent UART with UART_SELECT macro." exitm endif txif_reg set pir1 txie_reg set pie1 ifdef tx1ip txip_reg set ipr1 endif rcif_reg set pir1 rcie_reg set pie1 ifdef rc1ip rcip_reg set ipr1 endif rcsta set rcsta1 txsta set txsta1 spbrg set spbrg1 txreg set txreg1 rcreg set rcreg1 ifdef spbrgh1 spbrgh set spbrgh1 endif ifdef baudcon1 baudcon set baudcon1 endif txif_bit set tx1if txif set tx1if ifdef txif_flag #undefine txif_flag endif #define txif_flag txif_reg, txif_bit txie_bit set tx1ie txie set tx1ie ifdef txie_flag #undefine txie_flag endif #define txie_flag txie_reg, txie_bit ifdef tx1ip txip_bit set tx1ip ifdef txip_flag #undefine txip_flag endif #define txip_flag txip_reg, txip_bit endif rcif_bit set rc1if rcif set rc1if ifdef rcif_flag #undefine rcif_flag endif #define rcif_flag rcif_reg, rcif_bit rcie_bit set rc1ie rcie set rc1ie ifdef rcie_flag #undefine rcie_flag endif #define rcie_flag rcie_reg, rcie_bit ifdef rc1ip rcip_bit set rc1ip ifdef rcip_flag #undefine rcip_flag endif #define rcip_flag rcip_reg, rcip_bit endif exitm endif ;end of UART 1 case ; ; Select UART 2. ; if n == 2 txif_reg set pir3 txie_reg set pie3 ifdef tx2ip txip_reg set ipr3 endif rcif_reg set pir3 rcie_reg set pie3 ifdef rc2ip rcip_reg set ipr3 endif rcsta set rcsta2 txsta set txsta2 spbrg set spbrg2 txreg set txreg2 rcreg set rcreg2 ifdef spbrgh2 spbrgh set spbrgh2 endif ifdef baudcon2 baudcon set baudcon2 endif txif_bit set tx2if ifdef txif_flag #undefine txif_flag endif #define txif_flag txif_reg, txif_bit txie_bit set tx2ie ifdef txie_flag #undefine txie_flag endif #define txie_flag txie_reg, txie_bit ifdef tx2ip txip_bit set tx2ip ifdef txip_flag #undefine txip_flag endif #define txip_flag txip_reg, txip_bit endif rcif_bit set rc2if ifdef rcif_flag #undefine rcif_flag endif #define rcif_flag rcif_reg, rcif_bit rcie_bit set rc2ie ifdef rcie_flag #undefine rcie_flag endif #define rcie_flag rcie_reg, rcie_bit ifdef rc2ip rcip_bit set rc2ip ifdef rcip_flag #undefine rcip_flag endif #define rcip_flag rcip_reg, rcip_bit endif exitm endif ;end of UART 2 case error "Selected UART does not exist or not implemented in UART_SELECT." endm ;******************* ; ; Make sure unqualified UART symbols exist for the first or only UART. ; uart_select 1 ;select first UART, if any present ;******************************************************************************* ; ; Macro UART_BAUD <baud> ; ; This macro sets assembler variables to indicate the best baud rate generator ; configuration for the USART in asynchronous mode, given the desired baud ; rate and the oscillator frequency. No code is generated. The following ; assembler variables are set: ; ; VAL_SPBRG - Value for the SPBRG baud rate generator register. For UARTs ; with 16 bit baud rate generators this will be the full 16 bit value. ; The low byte is then intended for SPBRG, and the high byte for SPBRGH. ; ; BAUD_REAL - Real (not desired) baud rate resulting from the ; selected settings. ; ; VAL_TXSTA - Value for the TXSTA register. In addition to selecting the ; proper baud rate generator configuration, this value also selects the ; following: ; ; Asynchronous mode ; 8 bits per character ; Transmitter enabled ; ; VAL_RCSTA - Value for the RCSTA register. The value will select the ; following setup: ; ; Serial port enabled ; 8 bits per character ; Reception enabled ; Address detection disabled ; ; VAL_BAUDCTL - Value for the BAUDCTL register for those processors that ; have this. This register is part of the enhanced USART of parts like ; the 18F1320. ; ; VAL_BAUDCON - Value for the BAUDCON register for those processors that ; have this. ; ; This macro produces an assembly time warning if the closest available baud ; rate is more then 2.9% from the desired baud rate (off by 25% of a bit time ; in the middle of the last bit). It produces an assembly error if the baud ; rate error is 5.8% or higher. ; uart_baud macro baud local err ;baud rate error in parts per 1000 if uart_type == 0 error "UART_BAUD macro called, but this part has no UART." exitm endif ; ; Handle 18F1320 style advanced USART. ; if uart_type == 3 ;18F1320 type advanced USART ? val_txsta set b'00100100' ;set transmitter configuration ; X------- not used in asynchronous mode ; -0------ select 8 bits (not 9 bits) per char ; --1----- enable the transmitter ; ---0---- select asynchronous mode ; ----0--- do not send BREAK next char ; -----1-- init to using high speed baud rate mode ; ------X- read-only status bit ; -------X 9th bit of transmit data, not used val_rcsta set b'10010000' ;set receiver configuration ; 1------- enable the serial port hardware ; -0------ select 8 bits per received character ; --X----- unused in asynchronous mode ; ---1---- enable the receiver ; ----0--- disable address detection ; -----XXX read-only status bits val_baudctl set b'00001000' ;set baud rate configuration ; X-X--X-- unused bits ; -X------ read-only status bit ; ---X---- unused in asynchronous mode ; ----1--- use full 16 bit buad rate generator ; ------0- no wakeup on next falling edge ; -------0 disable auto baud rate detection on next char val_baudcon set b'00001000' ;set baud rate configuration ; 0------- clear auto-baud rollover status ; -X------ read-only status bit ; --0----- normal receive polarity, idle is high ; ---0---- normal transmit polarity, idsl is high ; ----1--- use full 16 bit baud rate generator ; -----X-- unused ; ------X- used only with auto-baud mode ; -------0 auto-baud mode disabled val_spbrg set (freq_osc / baud) - 4 ;baud rate value, 2 fraction bits val_spbrg set (val_spbrg + 2) >> 2 ;round and scale to SPBRG value if val_spbrg > 65535 val_spbrg set 65535 ;clip at largest allowable value endif baud_real set freq_osc / (4 * (val_spbrg + 1)) ;find actual baud rate err set (1000 * (baud_real - baud)) / baud ;baud rate err in parts/1000 if err < 0 err set -err ;make absolute value of error endif if err > 58 error "Baud rate error exceeds 5.8%" endif if err > 29 messg "WARNING: Baud rate error exceeds 2.9%" endif exitm endif ;end of 18F1320 type advanced USART ; ; Assume normal 16 or 17 family USART. ; ; Init the non-baud rate bits of TXSTA. ; val_txsta set b'00100000' ; X------- not used in asynchronous mode ; -0------ select 8 bits (not 9 bits) per char ; --1----- enable the transmitter ; ---0---- select asynchronous mode ; ----X--- unused ; -----0-- high/low baud rate select, will be set later ; ------X- read-only status bit ; -------X 9th bit of transmit data, not used val_rcsta set b'10010000' ;set receiver configuration ; 1------- enable the serial port hardware ; -0------ select 8 bits per received character ; --X----- unused in asynchronous mode ; ---1---- enable the receiver ; ----0--- disable address detection ; -----XXX read-only status bits ; ; Init values assuming will use high speed mode. ; if fam_16 || fam_18 ;init to high speed mode if supported val_txsta set val_txsta | (1 << brgh) endif val_spbrg set freq_osc * 16 / baud - 256 ;find divider value, 8 fraction bits val_spbrg set (val_spbrg + 128) >> 8 ;round and scale to final SPBRG value baud_real set freq_osc / (16 * (val_spbrg + 1)) ;find actual selected baud rate ; ; Switch to low speed mode if the baud rate generator value would require more ; than 8 bits to represent, or this chip has no high speed mode. ; if (val_spbrg > 255) || fam_17 ;too slow for high speed mode or no HS ? if ! fam_17 val_txsta set val_txsta & ~(1 << brgh) ;disable high speed mode endif val_spbrg set freq_osc * 4 / baud - 256 ;find divider value, 8 fraction bits val_spbrg set (val_spbrg + 128) >> 8 ;round and scale to final SPBRG value if val_spbrg > 255 ;clip at largest allowable baud rate generator value val_spbrg set 255 endif baud_real set freq_osc / (64 * (val_spbrg + 1)) ;find actual selected baud rate endif err set (1000 * (baud_real - baud)) / baud ;baud rate err in parts/1000 if err < 0 err set -err ;make absolute value of error endif if err > 58 error "Baud rate error exceeds 5.8%" endif if err > 29 messg "WARNING: Baud rate error exceeds 2.9%" endif endm ;******************************************************************************* ; ; Macro UART_SETUP ; ; Set up the UART according to the assembler variables VAL_SPBRG, VAL_TXSTA, ; VAL_RCSTA, VAL_BAUDCTL, and VAL_BAUDCON. See the documentation for the ; UART_BAUD macro for a description of these variables. ; ; The values do not need to be set by the UART_BAUD macro, although that may ; be a convenient way to do so. ; uart_setup macro dbankif rcsta clrf rcsta ;disable UART and clear any error conditions dbankif txsta movlw val_txsta ;set transmitter configuration movwf txsta ifdef baudcon dbankif baudcon movlw val_baudcon movwf baudcon else ifdef baudctl dbankif baudctl movlw val_baudctl movwf baudctl endif endif dbankif spbrg movlw low val_spbrg movwf spbrg ;set baud rate generator period low byte ifdef spbrgh dbankif spbrgh movlw high val_spbrg movwf spbrgh ;set baud rate generator period high byte endif dbankif rcsta movlw val_rcsta movwf rcsta ;set receiver configuration and enable UART endm ;******************************************************************************* ;******************************************************************************* ; ; I/O bits and ports handling. ; ; Create aliases for the data direction registers of the 17C family. Names ; will be created called TRISp to be compatible with the 16C naming ; convention. ; ; Create the aliases PORTA and TRISA on processors that have a single port ; called GPIO and an associated TRIS register called TRISIO. All the ; subsequent port handling code assumes that ports and their tristate ; registers are called PORTx and TRISx, with "X" starting at "A" for the ; first and continuing with consecutive letters for any additional ports. ; ifdef gpio porta equ gpio endif ifdef trisio trisa equ trisio endif if fam_17 ;special handling for PIC 17 family ifdef ddra trisa equ ddra endif ifdef ddrb trisb equ ddrb endif ifdef ddrc trisc equ ddrc endif ifdef ddrd trisd equ ddrd endif ifdef ddre trise equ ddre endif ifdef ddrf trisf equ ddrf endif ifdef ddrg trisg equ ddrg endif ifdef ddrh trish equ ddrh endif ifdef ddrj trisj equ ddrj endif endif ;end of 17C family case ;******************************************************************************* ; ; I/O port initialization. ; ; Create and initialize the various assembler variables assumed by the /INBIT, ; /INANA, and /OUTBIT commands. These variables are later used in the PORT ; module to initialize the I/O pins according to the /INxxx and /OUTxxx ; commands. ; analogused0 set 0 analogused1 set 0 /loop with ii from 0 to 25 /var local c string /set c [char [+ [ccode "a"] ii]] /write ifdef port[chars c] val_port[chars c] set 0 val_tris[chars c] set 0 val_pullup[chars c] set 0 val_analog[chars c] set 0 ifndef pullups_port[chars c] pullups_port[chars c] equ 0 endif endif /endloop //////////////////////////////////////////////////////////////////////////////// // // Subroutine PINNAME_PARSE name // // Parse a composite pin name, like "RA1" or "RB5" into the port name and pin // number. NAME is a string. // // The following variables will be created if not already existing, then set: // // PINNAME_PORT, string - Single upper case port name. // // PINNAME_BIT, integer - 0-N bit number within the port. // /subroutine pinname_parse /var local name string = [ucase [vnl [arg 1]]] //pin name, like "RA1" or "RB5" /var local bstr string //bit number string /var exist pinname_port string //upper case port name letter /var exist pinname_bit integer //0-N bit number of pin within its port /block //abort block on any error /if [< [slen name] 3] then //too short for minimum pin name ? /quit /endif /if [<> [sindx 1 name] "R"] then //not Rxx ? /quit /endif /set pinname_port [sindx 2 name] //extract the port name /set bstr [substr 3 [- [slen name] 2] name] //extract bit number string /if [not [isint [chars bstr]]] then //invalid bit number ? /quit /endif /set pinname_bit [chars bstr] //return bit number /return /endblock /show " Invalid pin name " name " passed to subroutine PINNAME_PARSE." error Pin Name end /stop /endsub //////////////////////////////////////////////////////////////////////////////// // // Subroutine PINDATA name // // Get data about the named pin NAME. NAME is a string. // // The following variables are set: // // PINDATA_NAME, string // // The pin name. This is the same name as passed in by the NAME // parameter. // // PINDATA_EXIST, bool // // TRUE iff the named pin exists. This means it was defined with a INBIT, // OUTBIT, etc command. The values of the remaining variables are // undefined when this variable is FALSE. // // PINDATA_PORT, string // // Upper case port name single letter. // // PINDATA_BIT, integer // // 0-N bit number within port and other I/O pin registers. // // PINDATA_OUT, bool // // TRUE iff the pin was defined as an output. // // PINDATA_POS, bool // // TRUE iff the pin was defined with positive logic. // // PINDATA_DIG, bool // // TRUE iff the pin was defined as digital, not analog. // // PINDATA_AN, integer // // ANx analog input number when the pin was defined as analog. Set to the // special value of -1 when the pin is not analog. // // These variables are created if not already existing. When the pin name // is not defined (PINDATA_EXIST = FALSE), then the state of the remaining // variables is undefined. // /subroutine pindata /var local name string = [vnl [arg 1]] //pin name /var local vn string //scratch variable name /var local ind integer //parse index /var local tk string //parsed token /var exist pindata_name string /var exist pindata_exist bool /var exist pindata_port string /var exist pindata_bit integer /var exist pindata_out bool /var exist pindata_pos bool /var exist pindata_dig bool /var exist pindata_an integer /set pindata_name name //save name of pin other vars pertain to /set pindata_exist false //init to pin is not defined. // // Get PORT, BIT, and OUT by looking at the Inbit/Outbit PORT and BIT // constants. // /block /set vn [str "Outbit_" name "_port"] //try output pin /if [exist vn] then /set pindata_exist true /set pindata_port [chars vn] /set pindata_out true /set vn [str "Outbit_" name "_bit"] /quit /endif /set vn [str "Inbit_" name "_port"] //try input pin /if [exist vn] then /set pindata_exist true /set pindata_port [chars vn] /set pindata_out false /set vn [str "Inbit_" name "_bit"] /quit /endif /return //pin doesn't exist /endblock /set pindata_bit [chars vn] // // Get POS, DIG, and AN by parsing Portdata_xxx for this pin. // /set vn [str "Portdata_" [lcase pindata_port] pindata_bit] /set ind 1 //init parse index /call string_token [chars vn] ind tk //get name /call string_token [chars vn] ind tk //get IN/OUT /call string_token [chars vn] ind tk //get POS/NEG /set pindata_pos [= tk "POS"] /call string_token [chars vn] ind tk //get DIG/ANA /set pindata_dig [= tk "DIG"] /set pindata_an -1 //init to no analog pin /if [not pindata_dig] then //is analog pin ? /call string_token [chars vn] ind tk //get ANx /set tk [substr 3 [- [slen tk] 2] tk] //extract analog input number string /set pindata_an [chars tk] /endif /endsub //////////////////////////////////////////////////////////////////////////////// // // Macro SETPIN name state // // Set a pin to a particular state. // // NAME is the pin name as defined with the INBIT, OUTBIT, INANA, etc, // command. NAME is a token, not a string. // // STATE is one of the keywords: // // ON - The pin will be driven. // // OFF - Set the pin to high impedance. // // HIGH - Logic high. This does not set the pin to driven, only the state // it will be driven to if driven. // // LOW - Logic low. This does not set the pint to driven, only the state // it will be driven to if driven. // // TRUE - Like HIGH or LOW according to the logic polarity the pin is // defined with. // // FALSE - Like HIGH or LOW according to the logic polarity the pin is // defined with. // /macro setpin /var local name string = [qstr [arg 1]] //pin name /var local s string //scratch string /var local port string //lower case port name letter /var local pindata_name string //PINDATA subroutine return values /var local pindata_exist bool /var local pindata_port string /var local pindata_bit integer /var local pindata_out bool /var local pindata_pos bool /var local pindata_dig bool /var local pindata_an integer /call pindata name //get info about this pin /if [not pindata_exist] then /show " I/O pin " [pick] " is not defined." error SETPIN name end /stop /endif /set port [lcase pindata_port] //save lower case port name letter /pick one by [ucase [qstr [arg 2]]] /option "ON" dbankif tris[chars port] /call tabopcode s /append s "bcf" /call taboperand s /append s "tris" port ", " pindata_bit /call startcomm s /append s "enable output drive for pin " [ucase name] /write s /option "OFF" dbankif tris[chars port] /call tabopcode s /append s "bsf" /call taboperand s /append s "tris" port ", " pindata_bit /call startcomm s /append s "set pin " [ucase name] " to high impedance" /write s /option "HIGH" dbankif lat[chars port] /call tabopcode s /append s "bsf" /call taboperand s /append s "lat" port ", " pindata_bit /call startcomm s /append s "set pin " [ucase name] " output value to HIGH" /write s /option "LOW" dbankif lat[chars port] /call tabopcode s /append s "bcf" /call taboperand s /append s "lat" port ", " pindata_bit /call startcomm s /append s "set pin " [ucase name] " output value to LOW" /write s /option "TRUE" dbankif lat[chars port] /call tabopcode s /if pindata_pos /then /append s "bsf" /else /append s "bcf" /endif /call taboperand s /append s "lat" port ", " pindata_bit /call startcomm s /append s "assert pin " [ucase name] /write s /option "FALSE" dbankif lat[chars port] /call tabopcode s /if pindata_pos /then /append s "bcf" /else /append s "bsf" /endif /call taboperand s /append s "lat" port ", " pindata_bit /call startcomm s /append s "de-assert pin " [ucase name] /write s /optionelse /show " """ [pick] """ is not a valid option to SETPIN." error SETPIN option end /stop /endpick /endmac ;******************************************************************************* ;******************************************************************************* ; ; Bank and page switching, memory allocation. ; ; The assembler variables CURRIB and CURRDB are used to keep track of the ; current indirect and direct register bank settings. Note that these are ; only assembly time, not run time variables. They reflect which bank is ; currently ASSUMED to be selected. The programmer must therefore play an ; active role in making sure these variables are set correctly if they are ; used. Proper use can avoid unneccessary bank switching instructions. ; ; On the 17Cxxx series, similar assembler variables CURRRB and CURRGB are ; used. CURRRB is the current special function register bank set by the low ; nibble of BSR. CURRGB is the current general register bank set by the high ; nibble of BSR. ; ; On the 18 family, CURRIB is not used because each FSR contains the full ; target address. There are no indirect RAM banks on the 18 family. ; nobank equ -1 ;value to indicate current bank is unknown ;***** ; if fam_16 ;16Cxxx except 16C5xx ? currdb set nobank ;init current direct register bank assumption currib set nobank ;init current indirect register bank assumption endif ;***** ; if fam_16b currdb set nobank ;init current direct register bank assumption endif ;***** ; if fam_17 ;17Cxxx ? currrb set nobank ;init current special function register bank currgb set nobank ;init current general register bank endif ;***** ; if fam_18 ;18 family currdb set nobank ;init current direct register bank assumption endif ;******************************************************************************* ; ; BANKOF (ADR) ; ; Returns the direct register bank number containing the RAM address ADR. ; if fam_12 #define bankof(adr) (((adr) >> 5) & 3) endif if fam_16 #define bankof(adr) (((adr) >> 7) & 3) endif if fam_16b #define bankof(adr) (((adr) >> 7) & 63) endif if fam_17 #define bankof(adr) (((adr) >> 8) & 15) endif if fam_18 #define bankof(adr) (((adr) >> 8) & 15) endif ;******************************************************************************* ; ; IBANKOF (ADR) ; ; Returns the indirect register bank number containing the RAM address ADR. ; if fam_12 #define ibankof(adr) (((adr) >> 5) & 3) endif if fam_16 #define ibankof(adr) (((adr) >> 8) & 1) endif if fam_17 #define ibankof(adr) (((adr) >> 8) & 15) endif if fam_18 || fam_16b #define ibankof(adr) (0) ;this processor has no indirect banks endif ;******************************************************************************* ; ; BANKADRR (BANK) ; ; Returns an address within the special function register bank BANK. ; if fam_17 #define bankadrr(bank) ((((bank) & 15) << 8) + h'10') endif ;******************************************************************************* ; ; BANKADRG (BANK) ; ; Returns an address within the general RAM register bank BANK. ; if fam_17 #define bankadrg(bank) ((((bank) & 15) << 8) + h'20') endif ;******************************************************************************* ; ; BANKADR (BANK) ; ; Returns an address within the direct register bank BANK. An arbitrary ; address within the bank is returned, except that DBANKIF will not recognize ; it as an unbanked location. ; if fam_12 #define bankadr(bank) (((bank) & 3) << 5) endif if fam_16 #define bankadr(bank) (((bank) & 3) << 7) endif if fam_16b #define bankadr(bank) ((((bank) & 31) << 7) + h'20') endif if fam_17 #define bankadr(bank) ((((bank) & 15) << 8) + h'20') endif if fam_18 #define bankadr(bank) ((((bank) & 15) << 8) + h'FF' - (((bank) & 15) << 4)) endif ;******************************************************************************* ; ; IBANKADR (BANK) ; ; Returns an address within the indirect register bank BANK. ; if fam_12 #define ibankadr(bank) (((bank) & 3) << 5) endif if fam_16 #define ibankadr(bank) (((bank) & 1) << 8) endif if fam_17 #define ibankadr(bank) ((((bank) & 15) << 8) + h'20') endif if fam_18 || fam_16b #define ibankadr(bank) (0) ;this processor has not indirect banks endif ;******************************************************************************* ; ; INACCESSBANK (ADR) ; ; Returns 1 if ADR is within the access bank, 0 otherwise. Always returns 0 ; on processors that don't have an access bank. ; if fam_18 #define inaccessbank(adr) (((adr) <= acclast) || ((adr) > (h'F00'+acclast))) else #define inaccessbank(adr) (0) endif ;******************************************************************************* ; ; INBANKED (ADR) ; ; Returns 1 if ADR is in banked memory, 0 otherwise. Banked memory means some ; sort of bank settings must be properly set to access the memory. ; if fam_16 #define inbanked(adr) (((((adr)&h'7F')<commregs_first)||(((adr)&h'7F')>commregs_last))) endif if fam_16b #define inbanked(adr) (((((adr)&h'7F')<commregs_first)||(((adr)&h'7F')>commregs_last)))&&(((adr)&h'7F')>h'0B') endif if fam_18 #define inbanked(adr) (((adr) > (acclast)) && ((adr) <= (h'F00'+acclast))) endif ;******************************************************************************* ; ; BANKnADR constants. ; ; These constants provide addresses that are guaranteed to be within ; particular register banks. Note that the bank switching macros below take ; addresses, not bank numbers. These constants can be used to convert from ; bank numbers to addresses within the banks by using the #v(n) assembler ; syntax. For example, if "b" is a bank number, then: ; ; bank#v(b)adr ; ; is an address within bank b, which can be passed to the DBANKIF macro, for ; example. ; ii set 0 ;init loop counter while ii < nregbanks ;once for each possible direct register bank bank#v(ii)adr equ bankadr(ii) ii set ii + 1 ;advance to number of next register bank endw ;******************************************************************************* ; ; Macro DBANK? ; ; Invalidate the current direct register data bank assumption. This macro ; produces no code, only sets assembly time state. ; ; On 17Cxxx processors, this sets both the special and general register bank ; assumptions to unknown. These processors make no distinction between a ; direct or indirect bank selection. ; dbank? macro if fam_17 currrb set nobank currgb set nobank exitm endif currdb set nobank endm ;******************************************************************************* ; ; Macro IBANK? ; ; Invalidate the current indirect register data bank assumption. This macro ; produces no code, only sets assembly time state. ; ; On 17Cxxx processors, this sets both the special and general register bank ; assumptions to unknown. These processors make no distinction between a ; direct or indirect bank selection. ; ibank? macro if fam_18 || fam_16b exitm ;this processor has no indirect banks endif if fam_17 currrb set nobank currgb set nobank exitm endif currib set nobank endm ;******************************************************************************* ; ; Macro UNBANK ; ; Invalidates all register bank assumptions. ; unbank macro dbank? ibank? endm ;******************************************************************************* ; ; Macro DBANKIS <adr> ; ; Set the current direct register bank assumption to the bank containing ADR. ; This macro generates no code to switch the register bank. It only updates ; assembly time state. ; dbankis macro adr if fam_17 currrb set bankof(adr) currgb set bankof(adr) exitm endif if fam_18 if inaccessbank(adr) currdb set nobank exitm endif endif currdb set bankof(adr) ;set assumed direct register bank number endm ;******************************************************************************* ; ; Macro IBANKIS <adr> ; ; Set the current indirect register bank assumption to the bank containing ; ADR. This macro generates no code to switch the register bank. It only ; updates assembly time state. ; ibankis macro adr if fam_18 || fam_16b exitm ;this processor has no indirect banks endif if fam_17 currrb set bankof(adr) currgb set bankof(adr) exitm endif currib set ibankof(adr) ;set assumed indirect register bank number endm ;******************************************************************************* ; ; Macro RBANKIS <adr> ; ; Set the current special function register bank assumption to the bank ; containing ADR. ; if fam_17 rbankis macro adr currrb set bankof(adr) endm endif ;******************************************************************************* ; ; Macro GBANKIS <adr> ; ; Set the current general RAM register bank assumption to the bank containing ; ADR. ; if fam_17 gbankis macro adr currgb set bankof(adr) endm endif ;******************************************************************************* ; ; Macro RBANK? ; ; Invalidates the current special function register bank assumption. ; if fam_17 rbank? macro currrb set nobank endm endif ;******************************************************************************* ; ; Macro GBANK? ; ; Invalidates the current general RAM register bank assumption. ; if fam_17 gbank? macro currgb set nobank endm endif ;******************************************************************************* ; ; Macro DBANKIF <adr> ; ; Set the register bank for direct access to address ADR. ; ; On a PIC 16, this macro sets the RP0, RP1 bits of STATUS appropriately. The ; bank bits are not set if they are assumed to already be set correctly as ; indicated by the assembler variable CURRDB. CURRDB is updated to the new ; setting. ; ; On 18 family processors, this macro sets BSR. No code is emitted and the ; bank setting is not altered if ADR is within the access bank, and can ; therefore be accessed regardless of the BSR setting. ; ; On the enhanced 16 family, this macro sets BSR. No code is emitted and the ; bank setting not altered if ADR is in unbanked memory. Generally, the first ; 12 locations of each bank are the unbanked SFRs, and the last 16 locations ; unbanked general RAM. ; dbankif macro adr local newbank, ii newbank set bankof(adr) ;find 0-N desired bank number ;***** ; ; 12 bit core devices. ; if fam_12 if nregbanks <= 1 ;this processor only has one register bank ? exitm endif endif ;***** ; ; 17 family devices. ; if fam_17 if nregbanks <= 1 ;this processor only has one register bank ? exitm endif ii set (adr) & h'FF' ;make address offset within bank ; ; Avoid switching the bank if the target address is in an unbanked ; region. Note that both bank selects are always switched for ; offset 0 even though this address is unbanked. This is because ; BANKADR always returns the first address within a bank. ; if ii == 0 ;special case to set both bank selects ? if currrb != newbank movlb newbank ;select new special function registers bank currrb set newbank endif if currgb != newbank movlr newbank ;select new general registers bank currgb set newbank endif endif if (ii >= h'10') && (ii <= h'17') ;adr is in banked special func regs region ? if currrb != newbank movlb newbank ;select new special function registers bank currrb set newbank endif endif if (ii >= h'20') && (ii <= h'FF') ;adr is in banked general regs region ? if currgb != newbank movlr newbank ;select new general registers bank currgb set newbank endif endif exitm endif ;end of 17Cxxx case ;***** ; ; Traditional 16 family devices. ; if fam_16 if nregbanks <= 1 ;this processor only has one register bank ? exitm endif ii set (adr) & h'7F' ;make address offset within bank if (ii >= commregs_first) && (ii <= commregs_last) ;in unbanked region ? exitm endif if nregbanks > 1 ;bank bit 0 is meaningful ? if (currdb < 0) || (currdb > 3) || ((currdb & 1) != (newbank & 1)) ;need update ? if newbank & 1 bsf status, rp0 else bcf status, rp0 endif endif endif if nregbanks > 2 ;bank bit 1 is meaningful ? if (currdb < 0) || (currdb > 3) || ((currdb & 2) != (newbank & 2)) ;need update ? if newbank & 2 bsf status, rp1 else bcf status, rp1 endif endif endif currdb set newbank ;update current bank assumption exitm endif ;end of 16Cxxx case ;***** ; ; Enhanced 16 family devices. ; if fam_16b if inbanked(adr) ;address is in banked memory ? if currdb != newbank ;not already in this bank ? movlb newbank ;switch to the new bank currdb set newbank ;update the current bank setting assumption endif endif exitm endif ;end of enhanced 16 family case ;***** ; ; 18 family devices. ; if fam_18 if inbanked(adr) ;address is in banked memory ? if currdb != newbank ;not already in this bank ? movlb newbank ;switch to the new bank currdb set newbank ;update the current bank setting assumption endif endif exitm endif ;end of 18 family case ;***** ; error "DBANKIF macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro DBANK <adr> ; ; Unconditionally set the direct access register bank for access to address ; ADR. The assembler state is updated. ; dbank macro adr dbank? ;invalidate current register bank assumption dbankif adr ;set the new register bank endm ;******************************************************************************* ; ; Macro IBANKIF <adr> ; ; Set the register bank for indirect access to address ADR, if needed. The ; assembler variable CURRIB is assumed to indicate the current indirect ; register bank setting. CURRIB is updated. This macro sets the IRP bit of ; STATUS appropriately. ; ibankif macro adr local newbank, ii if fam_18 || fam_16b exitm ;this processor has no indirect banks endif newbank set ibankof(adr) ;find 0-N desired bank number ;***** ; ; 17Cxxx devices. ; if fam_17 dbankif adr ;no direct/indirect distinction on these devices exitm endif ;end of 17Cxxx case ;***** ; ; 16Cxxx devices. ; if fam_16 if nregbanks <= 2 ;this processor only has one indirect register bank ? exitm endif ii set (adr) & h'7F' ;make address offset within direct bank if (ii >= commregs_first) && (ii <= commregs_last) ;in unbanked region ? exitm endif if nregbanks > 2 ;there is more than one indirect bank ? if (currib < 0) || (currib > 1) || (currib != newbank) ;need update ? if newbank & 1 bsf status, irp else bcf status, irp endif endif endif currib set newbank ;update current bank assumption exitm endif ;end of 16Cxxx case ;***** ; error "IBANKIF macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro IBANK <adr> ; ; Unconditionally set the indirect access register bank for access to address ; ADR. CURRIB is updated. ; ibank macro adr ibank? ;invalidate current register bank assumption ibankif adr ;set the new register bank endm ;******************************************************************************* ; ; Macro RBANKIF <adr> ; ; This macro is only defined for processors that have different bank settings ; for the built in special function registers and the general RAM registers. ; ; Set the special function register bank for access to ADR. Nothing is done ; if ADR is not within the special banked special function register range. ; if fam_17 rbankif macro adr local newbank, ii newbank set bankof(adr) ;find 0-N desired bank number ii set (adr) & h'FF' ;make address offset within bank if (ii >= h'10') && (ii <= h'17') ;adr is in banked special func regs region ? if currrb != newbank movlb newbank ;select new special function registers bank currrb set newbank endif endif endm endif ;end of FAM_17 case ;******************************************************************************* ; ; Macro GBANKIF <adr> ; ; This macro is only defined for processors that have different bank settings ; for the built in special function registers and the general RAM registers. ; ; Set the general RAM register bank for access to ADR. Nothing is done if ADR ; is not within the general RAM register range. ; if fam_17 gbankif macro adr local newbank, ii newbank set bankof(adr) ;find 0-N desired bank number ii set (adr) & h'FF' ;make address offset within bank if (ii >= h'20') && (ii <= h'FF') ;adr is in banked general regs region ? if currgb != newbank movlr newbank ;select new general registers bank currgb set newbank endif endif endm endif ;end of FAM_17 case ;******************************************************************************* ; ; Macro DEFRAM <address> ; ; Set up for allocating RAM with subsequent RES directives. ADDRESS is the ; address that will be passed to DBANKIF to access any of the RAM defined in ; this section. If ADDRESS indicates normal banked memory, then the ; appropriate .BANKn linker section will be started with a UDATA directive. ; If ADDRESS is in common RAM, then the RAM will be in the .UDATA_SHR section. ; If ADDRESS is in the access bank of an 18 family, then the RAM will be ; allocated in the .UDATA_ACS section. ; ; If the new linker section would be the same as the linker section defined by ; the previous DEFRAM, then no new directives are written since only one ; occurrence of a linker section is allowed per source module. ; ; The assembler symbol DEFRAM_LAST is used to track linker section of the last ; DEFRAM invocation. This symbol has the following values: ; ; -1 - No previous DEFRAM section ; ; -2 - Last linker section was .UDATA_SHR ; ; -3 - Last linker section was .UDATA_ACS ; ; 0-N - Last linker section was .BANKn ; defram_last set -1 ;init to DEFRAM not previously invoked defram macro address local badr badr set address ;***** ; ; 16 family devices. ; if fam_16 local ii ii set badr & h'7F' ;make address offset within bank if (ii >= commregs_first) && (ii <= commregs_last) ;in unbanked region ? if defram_last == -2 ;already in this region ? exitm endif udata_shr ;start the new RAM linker section defram_last set -2 ;remember which linker section now in exitm endif ii set bankof(badr) ;make 0-N register bank number if defram_last == ii ;already in this linker section ? exitm endif .bank#v(ii) udata ;start the new RAM linker section defram_last set ii ;remember which linker section now in exitm endif ;end of 16 family case ;***** ; ; Enhanced 16 family devices. ; if fam_16b local ii ii set badr & h'7F' ;make address offset within bank if (ii >= commregs_first) && (ii <= commregs_last) ;in unbanked region ? if defram_last == -2 ;already in this region ? exitm endif udata_shr ;start the new RAM linker section defram_last set -2 ;remember which linker section now in exitm endif ii set bankof(badr) ;make 0-N register bank number if defram_last == ii ;already in this linker section ? exitm endif .bank#v(ii) udata ;start the new RAM linker section defram_last set ii ;remember which linker section now in exitm endif ;end of 16 family case ;***** ; ; 18 family devices. ; if fam_18 local ii if ((badr >= 0) && (badr <= h'7F')) || ((badr >= h'F80') && (badr <= h'FFF')) if defram_last == -3 ;already in access RAM ? exitm endif udata_acs ;start the new RAM linker section defram_last set -3 ;remember which linker section now in exitm endif ;end of access RAM case ii set bankof(badr) ;make 0-N register bank number if defram_last == ii ;already in this linker section ? exitm endif .bank#v(ii) udata ;start the new RAM linker section defram_last set ii ;remember which linker section now in exitm endif ;end of 18 family case ;***** ; error "DEFRAM macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ;******************************************************************************* ; ; Software data stack. ; ; The processor has a limited stack which is only used for subroutine return ; addresses. It can not be accessed directly as a general data stack. This ; section implements a general data stack. ; ; Subroutines are normally expected to preserve the general registers REG0 - ; REGn. This can be done by using the software stack. ; ; The details of how the stack works and is managed varies by processor type: ; ; PIC 16 and 17 ; ; The stack grows from high to low addresses. The global register STACKP ; contains the address of the last byte pushed onto the stack. This is a ; software register on the 16 family, and one of the FSR registers on the 17 ; family. STACKP is assumed to be accessible without requiring bank ; switching. It is defined in the STACK module when it is a software ; register. ; ; Enhanced PIC 16 ; ; FSR1 is reserved as the stack pointer, and the stack grows from low to ; high addresses. The stack pointer points to the next empty location. A ; push is therefore a post-increment and a pop a pre-decrement access. The ; processor can perform both these operations natively. ; ; PIC 18 ; ; By default, FSR2 is used as the stack pointer, the stack grows from low to ; high addresses, and the pointer points to the last-pushed byte. A push is ; therefore a pre-increment and a pop a post-decrement. Both these can be ; performed natively by the processor. ; ; Unfortunately, the C18 compiler uses a different convention. It reserves ; FSR2 as a frame pointer and FSR1 as the stack point, thereby leaving only ; a single FSR for application use. Worse yet, it defines a stack ; convention that can not be realized with the native addressing modes. The ; stack grows from low to high addresses, but the pointer points to the next ; empty location. This means a push requires a post-increment and a pop a ; pre-decrement. Since the PIC 18 has no native pre-decrement capability, a ; pop operation takes multiple instructions. ; ; The stack is actually defined in module STACK.ASPIC. Routine STACK_INIT ; must be called to initialize the stack if it is used at all. The external ; reference to STACK_INIT also forces the STACK module to be loaded, thereby ; defining the stack. Otherwise, the stack is not loaded and it does not ; consume any memory. ; stackbank equ bankof(stacklast) ;direct register bank containing the stack if fam_17 stackp equ fsr1 ;second pointer reg will be used as stack pointer endif if fam_16b || fam_18 stackp equ fsr#v(fsrstack) ;FSR will be reserved as stack pointer endif ;******************************************************************************* ; ; Macro STACK_SET adr ; ; Set the data stack so that ADR will be the next byte pushed. The stack is ; handled and initialized differently depending on whether C18 compatibility ; is enabled. ; stack_set macro adr ;***** ; ; Enhanced PIC 16 family. ; if fam_16b movlw low (adr) movwf fsr#v(fsrstack)l movlw high (adr) movwf fsr#v(fsrstack)h exitm endif ;***** ; ; PIC 18 family. ; if fam_18 if c18comp ;C18 compatibility mode lfsr 1, adr lfsr 2, adr else ;normal Embed stack convention lfsr fsrstack, (adr) - 1 endif exitm endif ;***** ; error "STACK_SET macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro STACK_MAKEBUF n, reg16 ; ; Allocate a buffer of N bytes on the data stack and write the starting ; address of the new region to the 16 byte variable REG16. The address will ; be written to REG16 in low then high byte order. REG16 must be directly ; accessible with the current bank setting. The POPSTACK macro can be used ; to deallocate the buffer from the stack. ; ; On a PIC 18, REG16 can be one of the FSR registers but must not be the data ; stack pointer. ; ; NOTE: The assembly value USING_INTERRUPTS must be set correctly to indicate ; whether interrupts are currently in use or not. ; stack_makebuf macro n, reg16 ;***** ; ; Enhanced PIC 16. ; if fam_16b if ((n) <= 31) addfsr fsr#v(fsrstack), n exitm else movlw low (n) addwf fsr#v(fsrstack)l, w movwf (reg16)+0 movlw high (n) addwfc fsr#v(fsrstack)h, w movlw (reg16)+1 intr_off movwf fsr#v(fsrstack)h movf reg16, w movwf fsr#v(fsrstack)l intr_on exitm endif endif ;end of enhanced PIC 16 case ;***** ; ; PIC 18. ; if fam_18 ; ; Handle C18 compatibility. In this mode the data stack pointer ; points to where the next-pushed byte will be written. ; if c18comp movff fsr#v(fsrstack)l, (reg16) ;save buffer start address movff fsr#v(fsrstack)h, (reg16)+1 movlw low (n) intr_off ;disable interrupts while stack pointer inconsistant addwf fsr#v(fsrstack)l movlw high (n) addwfc fsr#v(fsrstack)h intr_on ;re-enable interrupts ; ; Handle normal Embed Inc data stack conventions. In this mode the ; stack pointer points to the last-pushed byte. ; else incf fsr#v(fsrstack)l, w movwf reg16 movf fsr#v(fsrstack)h, w skip_ncarr addlw 1 movwf (reg16)+1 movlw low (n) intr_off ;disable interrupts while stack pointer inconsistant addwf fsr#v(fsrstack)l movlw high (n) addwfc fsr#v(fsrstack)h intr_on ;re-enable interrupts endif exitm endif ;end of 18 family case ;***** ; error "STACK_MAKEBUF macro not implemented for this processor" endm ;******************************************************************************* ; ; Macro PUSHREG reg ; ; Push the contents of the register REG onto the stack. The direct bank must ; already be set for access to REG. On some processors, it is more efficient ; to push a set of general registers onto the stack with one PUSHREGS macro ; than to use the PUSHREG macro several times. This macro also trashes W on ; some processors. In that case the assembler variable W_TRASHED is set to ; TRUE, otherwise it is left unaltered. Some of the specifics per processor ; are: ; ; 16 family - Relatively inefficient to push a single value. W ; trashed. ; ; Enhanced 16 family and 18 family - Efficient to push a single value. ; Multiple PUSHREG just as efficient as a single PUSHREGS pushing the same ; state. ; pushreg macro reg ; ; Enhanced 16 family. ; if fam_16b movf reg, w movwi fsr#v(fsrstack)++ w_trashed set true exitm endif ; ; 18 family processor. The FSR indicated by FSRSTACK is reserved as the ; software stack pointer. Auto increment/decrement allows a push or pop in a ; single instruction. ; if fam_18 if c18comp movff reg, postinc#v(fsrstack) ;use C18 stack convention else movff reg, preinc#v(fsrstack) ;use normal stack convention endif exitm endif error "PUSHREG macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro PUSHW ; ; Push the contents of W onto the data stack. ; pushw macro ;***** ; if fam_16b movwi fsr#v(fsrstack)++ exitm endif ;***** ; if fam_18 if c18comp movwf postinc#v(fsrstack) else movwf preinc#v(fsrstack) endif exitm endif ;***** ; error "PUSHW macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro PUSH_TBLPTR ; ; Save TBLPTR on the stack. ; push_tblptr macro pushreg tblptrl pushreg tblptrh if progadrb > 2 pushreg tblptru endif endm ;******************************************************************************* ; ; Macro POPREG reg ; ; Pop the top of the software stack into REG. The direct bank must already be ; set for access to REG. On some processors, it is more efficient to pop a ; set of general registers onto the stack with one POPREGS macro than to use ; the POPREG macro several times. This macro also trashes W on some ; processors. In that case the assembler variable W_TRASHED is set to TRUE, ; otherwise it is left unaltered. Some of the specifics per processor are: ; ; 16 family - Relatively inefficient to pop a single value. W ; trashed. ; ; Enhanced 16 family - Multiple POPREG just as efficient as a single ; POPREGS. W trashed. ; ; 18 family - Efficient to pop a single value if normal Embed Inc ; conventions used. Multiple POPREG just as efficient as a single POPREGS. ; If C18 compiler compatibility enabled, then popping multiple registers ; with one POPREGS invocation is more efficient than popping the same ; registers with individual POPREG invocations. ; popreg macro reg ;***** ; ; Enhanced 16 family. ; if fam_16b moviw --fsr#v(fsrstack) movwf reg w_trashed set true exitm endif ;***** ; ; 18 family. ; if fam_18 if c18comp movf postdec#v(fsrstack) ;C18 stack convention movff indf#v(fsrstack), reg else movff postdec#v(fsrstack), reg ;normal stack convention endif exitm endif ;***** ; error "POPREG macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro POPW ; ; Pop the top of the data stack into W. ; popw macro ;***** ; if fam_16b moviw --fsr#v(fsrstack) exitm endif ;***** ; if fam_18 if c18comp movf postdec#v(fsrstack) movf indf#v(fsrstack), w else movf postdec#v(fsrstack), w endif exitm endif ;***** ; error "POPW macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro POP_TBLPTR ; ; Pop TBLPTR on the stack. This should have been pushed onto the stack with ; the PUSH_TBLPTR macro. ; pop_tblptr macro if progadrb > 2 popreg tblptru endif popreg tblptrh popreg tblptrl endm ;******************************************************************************* ; ; Macro POPSTACK n ; ; Pop N bytes off the top of the data stack. The byte values are lost. W is ; trashed. ; ; WARNING: This macro assumes that interrupts are enabled. It must not be ; used if interrupts are disabled because it could enable them. ; popstack macro n local psii, psjj ;***** ; ; Enhanced PIC 16 family. ; if fam_16b psii set n ;init number of bytes left to pop while psii psjj set psii ;init to do all bytes this time if psjj > 32 psjj set 32 ;clip to max for one ADDFSR instruction endif addfsr fsr#v(fsrstack), -psjj psii set psii - psjj ;update number of bytes left to pop endw exitm endif ;end of enhanced PIC 16 case ;***** ; ; PIC 18 family. ; if fam_18 if (n) <= 4 psii set 0 while psii < (n) movf postdec#v(fsrstack) psii set psii + 1 endw exitm endif movlw low n intr_off ;temp disable interrupts while stack pointer inconsistent subwf fsr#v(fsrstack)l movlw high n subwfb fsr#v(fsrstack)h intr_on ;re-enable interrupts after stack pointer consistent again exitm endif ;end of FAM_18 case ;***** ; error "POPSTACK macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Stack utility macros that are private to the other macros below. These are ; not intended to be used directly in source code. ; if fam_17 dopush macro adr ;push an individual register onto the stack if (bankof(adr) == bankof(stacklast)) || ((adr & h'FF') < h'20') ;same bank ? dbankif stacklast movfp adr, indf1 ;move value directly to the stack else ;variable and stack not in same bank dbankif adr movfp adr, wreg ;temp get value to push into WREG dbankif stacklast movwf indf1 ;move the WREG value onto the stack endif endm dopop macro adr ;pop an individual register from the stack if (bankof(adr) == bankof(stacklast)) || ((adr & h'FF') < h'20') ;same bank ? dbankif stacklast movpf indf1, adr ;get the value directly from the stack else ;variable and stack not in same bank dbankif stacklast movpf indf1, wreg ;temp save the value from the stack in WREG dbankif adr movwf adr ;move the WREG value to its final destination endif endm endif ;end of 17 family private stack macros ;******************************************************************************* ; ; Macro PUSHREGS <regflags> ; ; Push the indicated general registers onto the stack. The REGFLAGS argument ; is the OR of the REGFn flags for all the registers that are to be pushed. ; The registers are pushed in low to high order. ; ; It may be more efficient to call this macro once with all the registers to ; push than to call it separately for each register. See the PUSHREG macro ; description for details. ; ; The assembler variable N_BYTES_PUSHED is set to the number of bytes that ; were pushed onto the stack. ; ; NOTE: On PICs with indirect banks, the current indirect bank assumption must ; be correct. If the current indirect bank setting is not known, then the ; IBANK? macro should be invoked before this macro. ; ; Trashed: ; ; W, FSR, indirect register bank with CURRIB updated. ; pushregs macro regflags ;push the indicated general registers onto SW stack local f, reg n_bytes_pushed set 0 ;init to no bytes pushed f set (regflags) & regf_all ;make local sanitized flags value if f == 0 ;nothing to push ? exitm endif ;***** ; ; 16 family processors. These use a software stack pointer. ; if fam_16 ifndef stackp extern stackp endif ibankif stacklast ;make sure indirect reg bank set for stack access movf stackp, w ;set indirect pointer to current stack top movwf fsr ; ; There is at least one register to push and indirect addressing has been set ; up for the current top of the stack. ; ; Now push all the registers that have been selected. ; reg set 0 ;init register number while reg < numregs ;once for each possible register if f & regf#v(reg) ;this register enabled ? decf fsr ;point to new stack location movf reg#v(reg), w ;get the register value in W movwf indf ;write it to the new stack location n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack endif reg set reg + 1 ;advance to next register number endw ;back to process this new register number if f & regff ;FLAGS register enabled ? decf fsr ;point to new stack location movf flags, w ;get the register value in W movwf indf ;write it to the new stack location n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack endif ; ; Done pushing individual registers. ; movf fsr, w ;get new top of stack address movwf stackp ;update stack pointer register exitm endif ;end of 16 family case ;***** ; ; Enhanced 16 family processors. The FSR indicated by FSRSTACK is reserved as ; the stack pointer. ; if fam_16b reg set 0 ;init register number while reg < numregs ;once for each possible register if f & regf#v(reg) ;this register enabled ? movf reg#v(reg), w ;get the value to push movwi fsr#v(fsrstack)++ ;push it n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack w_trashed set true endif reg set reg + 1 ;advance to next register number endw ;back to process this new register number if f & regff ;FLAGS register enabled ? movf flags, w ;get the value to push movwi fsr#v(fsrstack)++ ;push it n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack w_trashed set true endif exitm endif ;end of enhanced 16 family case ;***** ; ; For the 17 family processors. FSR1 is reserved as the stack pointer. ; if fam_17 ; ; There is at least one register to push. FSR1 is pointing to the last byte ; pushed onto the stack. ; ; Set up FSR1 for post decrement, then decrement it once to be ready to write ; the first byte to the stack. ; bcf alusta, fs3 ;set FSR1 mode to post decrement bcf alusta, fs2 decf fsr1 ;set up for first write to the stack ; ; Now push all the registers that have been selected. ; reg set 0 ;init register number while reg < numregs ;once for each possible register if f & regf#v(reg) ;this register enabled ? dopush reg#v(reg) n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack endif reg set reg + 1 ;advance to next register number endw ;back to process this new register number if f & regff ;FLAGS register enabled ? dopush flags n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack endif ; ; Done pushing individual registers. ; incf fsr1 ;leave stack pointer pointing to last byte pushed exitm endif ;end of 17 family case ;***** ; ; 18 Family processors. The FSR indicated by FSRSTACK is reserved as the ; stack pointer. ; ; The normal Embed Inc convention is that the stack pointer points to the top ; byte on the stack. The C18 convention is that it points to the next byte ; after the top on the stack (where the next pushed byte would go). In both ; cases the stack grows towards higher addresses. ; if fam_18 reg set 0 ;init register number while reg < numregs ;once for each possible register if f & regf#v(reg) ;this register enabled ? if c18comp movff reg#v(reg), postinc#v(fsrstack) ;C18 stack convention else movff reg#v(reg), preinc#v(fsrstack) ;normal stack convention endif n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack endif reg set reg + 1 ;advance to next register number endw ;back to process this new register number if f & regff ;FLAGS register enabled ? if c18comp movff flags, postinc#v(fsrstack) ;C18 stack convention else movff flags, preinc#v(fsrstack) ;normal stack convention endif n_bytes_pushed set n_bytes_pushed + 1 ;count one more bytes pushed onto stack endif exitm endif ;end of 18 family case ;***** ; error "PUSHREGS macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro POPREGS <regflags> ; ; This macro performs the reverse operation to the PUSHREGS macro. The ; REGFLAGS argument is the OR of a set of REGFn flags to indicate the ; registers to be popped from the stack. The registers are popped in high to ; low order. ; ; It may be more efficient to call this macro once with all the registers to ; pop than to call it separately for each register. See the POPREG macro ; description for details. ; ; NOTE: On PICs with indirect banks, the current indirect bank assumption must ; be correct. If the current indirect bank setting is not known, then the ; IBANK? macro should be invoked before this macro. ; ; Trashed: ; ; W, FSR, indirect register bank, CURRIB updated ; popregs macro regflags ;pop the indicated general registers from the SW stack local f, reg f set (regflags) & regf_all ;make local sanitized flags value if f == 0 ;nothing to pop ? exitm endif ;***** ; ; 16 family processors. These use a software stack pointer. ; if fam_16 ifndef stackp extern stackp endif ibankif stacklast ;make sure indirect reg bank set for stack access movf stackp, w ;set indirect pointer to current stack top movwf fsr ; ; There is at least one register to pop and indirect addressing has been set ; up for the current top of the stack. ; ; Now pop all the registers that have been selected. ; if f & regff ;FLAGS register enabled ? movf indf, w ;get the top stack value movwf flags ;update the register value incf fsr ;point to new top of stack location endif reg set numregs - 1 ;init register number while reg >= 0 ;once for each possible register if f & regf#v(reg) ;this register selected ? movf indf, w ;get the top stack value movwf reg#v(reg) ;update the register value incf fsr ;point to new top of stack location endif reg set reg - 1 ;advance to next register number endw ;back to process this new register number ; ; Done poping poping individual registers. ; movf fsr, w ;get new top of stack address movwf stackp ;update stack pointer register exitm endif ;end of 16 family case ;***** ; ; Enhanced 16 family processors. The FSR indicated by FSRSTACK is reserved as ; the stack pointer. ; if fam_16b if f & regff ;FLAGS register enabled ? moviw --fsr#v(fsrstack) ;pop the value into W movwf flags ;save it in the target register w_trashed set true endif reg set numregs - 1 ;init register number while reg >= 0 ;once for each possible register if f & regf#v(reg) ;this register enabled ? moviw --fsr#v(fsrstack) ;pop the value into W movwf reg#v(reg) ;save it in the target register w_trashed set true endif reg set reg - 1 ;advance to next register number endw ;back to process this new register number exitm endif ;end of enhanced 16 family case ;***** ; ; For the 17 family processors. FSR1 is reserved as the stack pointer. ; if fam_17 local f, reg f set (regflags) & regf_all ;make local sanitized flags value if f == 0 ;nothing to push ? exitm endif ; ; There is at least one register to pop. FSR1 is pointing to the last byte ; pushed onto the stack, which is the first byte to pop. ; ; Set up FSR1 for post increment. ; bcf alusta, fs3 ;set FSR1 mode to post decrement bsf alusta, fs2 ; ; Now pop all the registers that have been selected. ; if f & regff ;FLAGS register enabled ? dopop flags endif reg set numregs - 1 ;init register number while reg >= 0 ;once for each possible register if f & regf#v(reg) ;this register selected ? dopop reg#v(reg) endif reg set reg - 1 ;advance to next register number endw ;back to process this new register number exitm endif ;end of 17 family case ;***** ; ; 18 family processors. The FSR indicated by FSRSTACK is reserved as the ; stack pointer. ; ; The normal Embed Inc convention is that the stack pointer points to the top ; byte on the stack. The C18 convention is that it points to the next byte ; after the top on the stack (where the next pushed byte would go). In both ; cases the stack grows towards higher addresses. ; if fam_18 if c18comp ;using C18 stack convention ? movf postdec#v(fsrstack) ;point to the top byte on the stack endif if f & regff ;FLAGS register enabled ? f set f & ~regff ;disable this register flag if c18comp if f movff postdec#v(fsrstack), flags ;at least one more to pop after this else movff indf#v(fsrstack), flags ;this is last pop this macro invocation endif else movff postdec#v(fsrstack), flags ;pop using normal stack convention endif endif reg set numregs - 1 ;init register number while reg >= 0 ;once for each possible register if f & regf#v(reg) ;this register selected ? f set f & ~regf#v(reg) ;disable this register flag if c18comp if f movff postdec#v(fsrstack), reg#v(reg) ;at least one more to pop after this else movff indf#v(fsrstack), reg#v(reg) ;this is last pop this macro invocation endif else movff postdec#v(fsrstack), reg#v(reg) ;pop using normal stack convention endif endif reg set reg - 1 ;advance to next register number endw ;back to process this new register number exitm endif ;end of 18 family case ;***** ; error "POPREGS macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro READSTACK <n>, <dest> ; ; Read the Nth byte from the top of the data stack into DEST. A N value of 0 ; indicates the top (last pushed) byte on the data stack, a value of 1 the ; next most recently pushed byte, etc. The direct register bank must be set ; for access to DEST. W is trashed. ; ; Additional details for some implementations: ; ; PIC 18 family - The register bank setting is irrelevant. It need not be ; set for access to DEST. ; readstack macro n, dest local ofs ;***** ; ; 18 family processors. ; if fam_18 ofs set (n) ;init offset from stack pointer if c18comp ;using C18 stack convention ? ofs set ofs + 1 ;stack pointer points to first free location endif if ofs == 0 ;no offset from where stack pointer is pointing ? movff indf#v(fsrstack), (dest) exitm endif if (ofs < -128) || (ofs > 127) error Stack offset of #v(ofs) out of range in READSTACK macro. exitm endif movlw low -ofs ;put address offset from stack pointer target in W movff plusw#v(fsrstack), (dest) exitm endif ;end of PIC 18 case ;***** ; error READSTACK macro not implemented for this processor. endm ;******************************************************************************* ;******************************************************************************* ; ; Subroutine linkage. ; ; Unless otherwise documented, subroutines are assumed to preserve the general ; registers REG0 - REGn and trash other state, such as W, FSR, and the current ; bank settings. ; ; Subroutines external to a module are assumed to be on any page, whereas all ; code in a particular linker section is on the same page. By convention, ; PCLATH is left pointing to the current code page. This means it must be set ; before and restored after external calls. ; ; On the 18 family, the GOTO and CALL instructions contain all address bits, ; so PCLATH and PCLATU do not matter. On these processors, PCLATH and PCLATU ; are not maintained to match the current execution address. There, PCLATH ; and PCLATU must be explicitly set before PCL is modified, such as with ; computed GOTOs and some table operations. ; ;******************************************************************************* ; ; Macro BREAKPOINT ; ; Add NOPs to the code when debugging with a ICD. These type of debuggers ; skid after a breakoint is hit, so using ordinary instructions for ; breakpoints is sometimes difficult. This macro provides instructions to set ; the breakpoint on, and include enough padding so that the next instruction ; after the macro is not executed despite debugging skidding. ; ; This macro only emits code when the DEBUG_ICD switch is set to TRUE. It can ; therefore safely be left in production code without harm. ; /macro breakpoint /if debug_icd then /write nop ;for debugger breakpoint /write /endif /endmac ;******************************************************************************* ; ; Macro ENTER <regflags> ; ; This macro performs the "standard" operations on subroutine entry. These ; actions are: ; ; 1 - Invalidate the current register bank assumptions. ; ; 2 - Save the registers indicated by REGFLAGS onto the software stack. ; REGFLAGS is the OR of the REGFn flags for each register to save. ; REGFLAGS may be NOREGS to indicate no registers are to be saved. ; ; 3 - Sets SAVEDREGS to the flags indicating which registers were saved. ; This can be used to automatically restore the saved registers later. ; enter macro regflags unbank ;invalidate all register bank assumptions pushregs regflags ;save the indicated registers on the software stack savedregs set regflags ;remember what was saved when routines was entered endm ;******************************************************************************* ; ; Macro LEAVE <regflags> ; ; This macro performs the "standard" operations on subroutine exit. These ; actions are: ; ; 1 - Restore the registers indicated by REGFLAGS from the software stack. ; They are assumed to have been pushed in low to high register number ; order, and will be popped in high to low order. REGFLAGS may be ; NOREGS to indicate no registers are to be restored. ; ; 2 - Return from the subroutine. ; ; 3 - Invalidate the current register bank assumptions. Note that this sets ; assembly state, not runtime state. The assumptions are therefore ; propagated in source code, not execution order. ; ; NOTE: The current indirect register bank assumption must be correct when ; this macro is called. Note that it is set correctly by the ENTER ; macro, but may have been altered by intervening code. Use the IBANK? ; macro to invalidate the current assumption if not sure. ; leave macro regflags popregs regflags ;restore the indicated register from the software stack return ;return from the subroutine unbank ;invalidate all register bank assumptions endm ;******************************************************************************* ; ; Macro LEAVEREST ; ; Just like LEAVE except that the registers indicated by SAVEDREGS are ; automatically restored. SAVEDREGS should have been set by the ENTER macro ; to the routine being left. ; leaverest macro leave savedregs endm ;******************************************************************************* ; ; <name> GLBSUB [<regflags>] ; ; Declare the start of a new global subroutine. <name> will be declared as ; the global subroutine entry point label at the current location. The ; subroutine entry point will be immediately followed by ENTER <regflags>. In ; other words, the new subroutine will be start by pushing the registers ; identified by <regflags> onto the stack. Nothing will be pushed and no code ; generated if <regflags> has the value NOREGS or is omitted. ; ; This macro was changed from a MPASM macro to a preprocessor macro in January ; 2012 and a different interface allowed as a result. The old interface of ; ; GLBSUB <name>, <regflags> ; ; is still allowed. Due to restrictions of MPASM macros, it was previously ; not possible to have the subroutine name appear as a label before the macro, ; and the REGFLAGS argument could not be defaulted. Preprocessor macros do ; not suffer from these restrictions, so the more visually clear interface was ; adopted as described earlier. ; ; For backwards compatibility with existing code, the old interface is still ; supported. If no label is supplied on the GLBSUB line, then it is assumed ; the old interface is being used. The first argument to the macro must be ; the subroutine name. The second is made optional and will default to NOREGS ; (no registers automatically saved) when left off. ; ; If a label is supplied then the new interface is assumed. The new interface ; is preferred and should be used in new code. ; /macro glbsub /var local name string ;subroutine name /var local regflags string = 0 ;<regflags> argument, init to no registers to save // // Set NAME and REGFLAGS to the subroutine name and register list arguments, // respectively. This is done differently depending on which interface is // used, as indicated by whether a leading label is present. // /if [exist -1 arg] /then ;new interface with leading label /set name [qstr [arg -1]] ;get the subroutine name /if [exist 1 arg] then /set regflags [qstr [arg 1]] ;get the explicit regflags argument /endif /else ;old interface, label is first argument /set name [qstr [arg 1]] ;get subroutine name /if [exist 2 arg] then /set regflags [qstr [arg 2]] ;get the explicit regflags argument /endif /endif /write name ;define the subroutine entry point /write " global " name ;define it global /write " enter " regflags ;perform standard subroutine entry /endmac ;******************************************************************************* ; ; Macro HLLSUB <name>, <regflags> ; ; Declare the start of the new global subroutine that must be callable from ; the current high level language. ; ; The supported high level languages are: ; ; C18 - Microchip MPLAB C18 compiler for the PIC 18. C18COMP must be set ; to TRUE. ; hllsub macro name, regflags if c18comp nexthllarg set 1 ;init number of next HLL argument to fetch glbsub name, regflags nexthllarg set nexthllarg + n_bytes_pushed exitm endif error Compiler not defined or supported in HLLSUB macro. endm ;******************************************************************************* ; ; Macro GETARG8 dest ; ; Get the next high level language argument, which is assumed to be 8 bits in ; size. DEST is the location to save the argument value in. It is assumed ; that DEST is directly accessible without banking, or that the bank is ; already set for access to DEST. ; ; The argument passing conventions differ between compilers. This macro will ; generate an error if one of the supported compilers is not in use. ; ; The supported high level languages are: ; ; C18 - Microchip MPLAB C18 compiler for the PIC 18. C18COMP must be set ; to TRUE. ; getarg8 macro dest ; ; C18 compiler. The caller pushes the call arguments onto the data stack in ; last to first order. The stack pointer points to the location where the ; next pushed byte will be written. On subroutine entry, the stack pointer is ; pointing to the next byte after where the first call argument is on the ; stack. ; if c18comp movlw low -nexthllarg movff plusw#v(fsrstack), dest nexthllarg set nexthllarg + 1 exitm endif error Compiler not defined or supported in GETARG8 macro. endm ;******************************************************************************* ; ; Macro GETARG16 dest ; ; Get the next high level language call argument, which is assumed to be 16 ; bits in size. The 16 bit value is saved in DEST+1:DEST. ; getarg16 macro dest if c18comp ;Microchip MPLAB C18 compiler getarg8 dest+1 getarg8 dest exitm endif error Compiler not defined or supported in GETARG16 macro. endm ;******************************************************************************* ; ; <name> LOCSUB [<regflags>] ; ; Just like GLBSUB except that the subroutine will not be global. It will ; only be known within its module. ; ; Like GLBSUB, the interface to this macro was changed in January 2012. See ; the GLBSUB description for details. ; /macro locsub /var local name string ;subroutine name /var local regflags string = 0 ;<regflags> argument, init to no registers to save // // Set NAME and REGFLAGS to the subroutine name and register list arguments, // respectively. This is done differently depending on which interface is // used, as indicated by whether a leading label is present. // /if [exist -1 arg] /then ;new interface with leading label /set name [qstr [arg -1]] ;get the subroutine name /if [exist 1 arg] then /set regflags [qstr [arg 1]] ;get the explicit regflags argument /endif /else ;old interface, label is first argument /set name [qstr [arg 1]] ;get subroutine name /if [exist 2 arg] then /set regflags [qstr [arg 2]] ;get the explicit regflags argument /endif /endif /write name ;define the subroutine entry point /write " enter " regflags ;perform standard subroutine entry /endmac ;******************************************************************************* ; ; name GLBENT ; GLBENT name ; ; Declare a global entry point that can be jumped to from other modules. ; /macro glbent /if [exist -1 arg] /then ;new interface with leading label [arg -1] global [arg -1] /else ;old interface, label is first argument [arg 1] global [arg 1] /endif unbank /endmac ;******************************************************************************* ; ; name LOCENT ; LOCENT name ; ; Declare a local entry point that can only be jumped to from within the same ; module. ; /macro locent /if [exist -1 arg] /then ;new interface with leading label [arg -1] /else ;old interface, label is first argument [arg 1] /endif unbank /endmac ;******************************************************************************* ; ; Macro SETPAGE <address> ; ; Select the code page state so that a GOTO to the indicated address ends at ; the right location. ; setpage macro adr if ncodepages <= 1 ;only one code page exists, no selection required ? exitm endif pagesel adr endm ;******************************************************************************* ; ; Macro MYPAGE ; ; Sets PCLATH to the current code page, if this machine has multiple code ; pages. W may be trashed. ; mypage macro if ncodepages <= 1 ;only one code page exists, no need to set PCLATH ? exitm endif if fam_17 ;17Cxxx processor ? movfp pcl, wreg ;read PCL to load PCH into PCLATH exitm endif local here setpage here here endm ;******************************************************************************* ; ; Macro GCALL <subroutine> ; ; Call a global subroutine, which could be anywhere in program memory. W is ; trashed between the caller and the subroutine, and can therefore not be used ; to pass data to the subroutine. The assumed register banks are set to ; invalid after the subroutine returns. ; ; No code page selection code is generated on machines that only have one code ; page. ; gcall macro subroutine extern subroutine setpage subroutine ;select the code page the subroutine is on if debug_icd nop endif call subroutine ;call the subroutine if debug_icd nop endif unbank ;invalidate register bank assumptions mypage ;restore PCLATH to the local page endm ;******************************************************************************* ; ; Macro GCALLNR <subroutine> ; ; Just like GCALL except that the current program memory page is not restored ; after the subroutine returns. This saves unnecessary instructions if the ; current code page setting is not used by subsequent code. This could be the ; case, for example, if another global subroutine was called immediately ; afterwards. ; ; WARNING: This macro will cause subsequent code to fail if it does rely on ; the current program memory page setting. Local CALLs and GOTOs may rely ; on the current page setting. ; gcallnr macro subroutine extern subroutine setpage subroutine ;select the code page the subroutine is on if debug_icd nop endif call subroutine ;call the subroutine if debug_icd nop endif unbank ;invalidate register bank assumptions endm ;******************************************************************************* ; ; Macro MCALL <subroutine> ; ; Call a local subroutine within the same module (code section, actually). ; The subroutine is therefore guaranteed to be on the same code page. The ; assumed register banks are invalidated after the subroutine returns. ; mcall macro subroutine if debug_icd nop endif if fam_18 if debug_icd call subroutine else rcall subroutine ;use short call within the module endif else call subroutine ;call the subroutine endif if debug_icd nop endif unbank ;invalidate register bank assumptions endm ;******************************************************************************* ; ; Macro MCALLL <subroutine> ; ; Like MCALL, except that a "long" call is used instead of the normal and ; possibly "short" call. If the processor has no long/short call distinction, ; then MCALLL is identical to MCALL. ; mcalll macro subroutine if debug_icd nop endif call subroutine ;use long call if debug_icd nop endif unbank ;invalidate register bank assumptions endm ;******************************************************************************* ; ; Macro GCALLWR <subroutine> ; ; Just like GCALL, except that W is preserved on return from the subroutine. ; Note that W is still trashed from the caller to the subroutine. ; gcallwr macro subroutine extern subroutine setpage subroutine ;select the code page the subroutine is on if debug_icd nop endif call subroutine ;call the subroutine if debug_icd nop endif unbank ;invalidate register bank assumptions if fam_12 || fam_16c5 || fam_16 if ncodepages > 1 ;code page selection is meaningful ? dbankif bankadr(0) extern tempw movwf tempw ;save W before it gets trashed by PAGESEL mypage ;restore PCLATH to the local page movf tempw, w ;restore W returned by subroutine endif endif endm ;******************************************************************************* ; ; Macro GCALLR <subroutine> <putret> ; ; Just like GCALL, except that the subroutine is assumed to return a value in ; W. This return value will be stored in PUTRET. Note that PUTRET must be in ; global (any bank) memory unless the subroutine is known to explicitly set ; the direct register bank. W is trashed. The direct and indirect register ; bank settings are preserved from the subroutine. ; gcallr macro subroutine, putret extern subroutine setpage subroutine ;select the code page the subroutine is on if debug_icd nop endif call subroutine ;call the subroutine if debug_icd nop endif unbank ;invalidate register bank assumptions movwf putret ;save the subroutine return value mypage ;restore PCLATH to the local page endm ;******************************************************************************* ; ; Macro MCALLR <subroutine> <putret> ; ; Just like MCALL, except that the subroutine is assumed to return a value in ; W. This return value will be stored in PUTRET. Note that PUTRET must be in ; global (any bank) memory unless the subroutine is known to explicitly set ; the direct register bank. W is trashed. The direct and indirect register ; bank settings are preserved from the subroutine. ; mcallr macro subroutine, putret if debug_icd nop endif if fam_18 rcall subroutine ;use short call within the module else call subroutine ;call the subroutine endif if debug_icd nop endif unbank ;invalidate register bank assumptions movwf putret ;save the subroutine return value endm ;******************************************************************************* ; ; Macro GJUMP <program address> ; ; Jump to a location that could be anywhere in program memory. W is trashed ; before execution starts at the new location. The assumed register banks are ; set to invalid for source code immediately following the GJUMP. ; gjump macro adr setpage adr ;select code page of target if debug_icd nop endif goto adr ;jump to the target unbank ;invalidate register bank assumptions endm ;******************************************************************************* ;******************************************************************************* ; ; Global flags. ; ; Global flags are just globally known 1-bit variables. The macros in this ; section are intended to work with the /FLAG preprocessor directive. The ; /FLAG directive is used to define each flag and update NFLAGB to the total ; number of bytes used for all the flag bits. The macros in this section ; assume that NFLAGB is already set correctly. ; ; The flag bytes are assumed to be named GFL0 thru GFLn. Flags are allocated ; in the flag bytes in least to most significant order. ; nflagb set 0 ;init number of GFLx variables allocated ;******************************************************************************* ; ; Macro FLAGS_DEFINE ; ; Define the GFLx variables required to hold all the flags. This macro will ; create one RES directive for each flag variable and declare it GLOBAL. ; This macro must only be called after all the /FLAG directives, else NFLAGB ; will not be valid. ; flags_define macro local ii ii set 0 while ii < nflagb gfl#v(ii) res 1 global gfl#v(ii) ii set ii + 1 endw endm ;******************************************************************************* ; ; Macro FLAGS_CLEAR ; ; Clear all the global flags defined with the FLAG macro. The GFLx variables ; are assumed to be declared in the GBANK register bank, and NFLAGB is the ; number of GFLx variables. ; flags_clear macro local ii ifdef gbankadr dbankif gbankadr endif ii set 0 while ii < nflagb clrf gfl#v(ii) ii set ii + 1 endw endm ;******************************************************************************* ; ; Macro EXTERN_FLAGS ; ; Declare all the GFLx flag bytes as external. ; extern_flags macro local ii ii set 0 while ii < nflagb extern gfl#v(ii) ii set ii + 1 endw endm ;******************************************************************************* ; ; Macro SETFLAG flag ; ; Set the flag defined by a /FLAG preprocessor command. ; /macro setflag dbankif gbankadr bsf flag_[arg 1]_reg, flag_[arg 1]_bit /endmac ;******************************************************************************* ; ; Macro CRLFLAG flag ; ; Clear the flag defined by a /FLAG preprocessor command. ; /macro clrflag dbankif gbankadr bcf flag_[arg 1]_reg, flag_[arg 1]_bit /endmac ;******************************************************************************* ; ; Macro SKIP_FLAG flag ; ; Skip the next instruction if the flag defined by a /FLAG preprocessor ; directive is set. ; /macro skip_flag dbankif gbankadr btfss flag_[arg 1]_reg, flag_[arg 1]_bit /endmac ;******************************************************************************* ; ; Macro SKIP_NFLAG flag ; ; Skip the next instruction if the flag defined by a /FLAG preprocessor ; directive is clear. ; /macro skip_nflag dbankif gbankadr btfsc flag_[arg 1]_reg, flag_[arg 1]_bit /endmac ;******************************************************************************* ;******************************************************************************* ; ; FIFO (first in, first out) queues. ; ; This section defines a set of macros and other facilities for creating and ; using FIFOs. ; ; Two types of FIFOs are available, old and new. The original (old) FIFOs ; were implemented with MPASM macros, requiring configuration information to ; be passed separately to some macros. The data structure also did not allow ; writing and reading from a FIFO simultaneously. When a FIFO was used in ; interrupt and foreground code, interrupts had to be disabled around FIFO ; accesses in the foreground code. ; ; The new (2021) FIFOs are implemented with pre-processor macros. ; Configuration state can be kept in known pre-processor constants, so does ; not need to be passed to subsequent macros to use a FIFO. Also, the data ; structure of this type of FIFO allows for simultaneous reading and writing ; by interrupt and foreground code. Interrupts do not need to be disabled ; around FIFO accesses in foreground code. ; ; For backwards compatibility, the new FIFO type is only selected when the ; pre-processor constant FIFOS_NEW exists and is set to TRUE before this file ; is referenced. ; ;******************************************************************************* ; ; Old FIFO type. ; /if [not fifos_new] then ; ; The format of a FIFO in memory is: ; ; name+0 - number of data bytes currently in the queue ; ; name+1 - offset into the queue of where to put the next byte + 1 ; ; name+2 - offset into the queue of where to get the next byte from + 1 ; ; name+3 thru name+2+size - Data buffer. Sequential bytes are written ; and read at decreasing addresses until the start of the buffer is ; reached, in which case the next address wraps back to the end of the ; buffer. ; ; Define symbolic constants for the offset of various special bytes from the ; start of a FIFO. All subsequent code uses these symbols instead of assuming ; the bytes are at fixed offsets. ; ; WARNING ; ; This current implementation on the PIC 18 family uses the PLUSW addressing ; mode to access data in the FIFO buffer. Due to the signed byte offset of ; that addressing mode and how the buffer indicies are used, the maximum ; usable FIFO size is 126 bytes. This is the maximum SIZE parameter that ; should be passed to FIFO_DEFINE. ; fifo_ofs_n equ 0 ;offset from FIFO label for number of bytes in buffer fifo_ofs_put equ 1 ;offset from FIFO label for PUT index fifo_ofs_get equ 2 ;offset from FIFO label for GET index fifo_ofs_buf equ 3 ;offset from FIFO label for start of buffer ;*********************************************************** ; ; Macro FIFO_DEFINE name, size ; ; Define a first in, first out queue. The symbol NAME will be defined as the ; first byte of the queue structure. Size is the maximum number of data bytes ; the queue will be able to hold. ; fifo_define macro name, size name res 1 ;number of data bytes currently in the queue res 1 ;put offset res 1 ;get offset res size ;the data buffer endm ;*********************************************************** ; ; Macro FIFO_INIT name ; ; Initialize the FIFO at NAME. The register bank must be set for access to ; the FIFO state. ; fifo_init macro name clrf name + fifo_ofs_n ;indicate the FIFO is empty movlw 1 movwf name + fifo_ofs_put ;init PUT index movwf name + fifo_ofs_get ;init GET index endm ;*********************************************************** ; ; Macro FIFO_INIT_P0 ; ; Initialize the FIFO pointed to by FSR0. FSR0 is preserved. ; fifo_init_p0 macro addfsr0 fifo_ofs_n ;point to N field clrf postinc0 movlw 1 addfsr0 fifo_ofs_put - (fifo_ofs_n + 1) ;point to PUT field movwf postinc0 addfsr0 fifo_ofs_get - (fifo_ofs_put + 1) ;point to GET field movwf postdec0 addfsr0 0 - (fifo_ofs_get - 1) ;point back to start of FIFO endm ;*********************************************************** ; ; Macro FIFO_GETN_P0 ; ; Get the number of bytes in the FIFO pointed to by FSR0 into W. FSR0 is ; preserved. ; fifo_getn_p0 macro addfsr0 fifo_ofs_n ;point to number of bytes in FIFO byte movf indf0, w ;get the number of bytes in the FIFO addfsr0 0 - fifo_ofs_n ;point back to start of FIFO endm ;*********************************************************** ; ; Macro FIFO_SKIP_EMPTY name ; ; Skips the next instruction after the macro if the FIFO at NAME is empty. ; The register bank must be set for access to the FIFO state. ; fifo_skip_empty macro name if fam_12 || fam_16 || fam_16b movf name + fifo_ofs_n ;set Z if FIFO empty skip_z exitm endif if fam_17 || fam_18 tstfsz name + fifo_ofs_n ;skip if FIFO empty exitm endif error "Macro FIFO_SKIP_EMPTY not implemented for this processor" endm ;*********************************************************** ; ; Macro FIFO_SKIP_NEMPTY name ; ; Skips the next instruction after the macro if the FIFO at NAME contains at ; least one data byte. The register bank must be set for access to the FIFO ; state. ; ; W may be trashed. ; fifo_skip_nempty macro name if fam_12 || fam_16 || fam_16b || fam_18 movf name + fifo_ofs_n ;set Z if FIFO empty skip_nz ;FIFO not empty ? exitm endif if fam_17 decf name + fifo_ofs_n, w ;set borrow flag if FIFO is empty skip_nborr ;FIFO not empty ? exitm endif error "Macro FIFO_SKIP_NEMPTY not implemented for this processor" endm ;*********************************************************** ; ; Macro FIFO_N_EMPTY name size ; ; Returns the number of empty locations in the FIFO in W. This is the number ; of consecutive PUSH operations that are guaranteed to work. The register ; bank must be set for access to the FIFO state. ; fifo_n_empty macro name, size movf name + fifo_ofs_n, w ;get number of bytes in the FIFO sublw size ;subtract it from the maximum bytes FIFO can hold endm ;*********************************************************** ; ; Macro FIFO_SKIP_FULL name size ; ; Skips the next instruction after the macro if the FIFO at NAME is completely ; full. The register bank must be set for access to the FIFO state. ; ; W is trashed. ; fifo_skip_full macro name, size movlw size ;get max bytes the FIFO can hold subwf name + fifo_ofs_n, w ;compare to number of bytes currently in FIFO skip_wle ;FIFO is completely full ? endm ;*********************************************************** ; ; Macro FIFO_SKIP_NFULL name size ; ; Skips the next instruction after the macro if the FIFO at NAME is not ; completely full. The register bank must be set for access to the FIFO ; state. ; ; W is trashed. ; fifo_skip_nfull macro name, size movlw size ;get max bytes the FIFO can hold subwf name + fifo_ofs_n, w ;compare to number of bytes currently in FIFO skip_wgt ;FIFO is not completely full ? endm ;*********************************************************** ; ; Macro FIFO_N_FULL name ; ; Returns the number bytes in the FIFO in W. This is the number of ; consecutive POP operations that are guaranteed to work. The register bank ; must be set for access to the FIFO state. ; fifo_n_full macro name movf name + fifo_ofs_n, w ;get the number of bytes in the FIFO endm ;*********************************************************** ; ; Macro FIFO_PUT name, size, data ; ; Add the byte in DATA as the last byte in the FIFO at NAME. SIZE must be the ; maximum number of bytes the FIFO was defined to hold. The register bank ; must be set for access to the FIFO and DATA. Since the FIFO is usually not ; in global RAM, this means DATA must be either in global RAM or in the same ; register bank as the FIFO. ; ; The FIFO may be trashed if it is already full. This should be checked ; before this macro is called. ; ; Note that if the FIFO could be accessed from the interrupt service routine, ; then interrupts should be temporarily disabled around this macro. ; ; The indirect register bank must be set for access to the FIFO. ; ; W and the first FSR are trashed. ; ; On a PIC 18, DATA may be in any bank. ; fifo_put macro name, size, data ;***** ; ; 12 and 16 family processors. ; if fam_16 || fam_12 movlw name + (fifo_ofs_buf - 1) ;get address for buffer index 0 addwf name + fifo_ofs_put, w ;make address of where to write this new byte movwf fsr ;point to where to write the byte movf data, w ;get the data byte into W movwf indf ;write the data byte into the buffer incf name + fifo_ofs_n ;indicate one more byte now in the FIFO movlw size ;get offset for last byte in buffer decf name + fifo_ofs_put ;update buffer index to next byte, set Z on wrap skip_nz ;not just decrement past beginning of buffer ? movwf name + fifo_ofs_put ;wrap back to end of buffer exitm endif ;***** ; ; Enhanced 16 family processors. ; if fam_16b movlw low (name + (fifo_ofs_buf - 1)) ;get address for buffer index 0 addwf name + fifo_ofs_put, w ;make address of where to write this new byte movwf fsr#v(fsrsc1)l ;set write pointer low byte movlw high (name + (fifo_ofs_buf - 1)) skip_ncarr addlw 1 movwf fsr#v(fsrsc1)h ;set write pointer high byte movf data, w ;get the data byte into W movwf indf#v(fsrsc1) ;write the data byte into the buffer incf name + fifo_ofs_n ;indicate one more byte now in the FIFO movlw size ;get offset for last byte in buffer decf name + fifo_ofs_put ;update buffer index to next byte, set Z on wrap skip_nz ;not just decrement past beginning of buffer ? movwf name + fifo_ofs_put ;wrap back to end of buffer exitm endif ;***** ; ; 17 family processors. ; if fam_17 movlw name + (fifo_ofs_buf - 1) ;get address for buffer index 0 addwf name + fifo_ofs_put, w ;make address of where to write this new byte movwf fsr0 ;point to where to write the byte movfp data, indf0 ;write the data byte into the buffer incf name + fifo_ofs_n ;indicate one more byte now in the FIFO movlw size ;get offset for last byte in buffer dcfsnz name + fifo_ofs_put ;update buffer index, skip on not wrap movwf name + fifo_ofs_put ;wrap back to end of buffer exitm endif ;***** ; ; 18 family processors. ; if fam_18 lfsr 0, name + (fifo_ofs_buf - 1) ;point to base for PUT offset movf name + fifo_ofs_put, w ;get PUT 0-N offset into W movff data, plusw0 ;copy the byte into the data buffer incf name + fifo_ofs_n ;indicate one more byte now in the FIFO movlw size ;get new PUT offset if wrapped to end of FIFO dcfsnz name + fifo_ofs_put ;update the PUT index, skip on not wrap movwf name + fifo_ofs_put ;wrap back to the end of the buffer exitm endif ;***** ; error "Macro FIFO_PUT not implemented for this processor" endm ;*********************************************************** ; ; Macro FIFO_PUT_P0 size, data ; ; Push the byte in DATA onto the FIFO at where FSR0 is pointing. FSR0 will be ; preserved. ; fifo_put_p0 macro size, data if fam_18 addfsr0 fifo_ofs_put ;point to PUT field movf postinc0, w ;get offset into FIFO to write this byte to addfsr0 (fifo_ofs_buf - 1) - (fifo_ofs_put + 1) ;point to one before buffer movff data, plusw0 ;stuff the byte into the buffer addfsr0 fifo_ofs_n - (fifo_ofs_buf - 1) ;point to N field incf postinc0 ;indicate one more byte now in the FIFO addfsr0 fifo_ofs_put - (fifo_ofs_n + 1) ;point to PUT field movlw size ;get new PUT value for wrapping to end of FIFO dcfsnz indf0 ;update PUT index, skip on not wrap movwf indf0 ;wrap back to end of the buffer addfsr0 0 - fifo_ofs_put ;point FSR0 back to start of FIFO structure exitm endif error "Macro FIFO_PUT_P0 not implemented for this processor" endm ;*********************************************************** ; ; Macro FIFO_GET name, size, data ; ; Get the next byte from the FIFO at NAME into DATA. SIZE must be the maximum ; number of bytes the FIFO was defined to hold. The register bank must be set ; for access to the FIFO and DATA. Since the FIFO is usually not in global ; RAM, this means DATA must be either in global RAM or in the same register ; bank as the FIFO. ; ; The FIFO may be trashed if it is already empty. This should be checked ; before this macro is called. ; ; Note that if the FIFO could be accessed from the interrupt service routine, ; then interrupts should be temporarily disabled around this macro. ; ; The indirect register bank must be set for access to the FIFO. ; ; W and the first FSR are trashed. ; ; On a PIC 18, DATA may be in any bank. ; fifo_get macro name, size, data ;***** ; ; 12 and 16 family processors. ; if fam_16 || fam_12 movlw name + (fifo_ofs_buf - 1) ;get address for buffer index 0 addwf name + fifo_ofs_get, w ;make address of where to read new byte from movwf fsr ;point to where to read the byte from movf indf, w ;get the data byte movwf data ;pass it back decf name + fifo_ofs_n ;indicate one less byte now in the FIFO movlw size ;get offset for last byte in buffer decf name + fifo_ofs_get ;update buffer index to next byte, set Z on wrap skip_nz ;not just decrement past beginning of buffer ? movwf name + fifo_ofs_get ;wrap back to end of buffer exitm endif ;***** ; ; Enhanced 16 family processors. ; if fam_16b movlw low (name + (fifo_ofs_buf - 1)) ;get address for buffer index 0 addwf name + fifo_ofs_get, w ;make address of where to read new byte from movwf fsr#v(fsrsc1)l ;set read pointer low byte movlw high (name + (fifo_ofs_buf - 1)) skip_ncarr addlw 1 movwf fsr#v(fsrsc1)h ;set read pointer high byte movf indf#v(fsrsc1), w ;get the data byte movwf data ;pass it back decf name + fifo_ofs_n ;indicate one less byte now in the FIFO movlw size ;get offset for last byte in buffer decf name + fifo_ofs_get ;update buffer index to next byte, set Z on wrap skip_nz ;not just decrement past beginning of buffer ? movwf name + fifo_ofs_get ;wrap back to end of buffer exitm endif ;***** ; ; 17 family processors. ; if fam_17 movlw name + (fifo_ofs_buf - 1) ;get address for buffer index 0 addwf name + fifo_ofs_get, w ;make address of where to read new byte from movwf fsr0 ;point to where to read the byte from movpf indf0, data ;get the data byte and pass it back decf name + fifo_ofs_n ;indicate one less byte now in the FIFO movlw size ;get offset for last byte in buffer dcfsnz name + fifo_ofs_get ;update buffer index, skip on not wrap movwf name + fifo_ofs_get ;wrap back to end of buffer exitm endif ;***** ; ; 18 family processors. ; if fam_18 lfsr 0, name + (fifo_ofs_buf - 1) ;point to base for GET offset movf name + fifo_ofs_get, w ;get GET offset into W movff plusw0, data ;copy the byte from the buffer into DATA decf name + fifo_ofs_n ;indicate one less byte now in the FIFO movlw size ;get offset for last byte in buffer dcfsnz name + fifo_ofs_get ;update buffer index, skip on not wrap movwf name + fifo_ofs_get ;wrap back to end of buffer exitm endif error "Macro FIFO_GET not implemented for this processor" endm ;*********************************************************** ; ; Macro FIFO_GET_P0 size, data ; ; Get the next byte from the FIFO at where FSR0 is pointing into DATA. FSR0 ; will be preserved. ; fifo_get_p0 macro size, data ; ; 18 family processors. ; if fam_18 addfsr0 fifo_ofs_get ;point to GET field movf indf0, w ;get the GET offset into W addfsr0 (fifo_ofs_buf - 1) - fifo_ofs_get ;point to base for GET offset movff plusw0, data ;fetch the byte from the FIFO into DATA addfsr0 fifo_ofs_get - (fifo_ofs_buf - 1) ;point to GET offset movlw size ;get offset for last byte in buffer dcfsnz indf0 ;update GET index, skip on not wrap movwf indf0 ;wrap back to end of buffer addfsr0 fifo_ofs_n - fifo_ofs_get ;point to N field decf indf0 ;count one less byte now in the FIFO addfsr0 0 - fifo_ofs_n ;restore FSR0 pointing to start of FIFO exitm endif error "Macro FIFO_GET not implemented for this processor" endm /endif ;end of old FIFOs selected ;******************************************************************************* ; ; New FIFO type. ; /if fifos_new then ; ; The FIFO macros are briefly listed here. See their header comments, below, ; for details. The FIFO macros are: ; ; name FIFO_DEFINE size ; ; Allocates memory for the FIFO. Leaves configuration info in ; preprocessor constants derived from NAME for the other macros to use. ; ; FIFO_INIT name ; ; Initialize the FIFO at run time. Will be empty. ; ; FIFO_N_FULL name [, dest] ; ; Get the number of data bytes in the FIFO. ; ; FIFO_N_EMPTY name [, dest] ; ; Get the number of empty slots in the FIFO. ; ; FIFO_BR_FULL name, n, label [, temp] ; ; Jumps to LABEL when the FIFO has N or less bytes of room left. ; ; FIFO_BR_EMPTY name, n, label [, temp] ; ; Jumps to LABEL when the FIFO has N or less data bytes in it. ; ; FIFO_PUT name, data ; ; Write the byte in DATA to the FIFO. ; ; FIFO_GET name, data ; ; Get the next byte from the FIFO into DATA. ; ; The format of a FIFO in memory is: ; ; PUT - Offset from the start of the buffer to write the next byte to. ; This is a single byte when the FIFO was configured for 255 bytes or ; less. It is two bytes when the FIFO was configured longer. ; ; GET - Offset from the start of the buffer to read the next byte from. ; This is a single byte when the FIFO was configured for 255 bytes or ; less. It is two bytes when the FIFO was configured longer. ; ; BUF - The data buffer. This is 1 byte longer than the size the FIFO was ; configured for. ; ; Bytes are written and read from the buffer in ascending address order. ; The buffer is circular, so the first byte of BUF immediately follows the ; last byte of BUF. ; ; The number of bytes in the FIFO is PUT-GET, after buffer wrapping is ; accounted for. The FIFO is full when PUT indicates the byte immediately ; before that indicated by GET. The FIFO is empty when PUT=GET. This means ; one byte of the buffer will always be unused. The buffer is made 1 byte ; larger to compensate. Note that the alternative is to store the number ; of bytes in the buffer, which would require additional state. The method ; used here is no less efficient of memory, but allows for interrupt and ; foreground code to simulataneously read and write from/to the FIFO. There ; is no need to disabled interrupts in foreground code around FIFO accesses. ; ; Preprocessor constants are created by FIFO_DEFINE so that configuration ; information is implicitly available to other macros. These preprocessor ; constants are: ; ; FIFO_name_SIZE, integer ; ; The maximum number of data bytes the FIFO can hold. ; ; FIFO_name_SMALL, bool ; ; TRUE if PUT and GET are a single byte, FALSE if they are 2 bytes. ; ; FIFO_name_BIG, bool ; ; The opposite of FIFO_name_SMALL. ; ; FIFO_name_BUFN, integer ; ; The size in bytes of BUF. This is the value that the PUT and GET ; indicies are wrapped to. ; ;******************************************************************************* ; ; name FIFO_DEFINE size ; ; Allocate the memory for a FIFO that can hold up to SIZE data bytes. SIZE ; must be resolvable to a value by the preprocessor. ; /macro fifo_define /var local size integer = [vnl [arg 1]] //SIZE argument /var local name string = [qstr [arg -1]] //FIFO name /var local memsz integer //total memory size of the FIFO /var local s string //for assembling output line /if [= [slen name] 0] then /show " Missing FIFO name label preceeding FIFO_DEFINE macro." error "FIFO_DEFINE name" /stop /endif /const fifo_[chars name]_size integer = size //save user-visible FIFO size /const fifo_[chars name]_small bool = [<= size 255] //PUT, GET fit in one byte ? /const fifo_[chars name]_big bool = [not fifo_[chars name]_small] /const fifo_[chars name]_bufn integer = [+ size 1] //memory size of buffer /set memsz [+ fifo_[chars name]_bufn 2] //init size assuming small /if fifo_[chars name]_big then //actually using big indicies ? /set memsz [+ memsz 2] //update total mem needed for big indicies /endif /set s name //init output line with leading label /call tabopcode s /append s "res" /call taboperand s /append s memsz /call startcomm s /append s "FIFO, " size " max data bytes" /write s /endmac ;******************************************************************************* ; ; Macro FIFO_INIT name ; ; Initialize the FIFO to empty. The previous state of the FIFO is irrelevant. ; ; The start of the FIFO must be directly accessible with the current bank ; setting. ; /macro fifo_init /var local name string = [qstr [arg 1]] ;FIFO name /var local size integer = fifo_[chars name]_size ;user-visible FIFO size /var local big bool = fifo_[chars name]_big ;PUT and GET are two bytes /var local bufn integer = fifo_[chars name]_bufn ;size of the data buffer /var local s string ;for assembling output line /var local nint integer ;number of bytes to initialize /write /set nint [if big 4 2] ;get number of bytes to initialize /loop with ii from 0 n nint ;once for each byte /set s "" ;clrf name+0 /call tabopcode s /append s "clrf" /call taboperand s /append s name "+" ii /if [= ii 0] then ;first CLRF instruction ? /call startcomm s /append s "initialize the FIFO " [ucase name] " to empty" /endif /write s /endloop /write /endmac ;******************************************************************************* ; ; Macro FIFO_N_FULL name [, dest] ; ; Get the number of data bytes in the FIFO NAME. Without the DEST parameter, ; the value is left in W. In that case, the FIFO size must be 255 bytes or ; less. ; ; DEST is the memory where to write the result. DEST is assumed to be two ; bytes in size when the FIFO was configured for more than 255 bytes. In that ; case, the low byte of the result is written to DEST+0, and the high byte to ; DEST+1. ; ; The start of the FIFO and DEST (if used) must be directly accessible with ; the current bank setting. ; /macro fifo_n_full /var local name string = [qstr [arg 1]] ;FIFO name /var local dest string = [qstr [arg 2]] ;destination variable /var local size integer = fifo_[chars name]_size ;user-visible FIFO size /var local big bool = fifo_[chars name]_big ;PUT and GET are two bytes /var local bufn integer = fifo_[chars name]_bufn ;size of the data buffer /var local s string ;for assembling output line /var local dvar bool = [> [slen dest] 0] ;destination is variable, not W /if [and [> size 255] [not dvar]] then /show " FIFO too large for FIFO_N_FULL macro without <dest>." error "No dest" end /stop /endif /write // // Handle case of the result is a single byte. // /if [not big] then /set s "" ;movf name+1, w /call tabopcode s /append s "movf" /call taboperand s /append s name "+1, w" /call startcomm s /append s "get FIFO GET index" /write s /set s "" ;subwf name, w /call tabopcode s /append s "subwf" /call taboperand s /append s name "+0, w" /call startcomm s /append s "make PUT-GET, N bytes in FIFO without wrap" /write s /if [<> bufn 256] then skip_wle ;no buffer wrapping ? /set s "" ;addlw bufn /call tabopcode s /append s "addlw" /call taboperand s /append s bufn /call startcomm s /append s "account for buffer wrapping" /write s /endif /if dvar /then ;write result to DEST /set s "" ;movwf dest /call tabopcode s /append s "movwf" /call taboperand s /append s dest /call startcomm s /append s "number of data bytes in FIFO " name /write s /else ;leave the result in W /write " ;W is number of data bytes in FIFO " name /endif /write /quitmac /endif ;end of single-byte result case // // The result is two bytes, and is to be stored in DEST. // // Make DEST <-- PUT - GET // /set s "" ;movf get+0, w /call tabopcode s /append s "movf" /call taboperand s /append s name "+2, w" /call startcomm s /append s "make PUT-GET low byte" /write s /set s "" ;subwf put+0, w /call tabopcode s /append s "subwf" /call taboperand s /append s name "+0, w" /write s /set s "" ;movwf dest+0 /call tabopcode s /append s "movwf" /call taboperand s /append s dest "+0" /write s /set s "" ;movf get+1, w /call tabopcode s /append s "movf" /call taboperand s /append s name "+3, w" /call startcomm s /append s "make PUT-GET high byte" /write s /set s "" ;subwfb put+1, w /call tabopcode s /append s "subwfb" /call taboperand s /append s name "+1, w" /write s /set s "" ;movwf dest+1 /call tabopcode s /append s "movwf" /call taboperand s /append s dest "+1" /write s /set s "" ;jmp_wle done /call tabopcode s /append s "jmp_wle" /call taboperand s /append s [qstr [lab fifn]] /call startcomm s /append s "no buffer wrap-around ?" /write s // DEST constains PUT-GET, but there is a buffer wrap between PUT and GET. // Add the buffer wrap size to make the actual distance from GET to PUT. // /set s "" ;movlw low bufn /call tabopcode s /append s "movlw" /call taboperand s /append s [and bufn 16#FF] /call startcomm s /append s "add buffer length into low byte" /write s /set s "" ;addwf dest+0 /call tabopcode s /append s "addwf" /call taboperand s /append s dest "+0" /write s /set s "" ;movlw high bufn /call tabopcode s /append s "movlw" /call taboperand s /append s [shiftr bufn 8] /call startcomm s /append s "add buffer length to high byte" /write s /set s "" ;addwfc dest+1 /call tabopcode s /append s "addwfc" /call taboperand s /append s dest "+1" /write s /set s [qstr [lab fifn]] ;final result in DEST /call startcomm s /append s "number of bytes in FIFO " name " is in 16 bit var " [ucase dest] /write s /endmac ;******************************************************************************* ; ; Macro FIFO_N_EMPTY name [, dest] ; ; Get the number of empty slots in the FIFO NAME. Without the DEST parameter, ; the value is left in W. In that case, the FIFO size must be 255 bytes or ; less. ; ; DEST is the memory where to write the result. DEST is assumed to be two ; bytes in size when the FIFO was configured for more than 255 bytes. In that ; case, the low byte of the result is written to DEST+0, and the high byte to ; DEST+1. ; ; The start of the FIFO and DEST (if used) must be directly accessible with ; the current bank setting. ; /macro fifo_n_empty /var local name string = [qstr [arg 1]] ;FIFO name /var local dest string = [qstr [arg 2]] ;destination variable /var local size integer = fifo_[chars name]_size ;user-visible FIFO size /var local big bool = fifo_[chars name]_big ;PUT and GET are two bytes /var local bufn integer = fifo_[chars name]_bufn ;size of the data buffer /var local s string ;for assembling output line /var local dvar bool = [> [slen dest] 0] ;destination is variable, not W /if [and [> size 255] [not dvar]] then /show " FIFO too large for FIFO_N_EMPTY macro without <dest>." error "No dest" end /stop /endif /write // // Handle case of the result is a single byte. // /if [not big] then /set s "" ;movf name+0, w /call tabopcode s /append s "movf" /call taboperand s /append s name "+0, w" /call startcomm s /append s "get FIFO PUT index + 1" /write s /set s "" ;addlw 1 /call tabopcode s /append s "addlw" /call taboperand s /append s 1 /write s /set s "" ;subwf name+1, w /call tabopcode s /append s "subwf" /call taboperand s /append s name "+1, w" /call startcomm s /append s "make empty slots without accounting for wrapping" /write s /if [<> bufn 256] then skip_wle ;no buffer wrapping ? /set s "" ;addlw bufn /call tabopcode s /append s "addlw" /call taboperand s /append s bufn /call startcomm s /append s "account for buffer wrapping" /write s /endif /if dvar /then ;write result to DEST /set s "" ;movwf dest /call tabopcode s /append s "movwf" /call taboperand s /append s dest /call startcomm s /append s "number of empty slots in FIFO " name /write s /else ;leave the result in W /write " ;W is number of empty slots in FIFO " name /endif /write /quitmac /endif ;end of single-byte result case // // The result is two bytes, and is to be stored in DEST. // // Make DEST <-- GET - PUT - 1 // /set s "" ;movf put+0, w /call tabopcode s /append s "movf" /call taboperand s /append s name "+0, w" /call startcomm s /append s "make GET-PUT low byte" /write s /set s "" ;subwf get+0, w /call tabopcode s /append s "subwf" /call taboperand s /append s name "+2, w" /write s /set s "" ;movwf dest+0 /call tabopcode s /append s "movwf" /call taboperand s /append s dest "+0" /write s /set s "" ;movf put+1, w /call tabopcode s /append s "movf" /call taboperand s /append s name "+1, w" /call startcomm s /append s "make GET-PUT high byte" /write s /set s "" ;subwfb get+1, w /call tabopcode s /append s "subwfb" /call taboperand s /append s name "+3, w" /write s /set s "" ;movwf dest+1 /call tabopcode s /append s "movwf" /call taboperand s /append s dest "+1" /write s /set s "" ;decf dest+0 /call tabopcode s /append s "decf" /call taboperand s /append s dest "+0" /call startcomm s /append s "-1 to make empty slots, without wrapping" /write s /set s "" ;movlw 0 /call tabopcode s /append s "movlw" /call taboperand s /append s "0" /write s /set s "" ;subwfb dest+1 /call tabopcode s /append s "subwfb" /call taboperand s /append s dest "+1" /write s // // DEST contains the number of empty slots without buffer wrapping taken // into account. // /set s "" ;bnn fifn /call tabopcode s /append s "bnn" /call taboperand s /append s [qstr [lab fifn]] /call startcomm s /append s "no buffer wrap to account for ?" /write s // // DEST contains GET-PUT-1, but there is a buffer wrap between PUT and // GET. Add the buffer wrap size to make the actual number of empty // slots in the FIFO. // /set s "" ;movlw low bufn /call tabopcode s /append s "movlw" /call taboperand s /append s [and bufn 16#FF] /call startcomm s /append s "add buffer length into low byte" /write s /set s "" ;addwf dest+0 /call tabopcode s /append s "addwf" /call taboperand s /append s dest "+0" /write s /set s "" ;movlw high bufn /call tabopcode s /append s "movlw" /call taboperand s /append s [shiftr bufn 8] /call startcomm s /append s "add buffer length to high byte" /write s /set s "" ;addwfc dest+1 /call tabopcode s /append s "addwfc" /call taboperand s /append s dest "+1" /write s /set s [qstr [lab fifn]] ;final result in DEST /call startcomm s /append s "empty slots in FIFO " name " is in 16 bit var " [ucase dest] /write s /endmac ;******************************************************************************* ; ; Macro FIFO_BR_FULL name, n, label [, temp] ; ; Branch to LABEL when the FIFO NAME has N or less bytes of room left. TEMP ; is temporary memory for the macro to use internally. It is required when ; the FIFO size exceeds 255 slots. Up to two bytes at TEMP may be trashed. ; ; The start of the FIFO and TEMP (if used) must be directly accessible with ; the current bank setting. ; /macro fifo_br_full /var local name string = [qstr [arg 1]] ;FIFO name /var local n integer = [vnl [arg 2]] ;full if this many or less empty slots /var local lab string = [qstr [arg 3]] ;label to jump to on FIFO full /var local temp string = [qstr [arg 4]] ;start of temporary scratch vars /var local size integer = fifo_[chars name]_size ;user-visible FIFO size /var local big bool = fifo_[chars name]_big ;PUT and GET are two bytes /var local bufn integer = fifo_[chars name]_bufn ;size of the data buffer /var local s string ;for assembling output line // // Handle case where the number of empty FIFO slots fits into one unsigned // byte. // /if [not big] then fifo_n_empty [chars name] ;get FIFO room into W /set s "" ;sublw n /call tabopcode s /append s "sublw" /call taboperand s /append s n /call startcomm s /append s "compare to threshold" /write s /set s "" ;jmp_wle lab /call tabopcode s /append s "jmp_wle" /call taboperand s /append s lab /call startcomm s /append s "too little room in the FIFO ?" /write s /write /quitmac /endif // // Two bytes are required to express the number of FIFO slots. // /if [= [slen temp] 0] then /show " TEMP parameter to FIFO_BR_FULL required but not supplied." error "No TEMP" end /stop /endif fifo_n_empty [chars name], [chars temp] ;get FIFO room into TEMP /set s "" ;movlw low n+1 /call tabopcode s /append s "movlw" /call taboperand s /append s [and [+ n 1] 16#FF] /call startcomm s /append s "compare to threshold low byte" /write s /set s "" ;subwf temp+0 /call tabopcode s /append s "subwf" /call taboperand s /append s temp "+0" /write s /set s "" ;movlw high n+1 /call tabopcode s /append s "movlw" /call taboperand s /append s [shiftr [+ n 1] 8] /call startcomm s /append s "compare to threshold high byte" /write s /set s "" ;subwfb temp+1 /call tabopcode s /append s "subwfb" /call taboperand s /append s temp "+1" /write s /set s "" ;bn lab /call tabopcode s /append s "bn" /call taboperand s /append s lab /call startcomm s /append s "too little room in FIFO ?" /write s /write /endmac ;******************************************************************************* ; ; Macro FIFO_BR_EMPTY name, n, label [, temp] ; ; Branch to LABEL when the FIFO NAME has N or less data bytes in it. TEMP is ; temporary memory for the macro to use internally. It is required when the ; FIFO size exceeds 255 slots. Up to two bytes at TEMP may be trashed. ; ; The start of the FIFO and TEMP (if used) must be directly accessible with ; the current bank setting. ; /macro fifo_br_empty /var local name string = [qstr [arg 1]] ;FIFO name /var local n integer = [vnl [arg 2]] ;empty if this many data bytes or less /var local lab string = [qstr [arg 3]] ;label to jump to on FIFO empty /var local temp string = [qstr [arg 4]] ;start of temporary scratch vars /var local size integer = fifo_[chars name]_size ;user-visible FIFO size /var local big bool = fifo_[chars name]_big ;PUT and GET are two bytes /var local bufn integer = fifo_[chars name]_bufn ;size of the data buffer /var local s string ;for assembling output line // // Handle case where the number of data bytes in the FIFO fits into one // unsigned byte. // /if [not big] then fifo_n_full [chars name] ;get the number of data bytes into W /set s "" ;sublw n /call tabopcode s /append s "sublw" /call taboperand s /append s n /call startcomm s /append s "compare to threshold" /write s /set s "" ;jmp_wle lab /call tabopcode s /append s "jmp_wle" /call taboperand s /append s lab /call startcomm s /append s "too few bytes in the FIFO ?" /write s /write /quitmac /endif // // Two bytes are required to express the number of bytes in the FIFO. // /if [= [slen temp] 0] then /show " TEMP parameter to FIFO_BR_EMPTY required but not supplied." error "No TEMP" end /stop /endif fifo_n_full [chars name], [chars temp] ;get number of data bytes into TEMP /set s "" ;movlw low n+1 /call tabopcode s /append s "movlw" /call taboperand s /append s [and [+ n 1] 16#FF] /call startcomm s /append s "compare to threshold low byte" /write s /set s "" ;subwf temp+0 /call tabopcode s /append s "subwf" /call taboperand s /append s temp "+0" /write s /set s "" ;movlw high n+1 /call tabopcode s /append s "movlw" /call taboperand s /append s [shiftr [+ n 1] 8] /call startcomm s /append s "compare to threshold high byte" /write s /set s "" ;subwfb temp+1 /call tabopcode s /append s "subwfb" /call taboperand s /append s temp "+1" /write s /set s "" ;bn lab /call tabopcode s /append s "bn" /call taboperand s /append s lab /call startcomm s /append s "too few bytes in the FIFO ?" /write s /write /endmac ;******************************************************************************* ; ; Internal macro FIFO_INDEX_INC name, ofs ; ; Increment a buffer index for FIFO NAME. OFS is the offset of the first byte ; of the index from the start of the FIFO data structure. The index will be ; incremented by 1, except if that would cause it to point past the end of the ; buffer. In that case, the index is wrapped back to 0. ; ; Interrupts are temporarily disabled during the write to any multi-byte ; index. ; ; This macro is not intended for application use. It is used internally in ; other FIFO_xxx macros. ; /macro fifo_index_inc /var local name string = [qstr [arg 1]] ;FIFO name /var local ofs integer = [vnl [arg 2]] ;offset of the index from FIFO start /var local size integer = fifo_[chars name]_size ;user-visible FIFO size /var local big bool = fifo_[chars name]_big ;PUT and GET are two bytes /var local bufn integer = fifo_[chars name]_bufn ;size of the data buffer /var local s string ;for assembling output line // // Handle case of index is one byte. // /if [not big] then /set s "" ;incf name+ofs, w /call tabopcode s /append s "incf" /call taboperand s /append s name "+" ofs ", w" /call startcomm s /append s "make candidate incremented index in W" /write s /set s "" ;sublw bufn-1 /call tabopcode s /append s "sublw" /call taboperand s /append s [- bufn 1] /call startcomm s /append s "compare to last valid buffer index" /write s /set s "" ;jmp_wle nwrap /call tabopcode s /append s "jmp_wle" /call taboperand s /append s [qstr [lab nwrap]] /call startcomm s /append s "still valid, don't wrap back to start" /write s /set s "" ;clrf name+ofs /call tabopcode s /append s "clrf" /call taboperand s /append s name "+" ofs /call startcomm s /append s "wrap index back to start of buffer" /write s /set s "" ;jump done /call tabopcode s /append s "jump" /call taboperand s /append s [qstr [lab done]] /write s /set s [qstr [lab nwrap]] ;nwrap: /call startcomm s /append s "don't wrap back to start of buffer" /write s /set s "" ;incf name+ofs /call tabopcode s /append s "incf" /call taboperand s /append s name "+" ofs /call startcomm s /append s "update the buffer index" /write s /set s [qstr [lab done]] ;done: /call startcomm s /append s "done updating FIFO buffer index" /write s /quitmac /endif // // The index is two bytes wide. // // // Compare the index to the last value that does not require wrapping. // /set s "" ;movlw low bufn-1 /call tabopcode s /append s "movlw" /call taboperand s /append s [and [- bufn 1] 16#FF] /call startcomm s /append s "compare to wrap level low byte" /write s /set s "" ;subwf name+ofs+0, w /call tabopcode s /append s "subwf" /call taboperand s /append s name "+" ofs ", w" /write s /set s "" ;movlw high bufn-1 /call tabopcode s /append s "movlw" /call taboperand s /append s [shiftr [- bufn 1] 8] /call startcomm s /append s "compare to wrap level high byte" /write s /set s "" ;subwfb name+ofs+1, w /call tabopcode s /append s "subwfb" /call taboperand s /append s name "+" [+ ofs 1] ", w" /write s /set s "" ;jmp_wle wrap /call tabopcode s /append s "jmp_wle" /call taboperand s /append s [qstr [lab wrap]] /call startcomm s /append s "need to wrap back to start of buffer ?" /write s // // Increment the index normally without wrapping. The increment is done // so that the two index bytes are changed on consecutive instructions. // This minimizes the time that interrupts are disabled for. // /set s "" ;incf name+ofs+0, w /call tabopcode s /append s "incf" /call taboperand s /append s name "+" ofs ", w" /call startcomm s /append s "set C bit from incrementing low byte" /write s /set s "" ;movlw 0 /call tabopcode s /append s "movlw" /call taboperand s /append s "0" /write s /if [not no_intr_disable] then intr_off ;temp disable interrupts /endif /set s "" ;addwfc name+ofs+1 /call tabopcode s /append s "addwfc" /call taboperand s /append s name "+" [+ ofs 1] /call startcomm s /append s "propagate carry into high byte" /write s /set s "" ;incf name+ofs+0 /call tabopcode s /append s "incf" /call taboperand s /append s name "+" ofs /call startcomm s /append s "update the low byte" /write s /if [not no_intr_disable] then intr_on ;re-enable interrupts /endif /set s "" ;jump done /call tabopcode s /append s "jump" /call taboperand s /append s [qstr [lab done]] /write s // // Wrap the index back to the start of the buffer. // /set s [qstr [lab wrap]] ;wrap: /call startcomm s /append s "wrap buffer index back to start of buffer" /write s /if [not no_intr_disable] then intr_off ;temp disable interrupts /endif /set s "" ;clrf name+ofs+0 /call tabopcode s /append s "clrf" /call taboperand s /append s name "+" ofs /write s /set s "" ;clrf name+ofs+1 /call tabopcode s /append s "clrf" /call taboperand s /append s name "+" [+ ofs 1] /write s /if [not no_intr_disable] then intr_on ;re-enable interrupts /endif // // Back to common code after no-wrap and wrap cases. // /set s [qstr [lab done]] ;done: /call startcomm s /append s "done updating FIFO buffer index" /write s /endmac ;******************************************************************************* ; ; Macro FIFO_PUT name, data ; ; Write the byte in DATA to the FIFO NAME. It is the caller's responsibility ; to ensure that the FIFO has room for the new byte. All manner of ; destruction can result from writing to a full FIFO. ; ; The bank must be set for access to the start of the FIFO. DATA can be ; anywhere in RAM. ; ; FSR0 is trashed. ; /macro fifo_put /var local name string = [qstr [arg 1]] ;FIFO name /var local data string = [qstr [arg 2]] ;variable to take the data byte from /var local size integer = fifo_[chars name]_size ;user-visible FIFO size /var local big bool = fifo_[chars name]_big ;PUT and GET are two bytes /var local bufn integer = fifo_[chars name]_bufn ;size of the data buffer /var local s string ;for assembling output line /write // // Handle case where 8 bit indicies are used. // // Write the data byte into the buffer at the slot indexed by PUT. // /if [not big] then /set s "" ;lfsr 0, buf /call tabopcode s /append s "lfsr" /call taboperand s /append s "0, " name "+2" /call startcomm s /append s "point to start of FIFO data buffer" /write s /set s "" ;movf put, w /call tabopcode s /append s "movf" /call taboperand s /append s name "+0, w" /call startcomm s /append s "add buffer PUT index to the pointer" /write s /set s "" ;addwf fsr0l /call tabopcode s /append s "addwf" /call taboperand s /append s "fsr0l" /write s /set s "" ;movlw 0 /call tabopcode s /append s "movlw" /call taboperand s /append s "0" /write s /set s "" ;addwfc fsr0h /call tabopcode s /append s "addwfc" /call taboperand s /append s "fsr0h" /write s /set s "" ;movff data, indf0 /call tabopcode s /append s "movff" /call taboperand s /append s data ", indf0" /call startcomm s /append s "stuff the data byte into the buffer" /write s fifo_index_inc [chars name], 0 ;update the buffer PUT index /write /quitmac /endif // // The buffer indicies are 16 bits wide. // /set s "" ;lfsr 0, buf /call tabopcode s /append s "lfsr" /call taboperand s /append s "0, " name "+4" /call startcomm s /append s "init pointer to start of data buffer" /write s /set s "" ;movf put+0, w /call tabopcode s /append s "movf" /call taboperand s /append s name "+0, w" /call startcomm s /append s "add buffer PUT index to the pointer" /write s /set s "" ;addwf fsr0l /call tabopcode s /append s "addwf" /call taboperand s /append s "fsr0l" /write s /set s "" ;movf put+1, w /call tabopcode s /append s "movf" /call taboperand s /append s name "+1, w" /write s /set s "" ;addwfc fsr0h /call tabopcode s /append s "addwfc" /call taboperand s /append s "fsr0h" /write s /set s "" ;movff data, indf0 /call tabopcode s /append s "movff" /call taboperand s /append s data ", indf0" /call startcomm s /append s "stuff the data byte into the buffer" /write s fifo_index_inc [chars name], 0 ;update the buffer PUT index /write /endmac ;******************************************************************************* ; ; Macro FIFO_GET name, data ; ; Get the next byte from the FIFO NAME into DATA. It is the caller's ; responsibility to ensure that the FIFO is not empty. All manner of ; destruction can result from reading from an empty FIFO. ; ; The bank must be set for access to the start of the FIFO. DATA can be ; anywhere in RAM. ; ; FSR0 is trashed. ; /macro fifo_get /var local name string = [qstr [arg 1]] ;FIFO name /var local data string = [qstr [arg 2]] ;variable to take the data byte from /var local size integer = fifo_[chars name]_size ;user-visible FIFO size /var local big bool = fifo_[chars name]_big ;PUT and GET are two bytes /var local bufn integer = fifo_[chars name]_bufn ;size of the data buffer /var local s string ;for assembling output line /write // // Handle case where 8 bit indicies are used. // // Read the from the FIFO slot indexed by GET. // /if [not big] then /set s "" ;lfsr 0, buf /call tabopcode s /append s "lfsr" /call taboperand s /append s "0, " name "+2" /call startcomm s /append s "point to start of FIFO data buffer" /write s /set s "" ;movf get, w /call tabopcode s /append s "movf" /call taboperand s /append s name "+1, w" /call startcomm s /append s "add buffer GET index to the pointer" /write s /set s "" ;addwf fsr0l /call tabopcode s /append s "addwf" /call taboperand s /append s "fsr0l" /write s /set s "" ;movlw 0 /call tabopcode s /append s "movlw" /call taboperand s /append s "0" /write s /set s "" ;addwfc fsr0h /call tabopcode s /append s "addwfc" /call taboperand s /append s "fsr0h" /write s /set s "" ;movff indf0, data /call tabopcode s /append s "movff" /call taboperand s /append s "indf0, " data /call startcomm s /append s "read the byte from the FIFO buffer" /write s fifo_index_inc [chars name], 1 ;update the buffer GET index /write /quitmac /endif // // The buffer indicies are 16 bits wide. // /set s "" ;lfsr 0, buf /call tabopcode s /append s "lfsr" /call taboperand s /append s "0, " name "+4" /call startcomm s /append s "init pointer to start of data buffer" /write s /set s "" ;movf get+0, w /call tabopcode s /append s "movf" /call taboperand s /append s name "+2, w" /call startcomm s /append s "add buffer GET index to the pointer" /write s /set s "" ;addwf fsr0l /call tabopcode s /append s "addwf" /call taboperand s /append s "fsr0l" /write s /set s "" ;movf get+1, w /call tabopcode s /append s "movf" /call taboperand s /append s name "+3, w" /write s /set s "" ;addwfc fsr0h /call tabopcode s /append s "addwfc" /call taboperand s /append s "fsr0h" /write s /set s "" ;movff indf0, data /call tabopcode s /append s "movff" /call taboperand s /append s "indf0, " data /call startcomm s /append s "read the byte from the FIFO buffer" /write s fifo_index_inc [chars name], 2 ;update the buffer GET index /write /endmac /endif ;end of new FIFOs selected ;******************************************************************************* ;******************************************************************************* ; ; Dispatch tables. ; ;******************************************************************************* ; ; Macro DISPATCH name ; ; Jump to the address selected from a dispatch table. ; ; REG0 contains the 0-N table entry number. If REG0 contains 0, this macro ; will jump to the address in the first table entry, if 1 it will jump to the ; address in the second table entry, etc. If REG0 indicates an entry past the ; end of the table, then execution falls thru to the end of this macro. ; ; NAME is the name of the table as defined with the DSP_START and DSP_END ; macros. ; ; The table must be defined with DSP_START, DSP_ENTRY, and DSP_END macros. ; These and the DISPATCH macro communicate with each other via various ; implicitly defined symbols. ; ; The following symbols are assumed to be defined. These are normally defined ; automatically by the DSP_START and DSP_END macros. ; ; <name> - Address of the start of the table. ; ; <name>0nent - Number of entries in the table. ; ; All the REGn general registers are preserved to the target routine. ; dispatch macro name local noent ; ; 18 family processors. ; if fam_18 ; ; Check for the table index in REG0 is within range. ; If not fall thru this macro. ; movf reg0, w ;get 0-N table index sublw name#v(0)nent-1 ;compare to last valid index skip_wle ;index is within bounds ? jump noent ;no, fall thru this macro ; ; Make the table byte offset for the selected entry in REG1:REG0. ; This is either 2x or 3x the table index depending on whether 2 byte ; or 3 byte program memory addresses are in use. ; pushregs regf0 | regf1 ;temp save registers that will be trashed clrf reg1 ;1x index now in REG1:REG0 if progadrb > 2 movf reg0, w ;save 1x index in W endif bcf status, c ;set 0 bit to shift in rlcf reg0 ;make 2x index in REG1:REG0 rlcf reg1 if progadrb > 2 addwf reg0 ;add 1x to make 3x index in REG1:REG0 movlw 0 addwfc reg1 endif ; ; Set TBLPTR to the first (least significant) byte of the target ; address. ; movlw low (name) addwf reg0, w movwf tblptrl movlw high (name) addwfc reg1, w movwf tblptrh if progadrb <= 2 clrf tblptru else movlw upper (name) skip_ncarr addlw 1 movwf tblptru endif popregs regf0 | regf1 ;restore REG0 and REG1 saved earlier ; ; Jump to the address starting at where TBLPTR is pointing. If the ; low bit of the address is set (invalid execution address), then the ; table entry is unimplemented and this macro falls thru the same as ; if the index was past the end of the table. ; tblrd*+ ;get jump address low byte into TABLAT btfsc tablat, 0 ;this is a valid execution address ? jump noent ;no, fall thru this macro movf tablat, w ;save low byte of jump address in W tblrd*+ ;set middle byte of jump address movff tablat, pclath if progadrb <= 2 clrf pclatu ;16 bit addressing, high byte always 0 else tblrd* ;24 bit addressing, set jump address high byte movff tablat, pclatu endif movwf pcl ;jump to the address from the table entry noent unbank ;jump to here on no matching table entry exitm endif ;end of PIC 18 family implementation error "Macro DISPATCH not implemented for this processor" endm ;******************************************************************************* ; ; Macro DSP_START name ; ; Start a dispatch table. Table entries must be created with the DSP_ENTRY ; macro, and the table ended with the DSP_END macro. The DISPATCH macro will ; jump to a selected table entry. ; ; NAME will be defined as the starting address of the table, and must be the ; name passed to the DISPATCH macro. ; ; On a PIC 18, this table must be defined in a CODE_PACK section, not an ; ordinary CODE section. ; dsp_start macro name name ;define the label for the start of the table next_entry set 0 ;init 0-N number of next table entry endm ;******************************************************************************* ; ; Macro DSP_ENTRY index, adr ; ; Define one dispatch table entry. INDEX is the 0-N sequential index for this ; table entry, and ADR is the address of the routine to dispatch to for this ; entry. ; ; Entries must be defined in ascending index order. An error is generated if ; this is not the case. A table entry is created for all values of INDEX up ; to and including the last one. Gaps are filled with invalid addresses. ; These are detected in the DISPATCH macro and treated the same as ; unimplemented table entries past the end of the table. ; dsp_entry macro index, adr ; ; 18 family processors. Table entries are either 2 or 3 bytes long, depending ; on whether 2 or 3 byte program memory addresses are in use. ; if fam_18 if (index) < next_entry error Table entry #v(index) not in ascending order. endif while next_entry != (index) db 1 ;set low bit to indicate invalid address db 0 if progadrb > 2 db 0 endif next_entry set next_entry + 1 endw db low (adr) db high (adr) if progadrb > 2 db upper (adr) endif next_entry set next_entry + 1 ;update expected index for next time exitm endif ;end of PIC 18 family implementation error "Macro DSP_ENTRY is not implemented for this processor" endm ;******************************************************************************* ; ; Macro DSP_EXTRN index, adr ; ; Like DSP_ENTRY, except that ADR is external to the module this macro is in. ; dsp_extrn macro index, adr extern adr dsp_entry index, adr endm ;******************************************************************************* ; ; Macro DSP_END name ; ; End the definition of a dispatch table started with DSP_START and filled ; in with DSP_ENTRY. NAME must be the same name that was passed to DSP_START. ; dsp_end macro name name#v(0)nent equ next_entry ;define symbol for number of entries in this table if name#v(0)nent == 0 error Empty dispatch table, must have at least one entry. endif endm //////////////////////////////////////////////////////////////////////////////// // // Function CmdRef ent // // Returns TRUE iff the entry point ENT is referenced as a command routine. // The ENT parameter is a sequence of characters, not a string. // // Commands are defined by variables or constants named "cmd_xxx". A // additional name can be inserted after "cmd_". This is necessary, for // example, when there are multiple command processors with different command // sets. The constant or variable SUFF denotes this additional part of the // CMD_ symbol names, if present and not set to the empty string. In that // case, the variable or constants defining commands are named "cmd<suff>_xxx" // where <suff> indicates the contents of the SUFF variable or constant. // /function CmdRef /var local ent string = [qstr [arg 1]] //entry point name checking for /var local opc integer //command opcode /var local entpnt string //command routine entry point name /var local retval bool //function return value /var local ii integer //scratch integer /var local tk string //scratch token /if [not [exist "suff:vcon"]] then /var local suff string /endif /set tk [str "cmd" suff "_"] //starting fixed part of CMD_xxx names /set ii [slen tk] //length of fixed part of CMD_xxx names /loop symbols sym vcon /var local sy string /set sy [sym sym name] /if [<= [slen sy] ii] then /repeat /endif /if [<> [substr 1 ii sy] tk] then /repeat /endif /call parse_cmd [chars sym] [chars suff] //get entry point of this command /if [= entpnt ent] then /set retval True /quit /endif /endloop /funcval retval /endfunc //////////////////////////////////////////////////////////////////////////////// // // Function Command entpnt // // Returns TRUE if the entry point ENTPNT is referenced as a command routine, // otherwise FALSE. When returning TRUE, the entry point of the command is // defined. ENTPNT is a sequence of characters, not a string. // /function Command /if [CmdRef [arg 1]] /then /funcval TRUE glbent [arg 1] /else /funcval FALSE /endif /endfunc ;******************************************************************************* ;******************************************************************************* ; ; Support for the multi-tasking system. ; ;******************************************************************************* ; ; Macro TASK_CREATE runadr, stackadr ; ; Create a new task, assuming the Embed Inc multi-tasking system. This macro ; is just a wrapper around TASK_NEW. It loads registers with the parameters ; for TASK_NEW, then calls it. RUNADR is the execution start address of the ; new task, and STACKADR is the data stack start address for the new task. ; ; REG0 - REG4 are trashed. ; task_create macro runadr, stackadr if fam_18 ;18 family devices movlw low stackadr ;pass stack address in REG1:REG0 movwf reg0 movlw high stackadr movwf reg1 movlw low runadr ;pass run address in (REG4):REG3:REG2 movwf reg2 movlw high runadr movwf reg3 if progadrb > 2 movlw upper runadr movwf reg4 endif gcall task_new ;create the new task exitm endif ;end of PIC 18 family version error "TASK_CREATE macro in STD.INS.ASPIC not implemented for this processor" endm ;******************************************************************************* ; ; Macro SKIP_NYIELDNOW ; ; Skip the next instruction if no condition exists such that the current task ; in this multi-tasking system should yield now. This macro is used by ; CHECK_YIELD (below) to determine whether a yield must be performed now. ; SKIP_NYIELDNOW is a separate preprocessor macro so that it can be easily ; customized. ; ; The default version defined here assumes the existance of the global ; FLAG_YIELDNOW. A yield must be performed when this flag is set. This flag ; would typically be set by the interrupt routine based on elapsed time or a ; particular hardware event. ; /macro skip_nyieldnow /if [exist -1 arg] then /show " Dumb place for a label, moron." error SKIP_NYIELDNOW label end /stop /endif extern_flags ;declare global flag bits EXTERN dbankif gbankadr btfsc flag_yieldnow /endmac ;******************************************************************************* ; ; Macro CHECK_YIELD ; ; Conditionally perform a task yield. This check is short and fast, and can ; therefore be performed often with little penalty. For example, this check ; can be performed each iteration of a inner loop that could possibly take ; longer than a single task should run. Calling TASK_YIELD would make the ; loop much slower due to many unnecessary task swaps. ; ; The existance of a condition that is set asynchronously to the current task ; is assumed. This could be set by the interrupt routine based on elapsed ; time, or a hardware condition, for example. The check for the condition is ; isolated in macro SKIP_NYIELDNOW (above) to allow for easy customization of ; the condition. The default condition is FLAG_YIELDNOW set. ; ; All the REGn general registers are preserved. This requires enough data ; stack space for all the general registers plus the little overhead required ; by TASK_YIELD. ; ; This is a preprocessor macro so that it can easily be redefined as needed. ; The decision as to whether a yield is required now is abstracted into the ; separate macro SKIP_NYIELDNOW (above), since most customization only use a ; different condition and don't need to change the mechanics as encoded in ; this macro. ; /macro check_yield /if [exist -1 arg] then /show " Dumb place for a label, moron." error CHECK_YIELD label end /stop /endif extern task_yield_save skip_nyieldnow ;don't need to yield now ? call task_yield_save ;perform the yield, perserve all registers unbank /endmac ;******************************************************************************* ; ; Macro LEAVECHECK ; ; Like LEAVEREST except that it runs CHECK_YIELD after restoring the registers ; saved on entry to the subroutine. If CHECK_YIELD does perform a yield, all ; the registers will be saved on the stack. It therefore takes less stack ; space to perform the yield after removing anything this subroutine ; temporarily pushed onto the stack. ; /macro leavecheck /if [exist -1 arg] then /show " Dumb place for a label, moron." error LEAVECHECK label end /stop /endif popregs savedregs ;restore registers saved on entry to routine check_yield ;let other tasks run if needed return ;return from the subroutine unbank ;invalidate bank assumptions in following source code /endmac ;******************************************************************************* ;******************************************************************************* ; ; Support for writing constants to program memory. ; //////////////////////////////////////////////////////////////////////////////// // // Subroutine PBYTE_INIT // // Initialize for writing a block of data to a CODE_PACK section of program // memory. The state is set to these initial values to this at the beginning // of normal code and after PBYTE_FINISH. This subroutine provides a positive // mechanism to reset to the initialized state at any time. // /subroutine pbyte_init /var exist pbyte_str string ;output line so far /var exist pbyte_nbytes integer ;number of data bytes on the current line /var exist pbyte_full bool ;output line is full /var exist pbyte_offset integer ;number of bytes written /var exist pbyte_global bool ;make labels global /var exist pbyte_memlab bool ;write labels as memory addresses /var exist pbyte_saveofs bool ;save label names and their offsets /set pbyte_str "" ;reset the current output line to empty /set pbyte_nbytes 0 /set pbyte_full false /set pbyte_offset 0 ;reset number of bytes written to prog memory /set pbyte_global false ;don't make labels global /set pbyte_memlab true ;label values will be the memory address /set pbyte_saveofs false ;don't save label names and offsets /endsub ;******************************************************************************* ; ; Macro [label] PBYTE val [, comment] ; ; Write to the next program memory byte in a CODE_PACK section. Multiple ; bytes are written on a single MPASM output line. This macro may buffer data ; until the PBYTE_FINISH subroutine is called. The byte value is in the low 8 ; bits of VAL. ; ; The preprocessor integer PBYTE_OFFSET is incremented by 1. This value is ; not used by PBYTE otherwise. It is intended to allow callers to track the ; amount of memory that was written from some starting point. PBYTE_OFFSET ; can be set to a arbitrary value by the caller. By default, PBYTE_OFFSET is ; initialized to 0 before the first byte, then incremented for each byte ; written. It therefore provides a count of the number of bytes that have ; been written with this mechanism. ; /macro pbyte /var exist pbyte_str string = "" ;MPASM line so far /var exist pbyte_nbytes integer = 0 ;number of data bytes on the current line /var exist pbyte_full bool = false ;indicates when the output line is full /var exist pbyte_offset integer = 0 ;offset of next byte from start /var exist pbyte_global bool = false ;make labels global /var exist pbyte_memlab bool = true ;write labels as memory addresses /var exist pbyte_saveofs bool = false ;save label names and their nvol offsets /var exist pbyte_labeln integer = 0 ;number of labels names and offsets saved /var local val integer = [and [arg 1] 16#FF] ;get the byte value /set pbyte_global [and pbyte_global pbyte_memlab] ;can't global if not define /if [exist -1 arg] then ;label provided ? /call pbyte_finish ;ensure this byte starts on a new line /write /if pbyte_global then ;label needs to be declared global ? global [arg -1] /endif /if pbyte_saveofs then ;save name and offset of this label ? /write "; " [qstr [arg -1]] " = " pbyte_offset /set pbyte_labeln [+ pbyte_labeln 1] ;count one more label and offset saved /const pbyte_labofs[chars pbyte_labeln] string = [str [qstr [arg -1]] " " pbyte_offset] /endif /endif /if [exist 2 arg] then ;comment supplied ? /call pbyte_finish ;ensure this byte starts on a new line /endif /if pbyte_full then ;existing line is full ? /call pbyte_finish ;write the previously completed line /endif /if [= pbyte_nbytes 0] /then ;starting a newline /if pbyte_memlab /then ;define labels in program memory /set pbyte_str [qstr [arg -1]] ;init the line with the label, if any /else ;don't define labels in program memory /set pbyte_str "" /endif /call tabto pbyte_str 10 ;go to start of opcode column /set pbyte_str [str pbyte_str "db"] /call tabto pbyte_str 18 ;go to start of operand column /else ;adding to end of existing line /set pbyte_str [str pbyte_str ","] /endif /set pbyte_str [str pbyte_str "h'" [int val "fw 2 lz base 16 usin"] "'"] /set pbyte_nbytes [+ pbyte_nbytes 1] /set pbyte_offset [+ pbyte_offset 1] /if [exist 2 arg] then ;comment supplied ? /call pbyte_finish [str [arg 2]] /endif /set pbyte_full [>= pbyte_nbytes 8] ;the output line is now full ? /endmac //////////////////////////////////////////////////////////////////////////////// // // Subroutine PBYTE_FINISH [comment] // // Write out any unwritten data that may have been buffered by PBYTE. The // optional call parameter will be written as a comment to the line, if the // parameter is supplied. If not, no comment will be written. The COMMENT // parameter must be convertable to a preprocessor string. It can therefore // be a numeric value, a string variable, or a literal string enclosed in // quotes. // /subroutine pbyte_finish /if [not [exist "pbyte_nbytes"]] then ;PBYTE never called ? /return /endif /if [<= pbyte_nbytes 0] then ;no buffered data to write ? /return /endif /if [exist 1 arg] then ;comment supplied ? /call startcomm pbyte_str /set pbyte_str [str pbyte_str [arg 1]] /endif /write pbyte_str ;write the MPASM line /set pbyte_nbytes 0 ;reset to no unwritten buffered bytes /set pbyte_str "" ;reset the buffered output line to empty /endsub ;******************************************************************************* ; ; Macro [label] PWORD val [, comment] ; ; Write a 16 bit value as the next two bytes of program memory. This macro ; must be run in a CODE_PACK section. VAL must be convertable to a integer ; value by the preprocesor. The low 16 bits of the integer will be written. ; ; If the optional comment string is supplied, then the word value will be ; written on a single line with that end of line comment. ; /macro pword /var local val integer = [and [arg 1] 16#FFFF] /if [exist 2 arg] then ;comment supplied ? /call pbyte_finish ;write out any previous buffered data /endif [arg -1] pbyte [shiftr val 0] ;write the low byte pbyte [shiftr val 8] ;write the high byte /if [exist 2 arg] then ;comment supplied ? /call pbyte_finish [str [arg 2]] /endif /endmac ;******************************************************************************* ; ; Macro [label] PWORD24 val [, comment] ; ; Write a 24 bit value as the next three bytes of program memory. This macro ; must be run in a CODE_PACK section. VAL must be convertable to a integer ; value by the preprocesor. The low 24 bits of the integer will be written. ; ; If the optional comment string is supplied, then the word value will be ; written on a single line with that end of line comment. ; /macro pword24 /var local val integer = [and [arg 1] 16#FFFFFF] /if [exist 2 arg] then ;comment supplied ? /call pbyte_finish ;write out any previous buffered data /endif [arg -1] pbyte [shiftr val 0] ;write the low byte pbyte [shiftr val 8] pbyte [shiftr val 16] ;write the high byte /if [exist 2 arg] then ;comment supplied ? /call pbyte_finish [str [arg 2]] /endif /endmac ;******************************************************************************* ; ; Macro [label] PWORD32 val [, comment] ; ; Write a 32 bit value as the next four bytes of program memory. This macro ; must be run in a CODE_PACK section. VAL must be convertable to a integer ; value by the preprocesor. The low 32 bits of the integer will be written. ; ; If the optional comment string is supplied, then the word value will be ; written on a single line with that end of line comment. ; /macro pword32 /var local val integer = [and [arg 1] 16#FFFFFFFF] /if [exist 2 arg] then ;comment supplied ? /call pbyte_finish ;write out any previous buffered data /endif [arg -1] pbyte [shiftr val 0] ;write the low byte pbyte [shiftr val 8] pbyte [shiftr val 16] pbyte [shiftr val 24] ;write the high byte /if [exist 2 arg] then ;comment supplied ? /call pbyte_finish [str [arg 2]] /endif /endmac ;******************************************************************************* ; ; Macro [label] FP48P fpval ; ; Write 6 consecutive bytes to program memory. These will be the 48 bit ; floating point representation of FPVAL in least to most significant byte ; order. The FPVAL parameter must be interpretable as a floating point value ; by the preprocessor. ; ; This macro must be used in a CODE_PACK section to produce the correct ; result. ; /macro fp48p /var local fpval real = [arg 1] /var local s string /call fp48_make [chars fpval] ;set FP48_EXP and FP48_MANT /call pbyte_finish ;write out any previously buffered data [arg -1] pbyte [shiftr fp48_mant 0] pbyte [shiftr fp48_mant 8] pbyte [shiftr fp48_mant 16] pbyte [shiftr fp48_mant 24] pbyte [shiftr fp48_exp 0] pbyte [shiftr fp48_exp 8] /call pbyte_finish [str "FP48 " [fp fpval "sig 7 mnr 1 mxr 8 eng"]] /endmac ;******************************************************************************* ; ; Macro [label] PGRAWSTR "..." ; ; Write a raw string constant into a CODE_PACK program memory section. Only ; the string bytes will be written. ; ; If a label is supplied, it will be defined as the address of the first ; program memory byte of the string. The string value will be written as a ; comment on the label line, which will be separate from the program memory ; words containing the string data. ; /macro pgrawstr /var exist pbyte_global bool = false ;makes labels global /var local s string = [arg 1] /var local ln string // // Write the label line, if a label was provided. If so, this will include // a comment that shows the value of the string. // /if [exist -1 arg] then ;label was provided ? /call pbyte_finish ;make sure all previously buffered data is written /if pbyte_global then global [arg -1] /endif /set ln [qstr [arg -1]] ;init label line with label name /call startcomm ln ;start the end of line comment /set ln [str ln '"'] /loop with ind from 1 to [slen s] ;loop once for each string character /var local cc integer ;character code of current char /set cc [ccode [sindx ind s]] ;get character code of this character /if [and [>= cc 32] [<> cc 127]] then ;this is a printable character ? /set ln [str ln [char cc]] /endif /endloop /set ln [str ln '"'] /write ln ;write the label line to the output file /endif // // Write the string bytes. // /loop with ind from 1 to [slen s] ;once for each character in the string pbyte [ccode [sindx ind s]] ;write this character /endloop /call pbyte_finish ;write any partial program memory word /endmac ;******************************************************************************* ; ; Macro [label] PGSTRING "..." ; ; Write a counted string constant into a CODE_PACK program memory section. ; The first byte is the length byte, which is then followed by exactly that ; many string bytes. ; ; If a label is supplied, it will be defined as the address of the first ; program memory byte of the string. The string value will be written as a ; comment on the label line, which will be separate from the program memory ; words containing the string data. ; /macro pgstring /var exist pbyte_global bool = false ;makes labels global /var local s string = [arg 1] /var local ln string /var local ii integer /var local cc integer // // Write the label line, if a label was provided. If so, this will include // a comment that shows the value of the string. // /if [exist -1 arg] then ;label was provided ? /call pbyte_finish ;make sure all previously buffered data is written /if pbyte_global then global [arg -1] /endif /set ln [qstr [arg -1]] ;init label line with label name /call startcomm ln ;start the end of line comment /set ln [str ln '"'] /set ii 1 ;init index of next character to write /block ;back here each character of the string /if [> ii [slen s]] then /quit /endif /set cc [ccode [sindx ii s]] ;get character code of this character /if [and [>= cc 32] [<> cc 127]] then ;this is a printable character ? /set ln [str ln [char cc]] /endif /set ii [+ ii 1] ;advance to next character in the string /repeat /endblock /set ln [str ln '"'] /write ln ;write the label line to the output file /endif // // Write the string bytes. // pbyte [slen s] ;write the string length byte /set ii 1 ;init next string character to write /block ;back here each new character /if [> ii [slen s]] then /quit /endif pbyte [ccode [sindx ii s]] ;write this character /set ii [+ ii 1] /repeat /endblock /call pbyte_finish ;write any partial program memory word /endmac ;******************************************************************************* ; ; Macro [label] NSTRING maxsize, "..." [, comment] ; ; Write a counted string constant into a CODE_PACK program memory section. ; The first byte is the length byte, which is then followed by exactly that ; many string bytes. MAXSIZE string bytes are reserved, for a total of ; MAXSIZE+1 bytes (string plus the length byte). The supplied string is ; truncated to MAXSIZE characters if it is longer. The length byte is set to ; the actual length of the string as initialized, not the maximum possible ; value. Any unused string bytes are set to the erased value of FFh. ; /macro nstring /var local maxsz integer = [arg 1] ;max allowed string length /var local s string = [arg 2] ;the initial string value /var local wcomm bool ;still need to write comment /var local len integer ;length of the string to write /var local ii integer ;string index of next char to write /var local cc integer ;character code of current character /set len [min maxsz [slen s]] ;make string length [arg -1] pbyte [v len] ;write the length byte /set wcomm [exist 3 arg] ;TRUE if comment still needs to be written /set ii 1 ;init index of next string char to write /block ;back here each new character /if [> ii maxsz] then ;done defining all the bytes ? /quit /endif /if [<= ii [slen s]] /then ;still within the supplied string /set cc [ccode [sindx ii s]] ;get this character from the string /else ;past the end of the supplied string /set cc 16#FF ;get the memory erased value for this byte /endif pbyte [v cc] ;write this string byte /if [and wcomm pbyte_full] then ;write comment here ? /call pbyte_finish [arg 3] /set wcomm false ;don't try to write the comment again /endif /set ii [+ ii 1] ;advance to next string index /repeat ;back to do next string character /endblock /if wcomm /then ;still have unwritten comment /call pbyte_finish [arg 3] /else /call pbyte_finish /endif /endmac ;******************************************************************************* ; ; Macro [label] MNSTRING maxsize, "..." [, comment] ; ; Like NSTRING (above) except that the maximum string length is also written ; to the memory. The maximum string length byte is written first, then the ; actual string length for the string the memory is initialized with, then the ; string bytes. This macro will define a total of MAXSIZE+2 bytes. ; /macro mnstring /var local maxsz integer = [arg 1] ;max allowed string length /var local s string = [arg 2] ;the initial string value /var local wcomm bool ;still need to write comment /var local len integer ;length of the string to write /var local ii integer ;string index of next char to write /var local cc integer ;character code of current character /set len [min maxsz [slen s]] ;make string length /set wcomm [exist 3 arg] ;TRUE if comment still needs to be written [arg -1] pbyte [v maxsz] ;write the maximum length byte pbyte [v len] ;write the current length byte /set ii 1 ;init index of next string char to write /block ;back here each new character /if [> ii maxsz] then ;done defining all the bytes ? /quit /endif /if [<= ii [slen s]] /then ;still within the supplied string /set cc [ccode [sindx ii s]] ;get this character from the string /else ;past the end of the supplied string /set cc 16#FF ;get the memory erased value for this byte /endif pbyte [v cc] ;write this string byte /if [and wcomm pbyte_full] then ;write comment here ? /call pbyte_finish [arg 3] /set wcomm false ;don't try to write the comment again /endif /set ii [+ ii 1] ;advance to next string index /repeat ;back to do next string character /endblock /if wcomm /then ;still have unwritten comment /call pbyte_finish [arg 3] /else /call pbyte_finish /endif /endmac ;******************************************************************************* ;******************************************************************************* ; ; Mutual exclusion locks for use with the Embed multi-tasking system. ; ; Simple exclusion locks. ; ; These are either held by a specific task or not held at all. Macros that ; operate on these locks are named MUTEX_xxx: ; ; MUTEX_INIT name ; ; One-time initialization of the lock state. ; ; MUTEX_SKIP_LOCK name ; ; Skips the next instruction if the lock is being held by any task. ; ; MUTEX_SKIP_LOCK_US name ; ; Skips the next instruction if the lock is being held by this task. ; ; MUTEX_SKIP_AVAIL name ; ; Skips the next instruction if the lock is available (not held by any ; task). ; ; MUTEX_SKIP_NLOCK_US name ; ; Skips the next instruction if the lock is not being held by this task. ; ; MUTEX_LOCK name ; ; Acquire the lock, wait as necessary until it is released first. ; ; MUTEX_UNLOCK name ; ; Release the lock, if held by this task. ; //////////////////////////////////////////////////////////////////////////////// // // Macro MUTEX_INIT name // // Initialize the simple exclusion lock NAME. The variable LOCK_<name> must // be defined in the local register bank. This macro must be run once before // this mutual exclusion lock is used. The other MUTEX_xxx macros can operate // on this lock only after it has been initialized. // /macro mutex_init dbankif lbankadr setf lock_[arg 1] /endmac //////////////////////////////////////////////////////////////////////////////// // // Macro MUTEX_SKIP_LOCK name // // Skip the next instruction after this macro if the simple exclusion lock // NAME is held by any task (lock is not available). The bank will be set to // the local bank. // /macro mutex_skip_lock dbankif lbankadr btfsc lock_[arg 1], 7 ;skip if locked /endmac //////////////////////////////////////////////////////////////////////////////// // // Macro MUTEX_SKIP_LOCK_US name // // Skip the next instruction after this macro if the simple exclusion lock // NAME is held by this task. The bank will be set to the local bank. // /macro mutex_skip_lock_us extern currtask ;0-N ID of the currently running task dbankif gbankadr movf currtask, w ;get the ID of this task dbankif lbankadr xorwf lock_[arg 1], w ;compare to lock state skip_z ;skip if we are holding the lock /endmac //////////////////////////////////////////////////////////////////////////////// // // Macro MUTEX_SKIP_AVAIL name // // Skip the next instruction after this macro if the simple exclusion lock // NAME is available (not held by any task). The bank will be set to the // local bank. // /macro mutex_skip_avail dbankif lbankadr btfss lock_[arg 1], 7 ;skip if not locked /endmac //////////////////////////////////////////////////////////////////////////////// // // Macro MUTEX_SKIP_NLOCK_US name // // Skip the next instruction after this macro if the simple exclusion lock // NAME is not held by this task. The bank will be set to the local bank. // /macro mutex_skip_nlock_us extern currtask ;0-N ID of the currently running task dbankif gbankadr movf currtask, w ;get the ID of this task dbankif lbankadr xorwf lock_[arg 1], w ;compare to lock state skip_nz ;skip if we are not holding the lock /endmac //////////////////////////////////////////////////////////////////////////////// // // Macro MUTEX_LOCK name // // Acquire the simple exclusion lock NAME. If the lock is not in use, then // it is locked to this task. If it is in use, then then TASK_YIELD_SAVE is // called until it is available. Nothing is done if this task is already // holding the lock. // /macro mutex_lock /write /write " ;acquire the " [ucase [qstr [arg 1]]] " lock." ; extern currtask ;0-N ID of the currently running task [lab retry] unbank dbankif lbankadr btfsc lock_[arg 1], 7 ;the lock is not currently available ? jump [lab grab] ;is available, go grab it movf lock_[arg 1], w ;get the ID of the task holding the lock dbankif gbankadr xorwf currtask, w ;compare to this task bz [lab done] ;we are already holding the lock, nothing to do ? gcall task_yield_save ;give other tasks a chance to run jump [lab retry] ;back to check the lock again [lab grab] dbankis lbankadr ;grab the lock movff currtask, lock_[arg 1] ;indicate this task has the lock [lab done] unbank /write /endmac //////////////////////////////////////////////////////////////////////////////// // // Macro MUTEX_UNLOCK name // // Release the simple exclusion lock NAME. Nothing is done if it is not held // by this task. // /macro mutex_unlock /write /write " ;release the " [ucase [qstr [arg 1]]] " lock." ; extern currtask ;0-N ID of the currently running task dbankif gbankadr movf currtask, w ;get the ID of this task dbankif lbankadr xorwf lock_[arg 1], w ;compare to ID of the holding task skip_nz ;we aren't hoding the lock ? setf lock_[arg 1] ;indicate the lock is now available /write /endmac