|Fixing the Blink|
dly: clrf i dly1: decf i,F btfss STATUS,Z goto dly1 return
This clears the i variable to 0 and then subtracts one from it until it is zero again. This provides 256 wasted cycles. I deliberately didn't use the decfsz command which is more efficient because I wanted to wast time, but a more efficient version of the loop would be:
dly: clrf i dly1: decfsz i,F goto dly1 return
So how long is the delay? The PIC executes an instruction in 4 cycles. At 20MHz, each cycle is 50nS (there are 20 million 50nS pulses in one second). That makes each instruction require 200nS -- sort of. Instructions that jump (including things like return) require the time it takes two instruction times. A skip instruction (like btfss) requires two cycles if it skips, but only one if it doesn't.
So consider this:
dly: clrf i ' 1 cycle dly1: decf i,F ' 1 cycle btfss STATUS,Z ' 1 or 2 cycles (mostly 1) goto dly1 ' 2 cycles return ' 2 cycles
The clrf and return instructions only execute once for a total of 3 cycles. The rest of the instructions will first execute 255 times. Then on the 256th attempt, the btfss will require 2 cycles, but the goto command will not execute. So the total time will be:
1 + 255 * (1 + 1 + 2) + (1 + 2) + 2
1 + 255*4+3+2
Of course, this is 1,026 cycles. Add the two cycles for the call instruction and you will delay 1,028 cycles. So this miniscule count only requires around 206uS.
There are two ways you could lengthen the delay. First, you could make the loop take longer by wasting time. The nop instruction is the obvious choice -- it wastes one cycle. Adding a nop to the middle of the loop would only add 51uS to each call (255*200nS).
There are some other sequences of instructions that waste time. For example:
This jumps to the next instruction (which is the next one that would execute anyway). This takes two cycles, so adding one of these would add 102uS to the delay.
You can get four extra instruction cycles using this trick:
dly: clrf i ; 1 cycle dly1:
call delay4 ; 2 cycles + 2 cycles = 4 cycles decf i,F ; 1 cycle btfss STATUS,Z ; 1 or 2 cycles (mostly 1) goto dly1 ; 2 cycles
delay4: return ; 2 cycles
Here, the return statement is doing double duty. It returns from the subroutine, but it also returns from the call delay4 instruction. This adds 204uS to the delay.
All of these are inconsequential for making a visible blink, however. But like compound interest, multiplying the delay can make things increase quickly.
dly: clrf i ; 1 dly1: clrf j ; 1 dly2: clrf k ; 1 dly3: decf k,F ; 1 btfss STATUS,Z ; 1/2 goto dly3 ; 2 decf j,F ; 1 btfss STATUS,Z ; 1/2 goto dly2 ; 2 decf i,F ; 1 btfss STATUS,Z ; 1/2 goto dly1 ; 2 return ; 2
Of course, you have to change the cblock to have variables j and k:
cblock 0x20 i ; byte variable j k endc
Can you compute how long the delay will take now?
The answer is nearly 13 seconds! Probably too long. Try it.
The trick is to not start the outer count at 0. The two inner loops take about 53mS per loop (256 * .053 = 13.47). So a count of 10 should be about 500mS or a half second. Try this delay:
dly: movlw .10 ; 1 movwf i ; 1 - start i at 10 dly1: clrf j ; 1 dly2: clrf k ; 1 dly3: decf k,F ; 1 btfss STATUS,Z ; 1/2 goto dly3 ; 2 decf j,F ; 1 btfss STATUS,Z ; 1/2 goto dly2 ; 2 decf i,F ; 1 btfss STATUS,Z ; 1/2 goto dly1 ; 2 return ; 2
The period in front of the 10 ensures MPASM will use decimal regardless of your other settings.
Try this code and you'll see that the LED blinks at a pretty rapid rate. There are other ways to implement delays (for example, using the timer). Experiment with this code for now.