mailto:w. v. ooijen / f. hanneman [wf@XS4ALL.NL] says:
The PIC-12 uses a banked architecture to address the internal registers (RAM).The addressing scheme used is:
< B: 3 bank bits > < S: 1 shared/banked bit > < O: 4 bits offset >
When an instruction specifies an address it specifies only the S and O bits, the B bits are taken from the status register. It is a major challenge for a compiler (or assembler programmer) to avoid the need to change the B bits.
When an indirect address is used (FSR register) all bits are taken from the FSR register.
In Michrochip terminology each bank (as selected by the 3 bank bits) consist of a shared part (S=1) and a banked part (S=0). The shared part always addresses the same registers, independent of the B bits. The shared part contains the hardware-specific registers (port buffers etc.) and a few (8) registers that can be accessed independent of the current value of the B bits.
When a pointer to a block of data must be manipulated the addressing scheme choosen by Microchip becomes a pain in the ass.
Within each bank things are fine, the pointer can be incremented, decremented etc. and it will point to the next or previous register. But when a bank boundary is crossed (the S bit changes from 1 to 0) the pointer ends up in the shared part instead of in the next (or previous) banked part, so without precaution a pointer can not simply be manipulated and then used in the FSR to address a register.
What we need is a well-behaved pointer (+1 addresses the next register, -1 address the previous). This can be achieved either by - using a different reprentation for pointers than the one envisioned by Microchip and / or - using a different representation for +1 and -1.
My preferred option is choose a pointer representation that has S as most significant bit:
< S: 1 shared/banked bit > < B: 3 bank bits > < O: 4 bits offset >
Now any carry/borrow from the O bits directly affects the B bits. When such a pointer is moved to the FSR the S bit must be returned to where Microchip in their wisdom has put it. This is no big deal but it requires a few assembler instructions.
Another option is
- to use < O: 4 bits offset > < B: 3 bank bits > < S: 1 shared/banked bit >
- to represent +1 and -1 by +16 and -16, and feed any carry back to the lowest (S) bits. Now the move from our representation to the FSR is a simple swap, but increment and decrement are more difficult to implement.
I have not found any way to keep both the increment / decrement and the transfer to FSR trivial.
Eric Smith says:
I use this method for incrementing a pointer if I want a buffer to span multiple banks:incf ptr bsf ptr,4Decrementing could probably be done similarly, except that W is needed:
decf ptr movlw 0x10 btfss ptr,4 subwf ptrNormally I use the increment method in a more elaborate macro that handles wrapping the pointer in a ring buffer, and tries to special case some boundary conditions that it can optimize. It's fairly complex; here's a simplified version without all of the optimization (presented as an example; I haven't tested this version):
ringadv macro ptr,rbase,rend incf ptr ; increment pointer bsf ptr,4 ; advance bank if needed movf ptr,w ; is pointer at end? xorlw rend movlw rbase ; prepare for worst btfsc status,z movwf ptr ; yes, it was at end, store base endif endm
Note that this assumes that the pointer must always be in banked memory. The buffer can't start in unbanked memory (0x00..0x0f). Here's the macro I use to allocate the buffer (again simplified and untested):
ringres macro size local s2,bend,bleft s2 set size while s2>0 bend set ($&0xe0)+0x20 ; where's the end of the current bank? bleft set bend-$ ; how many words can we reserve here if bleft>s2 ; more than we need? bleft set s2 ; yes, don't go too far endif res bleft ; reserve the space org ($|0x10) ; skip unbanked register area s2 set s2-bleft ; update remaining size endw endmThe way I use this stuff (again simplified and untested):
tx_ring_size equ 16 rx_ring_size equ 48 org 0x08 ; vars must be in unbanked regs temp: res 1 tx_ring_ip: res 1 ; transmit ring input tx_ring_op: res 1 ; and output pointers tx_ring_cnt: res 1 rx_ring_ip: res 1 ; receive ring input rx_ring_op: res 1 ; and output pointers rx_ring_cnt: res 1 org 0x90 tx_ring: ringres tx_ring_size tx_ring_end: rx_ring: ringres rx_ring_size rx_ring_end: [...] ; add a byte in W to the transmit buffer ; returns 1 in W if successful, 0 if buffer full xmit: movwf temp ; save character movf tx_ring_cnt,w ; test for buffer already full xorlw tx_ring_size btfsc status,z retlw 0 movf tx_ring_ip,w ; put character into buffer movwf fsr movf temp,w movwf ind,w ringadv tx_ring_ip,tx_ring,tx_ring_end ; advance input pointer incf tx_ring_cnt ; increment count retlw 1
Code:
I think Eric Smith's method of incrementing a pointer ; increment pointer incf ptr bsf ptr,4 is very clever. Here's a quick way to decrement that pointer: ; decrement pointer ; (warning: untested code) bcf ptr,4 decf ptr bsf ptr,4
Comments: