Subject:
Re: [gnupic] function pointers? with gplink)
From:
Iain Dooley ####@####.####
Date:
21 May 2005 14:39:56 +0100
Message-Id: <428FC883.6050409@iaindooley.com>
Bill Freeman wrote:
> [...]
>
> Let me preface this by confessing that I, too, have only used
> 12 and 14 bit devices. However, assuming that I understand what you
> are trying to do, there are a few bugs here.
>
> Assuming that all your other ducks are in a row, if theses are
> truely functions, that is, they come back with a return instruction,
> then the main problem is that writing PCL is like a goto, it is NOT
> like a call. (If these functions are only "called" from here, an
> alternate fix would be to put a lable on the decfsz and have the
> "functions" branch there instead of doing a return.) Try something
> like:
>
> ; We presume that count is already set to the table size
> ; Count ranges from N to 1, need N-1 to 0, thus:
> lfsr FSR0,function_table - 1
> loop:
> movf count, W
> call dispatcher
> decfsz count, F
> bra loop
> ; All functions have been called
> goto next_code ; or maybe return
>
> ; This dispatcher code can go anywhere since the 16 bit PIC call
> ; instruction can call anywhere.
> dispatcher:
> movff PLUSW0, PCL
>
> This will call the functions in the table in reverse order.
> Calling dispatcher saves the address of the decfsz on the stack, so
> that when the function returns, that's where it goes. The movff does
> indeed behave as a jump to the function.
that's great!! so you can do a call with no return in order to use the return stack. brilliant. i actually intend to use this to interpret user button input. i'm putting a keypad through a line decoder onto PORTB and going to use the RB interrupt to read the value in. depending on what button the user pressed, i will call a different function. i'm using a 4-16 line decoder, so i'll have an array of 16 function pointers and place them so that whatever button press comes in, i just have to dispatch to the appropriate function, so i'll be using:
;ASSUMES THAT THE VALUE OF PORTB HAS BEEN LOADED INTO current_button BY AN ISR
lfsr FSRO,function_table ;the array of function addresses
movff PORTB,temp_location ;get the current button press
swapf current_button ;we want the high four bits so..
m_andlf 0x0F,temp_location ;get rid of the rest
movf temp_location,W ;put the value from 0 - 15 into WREG
call dispatcher ;call the appropriate handler function
> [...]
>
> The other assumptions that I mentioned include that all
> functions are on the same page (their addresses differ only in the low
> 8 bits) and that PCLATU and PCLATH have been set correctly for them
> outside of this code. You could include bytes for them in the table,
> and put two or three movff instructions at dispatcher instead of 1 to
> load them before the final one writes PCL if you want to avoid this
> restriction.
i think the linker will never try to put a contiguous code segment on separate pages, so if i keep my handler functions in the same module as the button interpreter it shouldn't be a problem.
> The big assumption that is unusual is that the table must
> be located in RAM (gpfrs). If that's what you want, well and good.
> But if you meant for the table to be in program space (ROM), then
> you want to follow the MicroChip dispatch table examples
actually, i put my table in an idata segment and use the attached code to transfer into data memory using the _cinit table.
> [...]
thanks very much for all your help.
iain
;*******************************************************************************
;
; File Name: idata.asm
;
; Assembly language code to initalise IDATA sections on PIC18xxx family.
;
; Vers: 1.0
; Date: 28-Apr-2004
; Name: Rob Middleton (Mechatronics 2 student)
;
; Bugs: --
;
; ToDo: --
;
; Revision History
; Vers Date Who? Revision Detail?
; 1.1 11-Apr-2005 DCR Clarified comments.
; 1.0 28-Apr-2004 RM Released
;
;*******************************************************************************
;
;
processor 18f452
include "p18f452.inc"
EXTERN _cinit ; see comments about _cinit in subroutine header
;===============================================================================
;
; Declare Variables in RAM
;
;===============================================================================
; init_vars does not really need to be in Access RAM - and could be OVR - but
; this is easiest for now.
init_vars UDATA_ACS
init_num_sections RES 2 ; nonsensical to use more than 2 bytes due to
init_tblptr RES 3 ; pointer to the current position in the
; _cinit table.
; init_cur variables refer to the current IDATA section.
init_cur_size RES 2 ; address space allows max addressing of 2 bytes
; - thus the size must be a max of 2 bytes.
init_cur_from_addr RES 3 ; address space allows max of 3 bytes (prog memory)
;init_cur_to_addr RES 2 ; not needed as can be copied directly into FSR.
;===============================================================================
;
; Subroutine: idata_init
;
; This subroutine initialises variables in IDATA sections by copying
; their initialisers from ROM.
;
; Input:
; The linker generates '_cinit', which is a table defining sections of
; contiguous data (initialisers) to be copied from ROM to the
; corresponding locations in RAM. Use of IDATA directives in assembly
; language programs will automatically define this table. The table looks
; something like this:
; +==========================+==========================+
; _cinit | init_num_sections (low) | init_num_sections (high) |
; +==========================+==========================+-----+
; | init_cur_from_addr (low) | (high) | (higher) | (highest) |
; +--------------------------+--------+----------+------------+
; | init_cur_to_addr (low) | (high) | (higher) | (highest) | Section(N)
; +--------------------------+--------+----------+------------+
; | init_cur_size (low) | (high) | (higher) | (highest) |
; +==========================+========+==========+============+
; | . | . | . | . |
;
; That is, the table has a 2-byte header containing a value 'N', the
; number of sections to follow. N is stored least significant byte first.
; Then follow N sections of 12 bytes each.
;
; Usage:
; EXTERN idata_init
; call idata_init
;
; NOTE - this function should be called as the first stage of
; initalisation of a user program. It therefore does not attempt to
; save and restore the values of Special Function Registers that it
; modifies. These include TBLPTR, TABLAT, FSR1, WREG, STATUS. These
; SFRs are, however, cleared before exit.
;
; Output: No direct output is generated.
;
; Side effects:
; This subroutine modifies data memory, but this is its desired behaviour.
; It copies initialisers from ROM to the correct locations in RAM so that
; any variables declared in IDATA sections have their correct initial
; values before the user program begins.
;
; The following SFRs are cleared before the subroutine returns:
; TBLPTR, TABLAT, FSR1, WREG, STATUS
;
; Memory: Program Memory: ?? bytes
; Data Memory: ?? bytes
;
;-------------------------------------------------------------------------------
idata_init_code CODE
idata_init:
GLOBAL idata_init ; export symbol to other objects
; Load the 3 byte location of the start of the _cinit table into the
; Table Pointer registers.
MOVLW low _cinit
MOVWF TBLPTRL
MOVLW high _cinit
MOVWF TBLPTRH
MOVLW upper _cinit
MOVWF TBLPTRU
; Read the first two bytes from the _cinit table. These bytes are the number
; 'N' of init sections to follow. Two bytes stored least significant first.
TBLRD *+
MOVFF TABLAT, init_num_sections
TBLRD *+
MOVFF TABLAT, 1 + init_num_sections
; We now know the number of sections in the _cinit table. Loop through each.
; The number of sections is two-byte unsigned, stored LSB first.
MOVF init_num_sections,F ; dummy op to set status bits
BZ init_dec_high_byte
init_loop:
; Read in the info about the current init section.
; Get the ROM "From" address.
TBLRD *+
MOVFF TABLAT, init_cur_from_addr
TBLRD *+
MOVFF TABLAT, 1 + init_cur_from_addr
TBLRD *+
MOVFF TABLAT, 2 + init_cur_from_addr
TBLRD *+ ; discard the higest byte. Not needed for
; our 21 bit program address space.
; Get the RAM "To" address - and set FSR1 to point to this.
TBLRD *+
MOVFF TABLAT, FSR1L
TBLRD *+
MOVFF TABLAT, FSR1H
TBLRD *+ ; and discard 2 highest bytes outside data address space.
TBLRD *+
; Get the number of bytes to copy.
TBLRD *+
MOVFF TABLAT, init_cur_size
TBLRD *+
MOVFF TABLAT, 1 + init_cur_size
TBLRD *+
TBLRD *+
; Save the current table pointer so we can continue
; to walk the _cinit table later.
MOVFF TBLPTRL, init_tblptr
MOVFF TBLPTRH, 1 + init_tblptr
MOVFF TBLPTRU, 2 + init_tblptr
; Set the table pointer to the ROM "from" address.
MOVFF init_cur_from_addr, TBLPTRL
MOVFF 1 + init_cur_from_addr, TBLPTRH
MOVFF 2 + init_cur_from_addr, TBLPTRU
; Copy init_cur_size bytes from program memory to data memory.
; init_cur_size is two bytes long - hence the strange looking
; decrementing for loop.
; If low byte is zero go to init_chk_high_byte
MOVF init_cur_size,F ; dummy op to set status bits
BZ init_cur_dec_high_byte
init_cur_dec_low_byte:
DECF init_cur_size,F
TBLRD *+
MOVFF TABLAT, POSTINC1
BNZ init_cur_dec_low_byte
init_cur_dec_high_byte:
MOVF 1 + init_cur_size,F
BZ init_cur_dec_done
DECF 1 + init_cur_size,F
BRA init_cur_dec_low_byte
init_cur_dec_done:
; Restore Table Pointer so we can continue walking the cinit table.
MOVFF init_tblptr, TBLPTRL
MOVFF 1 + init_tblptr, TBLPTRH
MOVFF 2 + init_tblptr, TBLPTRU
DECF init_num_sections,F
BNZ init_loop
init_dec_high_byte:
MOVF 1 + init_num_sections,F
BZ init_dec_done
DECF 1 + init_num_sections,F
BRA init_loop
init_dec_done:
; Clear all used SFRs before returning
CLRF TBLPTRL ; clear Table Pointer and Table Latch
CLRF TBLPTRH
CLRF TBLPTRU
CLRF TABLAT
LFSR 1, 0x0000 ; clear FSR1
CLRF WREG
CLRF STATUS
RETURN
;-------------------------------------------------------------------------------
end