C Subroutine: Subroutines (Vlshift)
C Subroutine: Subroutines (Vlshift)
A subroutine is a block of code that is called from different places from within a main program or other subroutines.
Saves code space in that the subroutine code does not have to be repeated in the program areas that need it; only the code for the subroutine call is repeated.
/* variable left shift */ unsigned char vlshift(v,amt) unsigned char v, amt; { while (amt) { v = v << 1; amt--; } return(v); } main(){ unsigned char i,j,k; i=0x24; j = 2; k = vlshift(i,j); i,j,k); }
1 V 0.2
C Subroutine (vlshift)
subroutine parameters subroutine body subroutine return Main program subroutine call
A subroutine can have zero or more parameters that control its operation A subroutine may need to use local variables for computation. A subroutine may pass a return value back to the caller. Space in data memory must be reserved for parameters, local variables, and the return value.
V 0.2
Parameter space for vlshift CBLOCK 0x040 v, amt vlshift ENDC parameters
vlshift.asm
;Parameter space for main CBLOCK 0x020 i,j,k main() variables ENDC
vlshift.asm (cont.)
;; return value in w vlshift movf amt,f vlshift_loop btfsc STATUS,Z ; amt==0? goto vl_return bcf STATUS,C rlf v,f ; v = v << 1 decf amt,f ; amt-goto vlshift_loop vl_return movf v,w ;return(v) return
V 0.2
subroutine body
org 0 ; initialize main program variables movlw 0x24 movwf i ; i = 0x24 initialize i , j movlw 0x2 variables movwf j ; j = 2 ;; setup subroutine parms copy i , j variables movf i,w to parameters v, movwf v movf j,w amt for subroutine movwf amt call vlshift subroutine call movwf k ; k = vlshift(v,amt); here goto here
V 0.2
The Stack
In Ps, the stack is a memory area intended for storing temporary values. Data in the stack is usually accessed by a special register called a stack pointer. In the PIC, the stack is used to store the return address of a subroutine call.
The return address is the place in the calling program that is returned to on subroutine exit. On the PIC, the return address is PC+1, if PC is the location of the call instruction.
call (call subroutine) : Push PC of next instruction onto stack, theb PC[10:0] k, PC[12:11] PCLATH return (ret from subroutine): PC pop top-of-stack retlw (return with literal in w): w k, PC pop top-of-stack
V 0.2 5
V 0.2
8 locations
Notice that you only have access to the last item placed on the stack (the Top of Stack TOS). You retrieve the boxes from the stack in reverse order (C then B then A). A stack is also called a LIFO (last-in-first-out) buffer.
V 0.2 7
0: 0x????
Call/Return Example
main .... 0x40 .... call Sub_A
after step 6 SP 6 5 4
Stack Overflow
Stack overflows on the 9th call instruction without a return
Stack pointer wraps back to 7, overwrites the return address of the first call instruction Obviously, this results in incorrect program behavior Hard to debug, no status flags that indicate stack overflow
1 2 3 SP after step 3
Sub_A .... 2 0x69 call Sub_B .... return 6 Sub_B .... 0x13B call Sub_C 3 return 5 Sub_C .... return 4
V 0.2
V 0.2
10
This happens to be the most efficient method for the PIC16, but has limitations
A subroutine cannot be called from within itself (this is because the static memory area for parameters is already in use!!!) If a subroutine is interrupted, then the subroutine cannot be called from the interrupt service routine.
Program Execution
main(), call Subr A SubrA: .... .... return
Interrupt occurs
interrupt service subroutine, call Subr A return from interrupt SubrA: .... ..... return
SubrA parameters
Used by main() call when interrupt happens. ISR call to SubrA will change the parameters seen by main() call to SubrA
12
V 0.2
11
V 0.2
Indirect Addressing
The stack pointer is an example of a pointer register. A pointer register contains the address of data that is to be accessed. The data is retrieved or stored using the pointer register (data is accessed indirectly via the pointer register). The address of the data must first be loaded into the pointer register before using it to access the data. The previous addressing method we used is called direct addressing because the address is specified directly in the instruction: movf 0x20, w ; w [0x20] The address 0x20 is encoded directly in the instruction. This instruction will always access location 0x20.
V 0.2 13
Pointers in C
char s1[] = "Upper/LOWER."; unsigned char strcnt (ptr) unsigned char *ptr; { unsigned char i; i = 0; while (*ptr != 0) { ptr++; i++; } return(i); } main(){ unsigned char i; i = strcnt(s1); }
Will use C to illustrate pointer usage, then show how this is implemented in PIC16 assembly. strcnt returns number of characters in string reference by ptr. Recall that C strings are terminated by 0x00. * operator declares that a variable is a pointer variable *ptr returns data that pointer is accessing ptr++ increments pointer to next address of data. For char data, increment by 1. 14
Pointers in PIC16
FSR special register (location 0x04) : holds the value of the pointer itself (contains the address of the data) INDF special register (location 0x00) : used to access the data that FSR points to.
char s1[] = "Upper/LOWER."; char *s, c; /* point s at first char of s1 */ s = s1; /* get first char */ c = *s; CBLOCK 0x20 s1:16, c ENDC movwl 0x20 movwf FSR movf INDF,w ; w addr. of s1 ;FSR points at ;first char ; w [0x20] ; w *s ; c = *s
15
strcnt in PIC16
int strcnt (ptr) unsigned char *ptr; { unsigned char i; i = 0; while (*ptr != 0) { ptr++; i++; } return(i); }
;parms for strcnt CBLOCK 0x50 i,ptr ENDC strcnt clrf i ;; i = 0 movf ptr,w ;; w = ptr movwf FSR ;; FSR = ptr strcnt_loop movf INDF,w ;; w = *ptr btfsc STATUS,Z ;; *ptr == 0? return ;; yes, exit incf i,f ;; i++ incf FSR,f ;; ptr++ goto strcnt_loop
movwf c
V 0.2
V 0.2
16
Use label s1 instead of 0x020, increases code clarity. After call, variable i in strncnt parameter block has the string length.
The above string can be stored in program memory as a series of retlw instructions. s1const
s1const dt Upper/LOWER,0 retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw retlw
V 0.2
Assume we wanted to retrieve s1const[5] (/) Must be a call, so can do a retlw Assembler directive high returns higher 5-bits of address s1const+5, save in PCLATH. Put lower 8-bits of s1const+5 into PCL; write to PCL causes PCLATCH PCH. Next instruction executed is at PCH:PCL == s1const+5. Instruction is retlw 0x2f! So 0x2f (/) returned in w reg.
CBLOCK 0x020 ; tdata ; data value from table ENDC org 0 call get_data movwf tdata ; save tab data here goto here get_data movlw movwf movlw movwf high (s1const+5) PCLATH low (s1const+5) PCL ; does retlw!
The dt (define table) assembler directive causes byte data to be encoded as a series of retlw instructions in program memory.
0x55 0x70 0x70 0x65 0x72 0x2f 0x4c 0x4f 0x57 0x45 0x52 0x2E 0x00
;U ;p ;p ;e ;r ;/ ;L ;O ;W ;E ;R ;.
19
20
PCH:PCL Loading
Back to strcnt.asm
The strcnt subroutine expects s1 to be in data memory. The init_s1 subroutine uses table reads to copy s1const in program memory to s1 in data memory. Complete code for strcnt.asm attached to lecture, see if interested in details.
;data storage for s1 string CBLOCK 0x20 s1[16] ENDC org 0 ; copy string in prog. mem ; to data mem ; the init_s1 code is not shown call init_s1
Table read makes use of this capability. Any instruction with PCL as destination causes transfer of PCLATH<4:0> to PCH.
init_s1 ;;subroutine copies s1const ;; to data memory s1 . . . <code not shown > . . . s1const dt Upper/LOWER,0
V 0.2 22
V 0.2
21
However, there are four more INDF-like registers associated with each FSRx. These register names, and operations are shown below:
movf movf movf movf POSTDEC0, w POSTINC0, w PREINC0, w PLUSW0, w ; ; ; ;
V 0.2
/* variable left shift */ unsigned char vlshift(v,amt) unsigned char v, amt; { while (amt) { v = v << 1; amt--; } return(v); }
vlshift Revisited
Data stack on entry to vlshift TOS+2: amt TOS+1: v FSR0 TOS:
A data stack
A data stack will be used to pass parameters. Will place the top-of-stack at location 0x7F, will want to the stack to grow down as items are placed on it. Will use FSR0 as the stack pointer To store items on stack (push):
movf POSTDEC0, w
0x7f: ??
stack growth
To pass these parameters, will use a data stack. This is a different stack from what is used for return addresses. This is just a data structure created by the programmer. V 0.2
movf
PLUSW0, w
0x00: ??
static variables
26
vshift ;will use w to index to v movlw 2 movf PLUSW0,f ;test amt vshift_loop btfsc STATUS,Z ; amt==0? goto vl_return bcf STATUS,C movlw 1 rlcf PLUSW0 ; v = v << 1 movlw 2 Want new v value decf PLUSW0,f ; amt-to return in w reg, goto vshift_loop vl_return so pop this off of ;; 'v' has result, pop off into w reg stack and place in ;; before return w before returning. movf PREINC0,w ; w <- v return
V 0.2 28
lfsr FSR0, 0x7f ; initialize main program variables Place amt, then movlw 0x24 movwf i ; i = 0x24 onto data stack movlw 0x2 before call. movwf j ; j = 2 ;; setup subroutine parms movf j,w movwf POSTDEC0 ; push amt value onto stack movf i,w movwf POSTDEC0 ;push v value onto stack call vshift movwf k ; k = vshift(v,amt); movf PREINC0,f ; pop amt off stack to clean up here goto here
vlshiftp18.asm (main )
v
Interrupt occurs
interrupt service subroutine, call Subr A return from interrupt SubrA: .... ..... return
V 0.2
Stack
SubrA parameters used by main() call SubrA parameters used by interrupt service call
30
The v parameter was removed from stack by vlshift; need to remove the amt parameter.
V 0.2
29