Electronic – Does the location of a program need to be specified when programming a PIC

microcontrollerpic

I recently purchased a PIC programmer (cheap PIC K150) and I am attempting to program a PIC12F683. Upon reading over the datasheet it states that program memory locations are 0005h to 07ffh since other locations are used for special purposes. Do I need to specify to the programmer a kind of offset or is it done automatically? Does the chip automatically route the program into those memory locations? here is the datasheet PIC12F683

Best Answer

That PIC uses the basic 14 bit core, so there are only two special locations in program memory, 0 and 4. Execution starts at 0 after reset, and a call is performed to 4 on interrupt. You have to make sure the very first instruction of your code gets put into location 0. If you are using interrupts, you have to make sure the first instruction of the interrupt routine is placed in location 4.

Most of the time you want to not specify where code goes to give the linker the most flexibility in placing sections of code. This is done by using the CODE directive without any arguments. To force code to get placed into a particular location, you preceed it with a CODE directive followed by the fixed address. This tells the linker it has to go right there and nowhere else. The linker will issue a error message if that is not possible for some reason.

The simple thing is to place the start of the first module at 0, then let the linker place the others as it sees fit. However, most projects use interrupts, so the code at zero can't run over the interrupt vector at 4. As a result, you usually have the code at 0 jump to code in another segment that is relocatable, while making sure the fixed code at 0 is no more than 4 instructions long.

Here is general startup code for this processor core:

.reset   code    0
         clrf    intcon      ;disable all interrupts
         pagesel start       ;set PCLATH for page of START
         goto    start       ;jump to START, can be anywhere in memory

.strt    code
start

Note the 0 behind the first CODE directive. That forces the following code to start at location 0. Since there are 4 instructions available, you can do just a little stuff other than jumping to the relocatable startup code. In this case, I make sure all interrupts are off. Interrupts will be off when getting here due to a hardware reset, but it can be useful to have the code at 0 also work if jumped to at any time from the middle of application code. Some of the other cores have a RESET instruction for that purpose, but this core does not.

The next two lines perform a jump to any location in the full program memory address space. Since the jump is to fully relocatable code, there is no guarantee where it might end up getting placed by the linker. The GOTO instruction only contains the low 11 bits of the 13 bit program memory address, with the remaining bits coming from PCLATH. The PAGESEL directive sets these bits appropriately for the code page that START happens to be on.

This particular PIC only has one code page of program memory, and PCLATH is cleared on reset. In this particular case, you don't need to load PCLATH, but it's a good idea anyway. First, you have 4 instructions available for the fixed startup code. Not using PAGESEL would take two instructions less, but those instruction locations will be difficult to utilize otherwise anyway. Second, this is the kind of detail easily forgotten when the code is moved to a newer PIC with more memory.

Actually I use macros for this that are smart enough to know whether the machine has only one code page or more than one code page and emit the extra bank setting instructions in the second case. I am showing a simpler but totally robust version of the startup code here.

The second CODE directive starts a new linker section, but this time no address is specified. That makes the linker section relocatable, meaning the linker is allowed to place it where it sees fit. The code following the second CODE directive can end up anywhere in memory, and on larger PICs might not be in page 0.

The interrupt service routine is started similarly, with a "CODE 4" directive. However, since there are no other special fixed locations following 4, the interrupt routine can go there directly. Some code will end up following location 4, so it might as well be the only routine that actually benefits from being there. In other words, unlike with the startup code, don't split the interrupt code into fixed and relocatable sections. Put the whole thing in a fixed section starting at 4.

Added about interrupt vector

There is significant bad information out there about the best use of the interrupt vector on a 14 bit core part like the one asked about. Even Microchip is guilty of propagating some of this. Let's debunk some of the nonsense:

  1. No, you don't need to put a GOTO at the interrupt vector. The hardware dictates that the interrupt routine starts at 4. Essentially, the processor performs a call to location 4 and clears the GIE bit to take a interrupt. Some code is going to end up at location 4. It might as well be the one routine that benefits from being there.

    Without knowing the specifics of particular projects, the interrupt routine is likely the most timing sensitive. A GOTO takes two extra cycles for absolutely no benefit. It's not like you're making a tradeoff here to get something else.

    This is just a religious knee-jerk "rule of thumb".

  2. It's actually not as simple as just a GOTO at 4. The basic 14 bit core architecture can address 8192 words of program memory, which takes 13 address bits. The GOTO instruction only containst the lower 11 address bits, with the upper 2 address bits coming from the PCLATH register. Program memory can therefore be considered broken into pages, with each page being 2048 words long. A GOTO can jump to any word within the page specified by two bits in PCLATH.

    To jump to some arbitrary location in program memory therefore requires setting these two bits of PCLATH properly before the GOTO. But, you need the interrupt routine to preserve PCLATH. That means PCLATH needs to be saved somewhere first. However, it takes W to do that. That means W has to be saved before PCLATH can be saved. Depending on how you save these, STATUS may also have to be saved first since some of these operations can alter the status bits.

    This particular question was about the 12F683, which happens to only have one page of program memory. In this specific case, you could use the convention that the page bits in PCLATH are always kept cleared, and that the interrupt routine therefore does not need to set them. A GOTO at 4 would therefore work in this specific case, but only because this PIC doesn't have multiple pages of program memory. Doing that is still bad practise because this restriction built into the interrupt routine will be easily unnoticed if the code is ever moved to a PIC with more program memory.

  3. No, there are no further interrupt vectors that must not be overrun. This point is caused by confusion between the PIC 18 (16 bit "midrange" core) and the PIC 16 (used loosely for the 14 bit core) architecture. The PIC 18 does have two interrupt vectors, at 8 and 18h. High priority interrupts vector to 8, and low priority to 18h. If you are using both interrupt priorities, then you have to make sure the high priority routine doesn't overrun the low priority interrupt vector. Since a interrupt routine generally requires more than 8 instructions, this means a GOTO to elsewhere in the first 8 instructions. Note that this never applies to the low priority interrupt routine, only to the high priority routine if low priority interrupts are also in use, and only on a PIC 18. That's a different architecture, different instruction set, and different program memory addressing scheme than the PIC that was asked about, and doesn't pertain to this discussion.

So what's the right way? Here is the code I actually use on the basic 14 bit core:

;*******************************************************************************
;
;   Interrupt service routine.
;
;   The processor executes a call to location 4 on an interrupt, and in
;   addition globally disables interrupts.  These are re-enabled at the end
;   of the ISR by the RETFIE instruction.
;
;   Note that subroutine calls must be minimized or avoided in the ISR.
;   Since an interrupt can come at any time in the main code, any additional
;   call stack locations used here are not available anywhere else.
;
.intr_svc code   4           ;start at interrupt vector location
         movwf   w_save      ;save W
         swapf   status, w   ;make copy of status with nibbles swapped
         clrf    status      ;select direct and indirect register banks 0
         dbankis 0
         ibankis 0
         movwf   status_save ;save old STATUS value with nibbles swapped

  if save_fsr                ;FSR needs to be saved ?
         movf    fsr, w      ;save FSR
         movwf   fsr_save
    endif

  if ncodepages > 1          ;multiple code pages may be in use ?
         movf    pclath, w   ;save PCLATH
         movwf   pclath_save
         clrf    pclath      ;now definitely on code page 0
    endif
;
;   W, STATUS, FSR (if SAVE_FSR set), and PCLATH (if multiple code pages)
;   have been saved.  Direct and indirect register banks 0 are selected, and
;   the bank assumptions have been set accordingly.  Program memory page 0
;   is selected.
;

W is saved first, and W_SAVE must be in unbanked memory or replicated at the same offset in every bank. SWAPF is used to grab STATUS. This allows STATUS to be restored later (see below) without corrupting some of the status bits. STATUS is then cleared, which is the simplest way to set the two bank bits to a known state. Then STATUS is saved to STATUS_SAVE, which can now be at a single location in bank 0.

DBANKIS and IBANKIS are macros that are part of my system of managing the bank setting at build time. These two macros emit no code, but tell the build-time system that the direct and indirect bank settings are both 0.

Note that PCLATH is conditionally saved. My system maintains the NCODPAGES assembler constant for the target PIC to be the number of code pages that particular processor has. PCLATH is only saved if the machine has more than one code page. In the case of the 16F683, NCODPAGES is 1 and these instruction would not be included. However, if the code is ever moved to the next size up PIC, then NCODPAGES would be larger and PCLATH would be saved and restored by the interrupt routine. Note that in that case PCLATH not only needs to be saved, but also set to page 0 so that local GOTOs within the interrupt routine go to the correct page.

The code to return from the interrupt routine is:

         clrf    status      ;register bank settings are now 0
         dbankis 0
         ibankis 0
  if ncodepages > 1          ;multiple code pages may be in use ?
         movf    pclath_save, w ;restore PCLATH
         movwf   pclath
    endif

  if save_fsr                ;FSR needs to be restored ?
         movf    fsr_save, w ;restore FSR
         movwf   fsr
    endif

         swapf   status_save, w ;get old STATUS without changing status bits
         movwf   status      ;restore STATUS, register banks now unknown
         swapf   w_save      ;swap nibbles in saved copy of W
         swapf   w_save, w   ;restore original W

         retfie              ;return from interrupt, re-enable interrupts

Now it can be seen why the nibbles of STATUS were swapped earlier, which is so SWAPF can be used to read the saved byte into W. Unlike MOVF, SWAPF changes no status bits.