by Jon Wilder
On the PIC16F you have RAM registers organized into banks of 128 registers. This is because on direct addressing instructions, there are only 7 address bits available for direct addressing. In order to access addresses higher than 0x7F (decimal 127), the instruction has to get the 8th and 9th bits from bits RP0 and RP1 (RP = Register Page) in the STATUS register.
Each bank contains SFRs as well as general purpose RAM. In the case of the 16F628A, we have 96 general purpose registers in bank 0, 80 general purpose registers in bank 1, and 48 general purpose registers in bank 2 for a total of 224 general purpose registers.
16 of the registers in bank 0, however, are known as “common RAM”. The reason they are known as common RAM is that unlike the other general purpose registers, these registers are mapped in all 4 banks so we can always access them regardless of the currently active register bank. These locations reside at addresses 0x70-0x7F in bank 0.
This comes in handy for a couple of things –
Interrupt Context Saving (Software Stack)
The most common thing they’re handy for is interrupt context saving. This is where we save the current contents of the W register and the STATUS register upon executing the interrupt handler code. We do this because –
1) The interrupt handler can be executed at any time, and any bank can be active at the time the interrupt condition exists. This means that upon exiting the interrupt handler, we need to have a way to restore the pre-interrupt active bank so that the PIC is in the correct bank to continue executing the code it was interrupted from executing.
2) At the time that the PIC jumps to the interrupt handler, there is data in W that the main code is working with. This data will also need to be restored upon returning from the interrupt handler so that the correct data is in W for the code segment it returns to upon exiting the interrupt.
On microcontrollers that have a writable stack, we would typically push these registers onto the stack upon entering the interrupt handler, then pop them off the stack and restore them to their original locations upon returning from the interrupt. However, on the 16F series, the stack is not writable and only the program counter gets pushed/popped onto/off of the stack. This means that we need to implement a “software stack” of sorts.
Microchip’s minimum requirements for the interrupt software stack are that W must be stored in a register that is mapped in all of the banks while STATUS must be stored in bank 0. Placing the W_TEMP and STATUS_TEMP registers in common RAM satisfies both of these requirements.
Below is Microchip’s example software stack for the interrupt handler –
Since we can be in any one of the banks on the PIC when we enter the interrupt handler, we need to be able to drop the W and STATUS data into the software stack registers while still in the pre-interrupt bank, whichever bank it happens to be. We cannot switch to bank 0 prior to doing this, otherwise we lose the currently active bank before STATUS gets backed up, thus the code will not be able to return to the pre-interrupt bank when STATUS gets restored.
Notice that we default to bank 0 once we’ve backed up the STATUS register.
Once we go to exit the interrupt, the PIC will switch back to the pre-interrupt bank once STATUS is restored. Well, we still need to restore W after we restore STATUS, so again we need to be able to access W’s back up register in any one of the 4 banks.
By placing these registers in the common RAM space that resides between addresses 0x70-0x7F, this allows us to do just that.
Data EEPROM Write/Read Buffers
Of course we’re all aware of what a pain it is to write to EEPROM with all the hoops we have to jump through. Most of us tend to can the EEPROM write and read routines as a subroutine, then call the routine anytime we need to write to or read from EEPROM.
Some of us will even set up buffer registers that we preload the address and data information to prior to calling the read or write subroutines. In my code I set up DATA_EE_ADDR and DATA_EE_DATA.
Now if we place these registers in bank 0 RAM address space, we end up having to bank select several times in order to get the EEPROM address and data into the EEADR and EEDATA registers since they almost never reside in bank 0. Below is the example code of such –
Notice we have to bank select 3 times just to load the write address and data into their respective SFRs. However, if we place DATA_EE_ADDR and DATA_EE_DATA in common RAM, we would only have to bank select once to get the address and data where they need to go –
That gets rid of 4 instructions (the banksel directive generates 2 bank select instructions for both RP0 and RP1 on PICs with more than 2 banks). Pretty handy when you’re OCD about writing air tight code like I’ve been known to be.
Another thing that I feel should also be placed in common RAM are the counter registers that we use for our delay loops. Because I run fast processor speeds (8MHz and above), I use 3 counters, named COUNT1, COUNT2 and COUNT3. By placing these registers in common RAM, you can call your delay loops no matter which RAM bank is the current active RAM bank.
Hopefully, this article allows you to gain an understanding of the purpose of common RAM and allows you to make the most use of it.