mruby で Raspberry Pi の GPIO をいじるというやつで、sleep をビジーループにしていたのがどうしてもひっかかっていた。

どうも ARM には割込みが起きるまで眠る命令があるみたいなので、それを使ってみることにした。コード全体

static mrb_value mrb_mruby_raspberrypi_gpio_gem_delay_us(mrb_state* mrb, mrb_value self) {
	mrb_int delay;
	mrb_get_args(mrb, "i", &delay);

	// Reset timer flags
	PUT32(ARM_TIMER_CONTROL, 0x3E0020);
	// Load count down timer value
	PUT32(ARM_TIMER_LOAD, delay-1);
	PUT32(ARM_TIMER_RELOAD, delay-1);
	// predevider = (apb_clk - freq) / freq
	PUT32(ARM_TIMER_PRE_DIVIDER, 250 - 1);
	PUT32(ARM_TIMER_IRQ_CLEAR_ACK, 0);
	PUT32(ARM_TIMER_CONTROL,
		(0x3E<<16) | // default free-running pre-scaler
		(1<<7)     | // timer enabled
		(1<<5)     | // timer interrupt enabled
		(1<<1)       // 23-bit counter
	);

	// Enable ARM Timer IRQ
	PUT32(ARM_INTERRUPT_ENABLE_BASIC_IRQS, 1);
	
	while ((GET32(ARM_INTERRUPT_IRQ_BASIC_PENDING) & 1) == 0) {
		// Waiting For Interrupt
		asm volatile ("wfi");
	}

	// Disable ARM Timer IRQ
	PUT32(ARM_INTERRUPT_DISABLE_BASIC_IRQS, 1);

	return mrb_nil_value();
}

コメントにある通りだけど、割込みを設定して、ARM_INTERRUPT_IRQ_BASIC_PENDING のフラグを見つつ、セットされるまでは wfi 命令で継続的に眠る、というようにしてみた。他に割込みを設定していないので、while は1回で抜けるつもり……

wfi 命令はオプショナルな命令らしく、ハードによっては nop として解釈されるらしい。なのでこのような実装の場合、フラグをポーリングするようなコードを併用したほうが安全そう。

というか、実際 wfi 命令がちゃんと動いているかを確かめる方法が面倒くさい。電流を測るしかなさそう。電流を今回測るところまでやってないので、ちゃんと動いてないのかもしれない。ただ、挙動として割込みをポーリングで待つ、というのはできいてるっぽい。

どうでもいいけど wfi で検索しても wifi 扱いされてだいぶウザい。

  1. トップ
  2. tech
  3. 割込みと WFI 命令を使った sleep の実装
▲ この日のエントリ