6, 8, & 10 April 2002 ENGR 212 / CSCI 311 Lecture

Microchip PIC processors

We are going to concentrate on the PIC12C671 microcontroller for the following five reasons:

  1. The PIC computers are extremely popular in embedded systems applications. You probably already own a few of them. You just don't know it.
  2. The PIC12C671 instruction set is really quite simple. It has only 35 instructions.
  3. The PIC12C671 has a built-in Analog-to-Digital converter module
  4. Many of you used the PIC-based Boe-Bot in Dr. Gammon's Robotics class.
  5. The PIC IDE (Integrated Development Environment) has a pretty nifty simulator.

This page is only a summary of a few features of the PIC12C671 processor. If you want to know every thing about the PIC12C671, connect to the Microchip web site and download the 129-page PIC12C67X reference manual.

PIC memory

All PIC processors are based on the Harvard architecture. This means thaey have separate address spaces for program and data memory.

Program memory

The PIC12C671 can store 1024 14-bit machine instructions. That's only 2.25 kbytes, but it's enough to control simple devices.

Data memory

The PIC12C671 processors has 128 bytes of RAM. The Microchip folks call these general purpose file registers. There are also about 16 special purpose file registers that can be used for tasks such as reading the input pins.

The file registers are numbered from 00h (0 decimal) to ffh (255 decimal). However, several of these registers are unimplemented. If your program reads an unimplemented registers, the PIC always returns the value 0.

Because the registers are organized into two banks, tricky programming is required access all 128 general purpose registers. However, it's not hard to use 96 of these registers, in particular 20h (32) to 7Fh (127).

One further complication is that there are a few registers that are addressed by more than one number. For example, registers 70h and f0h are really two ways of referencing the same register.

There is also a W register which is used in many arithmetic operations. For example, if you want to add file registers 33h and 34h and store the result in file register 37h, you must

MOVE 33h,W Move the contents of register 33h to the W register.
ADDWF 34h,W Add the contents of register 34h to the W register.
MOVWF 37h Move the contents of the W register to register 37h.

Arithmetic and logical instructions

We're going to divide the "pure" arithmetic and logical instructions into three categories, according to where they store their results:

In the next three subsections, you'll see the "variables" k, f, d, and b used as follows:

Destination is W register

All these instructions, except one, performs an operation on W and a constant and then stores the result back in W.

CLRW Clears W to 0.
MOVLW k Sets W to k; that is, moves k into W.
ADDLW k Sets W to k plus W.
SUBLW k Sets W to k minus W.
ANDLW k Sets W to the bitwise-and of k and W.
IORLW k Sets W to the bitwise-or of k and W.
XORLW k Sets W to the bitwise-exclusive-or of k and W.

Destination is file register

These instructions modify file register f.

CLRF f Clears f to 0.
MOVWF f Sets f to w; that is, moves W into f.
BCF f,b Clears bit b of f to 0.
BSF f,b Sets bit b of f to 1.

Destination is W register or file register

All of these instructions perform an operation either using a single file register f or using a file register f and the W register. The destination of the resulting operation is either the source file register or the W register. If the last "parameter" of the instruction is F, the result is stored into the file register. If the last parameter is W, the result is stored into the W register.

INCF f,d Increment (add 1 to) f.
DECF f,d Decrement (subtract 1 from) f.
COMF f,d Complement (not) the bits of f.
SWAPF f,d Swap the nibbles, left and right half-bytes, of f.
MOVF f,d Move f. There really is a reason why you'd want to copy f back to itself.
RLF f,d Rotate f left one bit. This rotation also uses and modifies the carry bit. We'll have more on that later.
RRF f,d Rotate f right one bit. Again, the carry bit is involved.
ADDWF f,d Add f and W.
SUBWF f,d Subtract W from f.
ANDWF f,d And f and W.
IORWF f,d Inclusive or f and W.
XORWF f,d Exclusive or f and W.

Control instructions

Normally, the PIC processor executes one instruction and then proceeds to next. However, sometimes we need a change.

Unconditional branches

The GOTO is the simplest way to modify the flow of control. The single argument of the GOTO is a label, the address of another instruction to which control is transfered. . Here's an infinite loop. Try to figure out what it does.


loop    ADDLW   20h,F
        ADDLW   2
        GOTO    loop

By the way, when the PIC12C671 processor is reset, it always begins execute the instruction at location 0. Because instruction 4 has a special use, you should make sure the instruction at location 0 has a GOTO to the instruction at location 5.


        ORF     0h
reset   GOTO    start
        ORG     5h
start           ; this is where execution begins

Conditional branches

Of course, what we really need is a bit more control of where we go. We need a conditional instruction that goes according to the result of a test. In the PIC processor conditional execution is accomplished with skip instructions. For example, the instruction


        DECFSZ 35h,F

will decrement register 35h and skip the next instruction if the result is zero.

It takes a bit of practice to master the skip. Here's a little piece of C code that adds the numbers from 25 down to 1 together.


tot = 0 ;
for (i=25; i != 0; --i)
  tot = tot + i ;

Now, let's try that in the PIC assembly language.


I       EQU     30h     ; define I to be file register 30h
TOT     EQU     31h     ; define TOT to be register 31h

        CLRF    TOT     ; TOT = 0
        MOVLW   25      ; W = 25
        MOVWF   I       ; I = W = 25

FORL    MOVF    I,W     ; W = I
        ADDF    TOT,W   ; TOT = TOT + W = TOT + I
        DECFZ   I       ; I = I - 1
        GOTO    FORL    ; skip if I is 0

FORE:   ....            ; out of the loop

There are four PIC instructions that can skip. One increments, another decrements, and the other two check single bits of a byte.

INCFSZ f,d Increment f and store as specified by f. If the result is 0, skip the next instruction.
DECFSZ f,d Decrement f and store as specified by f. If the result is 0, skip the next instruction.
BTFSC f,b If bit b of f is 0 (clear), skip the next instruction
BTFSS f,b If bit b of f is 1 (set), skip the next instruction

Special file registers

The PIC12C671 processor has 17 special file registers. These are used to access and control the chip's:

Each of these registers consists of eight bits, and in some instances each of these eight bits control or access a different function of the PIC. Almost 50 pages of the PIC12C671 documentation describes these registers. We're only going to look at four of these registers in ENGR 212. That's enough to keep us busy.

Status register

File register 03h is the status register. For us there are two "interesting" bits of this register.

Bit 2 is the Z, or zero, bit. After an aritmetic or logical operations this bit is set (to 1), if the result of the operation is zero, and cleared (to 0), otherwise.

The Z bit is probably the most tested bit of the PIC processor. Suppose you want to write some PIC assembly code corresponding to the following C code:


  if (F20 == F21)
      F25 = 7

To accomplish this task, you should subtract F21 from F20 and then check the Z bit to see if the result is zero:


        MOVWF   20h             ; W = F20
        SUBWF   21h,W           ; W = F20-F21
        BTFSS   03h,2           ; skip next instruction if Z bit set
        GOTO    labelX
        MOVLW   7               ; W = 7
        MOVWF   25h             ; F25 = 7
labelX	...

MPASM, the PIC assembler has special names for 3, the number of the status register, and 2, the bit position of Z. You can use these to rewrite the skip involving the Z to much more readable statements as shown below.


        BTFSS   STATUS,Z

Another useful bit of the status file register is bit 0, the C, or carry, bit. After an addition or subtraction operation, this bit is set to the carry bit. This is useful if you wish to perform 16-bit arithmetic using the PIC's 8-bit adder. Think about it.

The C bit can also be used to compare numbers. To test if register 20h is less then regester 21h, subtract 21h from 20h and check the C. If C is 1, register 20h is indeed the smaller.

Finally, there is one more use of the C bit. When the RLF and RRF instructions are used to rotate a file register. The rotation is actually performed with the nine bits consisting of the file register plus the C bit.

Indirect register access

Suppose you wanted to store a C-like array of bytes in registers 40h to 4Fh. Since all of our PIC instructions use fixed registers, how could we index into this array? Similar problems would occur in accessing C-like pointers. Suppose register 25h contains the address of any register. How do we perform an operation similar to


  W = *F25

The special register pair 00h and 04h allow this sort of indirect address. Register 00h is also known as INDF; and register 04h, as FRS. If you read or write into INDF, you actually read or write into the register whose address is contained in register FRS.

Suppose an array is stored in registers 40h to 4Fh and that register 25h contains an index into that array. Here's a piece of code that uses file register indirection to read the addressed array element.


        MOVLW   40h     ; W = 40h -- note, W is 40h *not* the contents of F40!
        ADDLW   20h,W   ; W = W + F20
        MOVWF   FRS,F   ; FRS = W -- FRS is the address of the array element
        MOVF    INDF,W  ; W = *FRS -- the indexed array element is loaded

Another use of array indirection is a bit more exotic. On the PIC12C671 processor, instructions have seven bits that can be used to hold the address of file registers. This means that instructions can access file registers 00h to 7Fh. However, there are also a few significant files registers within the range 80h to FFh. The easiest way to access these file registers is via the FRS-INDF pair. For example, the following code sequence loads the contents of special register TRISIO (85h) into the W register.


        MOVLW   TRISIO
        MOVWF   FRS,F
        MOVF    INDF,W

PIC I/O

The PIC16C671 chip has six pins that can be used for digital input and output. Four of these six can also be used as a four-channel eight-bit analog-to-digital converter. It takes a few special registers to control these pins. We're going to ignore analog input here. We'll just show you how to set the six pins for digital I/O. We're also going to ignore several registers which can be used to handle interrupts.

For digital I/O, the six pins of the the PIC2C671 processor function as three-state interfaces to the "external" world. Externally, these pins are numbered 7 to 2 in chip pinout diagrams. However, internally your code will access them as bits 0 to 5 of a couple of special function registers. Pin 7 corresponds to bit 0, and pin 2 corresponds to bit 5. In the PIC assembler, These five pins are refered to as GP0 to GP5.

Being digital

On process reset, the chip I/O pins are initialized for analog input. To set the chip for digital input, you must set all three of bits 2 to 0 of special register ADCON1 (9Fh) to 1. Do this with the following code sequence:


        MOVLW   ADCON1
        MOVWF   FRS
        MOVLW   7
        MOVWF   INDF

We use file register indirection because ADCON1 is in the upper range of file registers.

Selecting I or O

Bits 5 to 0 of register TRISIO (85h) are used to set specific pins for either input or output. By default pins are set for input. Clear bit b, if the b'th pin is to be used for output.

In order to make is very clear about how your pins are being set in your program, start your program with code to set ADCON1 for digital I/O (if appropriate) and immediately follow that with code to set TRISIO. Here's some sample code to set TRISIO. By the way, TRISIO abbreviates "tri-state I/O".


        MOVLW   TRISIO
        MOVWF   FSR     ; FSR = W = &TRISIO
        BSF     INDF,0  ; GP0 (pin 7) is set for input
        ....
        BCF     INDF,5	; GP5 (pin 2) is cleared for output

1 or 0

Finally, we arrive at the special file register for actually reading and writing the pins, GPIO (05h). To write output pin GPi, just write to bit i of GPIO. Input pins are read similarly.

Here's a simple example. Suppose GP0 is set for input; and GP1 for output. Here's some code that "copies" GP0 to GP1.


        RLF     GPIO,W   ; W = GPIO << 1 
        ANDLW   2        ; W = GPIO & 2
        IORWF   GPIO,F   ; GPIO = GPIO | W

Adding the comments is left as an exercise the readers.

Chip configuration

Chip configuration is another complexity. When your program is downloaded to the PIC12C671, a special 14-bit configuration word is also placed in the processor. This configuration word can specify additional special uses for the I/O pins. For example, pin 4 (GP3) can be used as a Master Clear Reset for the chip, and pins 2 and 3 (GP5 and GP4) can also be used for an external clock.

Because the configuration word does not reside in normal program memory, it can not be set within your PIC assembly code. The MPLAB IDE (Integrated Development Environment) provides a way to saving the configuration word, along with your PIC assembly code, into a project for convenient download into the PIC processor.

Subroutines on the PIC

We start with a warning. The PIC family of processors does have a simple stack to hold subroutine call return points. However, that stack is not very large. On the PIC12c671 it can hold only eight return points. This means that the depth of ongoing subroutine calls must be eight or less. Since a computer with only 128 bytes of memory is an unlikely venue for fancy programming, so this should be a great problem.

Program counter

You can't understand the subroutine without understanding the program counter. The program counter records the address of the instruction being executed by the computer. Generally, the program counter is incremented by one each time an instruction is executed; however, some instructions, such as the goto, directly modify the program counter. For example, the real meaning of the following instruction is "Set the program counter to location l".


        GOTO   l

If you've guessed that the program counter is contained in a special file register, you're doing a good job of paying attention. If you've guessed it's in two special file registers, you're doing even better. You see, the PIC12C671 has room for 1024 machine instructions. Consequently, 10 bits are required to address all these locations. The lower 8 of these 10 are stored in special register PCL (02h) and the upper two are stored in PCLATH (0Ah). Fortunately we very rarely worry about PCL and PCLATH, and we'll just refer to the register duo as "PC" in this document.

Call and return

The PIC12C671 has one instruction for calling a subroutine and two instructions for returning from a subroutine call. The call is very similar to the goto. The difference is that the call pushes the address of the next instruction, or PC+1, onto the top of an eight-slot instruction address stack. The return instructions are very similar. Both pop, or remove, a value off the top of the stack and set the program counter to that value. That way, the subroutine routines to the instruction after the call.

Maybe we better look at an example.

A simple subroutine

How about a routine that increments a 24-bit number stored in the register pair 70h, 71h, and 72h. Assume the lower 8-bits are in 70h and the higher 8-bits are in 72h. The code for doing the incrementing isn't that hard. All we have to do if look out for the case when one of the 8-bit registers "rolls over", that is, turns from 255 to 0.

Using the property that bit ZERO of the STATUS register is set when the result of the increment is zero, we can write the PIC code as:


incr24				; start of the subroutine
        INCR    70h,F
        BTFSS   STATUS,ZERO
        RETURN
        INCR    71h,F
        BTFSS   STATUS,ZERO
        RETURN
        INCR    72h,F
        RETURN

Yep, that's it. So, how do we use it? Well, first we load registers 70h, 71h, and 72h with our arguments. Then all we need is a single PIC instruction.


        CALL   incr24
;; return to here

Calling conventions

The only means of passing values to and receiving results from PIC subroutine are the file registers. The writer of a PIC subroutine must clearly document the file registers used by his/her code. Furthermore, because it is likely that a non-trivial PIC routine will require additional "scratch" file registers for "local" variables, all used routines should be clearly documented.

In the real world, assembly language programmers follow calling conventions when writing subroutines. Calling conventions are rules which specify which registers or memory locations are used for arguments and which registers or memory locations are available for temporary results.

A subroutine which uses subroutines

Let's suppose you want to frequently reverse the bits of a byte, that is, turn 00110101 into 10101100 and turn 1010101011 into 11010101. So, you decide to write a subroutine called revb that performs this task. You also decide that revb will receive the input byte in the W register and will also store its output there. Finally, you decide that your routine will use registers 60h, 61h, and 62h as internal scratch registers. Here's the resultant code.


revb			; subroutine to reverse bits of a byte
			; input  -- passed in W
			; output -- returned in W
			; temps  -- register 60h is modified
	MOVWF	60h	; 60h now has the original byte
	CALL	revn	; W now has the reversed lower four bits
	MOVWF	61h	; 61h now has the reversed lower four bits
	SWAPF	60h,W	; W has has the original upper four bits
	CALL	revn	; W now has the reversed upper four bits
	SWAPF	61h,F	; 61h now has the reversed nibble in the upper half
	IORWF	61h,W	; W now has the whole enchilada reversed
	RETURN

The other return

The other return instruction, RETLW, not only pops the instruction stack and sets the PC, it also stores a new value into the W register. This provides an easy way to return, with a value, in a single instruction.

By devious programming combining the RETLW with some arithmetic on the PC, allows us to quickly lookup values in tables stored in program memory. For example, suppose we wanted to store a table of prime numbers and wanted to be able to quickly look up the i'th prime.

We would begin by storing the primes in a series RETLW instructions that begins are follows.


primes  RETLW   2
        RETLW   3
        RETLW   5
        RETLW   7
        RETLW   11 
        RETLW   13

Now suppose we wanted a function that would look up the i-th prime where both the argument to and result of this function is passed in register W. Assuming that both table and code are stored in the first 256 bytes of program memory, the following code does the trick!


primef  ADDLW   primes    ; W == W + primes
                          ; W is the address of an instrction within primes
        MOVWF   PCL       ; Set the PC to that instruction!

For more fun and information

Microchip has lots of on-line information about their processors available through their web site.

By the way, if you don't like PIC assembly language programming, it is possible to purchase C compilers for the PIC processors.