みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

Raspberry Pi Pico タイマーでLチカ

はじめに

Raspberry Pi Pico(Pico)は4つの64ビットタイマーを持っている。それぞれ割り込みを発生させることができる。

ここまででSysTickやその割り込みを使ってLチカをしてきたが、すべてビジーループでタイミングを測っていた。

今回はWFIを使ってスリープしてみる。使用するタイマーはTIMER0。

タイマー

リセット

タイマーを使用するにはまず最初にタイマーをリセットする必要がある。リセットにはRESETSレジスタを使用する。RESETSレジスタは下記のようになっている。

f:id:mickey_happygolucky:20210311062823p:plain

f:id:mickey_happygolucky:20210311062848p:plain

割り込み

SysTickとは違いタイマーの割り込みはペリフェラルの割り込みとなる。

NVIC、IRQ

ペリフェラルの割り込みはNested Vectored Interrupt Controller(NVIC)に接続され、それぞれの機能がIRQ(Interrupt ReQuest)に割り当てられている。

f:id:mickey_happygolucky:20210311062900p:plain

TIMER0の割り込みはIRQ0に割り当てられていることがわかる。

IRQ0の割り込みが発生するとベクターテーブルのIRQ0に飛んでくる。

NVIC割り込みの操作には下記のレジスタを使用する。

f:id:mickey_happygolucky:20210311063033p:plain

タイマー割り込み

NVICは接続されているペリフェラルの割り込みを処理するだけのものなので、IRQ0を有効化するだけではタイマーの割り込みは発生しない。

タイマーの割り込みを発生させるには、ペリフェラル側でも下記のような操作が必要になる。

  1. 割り込みの有効化
  2. 割り込み条件の設定

タイマーのレジスタ構成は下記のようになっている。

f:id:mickey_happygolucky:20210311062912p:plain

f:id:mickey_happygolucky:20210311062931p:plain

割り込みの有効化

タイマーの割り込みを有効化するにはINTEレジスタをを使用する。INTEレジスタは下記のようになっている。

f:id:mickey_happygolucky:20210311063020p:plain

割り込み条件の設定

割り込み条件の設定にはALARM0〜3のレジスタを使用する。

ALARM0〜3に書き込まれた値がタイマーのカウンタであるTIMELRと一致すると4つのタイマーのうちの対応する割り込みが発生する。

タイマー0で使用するALARM0は下記のようになっている。

f:id:mickey_happygolucky:20210311062945p:plain

割り込みのクリア

割り込みが発生したら、割り込みハンドラの中でその割り込みをクリアする必要がある。割り込みをクリアするにはINTRレジスタの対応するビットをクリアする。

f:id:mickey_happygolucky:20210311062955p:plain

f:id:mickey_happygolucky:20210311063009p:plain

実装

main.cの中でそれぞれ実装する。

タイマーの初期化

タイマーの初期化処理では下記のことを行う。

  1. タイマーのリセット
  2. リセット完了待ち
  3. タイマー割り込みの有効化
  4. IRQ0の有効化

init_timer()として下記のように実装する。

void init_timer() {
        /* reset TIMER */
        write_reg_op(RESETS_RESET, (1<<21), OP_CLR);
        /* wait for reset TIMER to be done */
        while (!read_reg(RESETS_RESET_DONE)&(1<<21)) ;

        /* Enable timer interrupt */
        write_reg_op(TIMER_INTE, 1, OP_SET);

        /* Enable IRQ */
        irq_set_enabled(0, true); /* TIMER_IRQ_0=0 */
}

割り込み条件の設定

sleep_us()として下記のように実装する。

void sleep_us(uint32_t us) {
        uint32_t wakeup = read_reg(TIMER_TIMERRAWL);
        wakeup += us;
        write_reg(TIMER_ALARM0, wakeup);

        /* Wait For Interupt */
        __asm__ __volatile__("wfi");
}

sleep_usでは下記の処理を行っている。

  1. 現在のタイマーの値を取得
  2. 待ちたい時間を加算した値をALARM0に書き込む
  3. WFIを使って割り込み待ち状態(sleep)にする

WFIはWait For Interrupt命令のことで、割り込みが入るまでCPUがスリープ状態になる。 割り込み(この場合はタイマー割り込み)が発生すると、スリープが解除されCPUが復帰する。

割り込みハンドラ

isr_timer0()として下記のように実装する。

void isr_timer0() {
        /* Clear the timer0 irq */
        write_reg_op(TIMER_INTR, 1, OP_CLR);
}

isr_timer0start.Sの割り込みベクタの定義のIRQ0の位置に登録しておく。

/* vector table */
    .section .vectors, "ax"
    .align 2
    .global __vectors
__vectors:
.word 0x20001000
.word reset
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word isr_timer0

メイン処理

main関数は下記のようになっている。

int main(void) {

        /////////////////////
        // This initialise code is ported from https://github.com/dwelch67/raspberrypi-pico/blob/master/blinker00/notmain.c
        // release reset on IO_BANK0
        write_reg_op(RESETS_RESET, 1<<5, OP_CLR); //IO_BANK0
        //wait for reset to be done
        while(1) {
                if((read_reg(RESETS_RESET_DONE)&(1<<5))!=0) break;
        }
        write_reg_op(RESETS_RESET, (1<<8), OP_CLR); //PADS_BANK0
        while(1) {
                if((read_reg(RESETS_RESET_DONE)&(1<<8))!=0) break;
        }
        /////////////////////

        // GPIO init
        write_reg(SIO_GPIO_OE_CLR, (1ul<<25));
        write_reg(SIO_GPIO_OUT_CLR, (1ul<<25));
        uint32_t ra = read_reg(PADS_GPIO25);
        write_reg_op(PADS_GPIO25, (ra^0x40)&0xC0, OP_XOR);

        write_reg(IO_GPIO25_CTRL, 0x5);

        // Timer0 init
        init_timer();

        // Blink
        write_reg(SIO_GPIO_OE_SET, (1ul<<25));
        while (1) {
                sleep_us(100*1000); //100ms
                write_reg(SIO_GPIO_OUT_XOR, (1ul<<25));
        }
        return 0;
}

sleep_us関数を使って100msごとにLEDを点滅させている。

波形

問題なくPluseが100msとなっている。

f:id:mickey_happygolucky:20210311063044p:plain

まとめ

タイマーペリフェラルの割り込みでスリープを実装した。

  1. NVICで割り込みを有効化
  2. TIMERで割り込みを有効化
  3. TIMERで割り込みの発生条件を設定

これで使える。

ビルド可能なソースコード一式はここに置いてある。

参考