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 の実装

//#!gcc -O0

#include <stdio.h>

int main (int argc, char *argv[]) {

        int a;
        int b;

        a = 0; b = 3;

        asm volatile (
                "mov r0, %[x];"
                "mov %[x], %[y];"
                "mov %[y], r0;"
                : [x] "+r" (a), [y] "+r" (b)
                :
                : "r0"
        );

        printf("a=%d, b=%d\n", a, b);

        return 0;
}

asm でやる必要は全然ないけど inline asm の文法が意味不明なのでちょっと書いてみた。

asm( code : output list : input list : clobber list)

という文法らしいけど、パっと見さっぱりわからない。上記の例(値の交換)だと

  • a と b どちらも読み書きが必要なので、output リストのほうで + (read/write) をつける。r はレジスタの意味らしい。
  • operand の指定は名前で行っている。角括弧を使うと名前で指定できるみたい
  • input list は空 (output list で read/write を指定しているので input として指定する必要はない)
  • clobber list というで、このコードによって上書きされるレジスタを指定する
    • これを指定しないと、意図せず他の変数のレジスタを破壊してしまったりする (GCC はどのレジスタが破壊されるかがわからないから、破壊されるレジスタに変数を割り当ててしまう)
    • 指定することで、GCC はそのレジスタを一旦退避させたりできる

このページが比較的わかりやすい。

  1. トップ
  2. tech
  3. ARM inline asm で値の交換を書いてみる