previous | start | next


The Instruction Set

Microchip present full details of the instruction set in [PIC, section 9, page 51 on]. [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 the current datasheet for the modern version of the '84 is available at microchip.com] They group these into 3 categories which I don’t 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. It’s 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 what’s going on as you step your program: keep an eye on registers you decide to use, as well as W and STATUS.

Instruction format

Microchip’s grouping does keep instructions of similar format together. The instructions look like this:

byte_command f,d

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 STATUS register

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.
 
 

1: Move instructions

We have met some of these instructions, which do nothing other than put things in registers.

MOVF f,d (Move f, PIC p58)

(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.

2: Clear instructions

These 2 commands clear a register.

CLRF f (Clear f, PIC p55)

00h -> (f)

CLRW (Clear W, PIC p55)

00h -> (W)

Exercise: Expand the above program to clear registers at the end. See clears .asm .

3: Arithmetic instructions

Performing arithmetic is pretty important: that’s why computers are called computers after all. The ’84 can only add and subtract though.

Arithmetic occurs either between W and an f register:

ADDWF f,d (Add W & f, PIC p53)

(W)+(f) -> (dest)

SUBWF f,d (Subtract W from f, PIC p61)

(f)-(W) -> (dest)

or between W and a literal:

ADDLW k (Add literal & W, PIC p53)

(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 2’s complement method. ¿Que? I hear you ask. This is the normal way that subtraction occurs in binary. I’ll explain, then look at an example. Express both numbers in binary. Leave the number from which you are taking away, unchanged. Form the 2’s 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. That’s the answer. Let’s check . .

If we want to subtract 20 from 27 we should get 7. Proceed as follows:

Convert to binary:
27 -> 11011 . . . . x
20 -> 10100 . . . . y

Make 2’s complement of y by inverting each bit and adding one
10100 . . . . y
   01011 complement:
+ 00001 add 1
= 01100 . . . . z

Add x and z:
   01100 . . . . z
+ 11011 . . . . x
[1]00111 = 7 QED.

The 1 in brackets is the carry, which gets discarded.

4: Logical functions

At this stage, before we examine the logical functions provided by the ’84, we’ll discuss what logical functions are. Consider for the time being an electronic device of some description, with 3 wires attached. Let’s say that 2 wires are inputs, and that what gets output on the 3rd wire depends on the inputs. Further, we’ll 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. I’ll explain them once you’ve 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 you’ll 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 H’5F’ (equivalent to D’95’ and B’01011111’) and H’A3’ (which is D’163’ and B’10100011’); resulting in H’03’ (which is D’3’ or B’00000011’).

 5F: 01011111
 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, let’s move to the instructions as provided by the ’84.

Comparison occurs either between W and an f register:

ANDWF f,d (AND W with f, PIC p53)

  1. .and. (f) -> (dest)

IORWF f,d (Inclusive OR W with f, PIC p58)

  1. .or. (f) -> (dest)

XORWF f,d (Exclusive OR W with f, PIC p62)

  1. .xor. (f) -> (dest)

or between W and a literal:

ANDLW k (AND literal with W, PIC p53)

  1. .and. k -> (W)

IORLW k (Inclusive OR literal with W, PIC p57)

  1. .or. k -> (W)

XORLW k (Exclusive OR literal with W, PIC p62)

  1. .xor. k -> (W)

Lastly, you may have noted that the ’84 doesn’t 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:

COMF f,d (Complement f, PIC p56)

complement of (f) -> (dest)

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:

DEC f,d (Decrement f, PIC p56)

(f)-1 -> (dest)

INC f,d (Increment f, PIC 57)

(f)+1 -> (dest)

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: we’ll rely on this fact in 2 more commands later. See dec_int .asm

6: Bit setting & clearing

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:

First, as an example, the register in question might be a port controlling some external equipment. Each bit could be switching a different device: a motor, a light or whatever. Setting and clearing each bit switches each device on or off.

Second, as we’ll 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>)

BSF f,b (Bit set f, PIC p54)

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

7: Program control

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 it’s finished.

Let’s look at looping first:

GOTO k (Go to address, PIC p57)

k -> (PC)

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, you’ll 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 you’ll 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 you’ll 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: it’s 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:

CALL k (Call subroutine, PIC p55)

(PC)+1 -> TOS, k -> (PC)

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 (Return from subroutine, PIC p60)

TOS -> (PC)

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 1st address
      CALL  . . . . push 2nd address
      RETURN. . . . pop 2nd address
      RETURN. . . . . pop 1st address.

But we don’t want this, which is not OK:

      CALL . . . . push 1st address
      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:

RETLW k (Return with literal in W, PIC p59)

k -> (W), TOS -> (PC)

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 program’s 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 subroutine’s 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.

  ;example 1 ;example 2
  . .
  . .
  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 I’ve put the return from interrupt here:

RETFIE (Return from Interrupt, PIC p59)

TOS -> (PC), 1 -> GIE

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 doesn’t 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.

8: Skipping instructions

There are 4 instructions which allow you to skip over the next instruction. I’ll explain this before we look at the commands themselves.

In the conveyor example above, we don’t want the vat to overflow, so let’s 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. I’ve 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:

DECFSZ f,d (Decrement f, skip if 0, PIC p56)

(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.

BTFSC f,b (Bit test f, skip if clear, PIC p54)

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 ‘84’s I/O ports as its f (H’05’ or H’06’ 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 you’ll 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? There’s an easy way of doing this in MPLAB’s simulator. Go Debug > Simulator stimulus > Asynchronous stimulus; you’ll get a table of Stim0 to Stim12 buttons. Right click on any one, perhaps Stim7, and click Assign pin. Double click on the pin you’ve 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 we’re 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, you’ll 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 we’ve toggled the pin and so the btfss should get a different answer.
 
 

9: Rotations & Swap

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 register’s two nybbles over.

RRF f,d (Rotate right f thru carry, PIC p60)

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.

10: Sleep & Watchdog Timer

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.

SLEEP (Sleep, PIC p60)

0 -> WDT, 0 -> WDT prescaler, 1 -> TObar, 0 -> PDbar

CLRWDT (Clear watchdog timer, PIC p56)

0 -> WDT, 0 -> WDT prescaler, 1 -> TObar, 1 -> PDbar

11: Miscellaneous

NOP (No operation, PIC p59)

This instruction literally does nothing

You should not use the following 2 instructions, which are included to provide backward compatibility:

OPTION (Not recommended)

TRIS (Not recommended)

Pause to reflect

We have now met each ’84 instruction, with the necessary background if appropriate, and used them in simple programs. Now, we’ll move on to explore two important areas of the PIC’84: interrupts and timers. It is these two areas which move the ’84 into the real world.





previous | start | next