delayMicroseconds()

概要

delayMicroseconds()は、指定した値(マイクロ秒秒単位)だけ待つ関数です。空ループを使ってビジーウエイトします。

delayMicroseconds()のリファレンスはこちらをご覧ください。

ソースコード

delayMicroseconds()は、hardware/arduino/avr/cores/arduino/wiring.c に定義されています。以下に全ソースコードを示します。実際には#if によりさまざまなチップに対応しています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* Delay for the given number of microseconds.  Assumes a 1, 8, 12, 16, 20 or 24 MHz clock. */
void delayMicroseconds(unsigned int us)
{
    // call = 4 cycles + 2 to 4 cycles to init us(2 for constant delay, 4 for variable)

    // calling avrlib's delay_us() function with low values (e.g. 1 or
    // 2 microseconds) gives delays longer than desired.
    //delay_us(us);

    // for the 16 MHz clock on most Arduino boards

    // for a one-microsecond delay, simply return.  the overhead
    // of the function call takes 14 (16) cycles, which is 1us
    if (us <= 1) return; //  = 3 cycles, (4 when true)

    // the following loop takes 1/4 of a microsecond (4 cycles)
    // per iteration, so execute it four times for each microsecond of
    // delay requested.
    us <<= 2; // x4 us, = 4 cycles

    // account for the time taken in the preceding commands.
    // we just burned 19 (21) cycles above, remove 5, (5*4=20)
    // us is at least 8 so we can subtract 5
    us -= 5; // = 2 cycles,
 
    // busy wait
    __asm__ __volatile__ (
        "1: sbiw %0,1" "\n\t" // 2 cycles
        "brne 1b" : "=w" (us) : "0" (us) // 2 cycles
    );
    // return = 4 cycles
}

入力はunsinged int型の変数です。戻り値はありません。

引数が1以下であれば、すでに1マイクロ秒経過しているので、何もしないでリターンします。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/* Delay for the given number of microseconds.  Assumes a 1, 8, 12, 16, 20 or 24 MHz clock. */
void delayMicroseconds(unsigned int us)
{
    // call = 4 cycles + 2 to 4 cycles to init us(2 for constant delay, 4 for variable)

    // calling avrlib's delay_us() function with low values (e.g. 1 or
    // 2 microseconds) gives delays longer than desired.
    //delay_us(us);
    
    // for the 16 MHz clock on most Arduino boards

    // for a one-microsecond delay, simply return.  the overhead
    // of the function call takes 14 (16) cycles, which is 1us
    if (us <= 1) return; //  = 3 cycles, (4 when true)

次に入力された引数を4倍(左に2ビットシフト)し、5を引きます。

16
17
18
19
20
21
22
23
24
    // the following loop takes 1/4 of a microsecond (4 cycles)
    // per iteration, so execute it four times for each microsecond of
    // delay requested.
    us <<= 2; // x4 us, = 4 cycles

    // account for the time taken in the preceding commands.
    // we just burned 19 (21) cycles above, remove 5, (5*4=20)
    // us is at least 8 so we can subtract 5
    us -= 5; // = 2 cycles,

引数を4倍するのは、ビジーウエイトするループ1回が0.25マイクロ秒かかるためです。このため、この時点で引数(us)は、マイクロ秒ではなく、ループ数になります。

余談ですが、delayMicroseconds()のリファレンスによると、delayMicroseconds()で指定できるのは16383までと書いてあります。これは、引数がunsinged intなので16ビット。delayMicroseconds()内で、引数を左に2ビットシフトしているため、実質14ビットが有効なビット数となるからです。また、引数のエラー処理をしていないことにも注意してください。

また、今までの処理に要した時間として、5ループ分=20クロック=1.25マイクロ秒減算します。

これまでの処理をアセンブラで見てみると、以下のようになりました。

1
2
3
4
5
6
7
8
cpi r24,2
cpc r25,__zero_reg__
brlo .L1
lsl r24
rol r25
lsl r24
rol r25
sbiw r24,5

最初の3行が if ( us <= 1) return;、次の4行が us «= 2;、最後の1行が us -= 5; に相当します。

sbiwが2クロック、その他は1クロックで、合計9クロックです。

また、関数呼び出しのオーバーヘッドが6から8クロックと書いてありますが、少し計算が合わない気がします。私がコンパイルオプションを間違えているのかもしれませんが。

最後に、ビジーウエイトします。

26
27
28
29
30
31
32
    // busy wait
    __asm__ __volatile__ (
        "1: sbiw %0,1" "\n\t" // 2 cycles
        "brne 1b" : "=w" (us) : "0" (us) // 2 cycles
    );
    // return = 4 cycles
}

sbiwは引き算、brneは比較を行うアセンブラ命令です。

sbiw、brneとも2クロックを要する命令で、全体で4クロックかかります。

Arduino Unoでは、クロック数は16MHzなので、1クロックに要する時間は、1/16000000秒=0.0000000625秒=0.0000625ミリ秒=0.0625マイクロ秒です。1回のループに要する時間は、4クロック分なので、0.0625*4=0.25マイクロ秒です。

バージョン

Arduino AVR Boards 1.8.6

最終更新日

March 21, 2023

inserted by FC2 system