// You should *NOT* use this code as-is!
#define SCALE 15501 // System-dependent time scaling factor
void WaitMS(unsigned long ms)
{ unsigned long counter = ms * SCALE; // Compute how long to wait
while(counter > 0) { counter--;} // Waste time
}
The idea is that the designer tweaks "SCALE" so that WaitMS takes exactly one msec for each integer value of the input ms. In other words WaitMS(5) waits for 5 msec. But, there are some significant problems with this code.
- Different compiler versions and different optimization levels could dramatically change the timing of this loop depending upon the code that is generated. If you aren't careful, your system could stop working for this reason when you do a recompile, without you even knowing the timing has changed or why that has happened.
- Changes to hardware can change the timing even if the code is recompiled. Changing the system clock speed is a fairly obvious problem. But other more subtle problems include clock throttling on high-end processors due to thermal management, putting the code in memory with a wait states for access, moving to a processor with instruction cache, or using a different CPU variant that has different instruction timings.
- The timing will change based on interrupt service routines delaying execution of the while loop. You are unlikely to see the worst case timing disruption in testing unless you have a very deterministic system and/or really great testing.
- This approach ties up the CPU, using power and preventing other tasks from running.
Sometimes there isn't a hardware timer or there is some other compelling reason to use a software loop. If that's the case, the following advice might prove helpful.
- Make sure you put the software timing loop in one place like this instead of having lots of small in-line timing loops all over the place. It is hard enough to get one timing loop right!
- The variable "counter" should be defined as "volatile" to make sure it is actually decremented each time through the loop. Otherwise the optimizer might decide to just eliminate the entire while loop.
- You should calibrate the "SCALE" value somehow to make sure it is accurate. In some systems it makes sense to calibrate a variable during system startup, or do an internal sanity check during outgoing system test to make sure that it isn't too far from the right value. This is tricky to do well, but if you skip it you have no safety net in case the timing does change.
(There are no doubt minor problems that readers will point out as well depending upon style preferences. As with all my code examples, I'm presenting a simple "how I usually see it" example to explain the concept. If you can find style problems then probably you already know the point I'm making. Some examples: WaitMS might check for overflow, uint32_t is better than "unsigned long," "const unsigned long SCALE = 15501L" is better if your compiler supports it, and there may be mixed-length math issues with the multiplication of ms * SCALE if they aren't both 32-bit unsigned integers. But the above code is what I usually see in code reviews, and these style details tend to distract from the main point of the article.)
No comments:
Post a Comment
Please send me your comments. I read all of them, and I appreciate them. To control spam I manually approve comments before they show up. It might take a while to respond. I appreciate generic "I like this post" comments, but I don't publish non-substantive comments like that.
If you prefer, or want a personal response, you can send e-mail to comments@koopman.us.
If you want a personal response please make sure to include your e-mail reply address. Thanks!