Here’s a head-scratcher: 8-bit PIC processors have a reset vector (where code execution starts, i.e. the value of the PC at reset) of 0x000. The interrupt vector is just four instructions higher, 0x004. What can a PIC coming out of reset accomplish in just a few instructions? A fair amount, it turns out.

Most general-purpose demonstration code doesn’t try to fit any meaningful action into such tight quarters. Since any useful program is bound to be more than four instructions long and have interrupts enabled, one of these four is invariably dedicated to a “goto” instruction to skip past the interrupt service routine, or ISR. Microchip’s XC8 (formerly Hi-Tech’s PICC) compiler doesn’t bother trying to fill the other three with any useful code at all: it merely rewrites the page register PCLATH explicitly with its reset value of zero, increments the PC via an equally meaningless “goto”–instead of a 100% faster “nop”–and then repeats the process with a real page location and a real “goto.”

That’s right, disassembled XC8 output begins with the following overly cautious code, in this case for the trivial “main() {}” program:

0x000: movlp   0
0x001: goto    0x002
0x002: movlp   7
0x003: goto    0x7fc

So at least this compiler’s output doesn’t give much insight into useful purposes for the first four memory locations, prior to the obligatory “goto” no later than 0x003. The first two instructions become the equivalent of a 3-cycle “nop” instruction. The next two instructions explicitly jump to code located at the top of memory. So basically a six-cycle “goto” over the ISR.

Assuming one page selection before the “goto” really could become necessary (if the ISR were to grow beyond one 2kword page–enough for 16 different interrupt sources with 256-word handlers each!), memory at 0x002 and 0x003 could always hold handy constants readily visible from a hex dump. In my own code I’ll sometimes replace any “nop” instructions at 0x002 and 0x003 with “movlw” opcodes that are my code version, values of my “#define” constants or some other at-a-glance indicator:

0x000: movlp   0
0x001: goto    start
0x002: movlw   CODE_VERSION
0x003: movlw   (REV_MONTH<<4)|REV_DAY

But clearly the architects of PIC could have made the reset vector be 0x002, thereby forcing 0x000 and 0x001 to be spent on a jump in all cases. They clearly left the door open for some useful work to get done before burning two instruction cycles (still at least 400ns on a modern 20MHz part) on a “goto” to get to the main code section.

Think quick!

I recently had occasion to make full use of the three instructions in the bottom of PIC instruction memory before the jump over my ISR. As part of a power management application I wanted to pull down on a pin if code happened to start executing from a cold (power-on reset) boot and that pin was low anyway.  This pin also drove a common cathode net for a bunch of Schottky diodes whose anodes were on other PIC pins, in order to clamp any signal back to the main microprocessor to within 0.2V of ground.

Likewise, after reading that pin’s state as an input and thus ascertaining the nature of the reset (cold POR versus warm) I changed it to an output and charged a capacitor on it up to the supply rail to allow the anode pins to toggle as well as to retain this state bit for any subsequent resets.  This seemingly complex decision can be made to fit in three instructions, due to several fortuitous behaviors:

  1. the BSR register defaults to zero at reset and thus PORTA/PORTB/PORTC can be read immediately
  2. “movf PORTA,f” has the effect of copying the current (input) state of those RA pins into their (output) latch register LATA on the first instruction cycle
  3. accessing the BSR then subsequently changing TRISA/TRISB/TRISC (to expose certain LATA/LATB/LATC bit states) takes the remaining two instructions before the “goto”

There are of course registers that can be read to ascertain the nature of a reset (STATUS and PCON) but there is too much bank selection and masking required for each register to make the decision within three instructions, let alone act on it.  Time is somewhat of the essence in my circuit since not only could the capacitor lose its charge due to leakage, but I also put a high-value discharge resistor to ensure that the next true cold boot would be correctly recognized.  Getting everything done before incurring a branch penalty cuts the execution time almost in half!

The following snippet of generic code senses the level of the RA0 pin and then keeps it driven to that state, thereby making an external capacitor-based scheme quite effective for quickly detecting and preserving information about a warm versus cold boot:

0x000:  movf    PORTA,f ; void main(void) { LATA = PORTA;
0x001:  movlb   2       ; 
0x002:  bcf     TRISA,0 ; TRISA &= 0xfe; //RA0 locked to initial value }

All eight pins RA0 through RA7 could even be nailed down with “clrf TRISA”.  Try getting the C code I’ve provided in the comments to compile down as tightly!

Advertisements