Programming in The PIC16 Family: Charles B. Cameron 18 January 2007
Programming in The PIC16 Family: Charles B. Cameron 18 January 2007
Contents
1 Introduction 3
1
List of Figures
1 Generating a machine program . . . . . . . . . . . . . . . . . . . 3
2
Assembly
language source
program
<name>.asm
ASCII version of
machine program PIC16F874
Assembler PICTSTART Plus
<name>.hex Microcontroller
MPASM Programmer
Flash Memory
Symbol definitions
<name>.inc
1 Introduction
Figure 1 illustrates the steps required in creating a program for execution by
a PIC16F874 microcontroller when the program is written in PIC16 assembly
language. The assembly language program is written as a text file with ASCII
characters and saved with the file extension .asm. The file may specify the
inclusion of other files having extension .inc. These are most commonly used
to hold definitions of symbols used by the programmer in writing the program.
The program and the included files can be created using any text editor.
However, Microchip provides a text editor with its free development environ-
ment, MPLAB, that is well-suited to the task. This editor recognizes the key
words that correspond to the 35 instructions of the PIC16 family of processors
and highlights them in a special color. In addition, the MPLAB program makes
it easy to perform the next step shown in Figure 1: the assembly of the pro-
gram by the MPASM assembler. The assembler scans the assembly language
program and its included files and converts them into equivalent machine code.
The output of the assembler is another text file with extension .hex. This is
another text file. However, its contents consist mostly of the 16 hexadecimal
characters 0 . . . 9A . . . F.
The MPLAB program also permits this file to be sent to a device called the
PICSTART Plus programmer. It converts the .hex file into a serial string of
ones and zeros and loads them into the flash memory of the processor itself.
Once the processor’s memory contains the program, it can be placed within
a suitable circuit. When power is applied, the stored program starts to execute,
3
processing its inputs and generating outputs as specified by the stored program
itself.
In this document we focus on the assembly language source program the
.asm file.
4
To understand these requires knowing what these operands really are. Table
15-2 on page 160 of the PIC16F87XA Data Sheet has a column showing the
14-bit pattern generated by the assembler for each of the 35 different kinds of
instruction. The number of times the letters d, f , k, and b appear in the 14-bit
pattern tells you how many bits the assembler requires from you before it can
fill in the required bits. For example, the BTFSS instruction needs three bits for
the b field and seven bits for the f field. To do this, the assembler reads the
operands and tries to squeeze them into the available space.
Of course, if you have numeric operands, your code will be all but unreadable.
You will use symbolic names in almost all cases. So the assembler will look up
the value of the symbol you have used as an operand and squeeze that into the
available space.
Here is a program fragment that illustrates this process.
1 X equ H’ 4A’ ; X = H’ 4A’ = D’ 5 8 ’
2 Y equ D’ 3 0 1 ’ ; Y = D’ 3 0 1 ’ = H’ 1 2D’
3 Z equ H’ 2 2 ’ ; L o c a t i o n 22 i s s c r a t c h s p a c e
4 STATUS equ H’ 0 3 ’ ; STATUS r e g i s t e r has a d d r e s s 3 .
5 W equ 0 ; W register specifier
6 F equ 1
7 RP1 equ 6
8 RP0 equ 5
9 ...
10 bcf STATUS, RP1 ; S e t bank 1
11 bsf STATUS, RP0
12 movlw X ; Load X
13 addlw Y ; Add Y t o i t
14 movwf Z ; Put t h e r e s u l t i n Z
The five equ directives tell the assembler to associate symbols with numeric
values. For example, X = 5810 = 4A16 .
The first bcf instruction needs a seven-bit f-field and a three-bit b-field. The
assembler uses the seven bits 000 0011 for the f-field because these are the least
significant seven bits of 0316 . It uses the three bits 110 for the b-field because
these are the least significant three bits of 616 = 01102 .
Similarly, the second bcf instruction uses 000 0011 for the f-field and 101
for the b-field.
Taken together, these two instructions cause the RP1 and RP0 bits of the
STATUS register to be set to the value 012 . When direct addressing is used, this
means that bank 1 is in use. All data addresses require nine bits. The remaining
seven bits will come from the instruction that is using direct addressing.
The movlw instruction needs eight bits for the k-field. It sees that the symbol
X has the value 4A16 = 0100 10102 and so these are the eight bits it uses. Using
an eight-bit constant that has been stored in an instruction is known as imme-
diate addressing: the data are immediately available, right in the instruction.
5
The addlw instruction contains a surprise. The assembler sees that the sym-
bol Y has the value 30110 = 12D16 = 0001 0010 11012 but extracts only the
least significant eight bits: 0010 11012 = 2D16 = 4510 . When the processor exe-
cutes the instruction, it knows nothing of the discarded bits. So the arithmetic
operation performed is not 58 + 301 = 359. Rather, it is 58 + 45 = 103. The
problem is that the programmer tried to cram a nine-bit value into an eight-bit
field and this is impossible.
The final instruction is movwf. It requires a seven-bit f-field. Symbol Z has
the value 2216 . Reference to Figure 2-4 in the PIC16F87XA Data Sheet shows
that this is one of the general purpose registers in bank 0. You may use these
registers for any purpose. Here, the location is being set aside to hold the value
of a variable Z. Note that the symbols X and Y are values associated with
variables X and Y whereas the symbol Z is an address associated with memory
where the value of variable Z will be stored. Because the values of X and Y are
fixed in program memory, they are not changeable except when the program is
written, so they are not really variables at all in the usual sense.
Some of the instructions cause the values of the Z, DC, and C bits in the
STATUS register to be determined. Table 15-2 in the PIC16F87XA Data Sheet
shows these instructions. If a status bit is not mentioned, then its value is
not altered by the instruction. This means that a program might not have to
examine a status bit immediately after it has been determined, as long as no
intervening instruction alters it.
The arithmetic and logic unit only computes three things: an eight-bit out-
put and the three status bits. The only decisions that can be made are based
on the three status bits. For example, if the eight-bit output of the ALU is
000000002 , then the Z bit will be set to the value 1; otherwise it will be reset to
the value 0. And this determination will only occur in the case of instructions
that determine Z, such as ADDWF and ANDWF. The DC and C bits are only deter-
mined by the two addition instructions and the two subtraction instructions.
Any decision that the program needs to make based on the output of the
ALU has to be based somehow on one or more of these three bits. So a program
cannot decide to do something if, say, the ALU outputs the value 73. But it
can subtract 73 from the output of the ALU, test the answer to see if it is 0,
and do different things depending on whether it is 0 or not.
6
We now consider how to relate the way one might program a task in C or
C++ and the equivalent code in the PIC16 family of microprocessors.
In each section, we present a fragment of code from the C programming
language (a subset of C++) and an equivalent fragment of PIC16 assembly
language code.
3.1 Assignment
In the C programming language, an example of an assignment is
1 x = 35;
For numbers that are not too large, this works very well in C. In many mod-
ern machines, C treats signed and unsigned integers as 32-bit numbers. In
the PIC16 family, registers only have eight bits, not 32. Therefore it is much
more common to run afoul of the processor’s limitations with PIC16 assembly
language programming than it is in C.
This particular assignment can be translated into PIC16 assembly language
very easily:
1 xinit equ D’ 3 5 ’ ; I n i t i a l value for x
2 x equ H’ 2 2 ’ ; A memory l o c a t i o n f o r x
3 ...
4 movlw xinit ; Retrieve x ’ s i n i t i a l value
5 movwf x ; Store the value in x ’ s l o c a t i o n
But what if you want to store a number that will not fit into an eight-bit
word? The usual approach is to allocate storage for as many words as needed.
For example, two eight-bit words would provide an aggregate of 16 bits, enough
for unsigned integers in the range from 0 to 216 = 65 536 or for signed integers in
the range from −32 768 to 32 767. Of course, the PIC16 does not know how to do
arithmetic on 16-bit numbers. It only knows how to do addition and subtraction
with eight-bit quantities. A sequence of additions can add this capability to the
PIC16.
Suppose, for example, that we want to add the 16-bit signed number x =
11 000 to the 16-bit signed number y = −12 000 to get z = x + y. Converting
these numbers to hexadecimal makes it clear that x = 2AF816 and y = 512016
when they are both expressed in the two’s-complement system. We could allo-
cate eight-bit storage for x1 and x0 and store 2A16 in the location for x1 and
F816 in the location for x0. Similarly, we could store 5116 in the location for y1
and 2016 in the location for y0.
To perform the required sum, we would need to do the following, all of which
can be done by the PIC16:
1. Set z1 to 0.
2. Add x0 and y0 to form z0.
3. If this resulted in a carry, increment z1.
7
4. Add x1 to z1.
5. If this resulted in a carry, the final result has a carry, too.
6. Add y1 to z1.
7. If this resulted in a carry, the final result has a carry, too.
8. The result is the final carry, if any, and the two bytes z1 and z0.
This is a lot for a beginning programmer to do. Here is a program that will
do it.
1 x1 equ H’ 2 2 ’ ; A memory l o c a t i o n for x1
2 x0 equ H’ 2 3 ’ ; A memory l o c a t i o n for x0
3 y1 equ H’ 2 4 ’ ; A memory l o c a t i o n for y1
4 y0 equ H’ 2 5 ’ ; A memory l o c a t i o n for y0
5 z1 equ H’ 2 7 ’ ; A memory l o c a t i o n for z1
6 z0 equ H’ 2 8 ’ ; A memory l o c a t i o n for z0
7 temp equ H’ 2 6 ’ ; Temporary s t o r a g e
8 ...
9 clrf z1 ; S e t z1 t o 0
10 clrf temp ; S e t temp t o 0
11 movf x0 ,W ; Get x0
12 addwf y0 ,W ; Add y0
13 movwf z0 ; S t o r e sum
14 bt fs c STATUS, C ; Was t h e r e a c a r r y ?
15 incf z1 , F ; Yes , so i n c r e m e n t z1
16 movf x1 ,W ; Get x1
17 addwf z1 , F ; Add i t i n t o z1
18 bt fs c STATUS, C ; Was t h e r e a c a r r y ?
19 incf temp , F ; Yes , r e c o r d t h e f a c t
20 movf y1 ,W ; Get y1
21 addwf z1 , F ; Add i t i n t o z1
22 bt fs c STATUS, C ; Was t h e r e a c a r r y ?
23 incf temp , F ; Yes , r e c o r d t h e f a c t
24 bcf STATUS, C ; Assume no c a r r y
25 bt fs c temp , 0 ; B i t 0 o f temp i s 1
26 ; i f t h e r e was a c a r r y
27 bsf STATUS, C ; so s e t t h e c a r r y b i t
8
3.2 Conditional Statements
It is easy to evaluate certain arithmetic conditionals in the PIC16 architecture,
but others are more difficult. This section looks at arithmetic comparisons
of the eight-bit values which can be stored in PIC16 registers. For equality
(=) and inequality (6=), it does not matter whether numbers are regarded as
unsigned integers or signed integers. However, for inequalities (<, ≤, >, and
≥), it matters a great deal.
In each of the conditional statement subsections below, we assume that the
variables a, b, and d have been defined as registers in the PIC16 microprocessor’s
program memory.1 For example, here is a way to place the decimal value 38
in the memory at address 20 hexadecimal and give that address the symbolic
name a: It entails declaring constants and reserved memory locations using the
equ directive.
1 a equ H’ 2 0 ’ ; Put ”a” i n l o c a t i o n 0 x20
2 avalue equ D’ 3 8 ’ ; ”a” w i l l be i n i t i a l i z e d
3 ; t o 38 ( d e c i m a l )
4 ...
5 movlw avalue
6 movwf a ; i n i t i a l i z e s the storage
7 ; l o c a t i o n f o r ”a”
9
3.3 Equality (a = b)
Subtracting a − b will set the zero flag Z if a = b and will reset it otherwise.
1 movf b ,W
2 subwf a ,W
3 btf sc STATUS, Z ; Do n e x t i f a = b
3.4 Inequality (a 6= b)
Subtracting a − b will set the zero flag Z if a = b and will reset it otherwise.
1 movf a ,W
2 subwf b ,W
3 btfss STATUS, Z ; Do n e x t i f a <> b
10
is, V = 1) if the result of the subtraction has the wrong arithmetic sign, an
indication that the difference is too big to fit in the eight bits available for
signed integers.
There are two ways this can happen.
1. If a ≥ 0 and b < 0, a − b should be positive: if it’s negative, overflow
occurred.
2. If a < 0 and b ≥ 0, a − b should be negative: if it’s positive, overflow
occurred.
A Boolean expression for this can be written if we calculate d = a − b. Then
V = a7 · b7 · d7 + a7 · b7 · d7 .
11
3.6 Less Than or Equal To (a ≤ b)
3.6.1 Unsigned Integers
This test is almost the same as for a < b, except we now should test the Z bit,
too, because a ≤ b if z = 1.
1 N equ 7 ; S i g n b i t i s t h e most s i g n i f i c a n t b i t
2 ...
3 movf b ,W
4 subwf a ,W
5 btf sc STATUS, Z ; Zero r e s u l t ( e q u a l i t y ) ?
6 goto LessThanOrEqual
7 btf sc STATUS, C ; Clear carry b i t ?
8 goto NotStrictlyLessThan
9 movwf a temp ; S i g n b i t = 1?
10 btfss a temp ,N
11 goto NotStrictlyLessThan
12 LessThanOrEqual
13 ...
14 goto Done
15 NotLessThanOrEqual
16 ...
17 Done
12
18 goto NotLessThanOrEqual
19 ThirdTest ; a > 0 and b < 0?
20 btf sc a ,N
21 goto LessThanOrEqual
22 btf sc b ,N
23 goto NotLessThanOrEqual
24 LessThanOrEqual
25 ...
26 goto Done
27 NotLessThanOrEqual
28 ...
29 Done
13
3.7 Strictly Greater Than (a > b)
Use the same method as for a < b, but interchange the rôles of a and b.
3.9 If . . . else . . .
C language construct:
1 i f ( x == c ) {
2 Do b l o c k 1 ;
3 } else {
4 Do b l o c k 2 ;
5 }
14
3.10 Do . . . while
C language construct:
1 do {
2 Block ;
3 } while ( x >= k ) ;
15
3.11 While . . .
C language construct:
1 while ( x >= k ) {
2 Block ;
3 }
16
3.12 For . . .
C language construct:
1 for ( i =0; i <n ; ++i ) {
2 Block ;
3 }
17
3.13 Table Look-up
C language construct:
1 x = list [ i ];
where list is n 8-bit characters and i is an index in the range [0, n − 1].
18
3.14 Switch Statement
C language construct:
1 switch ( x ) {
2 case n0 :
3 Block n0
4 break ;
5 case n1 :
6 Block n1
7 break ;
8 ...
9 case nk :
10 Blo c k nk
11 break ;
12 default :
13 DefaultBlock
14 }
19
27 goto Next
28
29 Blockn0 :
30 ...
31 goto Next
32
33 Blockn1 :
34 ...
35 goto Next
36
37 ...
38
39 Blocknk :
40 ...
41
42
43 Next :
20