| Joel | People | Hobbies | Fun | Miscellaneous | |||||||||
|
PIC 16F84 Code: Delays A common programming task is to create a delay. A delay is a mechanism that keeps the processor from executing a part of its program for a specific amount of time. Delays are often necessary when interfacing with slow I/O devices, or when the PIC needs to generate a pulse at a specific frequency. If your PIC is driving a servo motor, it is necessary to send a PWM signal to the control line of the servo. By varying the width of the pulse, you can command the servo to rotate to an orientation associated with the pulse width. A delay can be used to control the width of the pulse. When communicating over a serial data line, it is necessary to send the bits of data at a rate that matches the device you are communicating with. Since the device on the other end of the line is not likely to want the data to be delivered as fast as the hardware is able to send it, the software will need to slow things down. A delay can be used to control the time between bits of information delivered over the serial data line. There are many other examples where a delay is necessary. Below are some methods for creating these delays. Choose the one that is most appropriate for your application. "NOP" Delay Every instruction the CPU executes takes time. So, the simplest way of delaying the processor a bit is to execute instructions. A simple delay can be achieved by throwing in a few instructions that don't do anything useful except waste machine cycles. In addition to consuming cycles, most instructions also alter the values in the status registers in some way. This could adversely affect the behavior of other parts of the program that rely on those values. Fortunately, there is one instruction that does not alter any status registers. Its mnemonic is NOP, which stands for "no operation". Executing this instruction does nothing but waste time, making it ideal for short delays. A limitation of using this technique is the length of the delay that can be achieved. With a 4MHz external clock, it would require 1000 successive NOPs to produce a 1ms delay. It would take 1,000,000 consecutive NOPs to produce a delay of one second. Remember, each NOP uses one program memory address. Obviously, this technique is practical only when delays of a few µseconds is needed. You could put a NOP inside a loop instead of placing many NOPs in succession. Doing so allows for longer delays without consuming precious program memory addresses. However, since the instructions that form the loop consume machine cycles themselves, you may not need to put anything inside the loop. An empty loop is the next type of delay. Basic Loop Delay The next most straight forward method of implementing a delay is to use a loop. Here is an example of a simple delay loop wrapped up in a subroutine that can be called from anywhere in your program.
delay movwf dcount
dloop decfsz dcount,f
goto dloop
return
The entry point is the
The first instruction,
The next instruction is
If the value in dcount is not zero after being decremented, then the
When dcount reaches zero, the goto instruction is skipped.
Execution falls to the next instruction, which is a
To use the basic delay above, equate dcount with any unused general purpose register.
Set the w register with a value then execute
dcount equ 0x0c
org 0x000
goto start
start movlw 0x40
call delay ; do nothing for a while
loop goto loop
; insert delay routine here
Presumably a real application would do something useful above and below the call to the delay routine. By placing different values into w before calling delay, you can control the length of the delay. Remember, the w register is an eight bit register. The range of values it may hold is 0 to 255. This limits the range of possible delay values. Also remember that the loop starts by decrementing the counter register. Decrementing a zero changes its value to 255. Starting at zero means there will be 256 iterations through the delay loop. This means that a value of zero provides the longest delay. A value of 1 produces the shortest possible delay. As stated previously, the range of delay times is limited by the number of values you are able to load into w. Since w is an eight bit register, there are only 256 ( 28 ) possible values. This is a serious limitation of the basic delay routine. The longest delay achievable with this routine is about 770 microseconds (.77 milliseconds) (assuming the CPU is clocked by a 4MHz crystal or resonator). Another important point about the delay routine is that delay times are not perfectly linear with respect to the starting value passed in the w register. In English, this means a delay with a starting value of 4 will not be twice as long as a delay with a starting value of 2. To understand this, and to understand where the 770 microseconds figure comes from, an understanding of clock speeds and machine cycles is required. Delay Duration Calculations It is possible to predict the duration produced by the basic delay routine with a fairly high degree of precision. Several factors come into play. First, you will need to know how fast the CPU is running. If the CPU is clocked by a precise quartz crystal rated at 4MHz, then you can assume the clock speed is 4MHz. If you are driving your CPUs clock with a resistor/capacitor, all bets are off. The only way to get a remotely accurate calculation is to measure the clock rate with a frequency counter. Even then, temperature changes can alter the run rate significantly. We will assume the CPU is being clocked by a 4MHz crystal. The CPU clock rate is internally divided by 4 due to the architecture of the CPU. This means there are 1 million cycles per second, internally. (4MHz clock speed divided by 4 = 1MHz internal speed, or 1 million cycles per second). If you know how long each CPU instruction takes to execute (measured in cycles), you could add up all the execution times for every instruction in your program to determine how long it will take to run. Fortunately, this is pretty easy to do. Most CPU instructions require 1 cycle to execute. Program branching instructions require two cycles. In the basic delay routine, the instruction execution times are as follows:
* The decfsz instruction takes one cycle normally. If the value after the decrement is zero, then it takes two cycles. The latter case happens only once during the execution of the delay routine, the former happens n-1 times. Calculations for the Basic Delay Routine number of cycles = 3n + 4 where n is the value passed in the w register prior to calling the delay routine. The following table shows the delay times for various values of n. All calculations are based on a 4MHz clock.
n = 255 delay = 769 cycles 0.000769 seconds 769 µseconds
n = 254 delay = 766 cycles 0.000766 seconds 766 µseconds
n = 253 delay = 763 cycles 0.000763 seconds 763 µseconds
: : : :
n = 249 delay = 751 cycles 0.000751 seconds 751 µseconds
: : : :
n = 165 delay = 499 cycles 0.000499 seconds 499 µseconds
: : : :
n = 82 delay = 250 cycles 0.000250 seconds 250 µseconds
: : : :
n = 32 delay = 100 cycles 0.000100 seconds 100 µseconds
: : : :
n = 4 delay = 16 cycles 0.000016 seconds 16 µseconds
n = 3 delay = 13 cycles 0.000013 seconds 13 µseconds
n = 2 delay = 10 cycles 0.000010 seconds 10 µseconds
n = 1 delay = 7 cycles 0.000007 seconds 7 µseconds
n = 0* delay = 772 cycles 0.000772 seconds 772 µseconds
(*treat as n=256)
Included in these calculations are the execution times of the By examining the chart above, a limitation of the basic delay routine becomes obvious. The longest delay achievable is 0.000772 seconds. To achieve a delay on the order of 1 second, the delay routine would need to be called nearly 1300 times. Which leads to the next method of creating delays: loops within loops. Loops within loops are useful for creating extra long delays. The longest delay a double-nested loop can achieve is about 1/5 of a second. Since the outer loop can count only as high as 256, the double-nested loop can only delay approximately 256x0.000772 seconds. A triple-nested loop can be used to create a delay of several seconds. The code for nested loop delays is a simple extension of the basic delay routine shown above, and is therefore not shown here. Remember, the instructions that make up the outer loop will consume execution time and must be accounted for in the delay calculation. Since delay times are not linear with respect to loop start values, the calculations for multiple level nested loops can get tricky. If your application requires precise delays that are long enough to require two- or three-level nested loops, then you'll have a bit of a challenge on your hands. Consider the following: Your application requires a precise 1/10 second delay. You know that the longest delay you can achieve with a single loop is .000772 seconds, so obviously you'll need a double-nested loop. How many times must the outer loop execute to acheive the desired 1/10 second delay? The answer is about 129.5336788 times. Clearly this is a problem. It is not possible to execute a loop a fractional number of times. The best you can achieve is either 0.099588 second delay (129 x 0.000772 seconds) or 0.10036 second delay (130 x 0.000772 seconds). If you need a precise 1/10 second delay, neither solution may be acceptable. One possible solution to this problem is to change the inner loop to a lesser number of executions. For instance, if the inner loop executed 165 times instead of the maximum of 256, the inner loop would delay for 0.000499 seconds. This means the outer loop would need to execute 200 times to achieve the desired 1/10 second delay. 200 x 0.000499 = 0.0998 is close, but still may not be accurate enough. Keep in mind these calculations do not account for the execution time required by the code for the outer loop. There is a formula similar to the one for single-level loops that can be used to calculate delay times for double- and triple-nested loops. I haven't taken the time to derive those formulas. If someone wants to do that work, I'll post the results here and give due credit. Armed with these formulas, it would be possible to algorithmically find values for the two variables (or three for the triple-nested delay) that would produce the desired results. This method would allow you to answer the question: what values would I need to use for the outer and inner loops to achieve a delay of n seconds? My math skills are not strong enough to know how to do that, so I'd use the brute force method: try all possible combinations. Of course, I'd write a program to do the work for me. Final Notes If your application requires delays of several seconds or more, you might want to consider using an external timer that triggers an interrupt on the 16F84. The external timer could be calibrated to the precise time delay you need. There are other more esoteric methods for creating delays. For instance, it is possible to use the 16F84's built in timer/counter to trigger a periodic interrupt. Each time an interrupt occurs, a register could be decremented from some initial value. The code on the chip could wait until the register value reaches zero before proceeding. With a little imagination, many other methods could be discovered.
|
||||||||||||||||||||||||||||||||||||||||||||
| Tell me what you think | Copyright © 2000 Joel T. Anderson | |