delay.c: delay_us() and delay_ms() are now calibrated.

See comments in the code for the result.

This also fixes a bug where corner cases like delay(0) or
delay(13107) whould result in an extra long pause.
Note: 65535 / (F_CPU / 4000000) = 13107 on 20 MHz.

This costs 28 bytes binary size.
This commit is contained in:
Markus Hitter 2015-04-26 01:54:08 +02:00
parent 452908afa9
commit 3dfb8a83bc
1 changed files with 39 additions and 7 deletions

46
delay.c
View File

@ -12,21 +12,53 @@
#error Delay functions only work with F_CPU >= 4000000UL
#endif
/// delay microseconds
/// \param delay time to wait in microseconds
/** Delay in microseconds
\param delay time to wait in microseconds
Calibrated in SimulAVR.
Accuracy on 20 MHz CPU clock: -1/+3 clock cycles over the whole range(!).
Accuracy on 16 MHz CPU clock: delay is about 0.8% too short.
Exceptions are delays of 0..2 on 20 MHz, which are all 0.75 us and delays
of 0..3 on 16 MHz, which are all 0.93us.
*/
void delay_us(uint16_t delay) {
wd_reset();
// Compensate call overhead, as close as possible.
#define OVERHEAD_CALL_CLOCKS 39 // clock cycles
#define OVERHEAD_CALL_DIV ((OVERHEAD_CALL_CLOCKS / (F_CPU / 1000000)) + 1)
#define OVERHEAD_CALL_REM ((OVERHEAD_CALL_DIV * (F_CPU / 1000000)) - \
OVERHEAD_CALL_CLOCKS)
if (delay > OVERHEAD_CALL_DIV) {
delay -= OVERHEAD_CALL_DIV;
if (OVERHEAD_CALL_REM >= 2)
_delay_loop_2((OVERHEAD_CALL_REM + 2) / 4);
}
else {
return;
}
while (delay > (65536L / (F_CPU / 4000000L))) {
_delay_loop_2(65534); // we use 65534 here to compensate for the time that the surrounding loop takes. TODO: exact figure needs tuning
#define OVERHEAD_LOOP_CLOCKS 13
_delay_loop_2(65536 - (OVERHEAD_LOOP_CLOCKS + 2) / 4);
delay -= (65536L / (F_CPU / 4000000L));
wd_reset();
}
_delay_loop_2(delay * (F_CPU / 4000000L));
if (delay)
_delay_loop_2(delay * (F_CPU / 4000000L));
wd_reset();
}
/// delay milliseconds
/// \param delay time to wait in milliseconds
/** Delay in microseconds
\param delay time to wait in milliseconds
Accuracy on 20 MHz: delay < 0.04% too long over the whole range.
Accuracy on 16 MHz: delay < 0.8% too short over the whole range.
*/
void delay_ms(uint32_t delay) {
wd_reset();
while (delay > 65) {
@ -34,6 +66,6 @@ void delay_ms(uint32_t delay) {
delay -= 65;
wd_reset();
}
delay_us(delay * 1000);
delay_us(delay * 1000 - 2);
wd_reset();
}