please dont rip this site

PIC 16 Delay Tutorial

by Vasily Koudymov

In programming the PIC16 family of microcontrollers, it is sometimes necessary to do absolutely nothing for a certain number of cycles thereby causing a real world delay of some amount of time. This can be useful if one is programming a clock or frequency generator for example, and it is often easier to implement a delay loop rather than using a built in TMR timer.

This document will use the following PIC16 assembly instructions:

decfsz f,d:
Although f refers to a memory location, when referring to operations performed upon f it is easier to say something like [ f = (f - 1) ] to refer to the idea of “the value at the address which is called f will now be equal to the original value at address f with one subtracted from it.” I only say this as any reader who is feeling particularly anal will say that I am in error when I say, “The new value of f will be (f - 1).” Note that although I will say that, it refers to the longer idea, it just makes following the operations much easier. Let's return to the original topic.

The instruction, decfsz performs the operation (f - 1), and if d is substituted with the character 'W', the result (f - 1) will be placed in the Working Register. If d is substituted with the character 'F', it will be placed back into f such that [ f = (f - 1) ].

With regards to the cycle time of the decfsz instruction, it goes as follows “one cycle if the (f - 1) is not equal to 0 and execute the next instruction, and one cycles if (f - 1) is equal to 0 and discard the instruction immediately after.” This is irrespective of whether or not d is 'W' or 'F'.

Examples:

; W = 0x00
; file = 0x05

decfsz file,F ; 1 cycle, will execute 'decfsz file,W'

; W = 0x00
; file = 0x04

decfsz file,W ; 1 cycle, will execute 'movlw 0x01'

; W = 0x03
; file = 0x04

movlw 0x01 ; 1 cycle, move the value 0x01 to Working Register
movwf file ; 1 cycle, file = 0x01

; W = 0x01
; file = 0x01

decfsz file,F ; 2 cycle, file = (file - 1) is zero, so discard next instruction
movlw 0x20 ; discarded, we ignore

; W = 0x01
; file = 0x00

goto k:
This command goes to the address k in the program memory. Although in higher level languages, the “goto” statement is looked down upon, in assembly languages, it is necessary to complete most programs. What is great about Microchip's assembler, which we will use to compile our PIC16 code, is that it allows using relative addresses for k rather than absolute addresses. For example, 0x1234 is an absolute address which refers to the address 0x1234 in the PIC16's program memory. Please do not confuse program memory with RAM. Ram is where file registers are located and they can be modified at runtime. However, program memory is the actual programming code which you upload to the microcontroller. In all cases while the program is running, program memory is read only. In contrast, relative addresses are as follows: If the character '$' is substituted for k, then it would refer to the current address of the goto instruction (an infinite loop resulting from this code). If “$+1” is substituted for k, then it will go to the code one instruction below the goto. If k is replaced with “$-5”, it will go to the code 5 instructions above the goto. In all cases, goto takes two cycles to execute.

Examples:

clrf PORTA 	; 1 cycle - clear PORTA file register.
goto $-1 	; 2 cycles - go one instruction above and continue execution.

In the above case, it would cause an infinite loop where PORTA is constantly cleared.

goto $+2 	; 2 cycles - go two instructions below and continue execution.
movlw 0x00 	; 1 cycle - place the value 0x00 in the W register.
andlw 0x01 	; 1 cycle - AND 0x01 and the contents of W register.

In the above case, movlw 0x00 is skipped over due to the goto, and it takes 3 cycles altogether.

Lets combine the the decfsz and goto instructions to create the simplest loop:

decfsz aa, F
goto $-1

We will refer to this as a one stage delay loop, but what would this do? So long as the instruction decfsz does not produce a value of zero, it will take 1 cycle to execute, and then will execute the goto statement after it which takes 2 cycles to execute. In sum, this segment of code will take 3 instructions each time decfsz does not produce zero. When it produces zero, decfsz will discard the goto instruction and will then take 2 cycles. So in short: 3 cycles if not resulting in zero, 2 cycles if resulting in zero.

Lets plug in some values for aa:

Or, in general [3(aa - 1)] + 2 cycles for this delay loop, because eventually there will be one value for aa which yields zero, while all of the rest do not. As such, if we had aa = 32, 31 of the values will take 3 cycles, and 1 of the values will take 2 cycles. We can simplify this function as follows:

[3(aa - 1)] + 2
3aa - 3 + 2
3aa - 1

What about when aa is initialized to zero?

when aa = 0, it will be decreased by one first, which will yield 255, which is a non-zero value. Effectively, initializing aa = 0, is like initializing aa = 256. Note that 256 is not a valid value for an 8 bit number, so in order to initialize aa = 256, one has to use aa = 0.

What are the limits of the delay loop?

Given that aa can effectively be set from aa = 1 to aa = 256(by way of initializing aa = 0), the minimum and maximum number of cycles which can be generated using this delay loop are found by plugging those values into the derived equation:

min: 3(1) - 1 = 2 cycles

max:3(256) - 1 = 767 cycles

Knowing this range will become important when we want to figure out how many variables we need in a delay loop. In order to simplify its presentation, the minimum and maximum number of cycles for a delay loop will be written as [2 ~767] which indicates that that using the loop with that specified range will only allow for at least 2 cycles and at most 767 cycles.

If we want to make a delay loop that includes more cycles, we would simply add another variable as follows:

decfsz aa,F
goto $-1
decfsz bb,F
goto $-3

Before we delve into what this specific code does, we will introduce new notation. cyc() will be used to refer to a delay loop cyc(aa) would mean that the delay loop only has one eight-bit variable in it (called aa), and we refer to it as a one stage loop, its output being a number of cycles. cyc(aa,bb) would mean that the loop involves two variables(called aa and bb) respectively, and we refer to it as a two stage loop. This pattern continues onward to however many variables you use. From this point onward, the following:

cyc(aa) = 3aa - 1 u [2~767]

Will be taken to mean a one stage delay loop with the formula (3aa - 1) which has one 8 bit variable called aa within it, and which can generate between 2 and 767 cycles inclusively. Note that the range for all inputs variables (such as aa) is actually [0~255] with 0 equating to 256.

With regards to what the above code is, it is a two stage loop. If time is taken to analyze the code, the following pattern will emerge:

[3(aa - 1) + 2] + { [(max of one stage loop) + 3](bb - 1) + 2}

The reason for this pattern is beyond the scope of description, but to actually derive it, it would be prudent to sit down with an empty sheet of paper, and go through a few iterations of a two stage loop. Now to actually plug in the values for the above derivation:

[3(aa - 1) + 2] + {[767 + 3](bb - 1) + 2}
[3aa - 3 + 2] + {770(bb - 1) + 2}
[3aa - 1] + {770bb - 770 + 2}
3aa - 1 + 770bb - 770 + 2
3aa + 770bb - 769

After plugging in the minimum values allowed for aa and bb as well as the maximum values, we obtain the range and our result is the following:

cyc(aa,bb) = 3aa + 770bb - 769 u [4~197119]

In addition, a general formula for any stage is found in the following:

[ previous stage cycle formula ] + [(max of previous formula) + 3][(new variable) - 1] + 2

Let us apply this to a three stage loop:

decfsz aa,F
goto $-1
decfsz bb,F
goto $-3
decfsz cc,F
goto $-5

Cycle formula:

[3aa + 770bb - 769] + [197119 + 3](cc - 1) + 2
[3aa + 770bb - 769] + [197122](cc - 1) + 2
[3aa + 770bb - 769] + [197122cc - 197122] + 2
3aa + 770bb + 197122cc -769 -197122 + 2
3aa + 770bb + 197122cc - 197889

After finding the range via plugging in allowable minimums and maximums, we obtain the formula:

cyc(aa,bb,cc) = 3aa + 770bb + 197122cc - 197889 u [6~50463231]

Thus far, our formulas are:

cyc(aa) = 3aa - 1 u [2~767]
cyc(aa,bb) = 3aa + 770bb - 769 u [4~197119]
cyc(aa,bb,cc) = 3aa + 770bb + 197122cc - 197889 u [6~50463231]

These formulas are great, so long as you copy and paste the code each time you need a delay. However, in the real world, it's more efficient and often much easier to use subroutines. A subroutine in assembly language is analogous to a function in a higher level language. Instead of typing out your code each time you need a delay, you can use subroutines. Here's how they appear in code:

; this is an excerpt from the main code section of an assembly program
call delay 		; 2 cycles, call the subroutine which we call 'delay', it's like calling a function
; the actual code for a one stage delay loop subroutine
delay:
	decfsz a,F 	; use the formula for next two lines
	goto $-1
	return 		; 2 cycle return

Or, in general:

; this is an exerpt from the main code section of an assembly program
call delay 	; 2 cycles, call the delay subroutines
; the declaration for the delay subroutine
delay:
	<code for an N-stage loop>
	return

You may notice that before, we were just using the code for an N-stage loop, however, when we turn it into a subroutine, we add on four more cycles. This applies to any number of stages. In order to adjust the stage delay loop formulas for these addition cycles, we add four to both the formula and the limits. Such that:

cyc(aa) = 3aa - 1 u [2~767]
cyc(aa,bb) = 3aa + 770bb - 769 u [4~197119]
cyc(aa,bb,cc) = 3aa + 770bb + 197122cc - 197889 u [6~50463231]

becomes:

cyc(aa) = 3aa - 1 + 4 u [2 + 4~767 + 4]
cyc(aa,bb) = 3aa + 770bb - 769 + 4 u [4 + 4~197119 + 4]
cyc(aa,bb,cc) = 3aa + 770bb + 197122cc - 197889 + 4 u [6 + 4~50463231 + 4]

and simplifies to:

cyc(aa) = 3aa + 3 u [6~771]
cyc(aa,bb) = 3aa + 770bb - 765 u [8~197123]
cyc(aa,bb,cc) = 3aa + 770bb + 197122cc - 197885 u [10~50463235]

Although the formula is becoming more and more proper, there is still one last step before our formula is complete. It involves the role of initialization a loop with specific values so as to get obtain the desired number of cycles.

It is best to initialize the loop within the routine as it makes the code less clunky and it is easier to use conditional code (such as btfss, decfsz, and incfsz). The reason being that if the loop is initialized after the call statement, code like this can be used:

btfss STATUS,Z 	; check if the previous operation yielded zero
call delay 	; delay for some amount of time

while in contrast, this would not be possible if the loop was initialized outside of the subroutine:

btfss STATUS,Z 	; check if the previous operation yields zero
clrf aa 	; clear the aa variable
call delay 	; call the delay

As you see by these two examples, only in the first example is the delay conditional. The second example is not equivalent to the first as the only part which is conditional, is the clrf aa. If the previous operation does not yield zero then do not clear aa is not equivalent to call the delay routine if the previous operation does not yield zero.

As to initializing within subroutines there are two choices, static delay loops and variable delay loops

Here are examples of how static loops are to be initialized:

; one stage delay loop subroutine
delay:
	movlw D'5' 	; 1 cycle
	movwf aa 	; 1 cycle
	<code for one stage delay loop>
	return
; two stage delay loop subroutine
delay:
	movlw D'5' 	; 1 cycle
	movwf aa 	; 1 cycle
	movlw D'23' 	; 1 cycle
	movwf bb 	; 1 cycle
	<code for two stage delay loop>
	return
; three stage delay loop subroutine
delay:
	movlw D'5' 	; 1 cycle
	movwf aa 	; 1 cycle
	movlw D'23' 	; 1 cycle
	movwf bb 	; 1 cycle
	movlw D'3' 	; 1 cycle
	movwf cc 	; 1 cycle
	<code for three stage delay loop>
	return

From here, it should be noticed that for static delay loop subroutines, that if the loop involves N variables, it will require 2N cycles to initialize a variable. What this translates to is that for a one stage loop it takes 2 cycles to initialize, for a two stage loop it takes 4 cycles to initialize, and for a three stage loop it takes 6 cycles to initialize. Similarly to the adjustments required in turning simple delay loops into a subroutines, we must also add to those formulas the following additional cycles as follows:

cyc(aa) = 3aa + 3 u [6~771]
cyc(aa,bb) = 3aa + 770bb - 765 u [8~197123]
cyc(aa,bb,cc) = 3aa + 770bb + 197122cc - 197885 u [10~50463235]

becomes:

cyc(aa) = 3aa + 3 + 2 u [6 + 2~771 + 2]
cyc(aa,bb) = 3aa + 770bb - 765 + 4 u [8 + 4~197123 + 4]
cyc(aa,bb,cc) = 3aa + 770bb + 197122cc - 197885 + 6 u [10 + 6~50463235 + 6]

and simplifies to:

cyc(aa) = 3aa + 5 u [8~773]
cyc(aa,bb) = 3aa + 770bb - 761 u [12~197127]
cyc(aa,bb,cc) = 3aa + 770bb + 197122cc - 197879 u [16~50463241]

Static delay loops are great for when you only need a constant number of delay cycles, but what about when it varies? We can use variable delay loops, and the code is as follows:

; one stage delay loop subroutine
delay:
	movf aak,W 	; 1 cycle
	movwf aa 	; 1 cycle
	<code for one stage delay loop>
	return
; two stage delay loop subroutine
delay:
	movf aak,W 	; 1 cycle
	movwf aa 	; 1 cycle
	movf bbk,W 	; 1 cycle
	movwf bb 	; 1 cycle
	<code for two stage delay loop>
	return
; three stage delay loop subroutine
delay:
	movf aak,W 	; 1 cycle
	movwf aa 	; 1 cycle
	movf bbk,W 	; 1 cycle
	movwf bb 	; 1 cycle
	movf cck,W 	; 1 cycle
	movwf cc 	; 1 cycle
	<code for three stage delay loop>
	return

Where aak, bbk, and cck, and the values you need to initialize only once. Afterward, whenever you call the delay subroutine, it initializes the loop with aa = aak, bb = bbk, cc = cck, or mnemonically aa(variable) = aa(constant). Should you desire to change the number of cycles to delay for, all that is needed to be changed are the constant values.

With regards to the number of cycles that variable delay loops take, it is exactly the same as static delay loops, thereby making the formulas (with the exception of the constant values playing a role):

cyc(aak) = 3aak + 5 u [8~773]
cyc(aak,bbk) = 3aak + 770bbk - 761 u [12~197127]
cyc(aak,bbk,cck) = 3aak + 770bbk + 197122cck - 197879 u [16~50463241]

Please be certain that you initialize variable delay loops before their initial calling.

Now we can discuss the topic of solving for the the constants given that a certain number of cycles is required:

We need 600 cycles:

cyc(aak) = 3aak + 5 = 600
3aak = 600 - 5
3aak = 595
aak = 595/3 = 198.3333

as aak can only be a whole integer, we assign 198 to it. We absolutely do not round up or down, we always truncate. What we do with the fractional part 0.3333 is multiply it by 3 to convert back to who many cycles are needed in addition to what the loop can supply, essentially, we are finding the remainder:

198.3333 - 198 = [value after division - value assigned to constant aak] = 0.3333
0.3333 x 3 = 0.9999

We round at the very last step to the nearest integer, thereby this remainder will tell us how short of our desired number of cycles we are if we use the value 198 for aak so as to obtain a cycle count of 600. Therefore, as 0.9999 rounded to the nearest integer is 1, we are short 1 cycle which indicates that the delay loop only generates 595 cycles. In practice, after the final division, before multiplying by three a 0.3333 excess indicates one cycle short, while a 0.6666 indicates that it is two cycles short. To remedy this deficiency, we recommend the following:

; this can be placed in the main code
call delay 		; 595 cycle
	nop 		; 1 cycle
; or we can add the null operation to the subroutine
delay:
	movf aak,W 	; 1 cycle
	movwf aa 	; 1 cycle
	<code for one stage delay loop>
	nop 		; 1 cycle
	return

Note that for the second solution, embedding a one cycle null operation within the delay loop will add one more cycle to this subroutine making the formula change from:

cyc(aak) = 3aak + 5 u [8~773]

to this:

cyc(aak) = 3aak + 5 + 1 u [8 + 1~773 + 1]

which simplifies to:

cyc(aak) = 3aak + 6 u [9~774]

Therefore, in case you decide to embed the nop instruction within your delay loop subroutine, make certain to modify the formula as well.

Before we continue, I will now explain the procedure for finding the remainder on a calculator:

We want to take 50002 and divide it by 35. We require both the quotient and the remainder and begin by dividing this expression in our calculators:

50002/35 = 1428.628571

This makes the quotient 1428, an now to find the remainder:

1428.628571 - 1428 = 0.628571

We take this and multiply it by the number we divided by:

0.628571 x 35 = 22

Therefore, the final answer is:

1428 remainder 22

or in the shorthand we will use (where ex is excess) throughout this document:

1428 ex 22

With this knowledge in mind, we can now solve for a three stage delay loop for 600 cycles:

cyc(aak,bbk,cck) = 3aak + 770bbk + 197122cck - 197879 u [16~50463241]

The procedure should be fairly intuitive if followed. Its rules are nearly the same as above, but remember to bring the constant (integer at the very end) to the other side, and to start with the largest coefficient in division:

cyc(aak,bbk,cck) = 3aak + 770bbk + 197122cck - 197879 = 600
3aak + 770bbk + 197122cck = 600 + 197879
3aak + 770bbk + 197122cck = 198479

solve for cck by taking the new constant, and dividing it by the coefficient in front of cck:

198479/197122 = 1 ex 1357 [as a result, cck = 1]

we find bbk by taking the remainder and dividing it by the coefficient in front of bbk:

1357/770 = 1 ex 587 [as a result, bbk = 1]

and finally, we find cck by taking this remainder and dividing it by the coefficient in front of aak:

587/3 = 195 ex 2 [as a result, aak = 195]

Because of the last remainder being two, we now know that this formula is two cycles short of 600. Lets verify:

cyc(aak,bbk,cck) = 3aak + 770bbk + 197122cck - 197879
cyc(195,1,1) = 3(195) + 770(1) + 197122(1) - 197879
cyc(195,1,1) = 585 + 770 + 197122 - 197879
cyc(195,1,1) = 585 + 770 - 757
cyc(195,1,1) = 585 + 13
cyc(195,1,1) = 598

We must also discuss the topic of picking stages. Clearly with the three formulas we have derived before, any of them will work for a 600 cycle loop, however, in practice it is better to use the loop with fewest variables where possible as it conserves memory. Consider that a one stage loop requires two bytes of ram for aa and aak, a two stage loop requires four bytes of ram for aa, aak, bb, and bbk. As you can see by the pattern a three stage loop will require six bytes of ram, and in general an N-stage loop will require 2N bytes of ram. In addition, to initialize the constants aak, bbk, and cck, it takes two cycles per each variable, so for a six stage loop, this means 6 cycles. This may be wasteful if your application barely fits into the microcontroller.

Another concern when using delay loops is offset errors. You may be tempted to use a 5000 delay loop if you need your instruction to execute every 5000 cycles, however this would create an offset error since your instruction would take at least 1 cycle, thereby making your instruction would now execute every 5001 cycles.

In order to avoid this, pad your time critical routine or set of instructions so that it always takes the same amount of cycles to process, and set the delay loop to be equal to whatever your desired value is with the number of cycles your instructions or routine takes.

Sometimes, it is important to use delay loops to create real time delays. What this means is that sometimes it is necessarily to calculate how many seconds a delay loop will take. To do this, we include the following:

The PIC16 architecture is odd in that when you use a crystal oscillator oscillating at 4.000 Mhz, the number of instructions per second with simple instructions (such as nop, movlw, bcf, etc.) is actually 1.000 MIPS (million instructions per second). Therefore, the number of MIPS can be found by taking the frequency in megahertz, and dividing it by 4, and the number of instructions per second(IPS) can be found by multiplying this answer by 1,000,000. Thus:

[ips] = [xtal frequency in Mhz] * 1,000,000 / 4

[ips] = [xtal frequency in Mhz] * 250,000

Now that we know how many instructions per second it executes, we can figure out how many seconds a number of instructions will take by dividing the number of cycles by the number of instructions per second.

Thereby:

[seconds] = [cycles] / [ips]

or:

[seconds] = [cycles] / ([xtal frequency in Mhz] * 250,000)

and since frequency is the inverse of time(period):

[frequency in Hz] = 1 / [seconds]

[frequency in Hz] = 1 / [[cycles] / ([xtal frequency in Mhz] * 250,000)]

[frequency in Hz] = [xtal frequency in Mhz] * 250,000 / [cycles]

Last Revision: 2007-05-14


file: /Techref/microchip/PIC16DelayTutorial.htm, 22KB, , updated: 2007/10/5 13:04, local time: 2024/10/31 16:33,
TOP NEW HELP FIND: 
3.12.154.172:LOG IN
©2024 PLEASE DON'T RIP! THIS SITE CLOSES OCT 28, 2024 SO LONG AND THANKS FOR ALL THE FISH!

 ©2024 These pages are served without commercial sponsorship. (No popup ads, etc...).Bandwidth abuse increases hosting cost forcing sponsorship or shutdown. This server aggressively defends against automated copying for any reason including offline viewing, duplication, etc... Please respect this requirement and DO NOT RIP THIS SITE. Questions?
Please DO link to this page! Digg it! / MAKE!

<A HREF="http://www.piclist.com/techref/microchip/PIC16DelayTutorial.htm"> PIC 16 Delay Tutorial by Vasily Koudymov</A>

After you find an appropriate page, you are invited to your to this massmind site! (posts will be visible only to you before review) Just type a nice message (short messages are blocked as spam) in the box and press the Post button. (HTML welcomed, but not the <A tag: Instead, use the link box to link to another page. A tutorial is available Members can login to post directly, become page editors, and be credited for their posts.


Link? Put it here: 
if you want a response, please enter your email address: 
Attn spammers: All posts are reviewed before being made visible to anyone other than the poster.
Did you find what you needed? From: "/piclist/microchip/PIC16DelayTutorial.htm"

  PICList 2024 contributors:
o List host: MIT, Site host massmind.org, Top posters @none found
- Page Editors: James Newton, David Cary, and YOU!
* Roman Black of Black Robotics donates from sales of Linistep stepper controller kits.
* Ashley Roll of Digital Nemesis donates from sales of RCL-1 RS232 to TTL converters.
* Monthly Subscribers: Gregg Rew. on-going support is MOST appreciated!
* Contributors: Richard Seriani, Sr.
 

Welcome to www.piclist.com!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  .