Lucky for us, C provides an easy way of generating time delays: the __delay_us(x) and __delay_ms(x) macros. We're going to leave it up to the compiler to generate the proper delay code, but first we have to tell the compiler what speed oscillator we're using so it has enough information for the calculations. We'll use the #define preprocessor directive to define our crystal frequency. It doesn't matter if we're using a crystal, a resonator, a RC oscillator, etc. For a 4MHz oscillator, such as we're using with the 12F629's internal RC oscillator, the preprocessor directive is:
#define _XTAL_FREQ 4000000
The crystal frequency is provided in Hertz, so for 4MHz, we need to put four million. The use of these macros and their requirements is described in higher detail in the XC8 Compiler User's Guide. It states:
"It is often more convenient to request a delay in time-based terms, rather than in cycle counts. The macros __delay_ms(x) and __delay_us(x) are provided to meet this need. These macros convert the time-based request into instruction cycles that can be used with _delay(n). In order to achieve this, these macros require the prior definition of preprocessor symbol _XTAL_FREQ, which indicates the system frequency. This symbol should equate to the oscillator frequency (in hertz) used by the system."
For the delay itself, the XC8 compiler gives us an option for delays in microseconds and milliseconds. You write those as:
__delay_us(x);
__delay_ms(x);
Double underscore before the delay, single underscore in between. Microsecond is "us" and millisecond is ms. X is representative of the number of micro or milliseconds we want in our delay. The compiler will generate a delay as long as we have defined the crystal frequency. As a note, an error will be generated during the compiling process if our delay number is too large. The delay instructions must be followed by a semicolon. The compiler flags these delay instructions with a red underline and assumes it's an error, but that is just a compiler issue and we can ignore it. The program will successfully compile despite these flags.
Let's see a delay in action. Our new program in figure 1 is another slight alteration of our first program, thus the hardware has not changed.
Figure 1 |
Because we're in a while loop, the flashing of the LED will continue for as long as we hold down the button. When we release the button, the button pin will read 0, and the while loop will not execute, thus the LED will remain off.
As a point of interest, I should make note that these delay instructions are considered "dumb". In other words, they may or may not be precise. These routines are good enough for LEDs and other timing operations that don't need to be spot on, but for operations where precise timing is very important, such as timing the return of a sonar pulse, I'm afraid that the __delay instruction won't cut it. For more precise timing operations, alternative methods must be sought. Of course, the accuracy of timing operations depends on more than just software, but also the oscillator type we chose and its accuracy.
In a later lesson, we'll talk about how to generate longer time delays. What happens when we want to delay a program for seconds? Minutes? An hour? We can do all these things, and we'll see how to soon.
November 25, 2014: Post edited to include additional reference material for a more thorough lesson.