diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ce20758 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +# Makefile useful during development. +# `make asm` compiles examples/codegen.rs to assembly. +# The output can then be reviewed for correctness. + +NV?=nightly +override FLAGS += -Z build-std=core -Z build-std-features=panic_immediate_abort --release +MCU?=avr-atmega328p +AVR_CPU_FREQUENCY_HZ?=16000000 +export AVR_CPU_FREQUENCY_HZ + +build: + cargo +$(NV) build $(FLAGS) --target=$(MCU).json -v + cargo +$(NV) build --example codegen $(FLAGS) --target=$(MCU).json -v + +asm: + cargo +$(NV) rustc --example codegen $(FLAGS) --target=$(MCU).json -- --emit asm + @echo target/$(MCU)/release/examples/*.s + +asmclean: + -rm target/*/release/deps/*.s diff --git a/README.md b/README.md index ddfcd57..ccf1263 100644 --- a/README.md +++ b/README.md @@ -5,36 +5,35 @@ [API Documentation](https://docs.rs/avr_delay/) -The intent of this library is to provide avr specific delay routines similar to the ones provided by the arduino library. The public functions are: +The intent of this library is to provide avr specific delay routines similar to the ones provided by the arduino library. ## `$AVR_CPU_FREQUENCY_HZ` This crate uses the [`avr-config`](https://crates.io/crates/avr-config) crate for fetching the CPU frequency. As such, the `AVR_CPU_FREQUENCY_HZ` environment variable will need to be set when compiling your crate for AVR. -Example: - ```bash export AVR_CPU_FREQUENCY_HZ=16000000 cargo build -Z build-std=core --target avr-atmega328p.json --release ``` -```rust -delay(count: u32) -``` - -is a raw delay loop. Each loop is 4 cycles. The asm section can loop 65536 times. Initial overhead is about 13 cycles. Each outer loop has an overhead of about 11 cycles. +## API ```rust -delay_us(us: u32) +delay_cycles() +delay_us() +delay_ms() +delay_sec() ``` -delay _us_ microseconds +`delay_cycles` accepts 0 to 25_769_803_784 cycles (almost 18 minutes at 24Mhz). + +The other functions convert time to cycles by using CPU_FREQUENCY_HZ. ```rust -delay_ms(ms: u32) +delay_ms<42>(); // delay by 42ms (exactly 1_008_000 cycles at 24Mhz). ``` -delay _ms_ milliseconds +## Example A simple example of how to use it follows. @@ -64,7 +63,7 @@ extern crate avr_delay; use arduino::{DDRB, PORTB}; use core::ptr::write_volatile; -use avr_delay::{delay, delay_ms, delay_us}; +use avr_delay::delay_ms; #[no_mangle] pub extern fn main() { @@ -73,7 +72,7 @@ pub extern fn main() { loop { out = out ^ 0xff; unsafe { write_volatile(PORTB, out) } - delay_ms(1000000); + delay_ms<1000000>(); } } diff --git a/examples/codegen.rs b/examples/codegen.rs new file mode 100644 index 0000000..efbac13 --- /dev/null +++ b/examples/codegen.rs @@ -0,0 +1,143 @@ +#![no_std] +#![no_main] + +extern crate avr_std_stub; + +use avr_delay::delay_cycles; + +#[no_mangle] +fn main() {} + +/* + +Interesting corner case values. They are picked to be right around the switch between lower to +higher number of bits for the dealy counter. + +#!/bin/bash +while read i; do + echo "#[inline(never)] #[no_mangle] pub fn test_${i}() { delay_cycles::<${i}>(); }" +done <(); } +#[inline(never)] #[no_mangle] pub fn test_1() { delay_cycles::<1>(); } +#[inline(never)] #[no_mangle] pub fn test_2() { delay_cycles::<2>(); } +#[inline(never)] #[no_mangle] pub fn test_3() { delay_cycles::<3>(); } +#[inline(never)] #[no_mangle] pub fn test_4() { delay_cycles::<4>(); } +#[inline(never)] #[no_mangle] pub fn test_5() { delay_cycles::<5>(); } +#[inline(never)] #[no_mangle] pub fn test_6() { delay_cycles::<6>(); } +#[inline(never)] #[no_mangle] pub fn test_7() { delay_cycles::<7>(); } +#[inline(never)] #[no_mangle] pub fn test_8() { delay_cycles::<8>(); } +#[inline(never)] #[no_mangle] pub fn test_9() { delay_cycles::<9>(); } +#[inline(never)] #[no_mangle] pub fn test_11() { delay_cycles::<11>(); } +#[inline(never)] #[no_mangle] pub fn test_12() { delay_cycles::<12>(); } +#[inline(never)] #[no_mangle] pub fn test_767() { delay_cycles::<767>(); } +#[inline(never)] #[no_mangle] pub fn test_768() { delay_cycles::<768>(); } +#[inline(never)] #[no_mangle] pub fn test_769() { delay_cycles::<769>(); } +#[inline(never)] #[no_mangle] pub fn test_770() { delay_cycles::<770>(); } +#[inline(never)] #[no_mangle] pub fn test_771() { delay_cycles::<771>(); } +#[inline(never)] #[no_mangle] pub fn test_772() { delay_cycles::<772>(); } +#[inline(never)] #[no_mangle] pub fn test_773() { delay_cycles::<773>(); } +#[inline(never)] #[no_mangle] pub fn test_774() { delay_cycles::<774>(); } +#[inline(never)] #[no_mangle] pub fn test_262_144() { delay_cycles::<262_144>(); } +#[inline(never)] #[no_mangle] pub fn test_262_145() { delay_cycles::<262_145>(); } +#[inline(never)] #[no_mangle] pub fn test_262_146() { delay_cycles::<262_146>(); } +#[inline(never)] #[no_mangle] pub fn test_262_147() { delay_cycles::<262_147>(); } +#[inline(never)] #[no_mangle] pub fn test_262_148() { delay_cycles::<262_148>(); } +#[inline(never)] #[no_mangle] pub fn test_262_149() { delay_cycles::<262_149>(); } +#[inline(never)] #[no_mangle] pub fn test_262_150() { delay_cycles::<262_150>(); } +#[inline(never)] #[no_mangle] pub fn test_262_151() { delay_cycles::<262_151>(); } +#[inline(never)] #[no_mangle] pub fn test_262_152() { delay_cycles::<262_152>(); } +#[inline(never)] #[no_mangle] pub fn test_262_153() { delay_cycles::<262_153>(); } +#[inline(never)] #[no_mangle] pub fn test_262_154() { delay_cycles::<262_154>(); } +#[inline(never)] #[no_mangle] pub fn test_262_155() { delay_cycles::<262_155>(); } +#[inline(never)] #[no_mangle] pub fn test_83_886_081() { delay_cycles::<83_886_081>(); } +#[inline(never)] #[no_mangle] pub fn test_83_886_082() { delay_cycles::<83_886_082>(); } +#[inline(never)] #[no_mangle] pub fn test_83_886_083() { delay_cycles::<83_886_083>(); } +#[inline(never)] #[no_mangle] pub fn test_83_886_084() { delay_cycles::<83_886_084>(); } +#[inline(never)] #[no_mangle] pub fn test_83_886_085() { delay_cycles::<83_886_085>(); } +#[inline(never)] #[no_mangle] pub fn test_83_886_086() { delay_cycles::<83_886_086>(); } +#[inline(never)] #[no_mangle] pub fn test_83_886_087() { delay_cycles::<83_886_087>(); } +#[inline(never)] #[no_mangle] pub fn test_83_886_088() { delay_cycles::<83_886_088>(); } +#[inline(never)] #[no_mangle] pub fn test_83_886_089() { delay_cycles::<83_886_089>(); } +#[inline(never)] #[no_mangle] pub fn test_83_886_090() { delay_cycles::<83_886_090>(); } +#[inline(never)] #[no_mangle] pub fn test_83_886_091() { delay_cycles::<83_886_091>(); } +#[inline(never)] #[no_mangle] pub fn test_83_886_092() { delay_cycles::<83_886_092>(); } +#[inline(never)] #[no_mangle] pub fn test_83_886_093() { delay_cycles::<83_886_093>(); } +#[inline(never)] #[no_mangle] pub fn test_83_886_094() { delay_cycles::<83_886_094>(); } +#[inline(never)] #[no_mangle] pub fn test_83_886_095() { delay_cycles::<83_886_095>(); } +#[inline(never)] #[no_mangle] pub fn test_25_769_803_778() { delay_cycles::<25_769_803_778>(); } +#[inline(never)] #[no_mangle] pub fn test_25_769_803_779() { delay_cycles::<25_769_803_779>(); } +#[inline(never)] #[no_mangle] pub fn test_25_769_803_780() { delay_cycles::<25_769_803_780>(); } +#[inline(never)] #[no_mangle] pub fn test_25_769_803_783() { delay_cycles::<25_769_803_783>(); } +#[inline(never)] #[no_mangle] pub fn test_25_769_803_784() { delay_cycles::<25_769_803_784>(); } + +// This shouldn't compile, but we don't have static assertion yet. The code produced is still +// correct. But it costs more than calling delay_cycles twice. +#[inline(never)] #[no_mangle] pub fn test_25_769_803_785_bad() { delay_cycles::<25_769_803_785>(); } + +// This shouldn't compile, but we don't have static assertion yet. The code produced is still +// correct. But it costs more than calling delay_cycles twice. +#[inline(never)] #[no_mangle] pub fn test_25_853_952_778_bad() { delay_cycles::<25_853_952_778>(); } + +// This shouldn't compile, but we don't have static assertion yet. The code produced is still +// correct. But it costs more than calling delay_cycles twice. This is the absolute limit. +#[inline(never)] #[no_mangle] pub fn test_25_853_952_779_bad() { delay_cycles::<25_853_952_779>(); } + +// This shouldn't compile, but we don't have static assertion yet. This does overflow and should +// produces the same function as 25_769_803_778. +#[inline(never)] #[no_mangle] pub fn test_25_853_952_780_overflow() { delay_cycles::<25_853_952_780>(); } diff --git a/examples/milliseconds.rs b/examples/milliseconds.rs index d808d26..906261d 100644 --- a/examples/milliseconds.rs +++ b/examples/milliseconds.rs @@ -5,5 +5,5 @@ extern crate avr_std_stub; #[no_mangle] fn main() { - avr_delay::delay_ms(4500); + avr_delay::delay_ms::<4500>(); } diff --git a/src/delay_cycles.rs b/src/delay_cycles.rs new file mode 100644 index 0000000..9aac747 --- /dev/null +++ b/src/delay_cycles.rs @@ -0,0 +1,294 @@ +use core::arch::asm; + +/// Delayer::delay_impl() generates the inline assembly to delay by an exact amount of cycles. +/// +/// The total number of cycles is computed as CYCLES * MUL / DIV. +/// With a maximum of 25_769_803_784 cycles. +/// +/// Zero cycles does nothing. One cycle emits a `nop` instruction. 2 cycles one `rjump`. Above 5 +/// cycles, we get into loops. With counters starting at 8 bits, and progressing through 16, 24, +/// and ultimately 32 bits. For a maximum of 11 instructions. +/// +/// +/// Two nightly features are required for this implementation: +/// #![feature(asm_experimental_arch)] +/// #![feature(asm_const)] +/// +/// When the rustc `feature(generic_const_exprs)` is complete +/// (https://github.com/rust-lang/rust/issues/76560) it will become possible to do this directly: +/// ``` +/// fn delay_ms() { +/// Delayer::<{SECS * CPU_FREQUENCY_HZ / 1000}>::delay_impl(); +/// } +/// ``` +/// +/// This is also why the code is structured in such a way. With everything as associated consts. +/// Because do support evaluation of expressions at compile time just fine contrary to const +/// generics. The implementation goes from generic consts, to associated consts on the Delayer +/// struct. And in turn those associated consts are fed to the `asm!` macro. +/// +/// The rustc `feature(asm_const)` is also a work in progress +/// (https://github.com/rust-lang/rust/issues/93332). It appears to work well in the present code. +/// It also depends on the feature discussed in the next paragraph. +/// +/// When `feature(inline_const)` (https://github.com/rust-lang/rust/issues/76001) is complete, all +/// the conditionals used in `delay_impl()` can be wrapped within `const {}` blocks. To ensure +/// beyond a shadow of a doubt that the whole function is fully linearised at compile time. +/// Nevertheless; thanks to constant propagation; this already happens implicitly. +/// +/// The maximum number of cycles is 25_769_803_784 when `delay_cycles_u32()` iterates 2^32 +/// times, `delay_2cycles()` is used twice, and `delay_1cycle()` once. +/// +/// Every `delay_cycles_u*()` function has a minimum and maximum number of cycles it can consume. +/// The minimum is: (cycles per run). +/// The maximum is: (cycles per run) + (cycles per iteration) * (counter-1). +/// Note that a counter of zero iterates 2^bits time. +/// +/// Example with `delay_cycles_u32()`. +/// Minimum: 9 cycles with 1 iteration. +/// Maximum: 9 + 6 * (2^32-1) == 25_769_803_779 cycles with 2^32 iterations. +/// +/// Cycles 1..=5 are implemented by a combination of up to two `delay_2cycles()` and up to one +/// `delay_1cycle()`. Which gets us our maximum of 25_769_803_779 + 5 == 25_769_803_784. +/// +/// Technically, beyond this value, the counters of various sizes will be combined until they are +/// all used up. This means the absolute limit is the sum of the maximum cycles of all counters +/// combined plus five: +/// (3+3*0xFF) + (5+4*0xFFFF) + (7+5*0xFF_FFFF) + (9+6*0xFFFF_FFFF) + 5 == 25_853_952_779. +/// But at this point, this is costing 23 instructions, for very little gain (~3.5s at 24Mhz). +/// Calling delay_cycles twice would be far more efficient. +pub struct Delayer; + +struct Cycles { + counter_mask: u64, + cycles_per_run: u64, + cycles_per_iter: u64, + max_cycles: u64, +} + +struct Selection { + selected: bool, + counter: u64, + remainder: u64, +} + +const fn cycles(counter_mask: u64, cycles_per_run: u64, cycles_per_iter: u64) -> Cycles { + Cycles { + counter_mask, + cycles_per_run, + cycles_per_iter, + max_cycles: cycles_per_run + cycles_per_iter * counter_mask, + } +} + +const fn select(info: Cycles, cycles: u64, above: u64) -> Selection { + if !(cycles > above) { + return Selection { selected: false, counter: 0, remainder: cycles }; + } + let counter = (cycles - info.cycles_per_run) / info.cycles_per_iter + 1; + let counter = if counter > info.counter_mask { + info.counter_mask + 1 + } else { + counter + }; + Selection { + selected: true, + counter: if counter > info.counter_mask { + 0 // Counter wrap around. + } else { + counter + }, + remainder: cycles - (info.cycles_per_run + info.cycles_per_iter * (counter - 1)) + } +} + +impl Delayer { + // Multiply first to avoid precision loss. + // With a u64 there is no overflow when MUL is lower than: + // (2^64-1)/25_769_803_784 == 715_827_882. + // Since MUL is usually CPU_FREQUENCY_HZ, this allows up to 715.83 MHz. + const TOTAL_CYCLES: u64 = CYCLES * MUL / DIV; + + // With `feature(generic_const_exprs) it becomes possible to construct a static assertion. + //const _: [(); 0 - ((Self::TOTAL_CYCLES > 25_769_803_784) as usize)] = []; + + // counter mask, cycles per run, cycles per iteration. | cost + worst case remainder cost + const U32_INFO: Cycles = cycles(0xFFFF_FFFF, 9, 6); // 8 + 3 + const U24_INFO: Cycles = cycles( 0xFF_FFFF, 7, 5); // 6 + 2 + const U16_INFO: Cycles = cycles( 0xFFFF, 5, 4); // 4 + 2 + const U8_INFO: Cycles = cycles( 0xFF, 3, 3); // 3 + 1 + + // The selection process stops at the smallest counter size that can handle the number of + // cycles to consume with a remainder of up to 5 cycles. This will not always produce the + // smallest possible number of instructions. In some cases, the cost of U16+U8 might be one + // instruction lower than that of the U24. This is because the U16+U8 would have no remainder + // contrary to the U24. Many combinations of the various counter sizes are possible, dividing + // the number of cycles more or less evenly. Implementing this without + // `feature(generic_const_exprs) seems daunting. It would require to compute the various + // combinations and compare the cost. Note that gcc-avr intrinsics delay_cycles + // doesn't bother to optimize this if this can be of any consolation. + const U32: Selection = select(Self::U32_INFO, Self::TOTAL_CYCLES, Self::U24_INFO.max_cycles + 4); + const U24: Selection = select(Self::U24_INFO, Self::U32.remainder, Self::U16_INFO.max_cycles + 5); + const U16: Selection = select(Self::U16_INFO, Self::U24.remainder, Self::U8_INFO.max_cycles + 4); + const U8 : Selection = select(Self::U8_INFO, Self::U16.remainder, 5); + // The extras +4, +5, and +4 cycles take into account that even though the number of cycles is + // beyond the capacity of the counter, the overflow can be served by the 1.=5 cycles + // implementation. In those instances, it so happens that the counter of the next size up would + // take more instructions because it also requires a remainder. + + // The counters leave up to 5 cycles as a remainder. They are consumed with up to two `rjump` + // and a `nop`. + // 5 cycles => 3 instructions. + // 4 cycles => 2 instructions. + // 3 cycles => 2 instructions. + // 2 cycles => 1 instruction. + // 1 cycle => 1 instruction. + + /// 8 instructions. + /// 9 cycles per run. + /// 6 cycles per iteration. + #[inline(always)] + fn delay_cycles_u32() { + unsafe { + asm!( + "ldi {r0:l}, {b0}", + "ldi {r0:h}, {b1}", + "ldi {r2}, {b2}", + "ldi {r3}, {b3}", + "1:", + "sbiw {r0}, 1", + "sbci {r2}, 0", + "sbci {r3}, 0", + "brne 1b", + r0 = out(reg_iw) _, + r2 = out(reg_upper) _, + r3 = out(reg_upper) _, + b0 = const (Self::U32.counter >> 0) as u8, + b1 = const (Self::U32.counter >> 8) as u8, + b2 = const (Self::U32.counter >> 16) as u8, + b3 = const (Self::U32.counter >> 24) as u8, + options(nomem, nostack), + ) + } + } + + /// 6 instructions. + /// 7 cycles per run. + /// 5 cycles per iteration. + #[inline(always)] + fn delay_cycles_u24() { + // Some way to static assert that COUNTER < 2^24 would be nice. + unsafe { + asm!( + "ldi {r0:l}, {b0}", + "ldi {r0:h}, {b1}", + "ldi {r2}, {b2}", + "1:", + "sbiw {r0}, 1", + "sbci {r2}, 0", + "brne 1b", + r0 = out(reg_iw) _, + r2 = out(reg_upper) _, + b0 = const (Self::U24.counter >> 0) as u8, + b1 = const (Self::U24.counter >> 8) as u8, + b2 = const (Self::U24.counter >> 16) as u8, + options(nomem, nostack), + ) + } + } + + /// 4 instructions. + /// 5 cycles per run. + /// 4 cycles per iteration. + #[inline(always)] + fn delay_cycles_u16() { + unsafe { + asm!( + "ldi {r0:l}, {b0}", + "ldi {r0:h}, {b1}", + "1:", + "sbiw {r0}, 1", + "brne 1b", + r0 = out(reg_iw) _, + b0 = const (Self::U16.counter >> 0) as u8, + b1 = const (Self::U16.counter >> 8) as u8, + options(nomem, nostack), + ) + } + } + + /// 3 instructions. + /// 3 cycles per run. + /// 3 cycles per iteration. + #[inline(always)] + fn delay_cycles_u8() { + unsafe { + asm!( + "ldi {r0}, {b0}", + "1:", + "dec {r0}", + "brne 1b", + r0 = out(reg_upper) _, + b0 = const Self::U8.counter, + options(nomem, nostack), + // The carry flag is not touched by `dec`. + // That's the difference between `dec` and `sub 1`. + // Is it possible to tell `asm!` that the carry is untouched? + // Something like `preserves_carry_flag`. + // The compiler wouldn't have to save the carry flag when delay_cycles_u8 is used + // within an outer loop using multiple-precision computations. + ) + } + } + + /// 1 instruction. + /// 2 cycles per run. + #[inline(always)] + fn delay_2cycles() { + unsafe { asm!("rjmp .", options(nomem, nostack, preserves_flags),) } + } + + /// 1 instruction. + /// 1 cycle per run. + #[inline(always)] + fn delay_1cycle() { + unsafe { asm!("nop", options(nomem, nostack, preserves_flags),) } + } + + #[inline(always)] + pub fn delay_impl() { + // Cycles 83_886_083 + 4 .. 25_769_803_779 (9+6*0xFFFF_FFFF) + 5 + if Self::U32.selected { + Self::delay_cycles_u32(); + } + + // Cycles 262_146 + 5 ..= 83_886_082 (7+5*0xFF_FFFF) + 4 + if Self::U24.selected { + Self::delay_cycles_u24(); + } + + // Cycles 769 + 4 ..= 262_145 (5+4*0xFFFF) + 5 + if Self::U16.selected { + Self::delay_cycles_u16(); + } + + // Cycles 6 ..= 768 (3+3*0xFF) + 4 + if Self::U8.selected { + Self::delay_cycles_u8(); + } + + // Remaining cycles 1..=5. + + if Self::U8.remainder >= 4 { + Self::delay_2cycles(); + } + + if Self::U8.remainder >= 2 { + Self::delay_2cycles(); + } + + if Self::U8.remainder % 2 == 1 { + Self::delay_1cycle(); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index da57eb3..c3862ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,73 +1,40 @@ -#![feature(asm_experimental_arch)] - -#![no_std] - #![crate_name = "avr_delay"] +#![no_std] +#![feature(asm_experimental_arch)] +#![feature(asm_const)] -use core::arch::asm; +mod delay_cycles; -/// This library is intended to provide a busy-wait delay -/// similar to the one provided by the arduino c++ utilities -/// If you need accurate time keeping you should consider a -/// hardware timer. +use delay_cycles::Delayer; -// This library does all of the busy-wait loop in rust. -// We pack as much of the looping as possible into asm! -// so that we can count cycles. -// -// Ignoring the overhead, which may be significant: -// An arduino runs at 16MHZ. Each asm loop is 4 cycles. -// so each loop is 0.25 us. -// -// the overhead of delay() seems to be about 13 cycles -// initially, and then 11 cycles per outer loop. We ignore -// all that. +/// Delay by the exact number of CYCLES. +/// The number of instructions generated goes up to 11. The higher the number of cycles, the higher +/// number of instructions, in a staircase effect. +/// Accepts 0 to 25_769_803_784 cycles (almost 18 minutes at 24Mhz). +#[inline(always)] +pub fn delay_cycles() { + Delayer::::delay_impl() +} -/// Internal function to implement a variable busy-wait loop. -/// # Arguments -/// * 'count' - a u64, the number of times to cycle the loop. +/// Maximum value is (25_769_803_784 * 1_000_000 / CPU_FREQUENCY_HZ). +/// Almost 18 minutes at 24Mhz. #[inline(always)] -pub fn delay(count: u64) { - // Our asm busy-wait takes a 16 bit word as an argument, - // so the max number of loops is 2^16 - let outer_count = count / 65536; - let last_count = ((count % 65536)+1) as u16; - for _ in 0..outer_count { - // Each loop through should be 4 cycles. - let zero = 0u16; - unsafe { - asm!("1: sbiw {i}, 1", - "brne 1b", - i = inout(reg_iw) zero => _, - ) - } - } - unsafe { - asm!("1: sbiw {i}, 1", - "brne 1b", - i = inout(reg_iw) last_count => _, - ) - } +pub fn delay_us() { + Delayer::::delay_impl() } -///delay for N milliseconds -/// # Arguments -/// * 'ms' - an u64, number of milliseconds to busy-wait +/// Maximum value is (25_769_803_784 * 1_000 / CPU_FREQUENCY_HZ). +/// Almost 18 minutes at 24Mhz. #[inline(always)] -pub fn delay_ms(ms: u64) { - // microseconds - let us = ms * 1000; - delay_us(us); +pub fn delay_ms() { + Delayer::::delay_impl() } -///delay for N microseconds -/// # Arguments -/// * 'us' - an u64, number of microseconds to busy-wait +/// Maximum value is (25_769_803_784 * 1 / CPU_FREQUENCY_HZ). +/// Almost 18 minutes at 24Mhz. #[inline(always)] -pub fn delay_us(us: u64) { - let us_in_loop = (avr_config::CPU_FREQUENCY_HZ / 1000000 / 4) as u64; - let loops = us * us_in_loop; - delay(loops); +pub fn delay_sec() { + Delayer::::delay_impl() } #[cfg(test)] @@ -77,4 +44,3 @@ mod tests { assert_eq!(2 + 2, 4); } } -