GoにいればGoに従え(40) ラズパイPicoの割り込み、その1、イネーブルなのは誰?

Joseph Halfmoon

前回はラズパイPicoのハードウエアの「もそっと下」のところをTinyGoから制御するためにハード固有のレジスタに直接アクセスしてみました。クロックとか電源とか最初に見ておきたい部分はいろいろあるのですが、今回は割り込みをみてみます。TinyGoのランタイムがデフォルトで割り込みイネーブルにしている周辺はあるのかしら。

※「GoにいればGoに従え」Go関連記事の総Index

※実機動作確認は Arm Cortex-M0+コアのRP2040チップ搭載、Raspberry Pi Pico機にTinyGoのオブジェクトを書き込んで行っています。ビルドはWindows11上です。

※RP2040のデータシート(PDF)はこちら

NVIC

ラズパイPicoが搭載しているARMv6-Mアーキテクチャのコアの場合、通常、NVIC(Nested Vectored Interrupt Controller)と呼ばれる割り込みコントローラが搭載されることになっています。NVICは最大32本の割り込み要因をサポートできます。タイマとかSPIとか各周辺デバイスが使用できる割り込みはこの中に含まれています。これ以外にNMIとかシステムが使用する割り込みもあるのですが、それらはNVICには接続されません。

ラズパイPicoの場合、前回同様 rp2040.go ファイルの中でNVICに接続されている最大の割り込みの番号が定義されています。

IRQ_max = 25

周辺装置からの割り込み要求が25種類あるということですな。当然、周辺装置のドライバ等を使っていけば、その中のいくつかは使われる可能性があり。また、TinyGoのランタイムを走らせただけで有無を言わさず使われている割り込みもあるかもしれません。今回はNVICの中の割り込みをイネーブル、ディセーブルするレジスタを読み取って、その使用状況を確認してみます。

今回実験に使ったソースコード

どうせ高が知れた数の割り込み要因だから、ベタに書いていってもいいんじゃね、などと思った私が馬鹿でした。25個、結構数あります。後半になってベタに書くんじゃなかった、と思いました。それがこちら。

package main

import (
    "device/rp"
    "fmt"
    "machine"
    "time"
)

var pat int;

func printIntVec() {
    nvicISER := rp.PPB.GetNVIC_ISER()
    fmt.Printf("NVIC ISER:  0x%08x\n", nvicISER)
    fmt.Printf("TIMER_IRQ_0:   %d\n", nvicISER&1)
    fmt.Printf("TIMER_IRQ_1:   %d\n", (nvicISER&2)>>1)
    fmt.Printf("TIMER_IRQ_2:   %d\n", (nvicISER&4)>>2)
    fmt.Printf("TIMER_IRQ_3:   %d\n", (nvicISER&8)>>3)
    fmt.Printf("PWM_IRQ_WRAP:  %d\n", (nvicISER&16)>>4)
    fmt.Printf("USBCTRL_IRQ:   %d\n", (nvicISER&32)>>5)
    fmt.Printf("XIP_IRQ:       %d\n", (nvicISER&64)>>6)
    fmt.Printf("PIO0_IRQ_0:    %d\n", (nvicISER&128)>>7)
    fmt.Printf("PIO0_IRQ_1:    %d\n", (nvicISER&256)>>8)
    fmt.Printf("PIO1_IRQ_0:    %d\n", (nvicISER&512)>>9)
    fmt.Printf("PIO1_IRQ_1:    %d\n", (nvicISER&1024)>>10)
    fmt.Printf("DMA_IRQ_0:     %d\n", (nvicISER&2048)>>11)
    fmt.Printf("DMA_IRQ_1:     %d\n", (nvicISER&4096)>>12)
    fmt.Printf("IO_IRQ_BANK0:  %d\n", (nvicISER&8192)>>13)
    fmt.Printf("IO_IRQ_QSPI:   %d\n", (nvicISER&16384)>>14)
    fmt.Printf("SIO_IRQ_PROC0: %d\n", (nvicISER&32768)>>15)
    fmt.Printf("SIO_IRQ_PROC1: %d\n", (nvicISER&0x10000)>>16)
    fmt.Printf("CLOCKS_IRQ:    %d\n", (nvicISER&0x20000)>>17)
    fmt.Printf("SPI0_IRQ:      %d\n", (nvicISER&0x40000)>>18)
    fmt.Printf("SPI1_IRQ:      %d\n", (nvicISER&0x80000)>>19)
    fmt.Printf("UART0_IRQ:     %d\n", (nvicISER&0x100000)>>20)
    fmt.Printf("UART1_IRQ:     %d\n", (nvicISER&0x200000)>>21)
    fmt.Printf("ADC_IRQ_FIFO:  %d\n", (nvicISER&0x400000)>>22)
    fmt.Printf("I2C0_IRQ:      %d\n", (nvicISER&0x800000)>>23)
    fmt.Printf("I2C1_IRQ:      %d\n", (nvicISER&0x1000000)>>24)
    fmt.Printf("RTC_IRQ:       %d\n", (nvicISER&0x2000000)>>25)
}

func main() {
    pat = 0
    keyA := machine.GPIO15
    keyA.Configure(machine.PinConfig{Mode: machine.PinInput})
    keyA.SetInterrupt(machine.PinFalling, func(machine.Pin) {
        pat += 1
    })

    for {
        printIntVec()
        fmt.Printf("KEY INT: %d\n", pat)
        time.Sleep(time.Second * 4)
    }
}

ちょっと細工をしたのがkeyAのところです。ただ割り込みイネーブルか否かをダンプしても真偽のほどがイマイチです。そこで積極的にこちらからGPIO割り込みを仕掛けました。実際に割り込みが発生すれば pat という変数の値がカウントアップされるので、ホントに割り込みかかっていることも確認できるという目論見。

実機実行結果

GPIO15番に、プルアップ抵抗4.7kΩと、プルダウンとして働くプッシュスイッチをとりつけてDUTとしました。こんな感じ。RPICO_KEYINT_DUTa

さて上記ハードウエアにオブジェクトを書き込んだ後、プッシュスイッチ(左の方です。右押すとRESETかかってしまう)を3回押した後、標準出力にでてきた内容が以下に(色のついた線は出力後の加工です。)RPICO_INT_ENABLE

何もしなくてもTinyGoのオブジェクトを書き込むだけで、デフォルトで

USBCTRL_IRQ(上の出力の緑太線のところ)

というUSBインタフェース用の割り込みはイネーブルになっているみたい。一方、上記のkeyAのSetInterruptにより

IO_IRQ_BANK0(上の出力の赤太線)

がイネーブルとなるのも確認できました。確かに割り込みの許可、不許可は観察できているようだね。

GoにいればGoに従え(39) ラズパイPicoでもレジスタ直接アクセス。最初はCPUID へ戻る

GoにいればGoに従え(41) ラズパイPico、ArmのSystick使えるの? へ進む