A beginners tutorial
Jim Brown BSc(Eng), HDipEdAd, GDE (Wits)
RTF --> HTML Translation by: Dan Creagan
Contents at a Glance
Contents
List of Programs
Introduction
Introducing the PIC84
The Instruction Set
Interrupts on the 84
Timers on the 84
Using the EEPROM data memory
Appendix: Program listings
Contents at a Glance
Contents
List of Programs
Introduction
What you need
What you should know
Introducing the PIC84
Instruction Set
A simple PIC84 program
Using MPLAB to debug the program
Pause to reflect
The Instruction Set
The STATUS register
1: Move instructions
MOVWF f (Move W to f, PIC p58)
MOVLW k (Move literal to W, PIC p58)
2: Clear instructions
CLRW (Clear W, PIC p55)
3: Arithmetic instructions
SUBWF f,d (Subtract W from f, PIC p61)
ADDLW k (Add literal & W, PIC p53)
SUBLW k (Subtract W from literal, PIC p61)
4: Logical functions
IORWF f,d (Inclusive OR W with f, PIC p58)
XORWF f,d (Exclusive OR W with f, PIC p62)
ANDLW k (AND literal with W, PIC p53)
IORLW k (Inclusive OR literal with W, PIC p57)
XORLW k (Exclusive OR literal with W, PIC p62)
COMF f,d (Complement f, PIC p56)
5: Decrementing & Incrementing
INC f,d (Increment f, PIC 57)
6: Bit setting & clearing
BSF f,b (Bit set f, PIC p54)
7: Program control
CALL k (Call subroutine, PIC p55)
RETURN (Return from subroutine, PIC p60)
RETLW k (Return with literal in W, PIC p59)
RETFIE (Return from Interrupt, PIC p59)
8: Skipping instructions
INCFSZ f,d (Increment f, skip if 0, PIC p57)
BTFSC f,b (Bit test f, skip if clear, PIC p54)
BTFSS f,b (Bit test f, skip if set, PIC p55)
9: Rotations & Swap
RLF f,d (Rotate left f thru carry, PIC p60)
SWAPF f,d (Swap nybbles in f, PIC p62)
10: Sleep & Watchdog Timer
CLRWDT (Clear watchdog timer, PIC p56)
11: Miscellaneous
OPTION (Not recommended)
TRIS (Not recommended)
Pause to reflect
Interrupts on the 84
Types of Interrupt and the INTCON register
Servicing an Interrupt
Timers on the 84
The TIMER0 module
Using the timers overflow
Using the EEPROM data memory
Appendix: Program listings
Program 1: simple . asm
Program 3: clears . asm
Program 4: arith . asm
Program 5: inter1.asm
Program 6: inter2.asm
Program 7: inter3.asm
Program 8: time0.asm
When I first started looking into programming the PIC84, I was daunted by what I saw as the complexity of the thing. After all, I was only getting into this for a hobby- not as a control engineer. I had had some experience years before with Zilogs Z80, so I knew what registers, op-codes and so on were, but was still a little perplexed. The PIC84 data sheet from Microchip is quite a technical document, and the MPASM manual is a reference. Neither of these documents served me as a tutorial, which is what I needed. So this manual is quite simply my idea of a tutorial for programming the PIC84, covering both the 84s instruction set and some MPASM directives. Along the way, it covers the 84 itself, in terms of registers, pins and so on.
By the end of working through this material you will not be a fully-fledged 84 expert. But you will be quite comfortable with the device, even though you wont necessarily have touched one!
First, you need a copy of this guide. Preferably your own copy, so you can scribble in it to your hearts content. Feel free to make as many copies of this as you wish- its freeware.
Second, you need to have some of Microchips documentation. As a minimum, youll need the PIC84 data sheet. This contains all the real info you will need on the chip itself. I refer to this from time to time: theres no point my trying to duplicate the detail of the info, or the diagrams. You should also have the MPASM User Guide. Both of these are available from Microchips website, at http://www.microchip2.com, or from their annual CD Microchip Technical Library. I refer to these thus: [PIC] or [MPASM] with a page, figure or table number; for instance [PIC figure 3.4.1] or [MPASM table 6]. [ed: The page, figure, and table numbers reffered to here come from an older version of the '84 datasheet which is available at http://www.piclist.com/images/piclist/30445B.PDF]
You could work through the material with just the above, but you will get a lot more out of it by being able to experiment. So, thirdly, load a copy of MPLAB (for Windows) onto your PC. This is their Interactive Development Environment (IDE), and it contains an editor, assembler and a simulator. Here, you can create your source code (editor), produce the executable code (assembler) and then run it on your PC (simulator). In the simulator, you can watch the program running, keeping an eye on the registers and even simulate events like externally caused changes to the I/O pins. You really should get this, and its also available as above. Work through its tutorial before you start this book, at least so you can find your way around it.
Thats all you need, but theres lots more you might like. Not least, is a PIC84 chip and some kind of PC based programmer. I havent covered the physical use of it in here, but might in a future volume. But there are many sources of info for that. I also found it handy to have a calculator that works in decimal, hex and binary for conversions: theres one in Windows which you just need to switch to scientific mode.
Of extreme help- perhaps in the longer run- are Microchips Application Notes (ANs). These are available from them, and a handy source is the CD-ROM mentioned above. These notes cover all sorts of uses of the PICs, with many helpful insights into real-world use. The ANs are in Adobes .pdf format: using Adobe Acrobat you can search through the ANs looking for terms like motor or serial and read the ANs appropriate to your needs.
One of the notes, AN585 on real-time operating systems for the PIC, refers to Real time programming- Neglected topics. I urge you to get your hands on a copy- it is a fascinating tutorial on the whole subject of interrupts, closed-loop control, and the like.
Its not really for me to say what you should know: I dont know who you are or what you do. But, I guess youll find it easier going if you do understand (or can find out about) basic computer terminology like bits, bytes & EEPROM and concepts like binary and hex. Of course, if youre using the simulator on your PC, you must feel comfortable with your PC and Windows, and have played with the IDE tutorial.
I certainly dont expect that you have any PIC84 knowledge- this tutorial is aimed at the absolute neophyte.
A microcontroller such as the 84 is- by its nature- a complete computer on a chip. It has 1: a processor and 2: registers, as well as 3: program and 4: data memory. This makes it different from mere CPUs which have only the processor and registers.
2: The 58 registers are the processors internal working storage. Referring to [PIC figure 4-2], youll see that some of these have names (STATUS, PORTB etc): these have special functions and well examine them later. PORTA, for instance, contains the contents of one of the I/O ports, namely port A. There are 36 general purpose registers, and we can consider these as ours (as opposed to the processors special ones) and use them as our scratch pad.
3: The 84 has 1K x 14 program memory: that means there are 1024 program locations, and each is 14 bits wide. Youll see from [PIC fig 3.4.1] that the memory goes from 0000h to 03FFh: 03FFhex is 1023 in decimal so there are 1024 locations including 0000. The 14 bits wide part comes from the length of each instruction: look at any one in [PIC section 4.2] and youll see that each is 14 bits long. For instance, ADDLW is 11 111X kkkk kkkk : dont worry about what this means, just count it! Well be using this memory extensively later on.
4: It has 64 bytes data memory, which is used for storage of, for instance, calibration values. Well look at the use of this EEPROM memory last in this booklet.
In addition, the device includes 5: four types of interrupt and 6: 13 I/O pins.
6: The use of the I/O pins is key to the use of a device like the 84, since any process consists of 3 parts- the input to the process, the process itself, and the output. The 84 has 13 pins configured as 2 ports: port A comprises 5 pins and port B has 8; and well be using them later.
There are 35 instructions in the PIC84 instruction set; all are 14 bits long, and consist of an opcode and operand/s. Basically, the opcode specifies what to do and the operand/s specify how or where. These instructions are split into 3 groups: byte-oriented, bit-oriented and literal & control. Later on we will use each and every one of these operations, firstly individually (usually trivially) and secondly with others in more meaningful scenarios.
For now, lets look at one of the instructions. Concentrate more on the format thereof, rather than the function which well see closely later. Ive chosen the instruction ADDWF- see [PIC table 4.2]. In this tutorial, I have adopted the Courier font below for all coding examples. The syntax of this instruction is:
and youll see this sort of look in many other instructions.
This is an appropriate time to introduce the working register: this is a special register (over and above those already mentioned) known as the W register, and it is where the arithmetic and logic unit (ALU) does the maths. Any instruction with the W in the name acts on the working register. Generally, some other register is known as F in these commands. So- almost intuitively- we can understand that the command ADDWF adds the working register to register F. But where is register F?
Have a look at [PIC fig 4-2]: each register (other than W, that is, which isnt in the figure since it is a special one) is given a hexadecimal address. For instance, PORTA is 05h, TRISA is 85h and we could use one of the 36 GP registers between 0Ch and 2Fh like 3Ch. The f in the command is a place holder for the actual address of the register F; remember there is no register F as such- it is a generic name for some register or other. So, we would code the instruction as:
Well, not quite- whats the d for? It is the destination of the result, and youll see this in many operations. Depending on our needs at the time, we need to choose to put the result either back into the working register or register F itself. [PIC page 51] explains that d may be 1 or 0: a 0 causes the result to go to W, and a 1 puts it in F. Finally, therefore, this command could be either of the following, depending on our needs:
ADDWF 3Ch,1 ; adds W to reg 3C, result to 3C
Lets get on with actually writing a simple program . . . .
This sample program serves a number of purposes. Apart from showing how to use a few instructions, it also introduces a couple of the assembler concepts and will also show some simple simulator techniques. The program, simple.asm is presented below; Ill walk you through it line by line.
;simple.asm ;to demonstrate the look & feel of a program ; and introduce some instructions & directives ;***************** setup ******************************* processor 16C84 ;processor type org 0010h ;origin of program in memory w equ 0 ;in byte instructions, use w & f f equ 1 ;instead of 0 & 1, its much clearer my_reg_1 equ H10 ;position my 2 registers in file my_reg_2 equ H15 ; map at h10 &H15 ;***************** program ****************************** ;all we're going to do, is load the accum (reg w) with ; some values and play around with some arithmetic in w ; and my_reg_1&2 movlw H08 ;put value H08 into w register movwf my_reg_1 ;move contents of w to my_reg_1 ; note- same as movwf 10h, since ; my_reg_1 and 10h are same thing movlw 32h ;put 32h into w reg addwf my_reg_1,f ;add contents of w to that of my_reg_1 ; answer goes into my_reg_1, due to f movlw 92h ;put value 92h into w register movwf my_reg_2 ;move contents of w to my_reg_2 ; note- same as movwf 15h, since ; my_reg_2 and 15h are same thing movlw 26h ;put 26h into w reg subwf my_reg_2,w ;subtr w from my_reg_2 ; answer goes into w end
Lets examine this sample program. You should verify the following by finding each directive or instruction in the appropriate Microchip book:
In the part called program, we meet a number of 84 instructions. Check the full descriptions in [PIC] if you like; well be looking at them later, anyway.
Lets assemble and run this program . . .
Using MPLAB to debug the program
This is a three step process: edit the source code, assemble it, and then run the simulator to see what happens. If you worked through the MPLAB tutorial, you will already know about the editing and assembling. Ill assume you have and do, and so we can move to the simulation.
With sample .asm successfully assembled, and the editor being the only window open in MPLAB, single-step through the program (the footstep icon). Not too helpful- you see each line of the program being highlighted as it is activated, but thats it.
So, lets start to use a few of the simulators other facilities. I suggest you open the following other windows in MPLAB. They all serve similar purposes- namely to see whats happening in the registers- but all implement differently. I found it useful to have them all open at once, to compare the implementations:
This window lists the contents of all file registers from 00 to 2F- ie, it does not show those in the region 80 to 8B. As you step through the program, you will be able to see the hex contents of registers 10h and 15h change. Note that any register whose contents have just changed is shown in red- this goes back to blue on the next step (unless theres another change of course).
Window > Special Function Registers
Here you can see the SFRs by name, rather than location which is what the previous window shows. The contents are shown in decimal, hex and binary.
Window > New Watch Window
This is your custom window into the registers, and it allows you to select which registers you want to monitor. All registers are available to choose, both SFRs and GPRs. When you choose New Watch Window, you are prompted with an input box and a down arrow. Click the arrow, and a list of all available registers appears: choose the one you want (eg, my_reg_1) and then OK. Youll see the contents of the register displayed in the window. To add other registers you can either click in the watch windows top-left-corner icon, and then choose Add Watch; or just press the Insert key (while the watch window is selected). You can save oft used combinations as .wat files, and then load them back later with Window > Load Watch Window.
A word of warning . . . Where did the list of register names to select from, come from? The SFRs are obvious: the MPLAB knows about them anyway. The others, like my_reg_1&2, come from the equ directives in your code, which is great. However, in our example, we have also used w equ 0 and f equ 1 although these are not intended as register names. However, they still appear in the list of possibilities. So whats the problem- just dont choose them you say. But dont forget that w is a register: so now there are 2 w entries in the list, one was there all along (the working register), and we caused the other with the equ. Using either of them, causes a symbol not found error in your watch table, meaning that you cannot monitor the working register if you have declared a w equ in your code. Try it yourself, then comment the offending line out with a ;, re-assemble and try again. Now you can use the working register in the watch.
With these three windows, step through your program: youve now got some insight into whats going on. Satisfy yourself that all is working to plan.
Where shall we go from here? Well, where are we now? Weve looked at the architecture and instruction set briefly; weve created a simple program and examined the code to get the feel of it; weve edited & assembled this program; and weve seen some of the simulators facilities.
I think we should move on to look more fully at the instruction set. I know that [PIC] covers all the registers, flags, interrupts, timers and stuff before going on to the instructions, but I found this the better way.
Microchip present full details of the instruction set in [PIC, section 9, page 51 on]. They group these into 3 categories which I dont find particularly useful. These are Byte- and Bit-oriented and Literal / Control operations. I have taken the view of grouping them into what we use each command for, such as performing arithmetic. Page 52 in [PIC] gives a one-page summary of the commands; I made a copy of this page loose from the data sheet, for easy reference. A windows help file is available on the Internet from Trisys Inc: I found this very useful. It contains details- similar to those in [PIC]- on the instruction set and the special function registers. Its a normal windows help file, with the expected hypertext links & searches.
For each instruction, I have explained the operation as described in [PIC], and have expanded on [PIC]s explanations where appropriate. (For instance, I have explained what an inclusive or actually is in IORWF.) Perhaps more importantly, I have suggested exercises to use the instructions as presented, with a possible solution given in the Appendix. The exercises suggested are quite trivial, but will afford you the opportunity to use each instruction, and also practice using the simulator. Remember to use things like the watch window to see whats going on as you step your program: keep an eye on registers you decide to use, as well as W and STATUS.
Microchips grouping does keep instructions of similar format together. The instructions look like this:
where f is the file register designation and d is the destination; if d=0, the result goes to the working register, w; if d=1, the result goes to register f
bit_command f,b
where f is the file register, and b is the bit therein; bits are numbered from 0 on the right, to 7 on the left. In the text, a bit is written as FILE_REG<n>, for instance INTCON<4> means bit no 4 in the intcon register at 0B
other_command k
where k is an 8-bit constant or literal.
The 84 implements a STATUS register at 03h, which contains the arithmetic
status of the arithmetic & logic unit; see [PIC fig 4-3]. Have a glance
down the Status Affected column of [PIC table 9-2], and you will
see that many 84 instructions affect certain parts of STATUS. Many
instructions affect STATUS<2>, Z- the Zero flag, which gets set if
the result of an operation was zero. Certain operations affect the carry
bits: STATUS<0>, C- the Carry bit, is set if a carry out occurs from
the left-most bit in the result; STATUS<1>, DC- the Digit Carry bit,
is set if there is a carry between the hex digits (ie, from the right hand
nybble (bit 3) to the left hand nybble (bit 4). Two commands affect
STATUS<3>, the Power Down bit PD, and STATUS<4>, the Time Out
bit TO.
We have met some of these instructions, which do nothing other than put things in registers.
(f)® (dest)
MOVWF f (Move W to f, PIC p58)
(w)® (f
MOVLW k (Move literal to W, PIC p58)
k® (W)
Exercise: Write a program to use these commands to (for instance) put something into W, move it from there to another register, put something else into W, and then move the original thing back to W. See moves .asm.
These 2 commands clear a register.
Exercise: Expand the above program to clear registers at the end. See clears .asm .
Performing arithmetic is pretty important: thats why computers are called computers after all. The 84 can only add and subtract though.
Arithmetic occurs either between W and an f register:
(W)+(f)® (dest)
SUBWF f,d (Subtract W from f, PIC p61)
(f)-(W)® (dest)
or between W and a literal:
(W)+k® (W)
SUBLW k (Subtract W from literal, PIC p61)
k-(W)® (W), [PIC p61]
Exercise: Use these and previous commands to load some registers and W, and do some adding and subtracting. Keep a close eye on the carry bit and on the zero bit in the status register. See arith .asm.
Technical aside: You may see in [PIC] that subtraction is performed using the 2s complement method. ¿Que? I hear you ask. This is the normal way that subtraction occurs in binary. Ill explain, then look at an example. Express both numbers in binary. Leave the number from which you are taking away, unchanged. Form the 2s complement of the one which is being taken away thus: change all 0s to 1s and all 1s to 0s (this is the complement), add 1 to the right hand digit, carrying to the left as necessary. Now add the result to the unchanged other number. Discard the carry at the left, if there is one. Thats the answer. Lets check . .
We want to subtract 20 from 27- we should get 7. Proceed as follows:
Convert to binary: 27®11011 . . . . x
20® 10100 . . . . y
Make 2s complement of y: complement: 01011
add 1: 01100 . . . . z
Add x and z: + 11011 . . . . x 00111 = 7 QED.
The 1 in brackets is the carry, which gets discarded.
At this stage, before we examine the logical functions provided by the 84, well discuss what logical functions are. Consider for the time being an electronic device of some description, with 3 wires attached. Lets say that 2 wires are inputs, and that what gets output on the 3rd wire depends on the inputs. Further, well say that this is a digital device, so that the 3 wires can only have binary values of 1 & 0.
What relationships can exist, then, between the 2 inputs and the output? The input combinations are easy: if wire/pin 1 can be 0 or 1, and so can pin 2, then the combinations are 00, 01, 10 & 11. What about pin 3: perhaps it must be a 1 if and only if the inputs are both 1 (ie 11); or perhaps a 1 if either or both inputs is 1 (ie 01, 10, 11). Or whatever!
The basic relationships are known as and and or; and
means both inputs while or means either or both. Most explanations
of this resort to the so called truth table, which I have drawn below
for the following logical operations: and, or, xor, nand, nor. Ill
explain them once youve had a look at the table.
Inputs A B | A AND B | A OR B | A XOR B | A NAND B | A NOR B |
both | either, both | either, not both | not both | neither, not both | |
00 | 0 | 0 | 0 | 1 | 1 |
01 | 0 | 1 | 1 | 1 | 0 |
10 | 0 | 1 | 1 | 1 | 0 |
11 | 1 | 1 | 0 | 0 | 0 |
The function and means that the result is 1 only when both inputs are 1. The or means that either input may be a 1 for output to be 1, but so may both. The function xor means exclusive or, and means that either input as 1 will cause a 1 to be output, and specifically excludes the situation where both inputs are 1. Lastly, the nand and nor are the negations of and and or respectively: compare the columns and youll see what this means.
By the way, the or function (as opposed to the xor function) is sometimes known as the inclusive or (ior). The 84 in fact uses this term.
The 84 provides a number of logical operations which act on 2, 8-bit values; these values are compared bit for bit. For example- without yet looking at an 84 instruction- consider the anding (is there such a word?) of the numbers H5F (equivalent to D95 and B01011111) and HA3 (which is D163 and B10100011); resulting in H03 (which is D3 or B00000011).
A3: 10100011
and: 00000011
Clearly, only in the rightmost 2 positions is the and satisfied, resulting is 1s there and 0s elsewhere. You should check this on the calculator in Windows, which has logical functions built in.
So, lets move to the instructions as provided by the 84.
Comparison occurs either between W and an f register:
or between W and a literal:
Lastly, you may have noted that the 84 doesnt provide nand or nor functions. Rather, it provides the means of complementing (negating) a register; this means to nand you must first and and then com:
Exercise: I suggest 2 things here. First, using Windows calculator, verify the results of the examples in [PIC] with each of the above: this will ensure that you understand the concepts. Then, write an assembler program to verify the 84 instructions, by loading the appropriate registers, performing the actions, then checking the results. See logic .asm.
5: Decrementing & Incrementing
Two simple instructions can decrement or increment the contents of a register, thus:
Exercise: In a program, perhaps by adding to one of the previous ones in which you do some arithmetic or some logic, check out that these commands do work. Keep an eye on the Zero flag, which is set if either command causes the register in question to go to zero: well rely on this fact in 2 more commands later. See dec_int .asm
Using the following 2 commands, you can set or clear any bit b in register f. But why would we want to? Two reasons come to mind:
Second, as well see in 8. below, we can use the fact that a bit is set or clear to skip instructions in our program. Being able to set or clear any bit gives us the ability to control this process.
BCF f,b (Bit clear f, PIC p54)
0® (f<b>)
1® (f<b>)
We read these two operations as "0 (or 1) becomes the content of bit b in register f".
Exercise: Sprinkle these commands into any of the programs above, and watch the changes to individual bits in your watch window. See bits .asm
For many reasons, we need to control the flow through our program. Normally, flow proceeds linearly from the top; often this is not suitable.
Firstly, we often need to loop through certain instructions: we might have a system in which one process is a continuous one. This might be the control of a bucket conveyor which continuously adds ingredients to a vat. Here, when a bucketful is added, we take it from the top and go through the same steps again, adding more on each pass.
Second, there may be part of our program which is useful in many other parts. Rather than repeating this code in many places, we separate the piece we like from the rest of the code; then we merely call it in as often as we like, from wherever. The reusable piece is called a subroutine. The subroutine returns control to the point from which it was called when its finished.
Lets look at looping first:
This instruction literally goes to k, which it does this by loading the address of k into the program counter, PC. In order to use goto you should start the line to which you want to go, with a label. Then you goto label. See [MPASM p15] for rules on labels.
Exercise: Modify one of the programs you have already written. You could put a label near the top, and a goto later on. As you step, youll see from the highlighted line that your program loops through the same part again and again. Look at the program counter (pcl) in a watch window and youll verify this. See goto .asm
Now we shall examine subroutines. We need to understand the concept of the stack first. Look at [PIC figure 4-1] and youll see a connection between the program counter and the stack. The stack- which has 8 levels in the 84- is the repository of where the PC was before the subroutine was invoked. This is so that the program can go back to take up where it left off when the subroutine is finished. Strictly speaking, the stack is loaded with the address of the instruction immediately after the one doing the sending; otherwise it would keep on going back and performing the instruction it had done earlier- we really want the next one. We refer to loading the stack as pushing and taking a value off later as popping. The stack is only accessible at the top: its like a pile of plates in one of those hoppers in a restaurant. The top of the stack is abbreviated as TOS.
There are 2 instructions associated with any subroutine- one to send the program off to it, the other to bring it back:
The call to a subroutine pushes the current PC+1 onto the stack, then changes the PC to the address k. This results in program flow jumping to the subroutine, but the address to come back to later is safely ensconced on the stack for later retrieval.
Return is the last instruction in the subroutine itself. The return instruction pops the stack, and so the program resumes in the right place. Think about the depth of the stack: it means that calls can be nested, and flow will be correct as long as the calls are matched by returns. Consider the following, which is OK:
CALL. . . . push 2nd address
RETURN. . . pop 2nd address
RETURN. . . . . pop 1st address.
But we dont want this, which is not OK:
CALL . . . push 2nd address
RETURN . . . . pop 1st address
RETURN . . . pop 2nd address.
As an alternative to the vanilla-flavoured return, you can bring a value back with you, thus:
This is the normal return, with the k® (W) added.
Exercise: Write a program to execute some code- anything you like- and then branch to a subroutine to do something else. Check that you return correctly. Use both types of return.
Problems? You probably had a little difficulty wondering where to put the subroutine in the source. How do you stop the subroutine being run by mistake? Because wherever you put it- either above or below the main part of the program, the programs natural flow will go through the subroutine, which is called subby here. If the stack is empty , this causes a stack underflow error, as when the subroutines return is encountered before a call has been made. If the stack has indeed been pushed by some other event- like an interrupt- then the return would be honoured but incorrectly.
. .
. .
label subby call subby
. subby code here .
. and here .
return label subby
. . subby code here
. . and here
call subby return
. .
. .
The way around this seems to be to proceed more or less as example 1, with the subroutines at the top. But, down below where the main part of the program is, have a label like start; above the subroutines, have a goto start to hop over them in the normal course of things. When a subroutine is called, the program shoots to the top: one subroutine will not flow into another, because the return will take care of this. Thus:
goto start label subby1 . return label subby2 . return start . . call subby2 . call subby1 . . etc
See subby .asm
Associated with these last 2 commands is another type of return, which is to do with interrupts. We will look at interrupts later, but for completeness Ive put the return from interrupt here:
When an interrupt occurs, the PC is pushed to the stack like with a subroutine call, so retfie pops it back out. Also, though, the occurrence of an interrupt disable further interrupts: one doesnt want interrupts interrupted after all. What happened was, that the interrupt caused the global interrupt enable flag, GIE (INTCON<7>) to be set to 0. Returning from an interrupt means that further interrupts should be allowed, hence retfie sets GIE back to 1.
There are 4 instructions which allow you to skip over the next instruction. Ill explain this before we look at the commands themselves.
In the conveyor example above, we dont want the vat to overflow, so lets have a level sensor in the vat (just like the ball-valve in the loo) which switches on when the vat is full. The conveyor is running, adding bucketsful; the program needs to monitor the switch, and when the vat is full (the switch closes) the program switches off the conveyor and starts some other part of the process, perhaps the mixer. This process is shown below: the dots show some or other process happening before and after the part of interest. Ive used line numbers for ease of reference. This is not in 84 assembler!
. line # 40: start conveyor . . line # 63: is the level sensor switch set? line # 64: no- go back to line # 63 and look again line # 65: yes- stop the conveyor line # 66: start the mixer and carry on . .
In this example, we need to skip line 64 when the vat is full. When the vat still has room, this code will cause more ingredient to be added; the conveyor will stop and the mixer will start when the vat full sensor has switched on.
Now we can look at the commands which do this skipping in the 84:
(f)-1® (dest), skip if result = 0
INCFSZ f,d (Increment f, skip if 0, PIC p57)
(f)+1® (dest), skip if result = 0
The above commands are based on the decf and incf commands seen earlier: the sz part means skip if zero.
Exercise: Verify these 2 instructions by loading a start value into a register called count or something. Then use either instruction to change it, looping through this block of code. Check that the instruction following the decfsz or incfsz (probably a goto, to cause the looping) is skipped or not, as appropriate. (This approach would work if the conveyor had to add say 10 bucketsful, rather than wait for a sensor.) See skip .asm.
skip if (f<b>)=0
BTFSS f,b (Bit test f, skip if set, PIC p55)
skip if (f<b>)=1
Read these instructions as bit test f, skip if clear/set".
These commands could be used to check the vat full sensor mentioned above. The bit to be tested would be the bit of a port on the 84: it would be set or cleared by the sensor from time to time.
Exercise: Write a program with a block of code to loop through. These can be just about any instructions, and will simulate the conveyor as nonsense. Have a btfss at the bottom, followed by a goto back to the top. Have some more code then, which simulates the mixer coming on. The btfss will need to refer to one of the 84s I/O ports as its f (H05 or H06 for porta & b respectively), and you can use any pin 0-4 on porta, 0-7 on portb you wish. You should have another goto right at the bottom, to make a sort of outer loop back to the top- this might not be too real-life, but youll want the whole program to loop to test what happens when the pin changes. See bits .asm
But how do we get the pin to change its state, simulating the vat full sensor? Theres an easy way of doing this in MPLABs simulator. Go Debug > Simulator stimulus > Asynchronous stimulus; youll get a table of Stim0 to Stim12 buttons. Right click on any one, perhaps Stim7, and click Assign pin. Double click on the pin youve elected to use as the sensor, perhaps it was RA3, say. Stim7 then changes to RA3 . Now right click on the button again: you can choose to pulse, make low, make high or toggle the pin. Here, we would want to toggle the pin, since the vat sensor will be changing from time to time probably, as it empties and fills.
Now were all set to test. Step through your program. Depending on the
initial state of the pin, which [PIC table 4-1] says is unknown at power
up, the program will loop or not. (At the inner loop, that is; it will always
go back to the top from the very bottom, for testing purposes.) Any time
you like, left click on your chosen Stim button, now known as RA3 or whatever.
(If you have the port showing in a watch window, youll see the pin
change state at the next instruction boundary.) In any case, next time the
program gets to the btfss step, it should behave differently, since
weve toggled the pin and so the btfss should get a different
answer.
Three instructions allow you to manipulate the bits within a register. Of these, two slide the bits to the right or left (through the carry bit), the third switches the registers two nybbles over.
Each bit in the f register is moved 1 to the right; the one that falls off the right is circled round to the carry, and the carry moves in from the left.
RLF f,d (Rotate left f thru carry, PIC p60)
Each bit in the f register is moved 1 to the left; the one that falls off the left moved into the carry, and the carry is circled round to the right.
SWAPF f,d (Swap nybbles in f, PIC p62)
(f<3:0)® (dest<7:4>), (f<7:4>)® (dest<3:0>)
Exercise: Observe the effect of these commands on the contents of some registers. See rot_swap.asm.
These 2 commands are related, in that the WDT is one of the ways of waking up from sleep. I cover the whole notion of timers later, so for now accept these instructions for what they are.
0® WDT, 0® WDT prescaler, 1® TObar, 0® PDbar
CLRWDT (Clear watchdog timer, PIC p56)
0® WDT, 0® WDT prescaler, 1® TObar, 1® PDbar
You should not use the following 2 instructions, which are included to provide backward compatibility:
We have now met each 84 instruction, with the necessary background if appropriate, and used them in simple programs. Now, well move on to explore two important areas of the PIC84: interrupts and timers. It is these two areas which move the 84 into the real world.
Put simply, its exactly what it says: a means of getting the computers attention. Refer to the conveyor/mixer scenario mentioned earlier (p17), where we saw the following snippet of pseudocode:
. line # 40: start conveyor . . line # 63: is the level sensor switch set yet? line # 64: no- go back to line # 63 and look again line # 65: yes- stop the conveyor, then line # 66: start the mixer and carry on . .
Here, the program will loop around line 63 and 64 for ever if necessary, waiting for the vat to fill up. Thats OK, the programs job is to run the process! Well, true, but its wasteful. It might take a few seconds, minutes or hours to fill the vat; we could have the program doing something useful while its waiting. But if the programs off somewhere else- doing the payroll?- and the switch closes, how will the program know? Its not looking.
Same way as you get someones attention when you come to visit. Theyre not standing looking out the front door all day, theyre going about their daily business; your knock on the door interrupts this, and they come to the door.
Our program would look something like this:
. line # 40: switch on the conveyor . line # 50: do some payroll work line # 51: and some more, and stay up here until interrupted . . . line # 60: only come here when interrupted line # 61: stop the conveyor line # 62: switch on the mixer line # 63: go back to where you were when you got sent here
Now, the program spends most of its time in the payroll section, not looking at the switch in the vat. When the switch closes, the hardware takes care of things: the switch is connected to the computer by the interrupt request line (IRQ), and program control is passed to a predetermined point. That point is line # 60 in the example above, and what happens at line 60 is up to the program/mer.
Notice something crucial here: once the interrupt has been taken care of, control must go back to where it was earlier. Not only that, but the state of the system must be put back: if any registers were changed while handling the interrupt, they must be restored. Well look into this later.
Now we know what an interrupt is in concept, we can look at how the 84
allows for this notion.
Types of Interrupt and the INTCON register
The 84 has 4 different types of interrupt [PIC p44]:
an external interrupt on pin 6, aka RB0
a change on any of pins 10-13, aka RB4-7
a timer overflow
an EEPROM write complete.
(Of these 4 types, the first one is the kind wed use in the above conveyor/payroll program.)
In order to allow interrupts, and to determine the source thereof, the processor uses the INTCON register (0Bh) whose contents are detailed in [PIC table 4-5], and which Ill explain here. Each bit serves a specific purpose, and interrupts work according to the value (set or clear) of the various bits.
Firstly, interrupts as a whole may be enabled or disabled by INTCON<7>, the GIE (Global Interrupt Enable) bit. If this bit is clear, then no interrupts may occur: this is the power-up value by the way. Also, if an interrupt occurs, then GIE is cleared to prevent the interrupt being interrupted; returning from the interrupt with the retfie instruction re-enables interrupts.
Second, each of the interrupt types must be enabled with its own bit in INTCON, before it can be used:
external interrupt on pin 6: INTCON<4>, the INTE bit
change on any of pins 10-13: INTCON<3>, the RBIE bit
timer overflow: INTCON<5>, the T0IE bit
EEPROM write complete: INTCON<6>, the EEIE bit.
Third, when interrupts occur, certain bits (known as interrupt flags)are set so that we may determine the source of the interrupt:
external interrupt on pin 6: INTCON<1>, the INTF bit
change on any of pins 10-13: INTCON<0>, the RBIF bit
timer overflow: INTCON<2>, the T0IF bit.
Why do we need to know the source? Well, depending on the type of interrupt,
we will take different action: checking the appropriate bit tells us which
interrupt it is. Note that the interrupt flags always get set when theres
an interrupt, regardless of the state of the corresponding enable bit.
In the conveyor/payroll example above, there must be a means of sending the program to the right place to sort out the interrupt; line 60 in the example. How does the processor know where to go? In the PIC84, all interrupts are sent to 0004h: in the parlance of microprocessors, this point is the interrupt vector, and the program is said to vector there. At the vector, we must make sure that we provide whatever code is necessary to take care of the problem.
A crucial point is that we might need to save the status of the machine as it was before interruption, before we service the interrupt. Clearly, the activities undertaken during servicing could change such things as the W & STATUS registers; we would need these restored after handling the interrupt, in order that our other work may resume. The 84 only saves the PC to the stack (PC+1, actually), and its up to us to ensure we save and restore anything else we need!
Lets walk through a scenario in pseudocode, without yet looking at the 84 code. Foster suggests 3 parts to a program which must handle simple interrupts: an initialize section, where in our case well enable the interrupts; a main section, where the bulk of the time is spent (doing the payroll, or whatever) and the handler part where the interrupt is taken care of.
set INTE, INTCON<4>, to enable pin 6 interrupt
Handler: save the status of the machine; probably W & STATUS
branch to the correct sub-handler for the
interrupt type
do whatever is required for the interrupt
restore the status
return
Main: do the payroll calculations
Exercise: Start slowly with interrupts- this can get quite hairy. Write a program to provide the absolute minimum interrupt ability: enable them, have an interrupt service routine that does little if anything other than re-enable interrupt and then return, and a main part that merely loops through some meaningless code waiting for an interrupt. You need not save the status of the machine, nor check for the type of interrupt at this stage. In order to see this program working properly in the simulator, I suggest using the interrupt on change in port b, using the asynchronous stimulus technique discussed before. Use a toggle on the pin to effect the change: while the program is looping in the main section, toggle the pin and check that the interrupt occurs when you next step the program. While the program is then in the isr, toggle the pin again: if its all working, the pin change will not cause an interrupt, because GIE will have been cleared. See inter1 . asm.
Exercise: Now add to the program, to save the state of the machine at the interrupt. To test this, make sure you load W (say) in the main part, then change it in the ISR, and check that it gets restored properly. Hint: see [PIC p46, example 8-1]. See inter2.asm
Exercise: Finally, allow for more than 1 kind of interrupt- change on one of portb pin 4:7 as well as an interrupt on the rb0/int pin. Now, youll need to determine the kind of interrupt, and handle it accordingly. See inter3.asm.
The basic idea, is that we will want to use our 84 to time something. Time, that is, in the way we would use a wall clock: we might switch on our fish-tank lights for instance, or close the curtains at 18h00 each night. But theres no clock in the 84: no clock in the wall clock sense anyway.
What there is though, is a simple relationship between the carrying out of instructions and the speed with which the processor ticks away. The book tells us [PIC p1] that an instruction completes in 1 cycle, namely 400ns when the processor is being clocked at 10MHz.
Lets verify this, and introduce a handy feature of MPLAB at the same time: the Stopwatch. In MPLAB prepare to run any of the programs you have written up to now. Open the stopwatch by going Window > Stopwatch. Youll see a simple window, containing 3 important pieces of information: the number of steps completed in what time, and at what frequency. Check that the frequency is set to 10MHz for now, click zero and step your program once. You should see that you progress to 1 cycle in 400ns. (Unless this was coincidentally a program branch, which is a 2-cycle instruction.) Experiment with various frequencies; for instance 400ns becomes 40ns at 100MHz, or 800ns at 5MHz.
So, we have the glimmerings of a wall clock type of timer here- we know how much time has passed if we know how many steps have occurred. Enter Timer0 . . .
The PIC84 timer, TIMER0, works on the basis that each time an instruction cycle occurs, a certain interval of time has elapsed. When TIMER0 is running, its register (TMR0) is incremented each cycle; each 400ns if the clock is a 10MHz one. Therefore, the value of TMR0 represents the time in 400ns steps. But TMR0, like all registers, is an 8-bit one and can thus only count as high as FF: that means time only goes as far as 255 x 400ns = 102000ns or about 100 m s which is not too long. Cast your mind back to the discussion of interrupts earlier, where we said that one of the interrupt types was a timer overflow interrupt: that means that the TMR0 register has gone over the top at FF and back to 00. That means the interrupt occurs every 100m s of real time. Its up to us to do something with this interrupt, which probably means incrementing a register of our own, clocked say: every time clocked gets to a certain value, that means 1 second of real time, and wed probably then go off and increment another register, perhaps seconds
Lets walk through the process, then, of simply getting TIMER0 going, using the initialize, handler, main interrupt technique used earlier.
set T0IE, INTCON<5>, to enable timer interrupt
clear T0CS, OPTION<5>, to enable timer- see
set PSA, OPTIOPN<3>, to keep prescaler out of this
Handler: increment clocked, our overflow counter
Main: have some trivial loop to keep the program going
Exercise: Write a program to implement the above simple timer, bearing in mind we wont be doing anything with the timer overflows other than clicking them in clocked. Check the program in the simulator, having visible any registers you think you should monitor, also the stopwatch. Before you start, youll need to figure out how to access the option register, which is not quite as simple as accessing say status. Ive explained it below. See time1.asm.
Accessing registers in Bank1: Youll have noticed in [PIC fig 4-2, p12] that some registers like option are in what they refer to as Bank1, as opposed to Bank 0. Others, like status are in both Banks. What this means in practical terms, is that these Bank 1 registers are not normally available, because our default sphere of operations is Bank 0. We use the status register to effect a switch between banks, with RP0 & RP1 (STATUS<5> & <6>) doing the trick. This simply means that we must set RP1 to go to Bank1 and clear it to return. For those registers which are in both banks, you can of course access them no matter where you are, but remember the register has 2 different addresses: status is known as h03 and h83.
Now we can probably see the wood through the trees: we have a register clocked which is updating whenever the timer overflows. Thats roughly every 100m s. Being an 8-bit register itself, clocked can of course only count to 255, and so it will overflow at 255 x 100m s which is 2.55 x 10-2 seconds. Well need to count these overflows too, into say clocked2. Now, were getting closer to the actual second, which is what were heading for: how high do we need to count in clocked2 to reach 1 second? Incrementing every .0255 seconds there are roughly 39 increments in a second , so this means, finally, we can count real-time seconds by incrementing another register, perhaps SECONDS, every 39 steps. To get to minutes and hours, we can simply overflow SECONDS into MINUTES at 60 seconds and MINUTES into HOURS every 60 minutes.
Exercise: Create a program to implement the above, at least as far as the SECONDS register.
The following programs are intended as illustrative of the use of the commands discussed. They are certainly trivial, and probably not paragons of programming technique! In each, I have added a section called simulator: this section is a suggestion for things you might like to do during the simulation of your program. For instance, you might need to have a watch window open showing the W register and portb, as well as having the stack open too. Of course, its up to you how you design and code these programs, and how you experiment with them in the simulator.
;moves.asm to show how MOVF, MOVWF & MOVLW work ;*********************************** simulator *** ;watch window: reg1, w, pcl ;*********************************** setup *** processor 16c84 reg1 equ h'10' ;*********************************** program *** start: movlw h'05' ;load w movwf reg1 ;move (w) to reg1 movlw h'82' ;change w movf reg1,0 ;restore w end
;clears.asm to show how clrf & clrw work ;based on moves.asm ;*********************************** simulator ;watch window: reg1, w, pcl ;*********************************** setup processor 16c84 reg1 equ h'10' ;*********************************** program start: movlw h'05' ;load w movwf reg1 ;move (w) to reg1 movlw h'82' ;change w movf reg1,0 ;restore w clear: clrf reg1 ;clear reg1 clrw ;clear w end
;arith.asm to show using ADDWF, SUBWF, ADDLW, SUBLW ;************************************* simulator ;watch window: reg1,reg2,status,w,pcl ;************************************* setup processor 16c84 reg1 equ h'10' reg2 equ h'12' ;************************************* program loads: movlw d'20' ;load w movwf reg1 ;load reg1 movlw d'80' ;load w anew movwf reg2 ;load reg2 arith: addlw d'05' ;add d'05' to w sublw d'100' ;sub w from d'100' addwf reg1,1 ;add w to reg1, into reg1 subwf reg2,1 ;sub w from reg2, into reg2 end
;inter1.asm is a simple interrupt handler- ; it does not save the state of the machine ; nor does it determine the kind of interrupt. ; ;*********************************************simulator ;watch window: intcon, pcl ;stack: have this open too- see the return address come & go ;asynch stimulus: have a toggle on rb4 ;********************************************* setup processor 16c84 movlw h'0' movwf h'0b' ;clear intcon goto main ;hop over the isr ;********************************************** isr start isr: org h'0004' ;interrupts always vector to here nop ;here we actually do the stuff nop ; of the interrupt bcf h'0b',0 ;clear int before going back retfie ;this re-sets intcon gie ;*********************************************** isr ends ; ;*********************************************** main start main: org h'0020' ; leave enough room for the isr! bsf h'0b',7 ; set global int enable bsf h'0b',3 ; set change on b int enable payrol: nop ; loop here until interrupted, nop ; doing payroll stuff goto payrol ; nop nop end
;inter2.asm does save the state of the machine ; but does not determine the kind of interrupt. ; ;****************************************************simulator ;watch window: intcon, pcl, portb, w, w_saved ;stack: have this open too- see the return address come & go ;asynch stimulus: have a toggle on rb4 ;**************************************************** setup processor 16c84 w_saved equ h'10' ;a place to keep w movlw h'0' movwf h'0b' ;clear intcon goto main ;hop over the isr at the beginning ;**************************************************** isr start isr: org h'0004' ;interrupts always vector to here movwf w_saved ;save w as it was in main movlw h'65' ;do something to change w nop ;do more isr stuff ;now, restore w: there is ; no "movfw"- 2 swapf's seems to be ; the way to do this.... swapf w_saved,1 ; first, swap w_saved into itself swapf w_saved,0 ; then, swap it into w bcf h'0b',0 ;clear int on b before going back retfie ;this re-sets intcon gie ;***************************************************** isr ends ; ;****************************************************** main start main: org h'0020' ; leave room for the isr! bsf h'0b',7 ; set global int enable bsf h'0b',3 ; set change on b int enable payrol: nop ; loop here until interrupted, nop ; doing payroll stuff movlw h'0f' ; simulate a calc by loading w goto payrol ; nop nop end
;inter3.asm saves the state of the machine ; and determines the kind of interrupt. ; ;**************************************************simulator ;watch window: intcon, pcl, portb, w_saved, w ;stack: have this open too- see the return address come & go ;asynch stimulus: have a toggle on rb4(for rbi) and rb0(int) ;************************************************* setup processor 16c84 w_saved equ h'10' ;a place to keep w movlw h'0' movwf h'0b' ;clear intcon goto main ;hop over the isr at the beginning ;*************************************************** isr start isr: org h'0004' ;interrupts always vector to here movwf w_saved ;save w as it was in main ;find out what kind of interrupt btfsc h'0b',0 ; is it an rbi? call _rbi ; yes- call the rbi 'sub-handler' btfsc h'0b',1 ; is it an int? call _int ; yes- call the int 'sub-handler' ;end up here after sub-handler ;now, restore w swapf w_saved,1 ; first, swap w_saved into itself swapf w_saved,0 ; then, swap it into w bcf h'0b',0 ;clear rbif before going back bcf h'0b',1 ;clear intf before going back retfie ; this re-sets intcon gie _rbi: movlw h'65' ;do something to change w return _int: movlw h'24' ;do something different to w return ;***************************************************** isr ends ; ;**************************************************** main start main: org h'0020' ; leave room for the isr! bsf h'0b',7 ; set global int enable bsf h'0b',3 ; set change on b int enable bsf h'0b',4 ; set ext int on pin6 enable ; we've got 2 types of int! payrol: nop ; loop here until interrupted, nop ; doing payroll stuff movlw h'0f' ;simulate a calc by loading w goto payrol ; nop nop end
;time0.asm to see how timer0 works ; ;************************************ setup processor 16c84 STATUS EQU H'03' OPTIO EQU H'81' tmr0 EQU H'01' BANK_ EQU H'05' T0CS EQU H'05' PSA EQU H'03' clocked equ h'10' INTCON equ h'0B' T0IE equ h'05' GIE EQU H'07' goto top ;************************************ isr isr: ORG h'0004' incf clocked ;clocked is the 'wall clock' BCF INTCON,2 retfie ;************************************ program top: org h'0010' clrf tmr0 ;clear the tmr0 register clrf clocked mode: bsf STATUS,BANK_ ;switch to page 1 bcf OPTIO,T0CS ;go from counter to timer bsf OPTIO,PSA ;no prescaler yet bsf INTCON,GIE bsf INTCON,T0IE loop: nop nop goto loop END
Questions:
I am new to PIC programming. Is there any good links from which i can learn the basics of PIC programming in C. I have downloaded MPLAB from microchip. Can you plese guide me where to start from.
James Newton answers: "I believe these are sample programs that come with MPLAB or MPASM"
Interested:
See:
See also:
Comments: