IoT何をいまさら(93) ATSAMD51、内蔵ペリフェラルの割り込み受け

Joseph Halfmoon

前回に続き、Microchip社ATSAMD51マイコンをローレベル(ハードに近いレベル)でプログラムしていきたいと思います。今回は内蔵ペリフェラルの発生する割り込みの受けです。「割り込み源」としては前回使用してみたTRNG(真性乱数ジェネレータ)を使います。今回は単なる割り込み源としての登場です。

(末尾に実験に使用したソース全文を掲げました。ATSAMD51P19A搭載のSeeedStudio社Wio Terminal、プラットフォームにArduino環境利用で動作確認したものです。)

以下の投稿で、外部端子からの割り込み入力については既にやってみました。

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

Arduino環境の場合、外部端子からの割り込みを処理するためのハンドラ(正確に言えばハンドラから呼び出されるコールバック関数)の設定用にattachInterrupt()関数が用意されています。Wio Terminalでは外部端子の何番が、外部割り込みコントローラ(EIC)の何番の割り込みに接続しているのか確認するのがチョイと面倒でしたが前回の表など見ていただけばOK。外部端子割り込みはローレベルでレジスタに直接アクセスするようなプログラミングをせずに使用できました。

ところが、EIC以外の内蔵ペリフェラルの多くも割り込みを発生できるのですが、これについては「表立って」attachInterrupt()のような関数が用意されていません。今回は、前回ポーリングで使用したTRNGに割り込みを発生してもらい、それを受け取ってみました。

割り込みハンドラのお名前

実際のSAMD51P19Aマイコンの割り込みハンドラのお名前は、私の環境(VS Code+PlatformIO利用)ではユーザホルダ配下の以下のファイルで定義されていました(以下のファイルを書き換えてはいけないですが念のため。)

.platformio\packages\framework-cmsis-atmel\CMSIS\Device\ATMEL\samd51\include\samd51\include\samd51p19a.h

CMSISなので、元の規格はArm社が決め、それにそって実体を記述したのが(Microchip社に買収された)ATMEL社であることが分かります。

Arduino環境でのハンドラの実体の有りかは以下でした(このファイルも書き換える必要はないですが念のため。)

.platformio\packages\framework-arduino-samd-seeed\cores\arduino\cortex_handlers.c

上記のファイルを読むと、割り込みハンドラは以下の3つのスタイルに区分されることが分かります。

  1. 実体ハンドラが定義されているもの。
  2. 実体ハンドラが定義されているが、”weak” アトリビュートのもの
  3. Dummyハンドラに向けられていて、”weak” アトリビュートのもの

第1のスタイルは、実体があり、”weak”でないので、同じ名前のハンドラを定義するとビルド時にエラーとなるはず(実際にやって確かめてないケド。)これに該当するのは以下の2つでした。

  • Reset
  • SysTick

どちらも勝手に書き換えられると「困る」部分だと思います。

第2のスタイルは、実体ハンドラが”weak”定義され、同名のハンドラが他になければそれが使用されるものです。同名のハンドラがあればそちらが使われるはず(実際にやってないケド。)これに該当するのは以下の5つでした。

  • USB_0
  • USB_1
  • USB_2
  • USB_3
  • TC0

USBはプログラムの書き込みやモニタにも使っているので当然かとおもいます。TC0(タイマ・カウンタ0)は、Arduino互換のtone()関数(任意周波数の音を出す)で使われているもよう。

その他の割り込みはすべて第3のカテゴリです。Dummyハンドラに突入すると無限ループなので割り込み発生するとそれっきりです。DEBUG時にはブレークポイント発生するようになってはいますが。

TRNG用の割り込みハンドラ

上記のファイルから、TRNG用の割り込みハンドラは以下のお名前で宣言すれば良いことがわかりました。分かり易いお名前。

void TRNG_Handler(void) {

さらに、SAMD51のデータシートを参照し、このTRNGの割り込み線が、NVIC(Nested Vectored Interrupt Controller)の0から数えて131番の割り込み線に接続されていることを確認しました。なおNVICについてはArm社の領分なので、ATMEL(Microchip社)のデータシートには詳しいことは書いてないです。Arm社のWebサイトのドキュメントなどを見に行かないとなりません、メンドイ。TRNGに関わらず他の周辺からの割り込みについては以下の3段階を「許可」設定しないとなりません。

  • 周辺装置自体(ここではTRNG)で割り込み発生を許可する
  • NVICレベルで割り込みを許可する
  • CPUレベルで割り込みを許可する

2番目のNVICレベルでは多数の割り込み要因があります(SAMD51P19Aの場合136番まで137要因。)先ほどの番号 131 を許可してやらないとなりません。割り込み許可のレジスタISERは32個の割り込み毎に1レジスタという割り付けです。131=32*4+3なので

NVICのISERレジスタの(0から数えて)4番のビット3

に1を立てねばなりません。その他の設定は末尾のソースの setupTRNG()関数の中を見ていただいた方が手っ取り早いかと。

TRNGの場合、イネーブルにしてやれば、決まったサイクル数毎に真性乱数を発生します。割り込み許可すれば発生毎に割り込みで知らせてきます。末尾のソースをWio Terminal上で動作させ、実際に割り込みでTRNGを読み取っている様子が以下です(なお、ソースを見ていただくと分かる通り、初回が0なのはプログラムの仕様<バグともいう>です。すみません。)

ATSAMD51 TRNG-INT test sample.
---LOOP---
INT: 0x00000000
---LOOP--- 
INT: 0xa9dda0b2
---LOOP--- 
INT: 0x240d3d69
---LOOP--- 
INT: 0x084ef1ce

まあ、これでTRNGに限らず周辺割り込みを受ける「形」はOKですかね。次回は、「眠らせておいて叩き起こす」イベント待ちをやってみますか。TRNG様には目的外使用ばかりで申し訳ないですが。

IoT何をいまさら(92) ATSAMD51、TRNG、「真性」乱数ジェネレータ へ戻る

IoT何をいまさら(94) ATSAMD51、ArmのWFI命令使ってみた へ進む

実験に使用したソースコード(Arduinoプラットフォーム環境用C++ソース)
// SAMD51 TRNG interupt handler test
#include <Arduino.h>

uint32_t dataTRNG;

void setupTRNG() {
  MCLK->APBCMASK.bit.TRNG_ = 1; //Enable CLK_TRNG_APB
  TRNG->CTRLA.bit.ENABLE = 0;   //Disable TRNG 
  TRNG->INTFLAG.bit.DATARDY = 1;  //Clear Interrupt Flag
  TRNG->INTENSET.bit.DATARDY = 1; //SET Interrupt Enable Flag
  NVIC->ISER[4] |= 0x8;         // Enable TRNG Interrupt #131 [4] bit 3
}

void startTRNG() {
  TRNG->INTFLAG.bit.DATARDY = 1;  //Clear Interrupt Flag
  TRNG->CTRLA.bit.ENABLE = 1;     //Enable TRNG 
}

int pollReadTRNG(uint32_t *datReturn) {
  int timeout = 5;
  while (!TRNG->INTFLAG.bit.DATARDY) {
    if (--timeout < 0) {
      return 0; //Error return.
    }
  }
  //Serial.println(timeout);
  TRNG->CTRLA.bit.ENABLE = 0;   //Disable TRNG 
  *datReturn = TRNG->DATA.reg;  //Reading DATA register make INT flag clear.
  return 1; //OK
}

void TRNG_Handler(void) {
  TRNG->CTRLA.bit.ENABLE = 0;   //Disable TRNG 
  dataTRNG = TRNG->DATA.reg;         //Reading DATA register make INT flag clear.
}

void setup() {
  Serial.begin(9600);
  while(!Serial);
  Serial.printf("ATSAMD51 TRNG-INT test sample.\r\n");
  dataTRNG = 0;
  setupTRNG();
  interrupts();
}

void loop() {
  uint32_t dat;
  Serial.println("---LOOP---");
  startTRNG();
  Serial.printf("INT: 0x%08x\r\n",dataTRNG);
  if (pollReadTRNG(&dat)) { // If no interrupt happened,
    Serial.printf("POLL: 0x%08x\r\n",dat);
  }
  delay(2000);
}