Delay by x microseconds in C for pic18f
I need an accurate time delay function written in C that delays the pic program execution by a given number of microseconds. I did find an example on microchipc.com which uses ASM, but the code only allows for clock speeds up to 32000000. My clock speed needs to be 64000000, but since I don't understand how the code is working I can't modify it to do what I need. Can anyone offer some explanation of the code or suggest how to implement something similar?
#if PIC_CLK == 4000000
#define DelayDivisor 4
#define WaitFor1Us asm("nop")
#define Jumpback asm("goto $ - 4")
#elif PIC_CLK == 8000000
#define DelayDivisor 2
#define WaitFor1Us asm("nop")
#define Jumpback asm("goto $ - 4")
#elif PIC_CLK == 16000000
#define DelayDivisor 1
#define WaitFor1Us asm("nop")
#define Jumpback asm("goto $ - 4")
#elif PIC_CLK == 20000000
#define DelayDivisor 1
#define WaitFor1Us asm("nop"); asm("nop")
#define Jumpback asm("goto $ - 6")
#elif PIC_CLK == 32000000
#define DelayDivisor 1
#define WaitFor1Us asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop")
#define Jumpback asm("goto $ - 12")
#else
#error delay.h 开发者_运维问答- please define PIC_CLK correctly
#endif
#define DelayUs(x) { \
delayus_variable=(unsigned char)(x/DelayDivisor); \
asm("movlb (_delayus_variable) >> 8"); \
WaitFor1Us; } \
asm("decfsz (_delayus_variable)&0ffh,f"); \
Jumpback;
It seems to me from this segment:
#elif PIC_CLK == 16000000
#define DelayDivisor 1
#define WaitFor1Us asm("nop")
#define Jumpback asm("goto $ - 4")
#elif PIC_CLK == 20000000
#define DelayDivisor 1
#define WaitFor1Us asm("nop"); asm("nop")
#define Jumpback asm("goto $ - 6")
#elif PIC_CLK == 32000000
#define DelayDivisor 1
#define WaitFor1Us asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop")
#define Jumpback asm("goto $ - 12")
that for each extra 4 million increase in PIC_CLK
, you need another nop
instruction.
I've not used the earlier ones since they just use a scaling function at lower clock speeds - since you can't execute half or quarter of a nop
, they just reduce the loop count to half or quarter and execute a full nop
that many times.
So, for 64 million (which 32 million more than the last) you would need another eight nop
instructions (32 million divided by 4 million) and, since each one increase the jump size by 2 (the PIC18F having a 2-byte instruction width), you should be able to use the following:
#elif PIC_CLK == 32000000
#define DelayDivisor 1
#define WaitFor1Us asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop")
#define Jumpback asm("goto $ - 12")
#elif PIC_CLK == 64000000
#define DelayDivisor 1
#define WaitFor1Us asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop") \
asm("nop"); asm("nop"); asm("nop"); asm("nop"); \
asm("nop"); asm("nop"); asm("nop"); asm("nop");
#define Jumpback asm("goto $ - 28")
#else
#error delay.h - please define PIC_CLK correctly
#endif
In summary, these are the values you need for each PIC_CLK value, on the off chance that the next generation will be even faster:
PIC_CLK Divisor NOP count Jump size
--------- ------- --------- ---------
1000000 16 1 4
2000000 8 1 4
4000000 4 1 4
8000000 2 1 4
16000000 1 1 4
20000000 1 2 6
24000000 1 3 8
28000000 1 4 10
32000000 1 5 12
64000000 1 13 28
96000000 1 21 44
128000000 1 29 60
Or, if you want the formulae for values greater than or equal to 16 million:
divisor = 1
nopcount = picclk / 4000000 - 3
jumpsize = nopcount * 2 + 2
The code simply loops over a set of nop
-instructions for a set amount of time. The movlb
instruction is used to load the BSR (only an 8-bit register, thus the shift). The decfsz
instruction is then used to decrement the loop counter and skip the next instruction if the result is zero, to break out of the loop. If the next instruction is not skipped, the Jumpback
instruction is called (a goto
) which jumps back to the top of the loop. Since each instruction on an 18F is two bytes wide (double word instructions are four bytes), you have to jump 12 lines back for the 32MHz version (5 nop
s and a decfsz
).
Now, you could follow paxdiablo's advice and make a new version with more nop
s, but that would take up some unnecessary space if you are only going to run @ 64MHz anyway. I would think that you could just do something like
#if PIC_CLK == 64000000
#define WaitFor1NOP asm("nop")
#define Jumpback asm("goto $ - 4")
#else
#error delay.h - please define PIC_CLK correctly
#endif
#define DelayUs(x) { \
delayus_variable=(unsigned char)(x*SOME_NUMBER); \
asm("movlb (_delayus_variable) >> 8"); \
WaitFor1NOP; } \
asm("decfsz (_delayus_variable)&0ffh,f"); \
Jumpback;
Here SOME_NUMBER is the number of nop
s you need to loop over to reach 1µs @ 64MHz, 13 according to paxdiablo's excellent math.
EDIT:
paxdiablo brought to my attention that this solution will limit the range of delay times more than his, since the largest number you can pass to the macro is 1/13th of what goes into an unsigned char. An unsigned char is 8 bits, which leaves us with 255/13 = 19. I don't know if that is too small for you. You could work around this by calling the delay macro multiple times, possibly even creating a new macro to do that for you.
精彩评论