We are going to concentrate on the PIC12C671 microcontroller for the following five reasons:
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.
All PIC processors are based on the Harvard architecture. This means thaey have separate address spaces for program and data memory.
The PIC12C671 can store 1024 14-bit machine instructions. That's only 2.25 kbytes, but it's enough to control simple devices.
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 . |
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:
k
is an eight-bit constant or
literal. The possible values of k
range from 0
to 255.f
is a file register.d
is a destination. There are only two
possible values for d
:
W
or F
. If
d
is W
, the result of the instruction is
stored in the W register. If d
is F
, the
result is store in a file register.b
is a bit index. It points to a
particular bit within a byte. Its range is 0 to 7All 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. |
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. |
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. |
Normally, the PIC processor executes one instruction and then proceeds to next. However, sometimes we need a change.
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
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 |
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.
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.
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
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
.
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.
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
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 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.
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.
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.
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.
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.
70h
.70h
is not zero, then return.71h
.71h
is not zero, then return.72h
.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
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.
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 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!
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.