【Raspberry Pi】システムタイマ

Raspberry PiでLチカ - 技術について語るときに僕の語ることでLED点滅をしたときにdelay(50000)という関数を使いましたが、「CPU50000クロックってどんだけやねん」という話なのでシステムタイマを利用して秒で指定できるようにしたいと思います。

なお主に以下の書籍の第5章およびサンプルコードを参考にしています。

システムタイマ

システムタイマのレジスタもGPIOと同じようにメモリマップト方式でメモリ上にマップされています。ペリフェラルマニュアルのp.172を参照するとシステムタイマが0x20203000に存在することがわかります1

システムタイマもいくつかレジスタを持ちますが今回は64bitのフリーランニングカウンタを使いたいと思います。

get_systime()関数

システムタイマの値を読む、get_systime()関数を定義します。

uint64_t get_systime(void)
{
    uint32_t clo, chi;

    chi = *(volatile uint32_t *) 0x20003008;
    clo = *(volatile uint32_t *) 0x20003004;

    return (chi << 32) + clo;
}

先ほどのフリーランニングカウンタは32bitずつLOWとHIGHの2つのレジスタに分かれており、メモリ上アドレスはそれぞれ0x202030040x20203008 になります。それぞれclo、chiに読み込んだのち、HIGHを32bit分繰り上げLOWを足して返しています。

ここでもしHIGHを読んだ直後、LOWを読む前にLOWがオーバーフローした場合、HIGHの値がインクリメントされてしまうので以下のようにもう一度読み直すことでこれに対応します。なおオーバーフローの間隔は70分程度あるので一回チェックすれば十分です。

uint64_t get_systime(void)
{
    uint64_t t;
    uint32_t clo, chi;

    chi = *(volatile uint32_t *) 0x20003008;
    clo = *(volatile uint32_t *) 0x20003004;

    if (chi != *(volatile uint32_t *) 0x20003008) {
        chi = *(volatile uint32_t *) 0x20003008;
        clo = *(volatile uint32_t *) 0x20003004;
    }

    t = (chi << 32) + clo;

    return t;
}

delay_s()関数

このget_systime()関数を使って秒で指定するdelay関数を作りたいと思います。流れは以下の通りです。

  1. 現在時刻+delayを目標時刻とする。
  2. ループ条件で現在時刻を再度取得し、目標時刻を過ぎていたらループを抜ける。

関数は以下の通りです。

void delay_s(uint32_t delay)
{
    uint64_t target = get_systime() + delay * 1000 * 1000; // 1
    while (get_systime() < target); // 2

    return;
}

再びLチカ

点灯と消灯の間にdelay_s()関数を挟んでLEDを点滅させます。

void kernel_main(uint32_t r0, uint32_t r1, uint32_t atags)
{
    (void) r0;
    (void) r1;
    (void) atags;

    *(volatile uint32_t *) 0x20200010 = (1 << 21);

    while (1) {
        *(volatile uint32_t *) 0x20200020 = (1 << 15);
        delay_s(1);
        *(volatile uint32_t *) 0x2020002C = (1 << 15);
        delay_s(1);
    }
}

ちなみにPWM的に制御したい時には以下のコードを参考にします。

実装にあたりdelay関数のマイクロ秒版を用意しました。

void kernel_main(uint32_t r0, uint32_t r1, uint32_t atags)
{
    (void) r0;
    (void) r1;
    (void) atags;

    uint32_t brightness = 255, speed = 16, up = 0;

    *(volatile uint32_t *) 0x20200010 = (1 << 21);

    while (1) {
        if (brightness > 0) {
            // turn off LED for "brightness" us
            *(volatile uint32_t *) 0x2020002C = (1 << 15);
            delay_us(brightness);
        }

        if ((255 - brightness) >= 0) {
            // turn on LED for "255 - brightness" us
            *(volatile uint32_t *) 0x20200020 = (1 << 15);
            delay_us(255 - brightness);
        }

        // decrement speed
        speed--;
        if (speed == 0) {
            // recover it
            speed = 16;

            // if the brightness is getting brighter
            if (up) {
                // not maximum yet
                if (brightness < 255)
                    // increment brightness
                    brightness++;

                // hit the maximum
                if (brightness == 255)
                    // go downward
                    up = 0;
            } else { // LED is getting dimmer
                // not minimum yet
                if (brightness > 0)
                    // decrement brightness
                    brightness--;

                // hit the minimum
                if (brightness == 0)
                    // go upward
                    up = 1;
            }
        }
    }
}

明るさを255段階に分け、255マイクロ秒のうちbrightnessマイクロ秒だけLEDを点灯させます。これをspeed回繰り返したのちup = 1であれば明るさを一つあげ、up = 0であれば一つさげ、明るさが0または255に達した時にはupを反転させます。


  1. 0x3F203000じゃんと思うかもしれませんが前回記事の補足を参照してみてください。