Arduinoで遊ぶページ

Arduinoで遊んだ結果を残すページです。
garretlab
delayMicroseconds()

delayMicroseconds()

概要

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

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

ソースコード

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

/* 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 preceeding commands.
	// we just burned 19 (21) cycles above, remove 5, (5*4=20)
	// us is at least 8 so we can substract 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マイクロ秒経過しているので、何もしないでリターンします。

/* 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を引きます。

	// 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 preceeding commands.
	// we just burned 19 (21) cycles above, remove 5, (5*4=20)
	// us is at least 8 so we can substract 5
	us -= 5; // = 2 cycles,

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

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

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

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

	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クロックと書いてありますが、少し計算が合わない気がします。私がコンパイルオプションを間違えているのかもしれませんが。

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

	// 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 1.8.4



メニューを表示するためにJavaScriptを有効にしてください。

Arduinoで遊ぶページ
Copyright © 2016 garretlab all rights reserved.
inserted by FC2 system