IoT何をいまさら(89) Wio Terminal、外部割込みとSAMD51 EIC

Joseph Halfmoon

前回までWio Terminal搭載のマイコンSAMD51の周辺回路を直接触ってきました。今回はArduino環境でもともと用意されている関数を使って割り込みを使用してみます。そしてそれら関数でどのようにSAMD51のEICが設定されているのか確認します。関数が用意されていない機能へのアクセスの準備ということで。

(末尾に実験用のコード全文を掲げました。Arduinoスタイルの .cpp)

例によってというか、毎度のことで、今回も「外部割込み受けの実験用のコードでしょ、ちょろい」などと思いながら、一発では動きませんでした。何のことはないです、Wio Terminal裏面の拡張端子のRaspberry Pi互換の端子名とArduino互換の関数の引数に渡す端子番号を取り違えるというお粗末のため。今回はSAMD51のEIC(External Interrupt Controller)の外部割込み番号も関係してくるので、今回使った端子も含め、よく使いそうな端子の対応一覧表を以下にまとめてみました。

Arduino# RaspberryPi# WioSch SAMD51 EXT_INTR
0 GPIO27 A0/D0 PB08 8
1 GPIO22 A1/D1 PB09 9
2 GPIO23 A2/D2 PA07 7
3 GPIO24 A3/D3 PB04 4
4 GPIO25 A4/D4 PB05 5
5 GPIO12 A5/D5 PB06 6
6 GPIO13 A6/D6 PA04 4
7 GPIO16 A7/D7 PB07 7
8 GPIO26 A8/D8 PA06 6
9 GPIO17 DAC0 PA02 2
10 GPIO7 DAC1 PA05 5

Arduino#欄はArduino互換の関数に引数として渡すピン番号です。RaspberryPi#欄は、Wio Terminal背面の拡張コネクタがRaspberry Piの40ピン拡張コネクタと互換配列になっているため、Raspberry Piの端子名です。WioSch欄は、Wio Terminalの回路図上での信号名です。SAMD51欄はSAMD51P19Aの端子名です。そしてEXT_INTRは、SAMD51内で外部端子からの割り込みをつかさどるEICの割り込み番号です。以下のような制限があるので注意が必要です。

  • 外部端子からの割込みは0番から15番までの16個が使用可能
  • 割込番号と外部端子の割り当ては固定
  • 1個の割込番号が複数の外部端子に割り当てられている
attachInterrupt()関数の仕様の微妙な違い

外部割込みのハンドラを設定するときに使う attachInterrupt()関数の第1引数はArduino環境での「標準機」Unoとは微妙に違いました。Unoの場合は以下のようです。

attachInterrupt(digitalPinToInterrupt(interruptPin), ハンドラ関数へのポインタ, モード);

Unoの場合の第一引数は、割り込み番号(0と1しか有効ではない)です。ピン番号を直接与えることはできず、上記のようにピン番号からは digitalPinToInterrupt()を使って変換することが必須です。特定の2ピンしか外部割込みに使うことはできません。

Wio Terminalでは、第一引数はピン番号そのものです。Unoとは違い、特定の数ピンを除くほぼすべてのピンを外部割込みに使用できます。ただし、前述のように実際に使用できる外部割込みは16個まで。端子と割り込みの対応に制約があります。なお、Wio Terminal上でも digitalPinToInterrupt()マクロは定義されているので、ソースコード上はUnoとコンパチブルな形で記述できます。しかし、digitalPinToInterrupt()マクロは引数のピン番号をそのままわたすだけの形だけのものです。

attachInterrupt()前後でEICの様子を観察

attachInterrupt()関数のソースを見ればやっていることは分かりますが、誰かがどこかの割り込みを使っていないか念のため確認すべく、ハンドラを1つ登録する前後でEICのレジスタがどうなっているのだか観察してみました。

Before attachInterrupt
EIC CTRLA.ENABLE=0, INTENSET=0x00000000
EIC CONFIG[0]=0x00000000
EIC CONFIG[1]=0x00000000

After attachInterrupt
EIC CTRLA.ENABLE=1, INTENSET=0x00000004
EIC CONFIG[0]=0x00000200
EIC CONFIG[1]=0x00000000

attachInterrupt()前は、EICはEnableになっておらず、全部で16個ある外部割込みソースのすべてもディセーブルになっていることが分かります。知らないうちに使われていることは無かったですね(当たり前か。)また、端子モード(立ち上がり、立下り検出などの指定)も初期値(検出しない)のままです。

attachInterrupt()を行うと、以下の設定がなされます。

  1. 端子の設定:指定の端子をEICに接続する(端子番号から割り込み番号決まる)
  2. ハンドラの設定:引数のコールバック関数が該当の割り込み番号のハンドラから呼び出されるように登録する
  3. EIC内でモード設定等を行い、EICレベルでの割込み許可を行う

上の例では、attachInterrupt後に、CTRLAレジスタのENABLEビットは許可状態となり、INTENSETのビット2が立っています。これは前述の表のようにArduino関数の引数上でのピン番号9番、Raspberry Pi互換端子名 GPIO17番は、外部割込み番号2番であるため、その割り込みが許可されたことを意味します。また、2個のCONFIGレジスタ内には各割り込みのモードビットが4ビットずつ確保されており、上記例では下から3つ目(やはり2番)の値が0x02であることが分かります。0x02はバイナリで

0010

であるので、データシートを参照すれば予定通り以下の設定であることが分かります。

  • 最上位ビットは0=デバウンスフィルタの使用なし
  • 下3ビットは010=Falling-Edge検出

検出エッジなどは、標準関数でも設定可能ですが、デバウンスフィルタなどは操作する関数が見当たらないので、このSAMD51の便利機能を使用したい場合はEICを直接いじるしかないように思います。

動作確認

末尾の実験コードをアイキャッチ画像のテスト回路で走らせ、外部から割り込みをいれた場合の結果を以下に掲げます。

EXT INT COUNT=0
EXT INT COUNT=0
EXT INT COUNT=0
EXT INT COUNT=1
EXT INT COUNT=1
EXT INT COUNT=2
EXT INT COUNT=2
EXT INT COUNT=2
EXT INT COUNT=2
EXT INT COUNT=2
EXT INT COUNT=4
EXT INT COUNT=4

割り込みの検出回数が0, 1, 2 と増えた後、4に飛んでいますが、これは

カチャカチャっと素早く2回押し

したためです。ちゃんと検出しておると。

IoT何をいまさら(88) Wio Terminal、SAMD51周波数メータを使う へ戻る

IoT何をいまさら(90) Wio Terminal、SAMD51、GCLK周波数リスト へ進む

実験に使ったコード全文
#include <Arduino.h>
#define EXTINTR (9)
#define EXTLED  (0)

volatile int intCount = 0;

void intCounter() {
  intCount++;
  digitalWrite(EXTLED, 0); // ACTIVE
}

void checkEICregisters() {
  uint8_t enbit = EIC->CTRLA.bit.ENABLE;
  uint32_t iset = EIC->INTENSET.reg;
  Serial.printf("EIC CTRLA.ENABLE=%d, INTENSET=0x%08x\r\n", enbit, iset);
  for (int i=0; i<2; i++) {
    Serial.printf("EIC CONFIG[%d]=0x%08x\r\n", i, EIC->CONFIG[i].reg); 
  }
}

void setup() {
  Serial.begin(9600);
  while(!Serial);
  pinMode(EXTLED, OUTPUT);
  digitalWrite(EXTLED, 1); // INACTIVE
  Serial.printf("Before attachInterrupt\r\n");
  checkEICregisters();
  attachInterrupt(EXTINTR, intCounter, FALLING);
  Serial.printf("After attachInterrupt\r\n");
  checkEICregisters();
  interrupts();
}

void loop() {
  Serial.printf("EXT INT COUNT=%d\r\n", intCount);
  delay(5000);
  digitalWrite(EXTLED, 1); // INACTIVE
}