4.3 Working with Data Banks
The Mid-range code in this chapter's example shows how to handle data memory banking. Check your device data sheet to ensure you understand how banking works on your chosen device.
The equivalent PIC18 example code, shown in section A Basic Example For PIC18 Devices, largely avoided
banked data memory complications by using the movff
instruction, which
can access the entire data memory without the need for the bank selection register to be
set. The movffl
instruction can also access the entire data memory on
PIC18 devices that have an expanded amount of data RAM, such as the 18F47K42.
Additionally, the max
and tmp
objects were placed into
a psect that was destined for Access bank memory, which can also be accessed using file
register instructions without using the bank selection register.
Mid-range and Baseline PIC devices do not implement the movff
instruction, so all movement of data is performed by banked instructions. So too, all
other instructions that access file registers (e.g. clrf
,
addwf
) are banked. Baseline and Mid-range devices also have only
very limited amounts of common memory, which can be accessed independently of the
currently selected bank, so most programs on Baseline and Mid-range devices will need to
consider the memory banks used by objects.
To illustrate how banked instructions should be used, the code in this
Mid-range example links the tmp
and max
objects into
banked rather than common memory, by placing them in the assembler-defined psect
udata_bank0
. This means that the code that accesses these objects
must deal with bank selection and address masking.
Bank selection (typically performed using the BANKSEL
directive) must
always be handled by the addition of instructions into your program, but there are two
methods of handling the masking of addresses used by instruction operands. One is to
manually mask the address by using either a predefined macro or by using an expression.
The other method is to use a linker option to have the linker automatically truncate
addresses to suite the instruction. This essentially suppresses the linker fixup
overflow error that would normally occur if the address was not masked. Both these
methods are discussed below.
BANKMASK()
macro)
or have the linker automatically truncate addresses, you must always include the
appropriate bank selection sequences before objects are accessed. This is typically
performed using the BANKSEL
directive.Manual Address Masking
BANKSEL max ;starting point
clrf BANKMASK(max)
BANKSEL ANSELC
clrf BANKMASK(ANSELC) ;select digital input for port C
loop:
BANKSEL PORTC ;read and store port value
movf BANKMASK(PORTC),w
BANKSEL tmp
movwf BANKMASK(tmp)
subwf max^(tmp&0ff80h),w ;is this value larger than max?
...
The BANKSEL max
directive has been used to select
the bank of the object max
before it is accessed by the
clrf
instruction following. Although you can manually write the
instruction or instructions needed to set the bank selection bits for the device you
are using, the BANKSEL
directive is more portable and is easier to
interpret. On devices other than PIC18s and Enhanced Mid-range devices, this
directive can sometimes expand into more than one instruction, so you should never
use it immediately after an instruction that can skip, such as the
btfsc
instruction.The clrf
instruction clears the object max
. Here,
the BANKMASK()
preprocessor macro has been used with the
instruction operand to remove the bank bits contained in the address of
max
. This directive ANDs the address with an appropriate
mask.
If this bank information is not removed, the linker might issue a fixup overflow error, which occurs when the operand value determined by the linker is too large for the relevant field in the instruction opcode. For example, Mid-range file register instructions have a 7-bit wide field in their op-code that specifies the offset into the currently selected bank of the location they are to access.
In the last few instructions of the code snippet shown above, the programmer has
assumed that max
and tmp
are defined in the same
psect, and hence will be located in the same data bank. The example code selects the
bank of tmp
, writes the W register into that object using a
movwf
instruction, and then max
is accessed by
the subwf
instruction. Based on the programmer's assumption, a
BANKSEL
directive to select the bank of max
before the subwf
instruction is redundant, and it has been omitted
to reduce the size of the program.
While the above assumption is valid and the code will execute correctly, it could
fail if the definitions for max
and tmp
were to
change and the objects ended up in different banks. Such a failure would be
difficult to track down. Fortunately, there are other ways to remove the bank
information from an address that can be used to your advantage.
subwf max^(tmp&0ff80h),w
contains
a check to ensure that the bank of tmp
and max
are
the same. Rather than ANDing out the bank information in max
by
using the BANKMASK()
macro, the operand expression XORs (
^
operator) the full address of max
with the
bank bits contained in the address of tmp
(which is obtained by
masking out only the bank offset from tmp
using the AND operator,
&
). If the bank bits in the address of max
and the bank bits in the address of tmp
are the same, they will XOR
to zero in this expression. If they are not the same, they will produce a non-zero
component in the upper bits of the address and trigger a fixup overflow error from
the linker. This is much more desirable than the code silently failing at
runtime.The operand expression to the subwf
instruction checks to ensure
that two objects are in the same bank, but you can also use this style of check to
ensure that an object is in a particular bank. If, for example, the code required
that max
must be in bank 2, then using the address expression
(max ^ 100h)
on a Mid-range device would trigger a fixup error
if that was not the case. Here the value 0x100 consists of the bit sequence that
represents bank 2, with all seven bits of the bank offset (the least significant
bits) zeroed.
The section relating to file register address masking in the MPASM™ to MPLAB® XC8 PIC®
Assembler Migration Guide has more information on the format of addresses
and the appropriate masks to use, should you decide to use the AND or XOR operators
and you not use the BANKMASK()
macro.
Automatic Address Masking
The other method of handling address masking is to have the linker automatic truncate
the addresses for you. This is by far the easiest to use and does not clutter your
assembly source with ancillary expressions in instruction operands. To enable this,
use the driver option -Wl,--fixupoverflow=action
option, with ignore
or warn
and/or
lstwarn
as the action
argument. Such an option will have the linker either totally ignore fixup overflow
errors or only issue a warning should it encounter such situations. The address that
will be used by the instruction is the full operand address masked to the exact
width expected by the instruction. See the MPLAB® XC8 PIC® Assembler User's Guide for more information on
this option.
BANKSEL max ;starting point
clrf max
BANKSEL ANSELC
clrf ANSELC ;select digital input for port C
loop:
BANKSEL PORTC ;read and store port value
movf PORTC,w
BANKSEL tmp
movwf tmp
subwf tmp,w ;is this value larger than max?
...
Notice that the BANKMASK()
macros have not been
used, nor any other expressions to remove the bank information from the instruction
operands; however, the code will still build and execute correctly if these are
present. Note that this option does not affect bank selection in any way. The
BANKSEL
directive (or equivalent instructions) must always be
used to select the bank of a memory location, regardless of how you handle address
masking.Throughout this document, source code examples will show instructions where their
operand addresses have been manually masked, but the macros and expressions that
perform this task do not need to be used if you prefer to use the
-Wl,--fixupoverflow
option and have the linker truncate
addresses for you. As noted earlier, bank selection directives or instructions must
always be present in the source when required.