Contents:
see also related pages:
High level languages ( languages.htm ) like BASIC and C and JAL just take care of this stuff. So C coders do not need to worry about it unless they inline ASM.
If you are lucky enough that your program fits in the first 2 Kwords or less, then you do not have to worry about PCLATH for any CALL or GOTO. CALL and GOTO have enough bits to directly call every destination in your program without having to modify PCLATH from its power-up value of 0.
If you are writing a simple subroutine (one that does not call other subroutines, does not do any "computed gotos", and does not cross any 2 Kword "page boundary"), you do not need to worry about PCLATH. When this subroutine was called, PCLATH must have already been set appropriately, so local GOTOs (used to implement loops and if-then-else branches) inside the subroutine work correctly. You do not need to set PCLATH on return, because
If you write any code that does "computed GOTOs" ( for example, for reading from a constant array or table tables.htm ) then you will have to worry about PCLATH, even if the entire program plus all the tables fit in the first 2 Kwords.
If you're writing the interrupt service routine, there are some ISR considerations .
[FIXME: which PICs does this apply to ? which PICs does this *not* apply to ?] This applies to the 14-bit PICs (the 16Cxx and 16Fxx series except 16C5x), and the 16F87x, and...
Core Size Page Size 12-bit 512 14-bit 2048 16-bit 8192
The "flow control" instructions ( CALL, GOTO, RETLW, RETFIE, RETURN, and any instruction that directly modifies the PC like ADDWF PCL ) modify all 16 bits of the PC at once.
This can be confusing, because none of the flow control instructions have enough address bits to specify the destination address completely. Where do the remaining bits come from ?
When the PIC is executing normal, straight-line code, it increments the 16 bit PC (program counter) by 1 at the end of every instruction. (This includes the "skip" instructions (BTFSC and BTFSS), they (conditionally) make the following instruction act like a NOP). all 16 bits of the PC are incremented. This works great.
For example, while executing a block of straight-line code starting at address 0x0FFC, the PC will count
0x0FFC ; executing code in "page 1" 0x0FFD 0x0FFE 0x0FFF 0x1000 0x1001 0x1002 0x1003 ; executing code in "page 2"without skipping a beat. (routines that cross "pages" like this are not recommended, because they make setting PCLATH properly for CALL or GOTO even more confusing than it normally is). Keep in mind that this rollover in the PC does not change the PCLATH bits: They must be explicitly changed.
A12 A11 A10 A9 A8 A7 A6 A5 A4 A3 A2 A1 A0 \4 3 2 1 0/ \7 6 5 4 3 2 1 0/ \_ PCLATH ____/ \_______ PCL _______/ (lower PCLATH bits are used)
Any instruction, other than a jump, that modifies the PCL register, also copies PCLATH into the high 8 bits of the 16 bit PC (obliterating anything that was there before).
A12 A11 A10 A9 A8 A7 A6 A5 A4 A3 A2 A1 A0 \____ goto or call _____________/ \4 3 2 1 0/ \_ PCLATH ____/ (PCLATH bits 0, 1, and 2 are ignored I guess)
(You must make sure PCLATH already contains the proper value when the CALL or GOTO instruction is executed).
Note that the PCLATH register is *not* the Hi byte of the program counter PC.
[FIXME: Should I make a distinction between the 8 bit ``PCL'' vs. the 16 bit ``PC'', or should I just talk about a 16 bit ``PCL'' ?]
No matter which paging strategy you pick, if you write an Interrupt Service Routine, it must save the original PCLATH on entry (typically in a register named ISR_PCLATH) and restore it on exit. See "Saving register state in an Interrupt Service Routine" for more information.
Roger Froud of Amytech Ltd. noted:
BEWARE!!! you must avoid GOTO INTERRUPT at the interrupt vector as this inherits the page bits from the page that's interrupted! Just put a NOP at the interrupt vector and drop through to an interrupt routine in bank 0 at address h'0005' I use a LONGCALL after saving the registers to place the bulky interrupt main code in page 2.
[``LONGCALL'' is the same as the ``fcall'' macro below]
If your program makes a far call (sets PCLATH and then does a CALL), and the subroutine it calls changes PCLATH again (so it could jump or call), that change remains after the routine has returned. So you can't assume that PCLATH is where you put it after a call unless you are very consistent about your handling of this issue.
Be very consistent in setting PCLATH before GOTOs, CALLs,
Table lookups, and any other instructions that modify
the PC.
Pick a method and use it every time.
There are several methods (paging strategies
)
in common use.
I [David Cary] currently use the
caller fixes PCLATH paging strategy
.
It's not original with me; I think I got it from
Olin Lathrop of Cognivis
with very little modification.
I also discuss some other paging strategies
that have some advantages (and disadvantages) compared to the
caller fixes PCLATH paging strategy
.
Unfortunately, there's a lot of code that uses
manual bit flipping
,
even when
relatively simple paging strategy.
Olin Lathrop of Cognivis recommends:
1 - Individual modules are not allowed to cross page boundaries. I make sure this happens automatically by creating a separate linker section for each page. There are occasional exceptions which so far have all been large data module (tables, etc.).
2 - always use "far" calls (fcall) and far gotos (lgoto). Use ``lgoto'' (built into MPASM) and ``fcall'' (defined above) for each and every GOTO, CALL, etc., then PCLATCH will always be set correctly and your program will run.
Use a macro to conditionally compile the appropriate instructions even including the jump, call or whatever. This is nice as it
The *only* reason not to use this strategy is that it generates slightly larger and slower than some of the strategies listed below. (But some of the strategies below have obscure ``gotchas'', causing minor changes in the code to make the code jump into hyperspace ). Also, since ``lgoto'' and ``fcall'' expand into multi-instruction sequences, the ``skip'' instructions (BTFSS and BTFSC) don't work right immediately before a "lgoto" or "fcall" macro call.
If you are working with a Scenix SX, there is a PAGE instruction (also supported) that loads a literal 3 bit value into the top of the PCLATH. The SXKey will automatically insert it if you put a @ before the target label in your CALL or JMP.
The SX has a RETP instruction that restores PCLATH on subroutine returns, so you don't have to.
MPASM recognizes the PAGESEL so it allows you to write relocatable modules that can be linked with code from other languages (C for example) after assembly. PAGESEL and BANKSEL and BANKISEL assemble to a set of 0 to 3 BCF/BSF instructions.
WARNING: old versions of MPASM sometimes assemble PAGESEL and BANKSEL to a MOVLW followed by a MOVWF PCLATH. That trashes W.
I'm told that future versions of MPASM will settle on BCF/BSF sequences for PAGESEL and add a new PAGESELW that always does the MOVLW thing.
(RECOMMENDED) (David thinks you should write code this way unless you *know* what you are doing).
Olin Lathrop of Cognivis recommends:
1 - Individual modules aren't allowed to cross page boundaries. I make sure this happens automatically by creating a separate linker section for each page. There are occasional exceptions which so far have all been large data module (tables, etc.).
2 - The high bits of PCLATH are always set to the page of the current module. This means CALLs and GOTOs within a module can always be done in one instruction without setting PCLATH first.
3 - PCLATH is always set before a CALL or GOTO to a label outside the current module.
4 - PCLATH is always reset to the current address after return from an external subroutine.
Use ``fcall'' and ``lgoto'' if the destination is outside the module. Use plain "call" and "goto" instructions if the destination is within the same module. (If you use ``fcall'' where you only needed to use ``call'', the compiled program will be a few bytes longer and a few cycles slower, but it will still run. But be very carefull you do not use ``call'' where you needed to use ``fcall'' -- your program will jump to a completely unexpected location at run time).
This method will produce relocatable, linkable code.
Disadvantage: *no* warnings if CALL or GOTO use the name of a subroutine on another page, but (when actually executing) don't really go there.
The GCALL macro by Olin Lathrop in STD.INS.ASPIC at http://www.embedinc.com/pic is slightly more sophisticated than the ``fcall'' macro below, but it does basically the same thing and has exactly the same disadvantage.
; example program ; the fcall macro ; by Roger Froud of Amytech Ltd. fcall macro subroutine_name local here lcall subroutine_name pagesel here here: endm interrupt_handler CODE 0x0005 ... RETFIE another_code_section CODE main_loop: fcall test1 fcall test2 call test4 fcall test3 goto main_loop test4: return yet_another_code_section CODE test1: call test2 call test3 fcall test4 return test2: return test3 return END
One interesting paging strategy is to use a "jump table". This gives the more compact code than any other paging strategy (but not the fastest code). Compilers using the ``optimize space'' option should use jump tables.
; sample code page 0 : .. call p0do_something_useful clrf PCLATH ... call p0do_something_useful clrf PCLATH .... p0do_something_useful: bsf pclath,page1bit errorlevel -306 ; supress warning, you know what you're doing... goto really_do_something_useful errorlevel +306 ; re-enable warning to catch real errors. page 1 : really_do_something_useful: .... return ... ; other subroutines
The SX has a RETP instruction that makes this the most compact paging strategy I've seen:
; sample code page 0 : .. call p0do_something_useful ... call p0do_something_useful .... p0do_something_useful: bsf pclath,page1bit errorlevel -306 ; supress warning, you know what you're doing... goto really_do_something_useful errorlevel +306 ; re-enable warning to catch real errors. page 1 : really_do_something_useful: .... RETP ... ; other subroutines
If one is unable to use the SX has a RETP instruction, you can get almost as compact as the above by combining ``jump tables'' with ``globals return to page 0'' .
Mike Harrison [mike at WHITEWING.CO.UK] combines ``jump tables'' with ``globals return to page 0'':
This particular problem, of subroutines called from lots of places, is easily solved efficiently - for each page-1 routine have a page-select and goto to it in page zero. adds a minor time penalty but space is usually more important.
After the first call of a subroutine, this adds no page-swap code overhead for subsequent calls, so is 'profitable' for any routines called more then once.
It can also be useful for all page 1 subroutines to exit via a common point that clears the page 1 bit first. This avoids the page 0 call having to worry about whether the routine is in page 0 or 1 - things tend to move about as code grows & changes...!
; sample code page 0 : .. call p0do_something_useful ... call p0do_something_useful .... p0do_something_useful: bsf pclath,page1bit errorlevel -306 ; supress warning, you know what you're doing... goto really_do_something_useful errorlevel +306 ; re-enable warning to catch real errors. page 1 : really_do_something_useful: .... goto pexit ... ; other subroutines pexit: bcf pclath,page1bit ; or: clrf pclath returnHaving a consistent naming convention is important to avoid major debug headaches.
Obviously it helps a lot to group subroutines by page to reduce the number of page-bit swaps, e.g. all subs called by p1sub1 above live in page 1.
For routines called only once, returning via the 'clear page bit' exit point is still useful as the calling code doesn't need to clear it.
1 - Individual modules aren't allowed to cross page boundaries.
2 - The high bits of PCLATH are always set to the page of the current module. This means CALLs and GOTOs within a module can always be done in one instruction without setting PCLATH first.
3 - Every module that makes "external" calls to some other page is placed on page zero. Those calls (from page 0 to some other page) set PCLATH (possibly using a jump table) before going to that routine.
4 - "global" routines on pages other than zero are responsible for clearing PLATH to zero before returning. (They must be returning to a CALL from page zero). (typically with CLRF PCLATH; RETURN)
5 - (optional) - "local" routines don't modify PCLATH.
Advantages:
If this is combined with "jump table", then all calls are ``short'' -- no need to worry about whether this is a ``long call'' or a ``short call''. Also, all GOTOs (except in the jump table) are short.
Takes less ROM space, because adjusting PCLATH on return is done once in the subroutine, rather than at every call of the subroutine.
disadvantages: all other page strategies have only 1 kind of subroutine. This requires 2 kinds of subroutines: "local" subroutines that never touch PCLATH. "global" subroutines that clear PCLATH before return. Brittle: no warning if you accidentally call a global routine from somewhere other than page0.
; example page_0_stuff CODE 0x0005 ;... ; ISR stuff here ;... mainloop: ; ... lcall do_something_useful goto mainloop page_1_subs CODE 0x0800 ;... ; this is a local routine -- it must *only* be called ; from this page, ; never from page0 or any other page. help_do_something_useful: nop return ; this is a global routine -- it must *only* be called ; from page0, ; never from this page or any other page. GLOBAL do_something_useful do_something_useful: call help_do_something_useful ; ... CLRF PCLATH return
Here are some of the more common paging strategies. Unfortunately, none of these will produce relocatable, linkable code.
Thorsten Klose says
To wrap around the problem I wrote two macros which produce smaller code than the "lgoto" and "lcall" macros from MPASM. They are for 1k devices but can be easily extended for 2k PICs (only 4 cases to check)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - XGOTO MACRO label if label & 0x800 bsf PCLATH, 3 else bcf PCLATH, 3 endif goto label ENDM XCALL MACRO label LOCAL testlabel testlabel: if label & 0x800 bsf PCLATH, 3 else bcf PCLATH, 3 endif call label if testlabel & 0x800 bsf PCLATH, 3 else bcf PCLATH, 3 endif ENDM
Tony K?bek [tony.kubek at flintab.com] of Flintab AB says
Trying to be clever I came up with the following:
LONG_CALL ROUTINE ; any page -> any page ( 4 BCF/BSF totally )
SHORT_CALL ROUTINE ; page0<->1 or page2<->3 ( 2 BSF/BCF totally )
PCALL ROUTINE ; same page call, however gives REAL warnings when crossing page ( not like mplink :-) )
Ideally one would use LONG_CALL XX all over until program has stabilized ( code not moving around ) then look at the messages and change accordingly.
;+++++ ; SET_PCLATH 'help' macro for LONG_CALL ; Set's/clears PCLATH bits 3:4 according to ; 'variable' PCLATH_34 ; SET_PCLATH MACRO PCLATH_34 IF(PCLATH_34&0x10) BSF PCLATH,4 ELSE BCF PCLATH,4 ENDIF IF(PCLATH_34&0x08) BSF PCLATH,3 ELSE BCF PCLATH,3 ENDIF ENDM ;+++++ ; SET_PCLATH4 'help' macro for LONG/SHORT_CALL ; Set's/clears PCLATH bit 4 according to ; 'variable' PCLATH_4 ; SET_PCLATH4 MACRO PCLATH_4 IF(PCLATH_4&0x10) BSF PCLATH,4 ELSE BCF PCLATH,4 ENDIF ENDM ;+++++ ; SET_PCLATH3 'help' macro for LONG/SHORT_CALL ; Set's/clears PCLATH bit 3 according to ; 'variable' PCLATH_3 ; SET_PCLATH3 MACRO PCLATH_3 IF(PCLATH_3&0x08) BSF PCLATH,3 ELSE BCF PCLATH,3 ENDIF ENDM ;+++++ ; LONG_CALL long call, sets the page bits 4:5 of PCLATH ; so call can cross ANY page boundary, reset's PCLATH after call. ; w-reg is left untouched. LONG_CALL MACRO LABEL LOCAL DEST_HIGH, SOURCE_HIGH, DIFF_HIGH DEST_HIGH SET (HIGH(LABEL)&0x18) ; save bit's 4:5 of dest address SOURCE_HIGH SET (HIGH($)&0x18) ; --- || --- source address DIFF_HIGH SET DEST_HIGH ^ SOURCE_HIGH ; get difference ( XOR ) IF (DIFF_HIGH == 0) ; same page, SHOULD generate no extra code, delta 0 pages MESSG "Call on same page, replace LONG_CALL with PCALL " LABEL NOP ; redundant NOP's NOP CALL LABEL NOP NOP ELSE ; test if both bits must be set ? ; i.e. page0<->page3 or page2<->page3 IF (DIFF_HIGH == 0x18) ; difference in BOTH bit's, delta 2 pages MESSG "Setting page bit's for long page crossing call" SET_PCLATH DEST_HIGH ; set both bits in PCLATH CALL LABEL SET_PCLATH SOURCE_HIGH ; reset both bits in pclath ELSE ; if we end up here then one BSF/BCF is enough, i.e. delta 1 page ; i.e. page0<->1 or page2<->3 MESSG "Call only one page, replace LONG_CALL with SHORT_CALL " LABEL IF (DIFF_HIGH == 0x10) ; diff in high bit NOP ; redundant NOP SET_PCLATH4 DEST_HIGH ; set high(4) bit of PCLATH CALL LABEL SET_PCLATH4 SOURCE_HIGH NOP ; redundant NOP ELSE ; lowest bit only NOP ; redundant NOP SET_PCLATH3 DEST_HIGH ; set low(3) bit of PCLATH CALL LABEL SET_PCLATH3 SOURCE_HIGH NOP ENDIF ENDIF ENDIF ENDM ;+++++ ; SHORT_CALL short call, code for calling between page0<->1 or page2<->3 ; Reset's PCLATH after call. ; w-reg is left untouched. SHORT_CALL MACRO LABEL LOCAL DEST_HIGH, SOURCE_HIGH, DIFF_HIGH DEST_HIGH SET (HIGH(LABEL)&0x18) ; save bit's 4:5 of dest adress SOURCE_HIGH SET (HIGH($)&0x18) ; --- || --- source adress DIFF_HIGH SET DEST_HIGH ^ SOURCE_HIGH ; get difference ( XOR ) IF (DIFF_HIGH == 0) ; same page, SHOULD generate no extra code, delta 0 pages MESSG "Call on same page, replace SHORT_CALL with PCALL " LABEL NOP ; redundant NOP's CALL LABEL NOP ELSE ; for safety check so we do not require LONG_CALL IF ((DIFF_HIGH&0x18)==0x18) MESSG " WARNING ! Replace SHORT_CALL with LONG_CALL " LABEL ENDIF MESSG "Setting page bit's for short page crossing call" IF (DIFF_HIGH == 0x10) ; diff in high bit SET_PCLATH4 DEST_HIGH ; set high(4) bit of PCLATH CALL LABEL SET_PCLATH4 SOURCE_HIGH ELSE ; lowest bit only SET_PCLATH3 DEST_HIGH ; set low(3) bit of PCLATH CALL LABEL SET_PCLATH3 SOURCE_HIGH ENDIF ENDIF ENDM ;+++++ ; PCALL page call, code for calling on same page ; outputs messages if LONG/SHORT call could/must be used PCALL MACRO LABEL LOCAL DEST_HIGH, SOURCE_HIGH, DIFF_HIGH DEST_HIGH SET (HIGH(LABEL)&0x18) ; save bit's 4:5 of dest address SOURCE_HIGH SET (HIGH($)&0x18) ; --- || --- source address DIFF_HIGH SET DEST_HIGH ^ SOURCE_HIGH ; get difference ( XOR ) IF (DIFF_HIGH == 0) ; same page, call ok CALL LABEL ELSE ; for safety check so we do not require LONG_CALL IF ((DIFF_HIGH&0x18)==0x18) MESSG " WARNING ! Replace PCALL with LONG_CALL " LABEL CALL LABEL ; INCORRECT Call !!! ELSE MESSG " WARNING ! Replace PCALL with SHORT_CALL " LABEL CALL LABEL ENDIF ENDIF ENDMWhich could be simplified to ( if one trusts pagesel directive )
L_CALL MACRO LABEL IF ((HIGH($)&0x18) == (HIGH(LABEL)&0x18)) ; same page, generates no extra code MESSG "Call on same page, use normal CALL" LABEL NOP ; redundant NOP's due to compiler linker funkiness NOP ; CALL LABEL ELSE MESSG "Using pagesel directive, call crossing page boundary" LABEL PAGESEL LABEL CALL LABEL ENDIF ENDMMy question is: Is there any way to remove these two extra NOP's ???
see also: USART, I2C, EERAM read/write, port setup, ISR, and full initialization sample from Tony K?bek of Flintab AB [tony.kubek at flintab.com]
(not recommended ... except for compilers): manually add code to set the PCLATH before jumps, calls, etc... using either
MOVLW HIGH(TARGET) MOVWF PCLATH
or a series of BCF/BSF instructions. or
clrf PCLATH ; set page 0If you are very careful, you may be able to use only 1, 2, or 3 instructions when your current PCLATH (not necessarily the same as your current location !) and target are close together.
This option is the least attractive for several reasons: A) You setup PCLATH how you think it should be at some point and then add a few lines before that. This pushs the code into the next page and you forget to manually update the setup. B) its damn hard to read C) it is not relocatable and can't be linked with other modules written in high level languages.
M. Adam Davis gives an example of this strategy: [macros added by DAV, but it gives the identical HEX file]
PCLATH retains its last setting, so if you set it for [a subroutine located in] the second page, call it, and return from it, your next call or goto will also go to the second page unless you reset the PCLATH (either during the subroutine or after the return from the subroutine). This is useful for staying in the 'current' page if you are careful about not changing the PCLATH in other functions. ...
ORG 0 CALL_0 macro subroutine_name CLRF PCLATH 'Next call will go to page 0 CALL subroutine_name CALL_1 macro subroutine_name MOVLW B'00001000' 'Next call or jump will go to page 1 MOVWF PCLATH CALL subroutine_name 'Jump to function that starts in this page endm CALL_2 macro subroutine_name MOVLW B'00010000' 'Next call will go to page 2 MOVWF PCLATH CALL subroutine_name endm CALL_3 macro subroutine_name MOVLW B'00011000' 'Next call will go to page 3 MOVWF PCLATH CALL subroutine_name endm Start: 'This resides in the page 0. Calls to 'functions past this page need PCLATH updated. CALL FuncIn1stPage ; now PCLATH is already set to B'00001000', ; so we don't need to set it again. CALL FuncIn2ndPage CALL_1 FuncIn2ndPage CALL_3 FuncIn2ndPage ... ... ORG 0x7FF 'This next function will cross page boundaries 'Which is fine, the program counter can handle 'that, and it won't mess the return up. 'Do not allow computed gotos to cross page 'boundaries, though, without thinking of PCLATH FuncIn1stPage: 'This function starts in the first page, but 'crosses into the second page. MOVLW .42 'Don't Panic! This instruction is in Page 0 CLRC 'This instruction is in Page 1. 'Since we are in the second page now we don't CALL_1 FuncIn2ndPage 'need to set the PCLATH, right? Wrong. So why 'did we set it? The PCLATH hasn't changed. 'Whatever was in it before is still in it, 'and will affect our calls and gotos. 'In this case, the last thing that modified 'it before this function was the device reset, 'which always makes it b'---00000' 'If we use a jump or call before setting it, 'we'll end up somewhere in the first page. RETURN 'This instruction is in page 1 FuncIn2ndPage: 'Function starts in page 1 CALL_0 FuncIn1stPage RETURN ORG 0x1000 'Function starts in page 2 FuncIn3rdPage: ... ... 'If you are in the third page, and you jump 'to another third page location, you can 'probably assume that the PCLATH contains ' B'00010000' because we had to do that to 'jump into the third page before. But I 'wouldn't assume that myself, unless I 'absolutely HAD to save those two extra cycles. CALL_2 SubIn3rdPage 'If you execute a call or goto without knowing 'what's in PCLATH, you can end up in the 'weirdest places, and debugging is a living 'nightmare... 'Tis better to be safe than sorry. RETURN SubIn3rdPage ... RETURN ORG 0x1800 'Function starts in 4th page FuncIn4thPage ... RETURNI think this covers about all the bases you need to know to cross page boundaries. This information was taken from the 16c6x data sheet, section 4.3 p.48 on PCL and PCLATH. Before you start writing code based on what I've written above, read that section (it's short) or the similar section in the 16f8xx sheet.
[1] OK, technically, the PC isn't really 16 bits. It is just barely big enough to address all the ROM space: 13 bits on PICs with 8 Kwords of program ROM (most PICs); 10 bits on the 16F84 and other PICs with 1 Kword of program ROM.
If I go on pretending it *is* 16 bits, will I ever write code that doesn't work ? Can you give an example that would give different results on the current PICs vs. a new, hypothetical PIC that has a 16 bit PC, and only the lower 10 or 13 bits are connected to the program ROM ?
2001-05-08:DAV: David Cary took ownership of this page. I'm trying to clean up some of the redundant information and confusing errors here.
Archive:
Questions:How do i create separate linker section for each page, thats recommended. Cant found that out.
so i get away from problem whit modules crossing page boundaries?
Thankful for help / Johan Olofsson
DAV: Where ? What "member's section" ?
David A Cary of Motorguide Pinpoint Says:
link to http://piclist.com/techref/microchip/instr/call.htm which discusses jump tables, and CALL on 12-bit-core PIC.
Questions:
Comments:
The special MPASM keyword ``CODE'' tells MPASM to start a new linker program section.
The special MPASM keyword ``UDATA'' tells MPASM to start a new linker data section.
When you run the linker, it creates a ``.map'' file which tells you which page it put each program section into, and which bank it put each data section into.
Questions:
Interested: