From 3dfb8a83bcf2d6a2958c1e6345c39f398323115a Mon Sep 17 00:00:00 2001 From: Markus Hitter Date: Sun, 26 Apr 2015 01:54:08 +0200 Subject: [PATCH] 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. --- delay.c | 46 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/delay.c b/delay.c index 713d59f..cfdff69 100644 --- a/delay.c +++ b/delay.c @@ -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(); }