6 Compiled Stack Example
The example code in this chapter shows how you can use the linker to place objects on a compiled stack.
Not to be confused with a software stack, which is a dynamic stack allocation accessed via a stack pointer, a compiled stack is a memory area that is designated for static allocation of local objects that should only consume memory for the duration of a routine, in the same way that a function's auto and parameter objects behave in C programs.
The main advantage of using objects on a compiled stack is that the memory used by each routine for its local objects can potentially be reused by local objects defined by other routines. This substantially reduces the amount of data memory used by your program.
The drawback of a compiled stack is that the programmer must maintain the directives used to indicate the memory requirements of each routine and how those routines interact. Failure to do so could lead to code failure.
Compiled stacks do not use a stack pointer. Objects on such a stack are assigned a static address, which can be accessed via a symbol, thus there is no code size or speed penalty for using a compiled stack over ordinary objects. The allocation of memory on the stack is performed by the linker, so the total size of the stack can be determined, and the linker will issue a memory error if space cannot be found for the requested stack objects.
As the memory assigned to compiled stack objects is statically allocated, routines that use these objects are not reentrant, so they cannot be called recursively, nor can they be called from both main-line code and interrupt routines (or anything called by an interrupt routine).
Assembly code which illustrates the basic principles of using a compiled stack on a PIC18F47K42 device is shown below.
A Compiled Stack Example
#include <xc.inc>
CONFIG "FEXTOSC = OFF" // External Oscillator not enabled
CONFIG "RSTOSC = HFINTOSC_1MHZ" // HFINTOSC, HFFRQ=4MHz, CDIV=4:1
CONFIG "CLKOUTEN = OFF" // Clock out Enable->CLKOUT function is disabled
CONFIG "PR1WAY = ON" // PRLOCKED One-Way cleared/set only once
CONFIG "CSWEN = ON" // Writing to NOSC and NDIV is allowed
CONFIG "FCMEN = ON" // Clock Monitor enabled
CONFIG "MCLRE = EXTMCLR" // LVP=0: MCLR pin is MCLR; LVP=1: RE3 pin is MCLR
CONFIG "PWRTS = PWRT_OFF" // PWRT is disabled
CONFIG "MVECEN = OFF" // Vector table isn't used to prioritize interrupts
CONFIG "IVT1WAY = ON" // IVTLOCK bit can be cleared and set only once
CONFIG "LPBOREN = OFF" // ULPBOR disabled
CONFIG "BOREN = SBORDIS" // Brown-out Reset enabled, SBOREN bit is ignored
CONFIG "BORV = VBOR_2P45" // Brown-out Reset Voltage (VBOR) set to 2.45V
CONFIG "ZCD = OFF" // ZCD disabled, enable by setting ZCDSEN in ZCDCON
CONFIG "PPS1WAY = ON" // PPSLOCK cleared/set only once
CONFIG "STVREN = ON" // Stack full/underflow will cause Reset
CONFIG "DEBUG = OFF" // Background debugger disabled
CONFIG "XINST = OFF" // Extended Instruction Set disabled
CONFIG "WDTCPS = WDTCPS_31" // Divider ratio 1:65536; software control of WDTPS
CONFIG "WDTE = OFF" // WDT Disabled; SWDTEN is ignored
CONFIG "WDTCWS = WDTCWS_7" // window open 100%; software control
CONFIG "WDTCCS = SC" // Software Control
CONFIG "BBSIZE = BBSIZE_512" // Boot Block size is 512 words
CONFIG "BBEN = OFF" // Boot block disabled
CONFIG "SAFEN = OFF" // SAF disabled
CONFIG "WRTAPP = OFF" // Application Block not incr protected
CONFIG "WRTB = OFF" // Configuration registers not incr-protected
CONFIG "WRTC = OFF" // Boot Block (000000-0007FFh) not incr-protected
CONFIG "WRTD = OFF" // Data EEPROM not incr-protected
CONFIG "WRTSAF = OFF" // SAF not Write Protected
CONFIG "LVP = ON" // LV programming enabled, MCLR pin, MCLRE ignored
CONFIG "CP = OFF" // PFM and Data EEPROM code protection disabled
;place the compiled stack in Access bank memory (udata_acs psect)
;use the ?au_ prefix for autos, the ?pa_ prefix for parameters
FNCONF udata_acs,?au_,?pa_
PSECT resetVec,class=CODE,reloc=2
resetVec:
goto main
PSECT code
;add needs 4 bytes of parameters, but no autos
FNSIZE add,0,4 ;two 2-byte parameters
GLOBAL ?pa_add
;add the two 'int' parameters, returning the result in
;the first parameter location
add:
movf ?pa_add+2,w,c
addwf ?pa_add+0,f,c
movf ?pa_add+3,w,c
addwfc ?pa_add+1,f,c
return
;incr needs one 2-byte parameter
FNSIZE incr,0,2
GLOBAL ?pa_incr
;return the additional of the 2-byte parameter with the
;value in the W register
incr:
addwf ?pa_incr+0,c
movlw 0h
addwfc ?pa_incr+1,c
return
GLOBAL ?au_main
GLOBAL result
result EQU ?au_main+0 ;create an alias for this auto location
GLOBAL incval
incval EQU ?au_main+2 ;create an alias for this auto location
FNROOT main ;this is the root of a call graph
FNSIZE main,4,0 ;main needs two 2-byte 'autos' (for result and incval)
FNCALL main,add ;main calls add
FNCALL main,incr ;main calls incr
PSECT code
main:
clrf result+0,c
clrf result+1,c
movlw 2 ;increment amount
movwf incval+0,c
clrf incval+1,c
loop:
movff result+0,?pa_add+0 ;load 1st parameter for add routine
movff result+1,?pa_add+1
movff incval+0,?pa_add+2 ;load 2nd parameter for add routine
movff incval+1,?pa_add+3
call add ;add result and incval
movff ?pa_add+0,result+0 ;store add's return value back to result
movff ?pa_add+1,result+1
movff incval+0,?pa_incr+0 ;load the parameter for incr routine
movff incval+1,?pa_incr+1
movlw 2
call incr ;add 2 to incval
movff ?pa_incr+0,incval+0 ;store the result of incr back to incval
movff ?pa_incr+1,incval+1
goto loop
END resetVec
int add(int a, int b) {
return a + b;
}
void incr(int val, char amount) {
return val + amount;
}
void main(void) {
int result, incval;
result = 0;
incval = 2;
while(1) {
result = add(result, incval);
incval = incr(incval, 2);
}
}