Consider the simple delay loop: movlw 47 ; Loop 47x3 instruction cyclesdecfsz WREG, f, 0 ALU Fast Stack Instruction Register/ Decode Second Instruction Register STATUS WREG BSR FSR Instr
Trang 1; i = ArrayVar(3) // Simulate an array read
movlw 2 ; Calculate Offset to 3rd Elementaddwf FSR, f
movf INDF, w ; Get the 3rd Elementmovwf i ; and store itmovlw -2 ; Restore FSR to point to first elementaddwf FSR, f
This code has to first add 2 to the current address in the FSR, followed by loadingand storing the third element and then returning the index pointer to the first element
in the array Now, compare this to the same code for the PIC18 in which FSR0 (whichmeans that the PLUSW0 register will be used to access the data) points to the start ofArrayVar
; i = ArrayVar(3) // Simulate an array read
movlw 2 ; Want Offset to the 3rd Elementmovff PLUSW0, i ; Move Contents of ArrayVar(3) into i
The PREINC and POSTDEC INDF registers can be used for popping and pushing,respectively, data onto a stack pointed to by an FSR register The POSTDEC INDF reg-ister is used for the push operation because it will allow the access of pushed data usingthe PLUSW INDF register as shown in the previous example
Using FSR0 for the stack, the byte push function could be as simple as:
BytePush: ; Push the contents of “i” onto the stackmovff i, POSTDEC0
return
and the byte pop could be:
BytePop: ; Pop the top of the stack
movff PREINC0, ireturn
The PLUSW INDF register comes in useful for high level functions in which datahas been pushed onto the stack to implement temporary variables In the example below,
I have specified a function that uses a data stack and with the parameters and local ables (the same thing) being pushed onto a stack implemented with FSR0:
vari-; int StackDemo(char i, char j) // “i” is stack top, “j” is one less
; {
; char k = 0; //
“k” is at two less than stack top
movlw 0 ; Initialize “k” to zeromovwf POSTDEC, 0
;
; i = j + k; // Perform a basic calculationmovlw 2 ; Get offset to “j”
Trang 2movff Temp, PLUSW0 ; Store value in “Temp”
movlw 1 ; Get offset to “k”
movf PLUSW0
addwf Temp, f, 0 ; Add “k” to “j” in “Temp”
movlw 3 ; Get offset to “i”
movff PLUSW0, Temp ; Store result
While this code may look very complex, it is actually simple and, once you are fortable with it, very easy to implement This capability is also critical for efficientimplementation of compilers that implement local variables as shown here
com-DATA PROCESSING INSTRUCTIONS
The PIC18 has some added flexibility and conventional capabilities compared to the otherPIC microcontroller processors As you look through the PIC18 instruction set, you willsee that the additions and modifications to its instruction set make it more similar to that
of other processors while retaining the PIC18’s ability to create very efficient code The most significant addition to the PIC18’s data processing instructions is thesubfwb(Fig 7.49) instruction This instruction carries out a subtract operation withborrow in the order most people are familiar with if they have worked with other proces-sors Instead of the typical PIC microcontroller subtraction instruction:
Result = (Source Value) – WREG [- !C]
the subfwb instruction executes as:
Result = WREG – (Source Value) - !C
ALU
Fast Stack
Instruction Register/
Decode Second Instruction Register
STATUS WREG BSR FSR
File Registers
Instruction Bit Pattern:
12345678 12345678
Instruction Operation: Destination = WREG - Reg - !C
Flags Affected:
N, OV, C, DC, Z Instruction Cycles:
1
Notes: This instruction behaves like a “Traditional” Subtract
and is different from the “Standard” subtraction Instructions
Available in the other PICmicro architectures
0 1 0 1 0 1 d a f f f f f f f f
Figure 7.49 Thesubfwbinstruction provides the expected subtract operation
instead of the addition of the negated value of WREG used by the other subtract instructions.
Trang 3This instruction frees you from the need of thinking backwards when subtractioninstructions are used in an application To use the subfwb instruction, WREG is loadedwith the value to be subtracted from (the subtend) and the value to take away (the sub-tractor) is specified in the instruction This means that if you have the statement:
A = B – C
the values of the expression can be loaded in the same left to right order as the PIC controller instructions and use the sequence:
micro-bcf STATUS, C, 0movf B, w, 0subfwb C, w, 0movwf A, 0
This is the same order as would be used in most other processors Note that I resetthe carry flag before the instruction sequence to avoid any possibilities of the carry beingreset unexpectedly and taking away an extra 1, which will be very hard to find inapplication code
A PIC18 16-bit subtraction operation could be:
bcf STATUS, Cmovf B, w, 0subfwb C, w, 0movwf A, 0movf B + 1, w, 0subfwb C + 1, w, 0movwf A + 1, 0
Or if you want to save on the instruction used to clear the carry flag at the start of thesequence:
movf C, w, 0subwf B, w, 0movwf A, 0movf B + 1, w, 0subfwb C + 1, w, 0movwf A + 1, 0
Another difference between the PIC18 and the other PIC microcontroller processors
is the inclusion of the negf (Fig 7.50) instruction, which can negate any register inthe PIC18’s register space
The single instruction cycle multiply instructions multiply the contents of WREGagainst the contents of another register (mulfw) or a constant (mullw) and store the16-bit product in the PRODH:PRODL register combination (Fig 7.51) These instruc-tions are very well behaved and will work for 2’s complement numbers and can pro-vide you with some basic digital signal processing (DSP) capabilities in the PIC18
Trang 4EXECUTION CHANGE INSTRUCTIONS
The PIC18’s execution change instructions, upon first glance, should be very familiar toyou if you are familiar with the other PIC microcontroller families The PIC18 has thebtfsc, btfss, goto, and call of the low-end and mid-range PIC microcontrollersalong with the compare and skip on equals (cpfseq), greater than (cpfsgt), and lessthan (cpfslt) The PIC18 also has the enhanced increment and skip on result not equal
to zero (infsnz and dcfsnz) Along with these similarities, the PIC18 has four newfeatures that you should be aware of (and remember their availability) when you are devel-oping applications for it
The first feature that you should be aware of is the goto and call instructions, whichcan directly any address in the program memory space As shown in Fig 7.52, theseinstructions are built from two 16-bit words and contain the entire 20 word address bits
to allow you to jump anywhere in program memory
ALU
Fast Stack
Instruction Register/
Decode Second Instruction Register
STATUS WREG BSR FSR
File Registers
Instruction Bit Pattern:
1 Notes: All Flags are Affected by this Instruction
0 1 1 0 1 1 0 a f f f f f f f f
Figure 7.50 Thenegfinstruction will two’s complement ny register in the
PIC18 register space.
PRODH:PRODL
8x8 Multiplier
WREG Parm (Register for mulwf Constant for mullw)
Figure 7.51 The two single instruction
cycle 8 by 8 multiplication instructions store
their result in PRODH:PRODL.
Trang 5The call instruction (as well as the corresponding return) instruction has the bility, when a 1 is specified as the s bit at the end of the instruction, of saving the con-text registers WREG, BRS, and STATUS in shadow registers of the fast stack, whichare retrieved by the return instruction by specifying a 1 as well The issue that youshould be aware of for the context save is that you can only save one set of values onthe fast stack and the context values of the mainline are always saved when an interrupt
capa-is acknowledged Thcapa-is limits the usability of the context regcapa-ister save to applicationsthat only have a single deep call and no interrupts
For your first PIC18 applications, I would recommend that you use the instructionset’s single word instructions only The only time you should be using the goto or callinstructions is if you have to access a memory location outside the range of the relativebranches This range is –512 to +511 instruction addresses for the bra (branch always)and rcall (relative call) instructions and –64 to +63 instruction addresses for the con-ditional branch instructions that I will discuss below The rcall instruction informa-tion is shown in Fig 7.53
Along with using the single word execution change instructions, I also recommendthat you be careful when using the $ directive and branching relative to it When theassembler is calculating addresses, it works on a byte basis, not a word basis as it doesfor other PIC microcontrollers This means that you must multiply the number of instruc-tions by 2 to get the correct address Consider the simple delay loop:
movlw 47 ; Loop 47x3 instruction cyclesdecfsz WREG, f, 0
ALU
Fast Stack
Instruction Register/
Decode Second Instruction Register
STATUS WREG BSR FSR
Instruction Bit Pattern:
12345678 12345678
Instruction Operation:
Call:
if (s == 1) Push Context Registers;
Push Next Address;
Jump to Address Goto:
Jump to Address
Flags Affected:
None Instruction Cycles:
2
Notes: “Call” and “Goto” areTwo Word
Instructions Each Instruction can
Access ANY Program Memory Location
in the PICmicro “Call” can optionally
do the “Fast Stack” Context Register Save
call 1110110s nnnnnnnn
12345678 12345678 1111nnnn nnnnnnnn
12345678 12345678 goto 11101111 nnnnnnnn
12345678 12345678 1111nnnn nnnnnnnn
File Registers
Figure 7.52 The PIC18 call and gotoinstructions provide the capability
of accessing any address in program memory without the need of updating the PCLATH or PCLATU registers.
Trang 6If you were to enter the code into the PIC18InsTemplt.asm project and build it,you would get a warning indicating that the instruction cannot start at an odd address.
To fix the problem, you have to multiply the offset by 2, producing the code:
movlw 47 ; Loop 47x3 instruction cycles
decfsz WREG, f, 0
bra $ - (1 * 2)
which will build cleanly and you can simulate to see that it actually takes 141 (47times 3) instructions If you want to avoid this difference between the PIC18 andthe other devices, I would recommend that you always use labels and never use rel-ative addressing
Above, I indicated that there was a one word goto instruction called bra (branchalways) This instruction type (shown in Fig 7.54) changes the program counter accord-ing to the 2’s complement offset provided in the instruction according to the formula:
PCnew = PCcurrent + 2 + Offset
where PCcurrent is the current address of the executing branch instruction The 2added to PCcurrent results in the address after the current one Offset is the 2’scomplement value, which is added or subtracted (if the Offset is negative) from thesum of PCcurrent and 2
The MPASM assembler computes the correct offset for you when the destination of abranch instruction is a label MPASM computes the 2’s complement offset using the formula:
Offset = Destination – (Current Address)
Program Memory Register Space
ALU
Fast Stack
Instruction Register/
Decode Second Instruction Register
STATUS WREG BSR FSR
File Registers
Instruction Bit Pattern:
Instruction Operation:
Push Next Address;
PC = PC + 2 + 2's Complement “n”;
Flags Affected:
None Instruction Cycles:
2
Notes: Rcall 2’s Complement Offset is
Added to the Address of the Next
Instruction Note, the 2’s Complement
Offset MUST be even
11011nnn nnnnnnnn
Figure 7.53 Thercallinstruction allows accessing subroutines that
start –64 to +63 instructions from the current program counter location.
Trang 7If the destination is outside the range of the instruction it is flagged as an error by theMPASM assembler
Along with the nonconditional branch, there are 8 conditional branch instructionsavailable in the PIC18 and they are shown in Fig 7.54 They are branch on zero flag set(bz), branch on zero flag reset (bnz), branch on carry flag set (bc), branch on carryflag reset (bnc), branch on negative flag set (bn), branch on negative flag reset (bnn),branch on overflow flag set (bov), and branch on overflow flag reset (bnov) Theseinstructions are equivalent to the branch on condition instructions found in otherprocessors
These instructions behave similarly to the bra instruction except that they have
8 bits for the offset address (to the bra instruction’s 11) This gives the instructionsthe ability to change the program counter by –64 to +63 instructions
The last new feature of the PIC18 architecture that is different from the other tectures is the fast stack, in which WREG, STATUS, and BSR registers are saved non-conditionally upon the interrupt acknowledge and vector jump and conditionally during
archi-a subroutine carchi-all instruction These registers carchi-an be optionarchi-ally restored archi-after archi-a return
or retfie instruction
Tables PIC18 tables are executed as:
TableRead:
movwf TableOff, 0bcf STATUS, C, 0 ; First Calculate if past first 256 rlcf TableOff, w, 0 ; addresses and by how much
Program Memory Register Space
PC Program Counter Stack
ALU
Fast Stack
Instruction Register/
Decode Second Instruction Register
STATUS WREG BSR FSR
File Registers
Instruction Bit Pattern:
12345678 12345678
Instruction Operation:
BC/BNC: Branch on Carry Flag BN/BNN: Branch on “N” Flag BOV/BNOV: Branch on “OV” Flag BZ/BNZ: Branch on Zero Flag BRA: Branch Allways
Flags Affected:
None Instruction Cycles:
12345678 12345678
BN 11100110 nnnnnnnn
12345678 12345678 BNN 11100011 nnnnnnnn
12345678 12345678 BOV 11100100 nnnnnnnn
12345678 12345678 BNOV 11100101 nnnnnnnn
12345678 12345678
BZ 11100000 nnnnnnnn
12345678 12345678 BNZ 11100001 nnnnnnnn
12345678 12345678 BRA 11010nnn nnnnnnnn
Figure 7.54 The branch instruction can access addresses –512 to +511 instructions from the current program counter location.
Trang 8addlw Table & 0xFF
If the purpose of the computed goto is to return a byte value (using retlw), then
I would suggest taking advantage of the 16-bit instruction word, store 2 bytes in aninstruction word, and use the table read instructions to read back two values This is some-what more efficient in terms of coding and requires approximately the same number ofinstructions and instruction cycles
A computed byte table read (which allows compressed data) consists of the ing subroutine
TBLRD * ; Read byte at address
movf TABLAT, w, 0 ; Return the byte
Trang 9microcon-multiple FSR registers available and the ability to jump anywhere within the tion without using the PCLATH or PCLATU registers If an FSR register is requiredwithin an interrupt handler, chances are it can be reserved for this use within the appli-cation when resources are allocated
applica-When a hardware interrupt request is acknowledged, the current WREG, STATUS,and BSR are saved in the fast stack The PCLATH (and PCLATU) registers should nothave to be saved in the interrupt handler unless a traditional table read (i.e., using a com-puted goto) is implemented instead of a table read using the built-in instructions (andshown in the previous section) The goto and branch instructions update the programcounter without accessing the PCLATH and PCLATU registers These conditions willallow a PIC18 interrupt handler with context saving to be as simple as:
org 8
Int
; #### - Execute Interrupt Handler Code
retfie 1
so long as nested interrupts are not allowed and subroutine calls do not use the fast stack
PROCESSOR CONTROL INSTRUCTIONS
The PIC18Cxx has the same processor instructions as the other PIC microcontrollers,but there is one instruction enhancement that I would like to bring to your attention Whendesigning the PIC18Cxx, the Microchip designers did something I’ve wanted for years:they created a nop instruction (Fig 7.55) that has two bit patterns, all bits set and all
Program Memory Register Space
ALU
Fast Stack
Instruction Register/
Decode Second Instruction Register
STATUS WREG
BSR FSR
File Registers
Instruction Bit Pattern:
12345678 12345678
Instruction Operation:
Flags Affected:
None Instruction Cycles:
Figure 7.55 Thenopinstruction is coded as either all bits set or
all bits reset.
Trang 10bits reset The profoundness of this instruction and what can be done with it will ably not be immediately obvious to you
prob-In the PIC18, just the patch space instructions that are to be modified are changedand no space is required for jumping around instructions For the same example in thePIC18, the patch space would be:
Trang 118
ASSEMBLY-LANGUAGE SOFTWARE TECHNIQUES
The PIC®microcontroller is an interesting device for which to write application software
If you have experience with other processors, you probably will consider the PIC controller to be quite a bit different and perhaps even “low end” if you are experiencedwith RISC processors Despite this first impression, very sophisticated application soft-ware can be written for the PIC microcontroller, and if you follow the tricks and sugges-tions presented in this chapter, your software will be surprisingly efficient as well Much of the information I will give you in this book will leave you scratching yourhead and asking, “How could somebody come up with that?” The answer often lies innecessity—the application developer had to implement some features in fewer instruc-
tions, in fewer cycles, or using less variable memory (file registers in the PIC
micro-controller) For most of these programming tips, the person who came up with them notonly had the need to do them but also understood the PIC microcontroller architectureand instruction set well enough to look for better ways to implement the functions thanthe most obvious
At the risk of sounding Zen, I want to say that the PIC microcontroller is best grammed when you are in the right “head space.” As you become more familiar withthe architecture, you will begin to see how to exploit the architecture and instruction-set features to best implement your applications The PIC microcontroller has beendesigned to pass and manipulate bits and bytes very quickly between locations in thechip Being able to plan your applications with an understanding of the data paths inmind will allow you to write applications that can require as little as one-third the clockcycles and instructions that would be required in other microcontrollers This level ofoptimization is not a function of learning the instruction set and some rules Instead, it
pro-is a result of thoroughly understanding how the PIC microcontroller works and beingable to visualize the best path for data within the processor and have a feel for the dataflowing through the chip
Copyright © 2008, 2002, 1997 by The McGraw-Hill Companies, Inc Click here for terms of use
Trang 12LIST R=DEC ; Device Specification
INCLUDE “p16cxx.inc” ; Include Files/Registers
; Variable Register Declarations
any-to delete or change the interrupt handler code because the vecany-tor at address 0x004
Trang 13is specific to the mid-range chip I first created this template around 1998, and it hasremained very constant over the years;I first started creating assembly-languagetemplates for IBM PC assembly-language programming, and this practice has served
me well with the PIC microcontroller as well as other devices
The title and _version at the top of the file show what the application does sothat I can scan the files very quickly instead of going by what the file name indicates.The title line will show up on the top of each new listing file page The _versiondefine statement then will show the code revision level and can be inserted in any textdisplayed by the application
There may be a Debug define directive after the _version define directive if theDebuglabel is going to be tested for in the application This directive is used withconditionally assembling code to take out long delays and hardware register opera-tions that are not available within the MPLAB IDE simulator Before building theapplication for burning into a PIC microcontroller, the Debug define is changed sothat the “proper” code will be used with the application Later in this book I will dis-cuss the Debug defines in more detail and how they can help you with debugging yourapplication code
Next, I put in a description of what the application does, along with its update tory (with specific changes) One thing that I do that seems to be a bit unusual is that Ilist the hardware operating parameters of the application I started doing this so that Icould create stimulus files easily for applications This seems to have grown into amuch more comprehensive list that provides a cross-reference between an application’shardware circuit and the PIC microcontroller software
his-Before declaring anything myself, I load in the required include files and specifythat the default number type is to be decimal As I will comment on elsewhere, I onlyuse the Microchip PIC microcontroller Include Files because these have all thedocumented registers and bits of the data sheets, avoiding the need for me to create myown There are two points here that you should recognize are implemented to avoidunnecessary work and possible errors The first is specifying that numbers default to adecimal radix to avoid having to continually convert decimal numbers to the normal hexa-decimal default The second is to only use Microchip-developed (or in the case of highlevel languages, the compiler provided) chip register and constant include files toavoid the possibility that I will mistype registers or constants that will leave me withmistakes that are very hard to find later It is interesting, but when I have worked withteachers, they tend to have their students specify registers and constants in the programand only work in hexadecimal; unfortunately, this causes a lot of problems that are verydifficult for the students (and the teachers helping them) to find because they are in areasthat are thought to be immune to errors) Another problem is that students, to avoid afew keystrokes, will give registers different labels, which adds the task of cross-referencing datasheet register names to the ones that the students have picked I highlyrecommend that you save yourself some mental effort and go with the register defini-tions that are predefined by Microchip or the compiler vendor
With the device declarations completed, I then do my variable, defines, and macrodeclarations When doing this, remember to always specify prerequisites before they areused The MPASM assembler will not be able to resolve any macro or define labels that
Trang 14are defined after their first use This is not true for labels that are referenced before theiruse in the application instruction code that follows the declarations and operatingparameters.
Finally, I declare the device operating parameters that are to be programmed into theCONFIGURATION register or CONFIGURATION fuses (which will be explained inmore detail later in this chapter) followed by the application code I put subroutines atthe end of the application code simply because the reset and interrupt handler vectorsare at the “beginning” of the data space Putting the subroutines after the mainline andinterrupt handler seems to be the most appropriate in this situation
This template is used for single-source file applications, which make up the vastmajority of my PIC microcontroller applications If multisource file applications are cre-ated, then the CONFIG line is left out of anything other than the first (or header) file,which is explained elsewhere Publics and externals are added in its place withthe code following as it does in the single-source file template Variables should bedeclared in the header file and then passed to the linked in files as publics.This template can be modified and used with the other PIC microcontroller devicearchitectures
Labels, Addresses, and Flags
If you have skipped ahead in this book and taken a look at some of the code examples
in the various chapters, you probably will be a bit concerned because there are a number
of different ways program addresses are used that probably won’t be familiar to you.The PIC microcontroller’s architecture and instruction set require a more careful watch
of absolute addresses than you are probably used to when programming other devices
In this section I want to discuss the different memory “spaces” in the PIC microcontroller,what is stored in them, and how they are accessed
The most familiar memory space to you is probably the instruction program memory
As I said earlier in this book, the program memory is the width of the instruction word.The maximum “depth” of the memory is based on the word size and can have theinstruction word size minus one for addressing for the low-end and mid-range PICmicrocontrollers The PIC18 is a bit different because it expands on the concepts used
by the other architectures, allowing you to have a much larger program space
To figure out the maximum depth of program memory in the low-end and mid-rangePIC microcontrollers, the formula
Maximum program memory = 2 ** (word size – 1)
is used It is important to note that while the low-end, mid-range, and PIC18 programcounters are the size of the instruction word (12, 14, and 16 bits, respectively), theupper half of the addressable space is not available to the application This upper half
to the program memory is used for storing the configuration fuses, IDLOC nibbles, anddevice identification bytes, as well as for providing test addresses used during PIC
Trang 15microcontroller manufacturing The PIC18 architecture is the exception to the rulebecause its configuration fuses can be accessed from the application using the tablereadfunction.
When an application is executing, it can only jump with a set number of instructions,
which is known as the page The page concept is discussed in more detail elsewhere in
the book The size of the page in the various PIC microcontroller architecture families
is based on the maximum number of bits that could be put into an instruction for theaddress In the low-end PIC microcontroller, a maximum of 9 bits are available in thegotoinstruction that is used to address a page of 512 instructions In the mid-rangePIC microcontroller, the number of bits specified in goto is 11, for a page size of 2,048instructions The PIC18 can either branch relative to the current program counter value
or jump anywhere within the application without regard to page size
The point of discussing this is to note that in these three families, addresses arealways absolute values within the current page For example, if there was the code
btfsc Button, Down ; Wait for Button to be Pressed
goto $ - 1
The $ character returns a constant integer value that is the address of the instructionwhere it is located The goto $ - 1 instruction loads the address that is the address
of the goto $ - 1 instruction minus 1
Further confusing the issue is how the PIC18 operates The PIC18 microcontrollerprocessor behaves more like a traditional processor and has absolute address jumps andrelative address branches and does not have a page per se The goto and call instruc-tions in the PIC18 can change application execution anywhere within the PIC micro-controller’s 1-MB program memory address range Along with this, the PIC18 has theability to “branch” with an 8- or 11-bit two’s complement number The branch instruc-tions do not use an absolute address and instead add the two’s complement to the cur-rent address plus two In the PIC18, the instruction sequence
btfsc Button, Down, 1 ; Wait for Button to be Pressed
bra $ - (2 * 1)
Trang 16would perform the same operation as the preceding example, but I replaced the goto
$ - 1instruction with a “branch always.”
In the PIC18 example, if an odd address is specified, the MPLAB simulator will haltwithout a message If the code is burned into the PIC18 along with a jump to an oddaddress, execution may branch to an unexpected address As I noted earlier, each byte
is addressed in the PIC18 and not the word Further complicating the use of relative jumps
is the instructions that take up more than one instruction word These complexities lead
me to recommend that you do not use relative jumps with the $ character with thePIC18 and instead use a define such as
#define CurIns(Offset) $+(2*Offset)
which would be inserted into the instruction sequence like
btfsc Button, Down, 1 ; Wait for Button to be Pressed
bra CurIns(-1)
and provide the same value as the original PIC18 example but eliminate the need foryou to create the formula for calculating the actual address Offset in CurIns can
be a negative or positive value
You probably will be comfortable with how the destination values for either thegotoand bra instructions are calculated depending on your previous experience Ifthis is your first assembly-language experience, the absolute addresses of the low-endand mid-range probably will make a lot of sense to you If you have worked with otherprocessors before, the PIC18 will seem more familiar to you
Regardless of which method is the most comfortable for you, I recommend writingyour applications in such a way that absolute addresses should not be a concern Thismeans that labels should be used in your source code at all times, and the org direc-tive statement is used only for the reset vector and interrupt vectors For all otheraddresses, the assembler should be used to calculate the absolute addresses for you Byallowing the assembler to generate addresses, you will simplify your application codingand make it much more “portable” to multiple locations within the same source file orothers
For example, the mid-range code
the MPLAB assembler, a label is defined as any unknown string of characters When
Trang 17one of these strings is encountered, it is loaded into a table along with the current gram counter value for when it is referenced elsewhere in the application
pro-Labels in MPLAB’s assembler can have a colon (:) optionally put on the end ofthe string To avoid any potential confusion regarding whether or not the label is to
be replaced with its address or is a define or macro, I recommend putting the colonafter it
Using the example above, a Loop label can be added to make the code a bit moreportable:
Loop:
btfsc Button, Down ; Address = “Loop”
The disadvantage of this method is that there can only be one Loop (and Skip) putinto an application Program memory labels are really best suited for cases where theycan be more global in scope For example, an application really should only have onemain Loop, and that is where this label should be used
Personally, I always like to use the labels Loop, Skip, and End in my tions To allow their use, I will usually preface them with something like the acronym
applica-of the current subroutine’s name For example, if the code was in the subroutineGetButton, I would change it to
GB_Loop:
btfsc Button, Down ; Address = “Loop”
goto GB_Loop: ; Address = “Loop” + 1
Instead of using labels in program memory for simple loops, I prefer using the $ tive, which returns the current address of the program counter as an integer constant andcan be manipulated to point to the correct address Going back to the original code forthe button poll snippet, the $ directive eliminates the need for a label altogether:
direc-btfsc Button, Down ; Wait for Button to be Pressed
goto $ - 1
You do not have to expend the effort trying to come up with a unique label (whichcan start becoming hard in a complex application), and as you get more comfortablewith the directive, you will see its what is happening faster than if a label were used The problem with using the $ directive is that it can be difficult to count out the offset
to the current instruction (either positive or negative) To avoid making mistakes in ing, the $ should be done only in short sections of code, such as the one above, becausethe destination offset to $ can be found Also, beware of using the $ directive in large sec-tions of code that has instructions added or deleted between the destination and the gotoinstruction The best way to avoid this is to use the $ only in situations such as the oneabove, where code will not be added between the goto and the destination
count-If you have worked with assemblers for other processors (Von Neumann), chancesare that you have had to request memory where variables were going to be placed This
Trang 18operation was a result of variable memory being in the same memory space as programmemory This is not a requirement of the PIC microcontroller in which the register space(where variables are located) is separate from the program memory space
To allocate a variable in the PIC microcontroller, you have to specify the references
to a label to a file register address In the first edition I specified that this was done byfinding the first file register in the processor and then starting a list of equates from there
As discussed elsewhere, an equate is a directive that assigns a label a specific stant value Every time the label is encountered, the constant that is associated with it
con-is used Program memory labels can be thought of as equates that have been given thecurrent value of the program counter
For the PIC16F84, variable equate declarations for an application could looklike
Using the CBLOCK and ENDC directives, the variable declarations above could beimplemented as
CBLOCK 0x00C ; Define the PIC16F84 File Register Start
i, j:2, k
ENDC
This is obviously much simpler than the previous method (i.e., it requires less thinking),and it does not require you to change multiple values or specify a placeholder if oneaddress is deleted from the list
What I don’t like about CBLOCK is that specific addresses cannot be specified within it.For most variables, this is not a problem, but as I will indicate elsewhere in this book,
I tend to put variable arrays on power of 2 byte boundaries to take advantage of the PICmicrocontroller’s bit set/reset instructions to keep the index within the correct range
To make sure that I don’t have a problem, I will specify an equate for the variablearray specifically and ensure that it does not conflict with the variables defined in theCBLOCK
The last type of data to define in the PIC microcontroller is the bit If you lookthrough the Microchip MPLAB assembler documentation, you will discover thatthere are no bit data types built in This is not a significant problem if you are will-ing to use the #define directive to create a define label that includes the register andbit together
Trang 19For example, you could define the STATUS register’s zero flag as
#DEFINE zeroflag STATUS, Z
A define is like an equate except where the equate associates a constant to thelabel, a define associates a string to the label For the zeroflag define, if it were used
Subroutines with Parameter Passing
For subroutines to work effectively, there must be the ability to pass data (known as
parameters) from the caller to the subroutine There are three ways to pass parameters
in the PIC microcontroller, each with their own advantages and potential problems Thefirst is to use global variables unique to each function, the second is to create a set ofvariables that are shared between the functions, and the third is to implement a data stack.Most high-performance computer systems have a stack for storing parameters (as well
as return addresses), but this feature is not built into the low-end and mid-range PICmicrocontrollers In this section I want to introduce you to each of the three methodsand show how they can be implemented in the PIC microcontroller
In most modern structured high level languages, parameters are passed to subroutines
as if they were parameters to a mathematical function One value (or parameter) isreturned An example subroutine (or function) that has data passed to it would look like
A = subroutine(parm1, parm2);
in C source code
Trang 20The subroutine’s input parameters (parm1 and parm2) and output parameter (which
is stored in A in the preceding above) can be shared and are common to the caller andsubroutine by the following methods:
subrou-call subroutine
The advantage of this method is that it requires a minimal amount of amount of codeand executes in the least number of cycles The problem with this method is that it doesnot allow implementation of nested subroutines, and if you do want to have nested sub-routines, you would have to copy one subroutine’s parameters into separate variablesbefore using the global variables for the nested subroutine call This method cannot beused for recursive subroutines, nor can it be used for interrupt handlers that may callsubroutines (or use the common global variables) that are already active Despite thesedrawbacks, this method of parameter passing is an excellent way for new-applicationdevelopers to pass subroutine and function parameters in assembly language because
it is so simple
The second method is to use unique parameter variables for each subroutine.Before the call, the unique variables are loaded with the input parameters, and afterthe call, the returned parameter is taken from one of the variables In this case, thestatement
A = subroutine(parm1, parm2);
can be implemented in assembler as
movwf subroutineparm1 ; passed to Subroutine
Trang 21often the preferred method of implementation because it is fast and very efficient.
memory-The method normally used by most processors and high level languages is to saveparameters on a stack and then access the parameters from the stack As indicated ear-lier, the low-end and mid-range PIC microcontroller cannot access stack data directly,but the FSR (INDEX) register offsets can be calculated easily Before any subroutinecalls can take place, the FSR has to be offset with the start of a buffer:
“Popping” data from the stack uses the format
movf INDF , w
decf FSR, f
With the simulated stack, the example call to subroutine could use the code
movf parm1 , w ; Save Parameters
The disadvantage of this method is the complexity required for accessing data withinthe subroutine and adding additional variables with low-end and mid-range PIC micro-controllers When accessing the variables and changing FSR, you will have to disable inter-rupts For the preceding example, to read parm1, the following code would have to used:
Trang 22movf SUBRTN + TEMP, w
The SUBRTN_TEMP variable is used to save the value read from the stack while theFSR is updated For most changes in the FSR, simple increment and decrement instruc-tions could be used instead and actually take fewer instructions and not require thetemporary variable The preceding code could be rewritten as
movff parm1, POSTDEC0 ; Save Parameters
movff parm2, POSTDEC0
decf FSR, f ; Make Space for Return
call subroutine
movff PREINC0, A ; Get Returned Value
incf FSR, f
This is just over half the number of instructions required for the call in low-end andmid-range devices An even better improvement can be demonstrated reading param1,which is 3 bytes down from the top of the stack:
movf PLUSW0, w
Trang 23In these instructions, the byte that was pushed down onto the stack with 2 bytes ontop of it is accessed by adding three to the stack pointer and storing the value in the wregister (destroying the offset to the byte put there earlier) This capability makes thePIC18 a very powerful architecture to work with and allows you and compiler writers
to develop code that is similar to what is used on high-end processors
There is one method of passing parameters to and from that I haven’t discussedbecause I do not believe that it is an appropriate method for the PIC microcontroller,and that is using the processor’s registers to store parameters For the PIC microcon-troller, there is only the w register, which is 8 bits wide, that can be guaranteed for thetask To frustrate using this method, the low-end devices’ lack of a return instructionprevents passing data back using the w register except in the case of using the retlwinstruction and a jump to a table offset The zero, carry, and digit carrySTATUS register flags also could be used for this purpose, and they are quite effectivefor being used as pass/fail return flags
Subtraction, Comparing and Negation
This section was originally titled “Working with the Architecture’s Quirks” because thereare some unusual features about the architecture that make copying assembly-languageapplications directly from another microcontroller to the PIC microcontroller difficult.However, as I started listing what I wanted to do in this and the following sections, Irealized that there were many advantages to the PIC microcontroller’s architecture andthat many of the “quirks” actually allow very efficient code to be written for differentsituations In this and the following sections I will discuss how the PIC microcontrollerarchitecture can be used to produce some code that is best described as “funky.”
In addition, the basic operation sequence of adding two numbers together is
1 Load the accumulator with the first additional RAM
2 Add the second additional RAM to the contents of the accumulator
3 Store the contents of the accumulator into the destination
In PIC microcontroller assembly language code, this is
Destination = Parm1 – Parm2
Trang 24the sequence of operations is
1 Load the w register with the second parameter (which is the value to be taken awayfrom the first)
2 Subtract the contents of the w register from the first parameter and store the result
in the w register
3 Store the contents of the w register in the destination
In PIC microcontroller assembly code, this is
PIC microcontroller subtract = parameter – w
= parameter + (w ^ 0x0FF) +1
This operation affects the zero, carry, and digit carry STATUS register flags In mostapplications, it is how the carry flag is affected that is of the most importance This flagwill be set if the result is equal to or greater than zero This is in contrast to how thecarry and borrow flags work in most processors I have described the carry flag after
a subtract operation as a “positive flag.” If the carry flag is set after a subtract tion, then a borrow of the next significant byte is not required It also means that theresult is negative if the carry flag is reset
opera-This can be seen in more detail by evaluating the subtract instruction sequencefor
I did not include the digit carry (DC) flag in the table because it will be the same ascarry for this example In subtraction of more complex numbers (i.e., two-digit hex),
Trang 25the DC flag becomes difficult to work with, and specific examples for its use (such asthe ASCII-to-nybble conversion routines) have to be designed.
When you are first learning how to program in assembly language, you may want toconvert high level language statements into assembly language using formulas or basicguides When you look at subtraction for comparing, the code seems very complex Inactuality, using the PIC microcontroller subtract instruction isn’t that complex, andthe instruction sequence
movf Parm1, w/movlw Parm1
subwf Parm2, w/sublw Parm2
btfsc status, C
goto label
can be used each time the statement
if (A Cond B) then go to label
is required, where Cond is one of the values specified in Table 8.2
By selecting a STATUS flag (carry on zero) to test, the execution of the goto tion can be specified, providing you with a simple way of implementing the conditionaljumps using the code listed in Table 8.3
ZERO FLAG RESULTS
Trang 26This is a useful table to remember when you are working on PIC applications, even
if you aren’t simply converting high level language source code by hand into PIC controller assembly
micro-Negation of the contents of a file register is accomplished by performing the two’scomplement operation By definition, this is done by inverting the contents of a regis-ter and then incrementing:
comf reg, f
incf reg, f
JUMP if CONDITION TO CHECK CODE
A == B A – B = 0 movf A, w/movlw A
subwf B, w/sublw Bbtfsc STATUS, Zgoto Label ; Jump if Z = 1
A != B A – B != 0 movf A, w/movlw A
subwf B, w/sublw Bbtfss STATUS, Zgoto Label ; Jump if Z = 0
A > B B – A < 0 movf B, w/movlw A
subwf B, w/sublw Bbtfss STATUS, Cgoto Label ; Jump if C = 0
A >= B A – B >= 0 movf B, w/movlw B
subwf A, w/sublw Bbtfsc STATUS, Cgoto Label ; Jump if C = 1
A < B A – B < 0 movf B, w/movlw B
subwf A, w/sublw Abtfss STATUS, Cgoto Label ; Jump if C = 0
A <= B B – A > 0 movf A, w/movlw A
subwf B, w/movlw Bbtfsc STATUS, Cgoto Label ; Jump if C = 1
Trang 27If the contents to be negated are in the w register, there are a couple of tricks that can
be used to carry this out For mid-range devices, the sublw 0 instruction can be used:
addwf Reg, w ; w = w + Reg
subwf Reg, w ; w = Reg – (w + Reg)
; = -w
Regshould be chosen from the file registers and not any of the hardware registers thatmay change between execution of the instructions
Bit AND and OR
One of the most frustrating things to do is to respond based on the status of two bits
In the past, I found that I had to come up with some pretty “funky” code, only to feellike it was not good enough To try and find different ways of carrying out thesetasks, I spent some time experimenting with two skip-on-bit-condition instructions.The two skip parameters are used in such a way that the first one jumps to an instruc-tion if a case is true, and the second jumps over the instruction if the second case isnot true
To show how the double-skip-on-bit-condition instructions could be used, considerthe example of setting a bit if two other bits are true (the result is the AND of two arbi-trary bits) You could use the code
bcf Result ; Assume A and C = 0
btfss A
goto Skip ; A = 0, don’t set Result
btfsc B ; B = 0, don’t set Result
bsf Result ; A = B = 1, set result
Skip:
This code is quite complex and somewhat difficult to understand A further problem with
it is that it can return after a different number of cycles depending on the state of A If
Ais reset, the code will return after four instruction cycles If it is set, six instructioncycles will pass before execution gets to Skip
Trang 28By combining the two tests, the following code could be used to provide the samefunction:
This trick of using two conditions to either skip to or skip over an instruction is useful
in many cases As I will show later in this chapter, this capability is used to implementconstant-loop timing for 16-bit delay loops
snip-of declaring and accessing 16-bit (and greater) variables and constants
Declaring 16-bit variables in MPASM using the CBLOCK directive is quite simple
To declare a variable that is larger than 8 bits using CBLOCK, a colon (:) follows thevariable name, and the number of bytes is specified afterward For example, 8-, 16-, and32-bit variables are declared in the PIC16F84 as
To access data, the address with the offset to the byte address can be used as shown
in the following example:
movf j + 1, w
When working with constant values, instead of coming up with arithmetic operations
to capture the byte data at specific locations, you can use the LOW, HIGH, and UPPERoperators (how they work is presented in Table 8.4)
Trang 29One confusing aspect of MPLAB for me is the default of “high/low” data storage inthe MPLAB simulator and MPASM The “low/high” format works better for using
application code and makes more sense to me (this is known as Intel format, and the
reason why it makes sense to me is because of all the years I’ve spent working with Intelprocessors) In addition, you will note that all 16-bit registers in the PIC microcontrollerare defined in “low” (byte/address) followed by “high” (byte/address) data format, sousing this format in my coding keeps me consistent with the hardware registers builtinto the chip processor architecture
The preceding paragraph may be confusing for you, but let me explain exactly what
I mean If 16-bit data is saved in the “high/low” (what I think of as Motorola format,
which is where I first saw it), when 16-bit information is displayed in memory, it lookscorrect For example, if 0x1234 was stored in “high/low” format staring at address0x10, the file register display would show
0010 1234
which appears natural
If the data is stored in “low/high” (Intel) format, 0x1234 at 0x10 would appear as
0010 3412
which is somewhat confusing
I recommend storing data in “low/high” format for two reasons; the first is that it makeslogical sense saving the “low” value byte at the “low” address The second reason is that
in your career, you probably will work with more Intel-architected devices than Motoroladevices, and you might as well get into the habit of mentally reversing the bytes now.The act of mentally reversing the two bytes becomes second nature very quickly, and Idare say that you will become very familiar and comfortable with it after workingthrough just a few applications
When multibyte data is displayed in MPLAB “watch windows,” the default is in the
“high/low” format Make sure that when you add a multibyte variable to the window,you click on the “low/high” selection Working with multibyte variables is not as simple
as working with single-byte variables because the entire variable must be taken intoaccount
Trang 30For example, when incrementing a byte, the only considerations are the value of theresult and the zero flag This can be implemented quite easily for a 16-bit variable:
incf LOW variable, f
btfsc STATUS, Z
incf HIGH variable, f
Addition with two 16-bit variables becomes much more complex because along withthe result, the zero, carry, and digit carry flags must be involved as well This code cor-rectly computes the 16-bit result and correctly sets the zero and digit carry flags.Unfortunately, it requires five more instructions than a simple case and does not set carrycorrectly To set carry correctly, a temporary variable and 20 instructions are required:
oper-MulDiv , Constant Multiplication
and Division
When you get into advanced mathematics (especially if you continue your academiccareer into electrical engineering), you will learn to appreciate the power of arithmeticseries With a modest amount of computing power, quite impressive results can be
Trang 31produced in terms of calculating data values A good example of this is using arithmeticseries to calculate a sine, cosine, or logarithmic function value for a given parameter.Arithmetic series can be used in analog electronics to prove that summing a number ofsimple sine waves can result in squarewave, sawtooth, or other arbitrary repeatingwaveforms
An arithmetic series has the form
Result = + P1X1 + P2X2 + P3X3 + P4X4 +
where the “prefix” value (P#) is calculated to provide the function value X# is the
“parameter” value that is modified for each value in the series The parameter changecan be a number of different operations, including squaring, square rooting, multiply-ing by the power of a negative number, and so on For the multiplication and divisionoperations shown here, I will be shifting the parameter by 1 bit for each series element The theory and mathematics of calculating the “prefix” and “parameter” for arith-metic series can be quite complex—but it can be used in cases such as producing theprefix values for simple multiplication or division operations, as I am going to show
in this section To demonstrate the operations, I have created the multiply anddivide macros that can be found in the muldiv.inc file in the Macros\MulDivfolder
The two macros provide the multiplication and division functions using MPLABassembler capabilities that I haven’t explained yet (although I do in Chap 10) To avoidconfusion, I will explain how the macros work from the perspective of a high level lan-guage before presenting the actual code To further help explain how the macros work,
I will present them from the perspective of implementing the function in straight PICmicrocontroller assembler
Multiplication (and division) can be represented by a number of different methods.When you were taught basic arithmetic, multiplication was repeated addition If you had
to program it, you would use the high level code
Product = 0;
for (i = 0; i < Multiplier; i++ )
Product = Product + Multiplicand;
This method works very well but will take a differing amount of time based on the tiplier (i.e., eight times something takes four times longer than two times something).This is not a problem for single-digit multiplication, but when multiplication gets morecomplex, the operations become significantly longer, which can have a negative impact
mul-on operatimul-on of the applicatimul-on code
Ideally, a multiplication method (or algorithm) that does not have such extreme
ranges should be used As you would expect, this is where the arithmetic series isinvolved
As you know, every number consists of constants multiplied by exponents of thenumber’s base For example, 123 decimal is actually
123 = 1 * Hundreds + 2 * tens + 3 * ones
Trang 32This also works for binary numbers and is used to convert constants between ing systems 123 decimal is 1111011 binary (0x07B) This can be represented like
number-123decimal above as
123 = 1 * sixty-four + 1 * thirty-two + 1 * sixteen +
1 * eight + 0 * four + 1 * two + 1 * one
In this binary sequence, I also have included any digits that are zero (which is 4 inthe case of 123) because they will be used when multiplying two numbers together Thisbinary sequence can be used as the “prefix” of a multiplication arithmetic series if eachvalue is used to add the multiplicand that has been shifted up by the number of the bit.The shifted-up multiplicand can be thought of as the “parameter” of the series This seriescan be written out as
A * B = ((A & (1 << 0)) != 0)*(B << 0) +
((A & (1 << 1)) != 0)*(B << 1) + ((A & (1 << 2)) != 0)*(B << 2) + ··· + ((A & (1 << 7)) != 0)*(B << 7)
This series can be converted to high level code very easily:
int Multiply(int A, int B) // Multiply two eight bit
A = A >> 1; // Shift down the Multiplier
B = B << 1; // Shift up the Multiplicand}
return Product; // Finished, Return the
// Result} // End Multiply
This function will loop only eight times, and each time will shift up the multiplicand,which is the B << 2 term in this series, and shift down the multiplier, which is theequivalent of the (A & (1 << 0)) != 0 term in this series This term is 100 per-cent mathematically correct; if the prefix result is not equal to zero, then the shifted termwill be added to the Product
Trang 33For example, if you were multiplying together 13 (0b01101) and 10 (0b01010), theterms would be
A * B = ((A & (1 << 0)) != 0)*(B << 0) +
((A & (1 << 1)) != 0)*(B << 1) + ((A & (1 << 2)) != 0)*(B << 2) + ((A & (1 << 3)) != 0)*(B << 3)
= ((13 & (1 << 0)) != 0)*(10 << 0) + ((13 & (1 << 1)) != 0)*(10 << 1) + ((13 & (1 << 2)) != 0)*(10 << 2) + ((13 & (1 << 3)) != 0)*(10 << 3)
= ((13 & 1) != 0)* 10 + ((13 & 2) != 0)* 20 + ((13 & 4) != 0)* 40 + ((13 & 8) != 0)* 80
= (1 != 0)* 10 + (0 != 0)* 20 + (4 != 0)* 40 + (8 != 0)* 80
= 130
For humans, this probably seems like a very slow way of implementing tion, but for the PIC microcontroller, it is actually very fast and consistent Doing an 8-bit by 8-bit multiply, the following PIC microcontroller code is used:
btfss Multiplier, 0 ; If Bit 0 Set, then Add
goto Skip ; “Multiplicand” to the Productmovf TempMultiplicand, w ; Add the High Eight Bits Firstaddwf Product + 1, f
movf Multiplicand, w ; Add Low Eight Bits Next
addwf Product, f
btfsc STATUS, C
incf Product + 1, fSkip:
bcf STATUS, C
Trang 34rlf Multiplicand, f ; Shift the Multiplicand Up
rlf TempMultiplicand, f
rrf Multiplier, f ; Shift the Multiplier Down for
goto Loop
When this code is exited, Product will contain a 16-bit result Note that I added aTempMultiplicandvariable for the high 8 bits of the shifted multiplicand Comparedwith the repeated addition case, i.e.,
“better” case executes in anywhere from 84 to 124 instruction cycles, which has a ability of no more than 47 percent and runs in fewer cycles for all cases except whenthe multiplier is less than 17
vari-This algorithm can be used to multiply a variable value by a constant and to allowthis operation to be used easily in your PIC microcontroller assembly code I have cre-ated the multiply macro shown below:
multiply macro Register, Value ; Multiply 8 bit value by a variable i = 0, TValue ; constant
; Multipliermovf Register, w
movwf Temporary ; Use “Temporary” as Shifted
; Valueclrf Temporary + 1
Trang 35bcf STATUS, C ; Shift Up Temporary
; multiplicandrlf Temporary, f
rlf Temporary + 1, fTValue = TValue >> 1 ; Shift down to check the
multiply Register, Constant
where Register is the file register containing the multiplicand to be multiplied withthe multiplier in Constant The result will be stored in the 16-bit variable Product.The macro itself will insert the code needed to perform the operation, but without thelooping functions This method of shifting data is how I perform all multiplication inthe PIC microcontroller If you look at Appendix G, you will see that this is the methodused to multiply two 16-bit numbers together
Division is always much more difficult to perform than multiplication Since plication is repeated addition, division could be thought of as repeated subtraction Thebasic code version for division is
multi-Remainder = Dividend;
for (Quotient = 0; (Dividend – Divisor) > 0; Quotient++)
Dividend = Dividend – Divisor;
Remainder = Remainder – (Quotient * Divisor);
This code also includes returning a remainder from the operation
To simplify the division operation, I can use an arithmetic series like I did for tiplication, but this one will work differently than multiplication I would call the seriesproduced for multiplication a “closed” series because the series is defined for a setrange of numbers In division, this is possible for some numbers, but not for all; num-bers do not always divide “evenly” (i.e., have a remainder equal to zero) into others Inthese cases, a decision has to be made about what to do about them
mul-To come up with a division arithmetic series, I would want to use the property of
divi-sion that a number can be divided by a second one by multiplying by the reciprocal of
the second number This can be shown as
A/B = A * (1/B)
This probably will be unexpected because fractions (which are what reciprocals really are)are not possible in the PIC microcontroller; to get around this problem, you just have tolook back in the history of mathematics to see how this problem has been encountered
Trang 36and solved before Four hundred years ago, when scientists were taking the results fromplotting the path of the planets about the sun and trying to come up with a general math-ematical theory about the motion of the planets, they had to work with trigonometrictables The problem with using these tables was that the decimal point had not yet beeninvented Instead of having values based as fractions of 1, these tables returned the numer-ical fraction over 6,000 For existence, if you look up the sine of 45 degrees, you wouldget the value 0.707107 In the 6,000-based table, the sine of 45 degrees would be 4,243.This same principal can be applied to the finding of fractions in the PIC microcontroller.Instead with coming up with a result that is less than 1, the division method that I am going
to present here calculates the fraction as a result less than 65,5236 (0x010000)
By doing this, 1/3 is not processed as 0.33333 but as 21845 (0x05555 or0b00101010101010101), 1/5 is 13,107 (0x03333 or 0b00011001100110011), 1/7 is9,362 (0x02492 or 0b00010010010010010), and so on It is important to note that thesefractions as binary strings are repeating or “open” series, which complicates the divisionoperations somewhat Powers of 2 will result in a closed series, but for the most part, thefractional values will not be closed To ensure that the result is as correct as possible, thefractional bits should be taken as far as possible, which is why I divided by 65,536 (2 ** 16)and not 256 (2 ** 8) Now that I have the fraction, I can develop the arithmetic series For 8-bit division, this series is
A / B = (((65,536 / B) & (1 << 15)) != 0) * (A >> 0) +
(((65,536 / B) & (1 << 14)) != 0) * (A >> 1) +(((65,536 / B) & (1 << 13)) != 0) * (A >> 2) + +(((65,536 / B) & (1 << 0)) != 0) * (A >> 15)
The reason why I shift A down for each element in the series is because each test ofthe shifting down bit in the fraction requires that the dividend be shifted down as well.This operation could be written in a high level language as
int Divide(int A, int B) // Carry out Eight Bit Division {
for (i = 0; i < 8; i++ ) { // Repeat for sixteen cycles
if ((Divisor & 0x08000) != 0) // Have to add Quotient
// FractionQuotient = Quotient + TempDividend;
TempDividend = TempDividend >> 1;
Divisor = (Divisor & 0x07FFF) << 1;
Trang 37As you work through this function, there should be two things that are unexpected
in the code The first is that I use 16-bit values for an 8-bit result This was done because
I wanted to get a “rounded” (to the nearest one) result If the result has a fraction of 0.5
or greater (in which bit 7 of the result is set), then I increment the returned Divisor.The second is that I shift up the dividend and divisor by 8 bits This is done so that
as I shift down the dividend, I do not loose the fractional bits of the result and cannotproduce an accurate “rounding” of the result
When I implemented this function, I had not written it out as straight PIC troller assembler for use in an application The multiplication operation, because it is
microcon-“closed,” can be carried out within straight code The division operation laid out abovedoes not have this advantage because the result is most likely “open.”
This open result means that the PIC microcontroller’s internal functions cannot beused for calculating the fraction of the divisor To calculate the divisor fraction, I havewritten the following macro with the divisor calculated by the “macro calculator”(explained later in this book):
divide macro Register, Value ; Divide 8 bit value by a constantvariable i = 0, TValue
TValue = 0x010000 / Value ; Get the Constant Divider
movf Register, w
movwf Temporary + 1 ; Use “Temporary” as the Shifted
; Valueclrf Temporary
Trang 38TValue = TValue << 1 ; Shift up to check the Next Bit
to execute This is only marginally larger than the analogous multiplication code There are two concerns with this code The first is that if 1 is selected as the divisor,the code will return a quotient of 0 This is due to the fact that 0x010000 divided by 1
is 0x010000 and will not cause any of the loops to add the current value A divisor Value
of 1 could be checked in the macro and an error returned if this is a potential problem.The second problem is a bit more insidious and is reflective of how division algorithmswork The quotient returned is rounded to the nearest 1 In many applications requiring
a division operation, this would not be acceptable—instead, the quotient and der would have to be returned
remain-This macro was written to round the value so that indicator operations (such as RPM
in a tachometer) could be implemented quickly and efficiently The value returned fromthe divide macro should not be passed onto any other arithmetic functions to preventthe error in the result from being passed down the line If the quotient were required forsubsequent operations, I would suggest that you use either the 16-bit division routinepresented in Appendix G If this macro is to be used, then the entire 16-bit quotient cal-culated by this macro (the lower 8 bits being the fractional value less than 1) is passedalong with the final result divided by 256 (by “lopping off” the least significant byte)
The basic unit of timing in an application is the instruction cycle The instruction clock
rate is one-quarter the external clock frequency (as was explained earlier in this book)
The reciprocal is the instruction cycle period The instruction cycle period is found using
the formula
Instruction cycle = 4/clock frequency
Trang 39Thus, for a clock frequency of 3.58 MHz, the instruction cycle is found as
Instruction cycle = 4/clock frequency
= 4/3.58 MHz
= 1.12 ms
Actual time delays should be converted into instruction cycle delays as quickly as sible The formula I use for doing this is
pos-Instruction Delay = Time Delay * clock frequency/4
For example, if you had a PIC microcontroller running at 10 MHz and wanted a5-ms delay, the preceding formula would be used:
Instruction Delay = Time Delay * clock frequency/4
instruc-For a one-instruction delay, a nop instruction is used instruc-For two cycles, the goto $ +
1 instruction is used Four cycles can be implemented by calling a subroutine thatsimply returns The two instructions take four instruction cycles to execute:
This won’t seem that special until you realize what can be done with it By putting
on another “layer” to the subroutine that calls Dlay 4, you can double the delay verysimply For example, to delay 16 instruction cycles, you could use the code
Trang 40In this code, when Dlay16 is called, the call instruction requires two tion cycles to reach Dlay16, the call to Dlay8 requires an additional two instruc-tion cycles, and it calls Dlay4 for a total of 6 instruction cycles When Dlay4returns, 8 instruction cycles have executed When the code returns to the call atDlay8, it then returns to the call at Dlay16, which continues executing to Dlay8,and the process continues As you work through the four instructions above, youwill find that a total of 16 instruction cycles are executed by the Dlay16subroutine.
instruc-For longer delays, I recommend a loop for two reasons The first is because of thelimited program memory stack built into PIC microcontrollers; the low-end PIC micro-controllers have a two-entry deep stack, which means that the 16-cycle delay uses morestack entries than are available in low-end devices Even if an 8-cycle delay were imple-mented as shown above, there would be no stack space available for subroutine calls
In mid-range PIC microcontrollers, the three subroutine calls of the cycle delay code are probably the practical maximum (with interrupts in the application).The second reason is that this method is generally suboptimal for delays that are notpowers of 2 For example, to get a 31-instruction-cycle delay, the following calls andinstructions are required: