IoT何をいまさら(96) Wio Terminal、困ったときの uf2。TC割り込み

Joseph Halfmoon

別件でSTM32とRP2040でAD, DAを使っているので、ATSAMD51でもやるべしと思い立ちました。先立つものはサンプリング周波数のタイミングということでTC7でタイミング(割り込み)を作ろうとしてハマりました。なぜか割り込み立て続けに入り過ぎ。そのあおりでオブジェクトのダウンロードすら不能。困りました。

(実験に使用したソース全文は末尾にあります。Microchip社ATSAMD51P19Aマイコン搭載のSeeedStudio社のWio Terminal向け、Arduinoプラットフォーム用のソースです。)

単なるインーバル・タイマでタイミングを作るなど直ぐにできる筈と甘くみていてハマリました。端的にいうと

ゼロクリアするために、ゼロを書いてはいけないこともある

ことを忘れていたためのポカミスです。ゼロにしよう、ゼロにしようと焦ってはいけない、1を書くとゼロになるのだ、と。なんじゃらほい。

ATSAMD51のレジスタを見ていると、近代的だな~と思う部分があります。古典的なマイコンではRead Modify Write操作が必要なところです。ATSAMマイコンでは1つの操作対象にCLEAR用のレジスタとSET用のレジスタを2本用意してあって、SETしたければSET用のレジスタに1を書く、CLEARしたければCLEAR用のレジスタに1を書くというスタイルです。そのようなレジスタでは0を書いても何も変化しません。Readは不要、Writeするのみで操作は完結。

Timer/Counterの割り込み許可/不許可のフラグ類についてもそのスタイルです。割り込み許可フラグの操作に関してはCLR/SETと対になるレジスタが並んでいるのでさすがに間違えませぬ。しかし、割り込みがおきたときに立つ割り込みフラグのレジスタは1本しかありません。勿論Read/Write可です。「1がたったフラグをクリアするのに0を書けば良いよね」データシートをよく読まずに、割り込みハンドラの中で割り込みフラグをクリアするために0を書き込んでました。大間違い。実際は、

0を書いても何も起こらない、1を書くとフラグを0クリアする

という方式で一貫してました。割り込みフラグを立てるのはハードなので、合理的です。しかし、間違えた結果、割り込みをクリアしないままハンドラを抜けてました。結果、連続して割り込みが掛かり続けたようです。割り込みハンドラの中で周波数測定用に外部端子を操作していたのでハンドラが動作(1秒に80万回!)しつづけているのは分かったのです。しかし最初はタイマの設定を疑ってしまいました。10kHzの頻度で割り込み入るようにプリスケーラやリロード値を設定した筈のタイマがなんでそんなに高速に回っているの?

見事にハマりました。これだけ高速に割り込みが掛かり続けると、外部からのオブジェクトファイルのダウンロードにも反応しなくなってしまいました。修正したオブジェクトも書き込めないで爆走しています。どうしたものか。困った。

ダウンロードが出来なくなった時の uf2 書き込み

オボロゲな記憶から、Wio Terminalの場合、側面のRESETボタンを素早く2回カチャカチャやると「ブートローダー」に入れることを思い出しました。Seeed社のページにやり方載っています。これで割り込みの無限地獄から抜けて、PCからはディスクとして見えるようになりました。一安心。しかし、ストレージデバイスに書き込んで認識されるのは .uf2 形式のファイルです。当方、VS Code + PlatoformIOで、SAMD51のビルドをやっており、通常生成されるオブジェクトは elfとbinです。binをuf2に変換するツールは ココ にありました。ダウンロードしてコマンドラインから走らせようかと思ったです。しかし、そのような面倒なことをしなくても良いように、Seeed社(K.K.とあるので日本法人だね)がWebサイトを立ち上げてくれていることに気づきました。その名も

uf2

binファイルをアップロードすると uf2 形式にして戻してくれるサイトです。なお、Wio Terminalの場合、先頭アドレスは 0x4000 指定とのこと。助かった!

その後も何度か無限地獄に陥りましたが、1つ大丈夫なファイルを作っておけば脱出できるのでOKっと(元気だけはいいな。)

TC7での動作設定

サンプリング周波数用のタイミングを作るのにTC7を使ったのは以下のような理由です。

  • 単なるインターバルタイマとして使うのにTCCxはモッタイない
  • 調べたところ、TC1はArduino関数で使用されるが、他は空いていそう。

TC7の設定としては以下としました

  • GCLKのジェネレータ1番からプリスケーラにクロックをもらう。GCLKの1番はUSB用に48MHzが動作しています。(USBの48Mが変更されるようなことは無いだろ~)
  • TC7は8ビットモード(デフォルトは16ビットモード)にしてつかう。プリスケーラは64分周に設定。

上記の設定にすると、以下のような「欲しかった周波数」が得られるのであります。

  1. リロード値17を設定、約44.1kHz
  2. リロード値75を設定、10kHz

1番はほぼほぼCD、2番は別件で使っているサンプリング周波数ピッタリです。いい感じじゃね、ということで「さらっと」コードを書いてハマりました。割り込みにしなければ予定どおりに動いたのです。前回やったようにEVSYS経由でTC7のオーバーフローを端子にトグル出力した場合、こんな感じ。

EVSYS経由の出力

TC8evnetOUTちゃんと10kHzに設定したら、ほぼほぼその通り(トグルなので観察できる周波数はその半分です。)

前述のとおり、割り込みに切り替えると、無限地獄にハマりましたが、地獄から脱出できたあとはこんな感じ。

ようやくできた割り込みによる出力

TC8intrまあ、出来てみれば何のことはないけれど、ハマっているときは分からないんだ、これが。今日はDA動かすところまでたどりつきませんでした。また来週。

IoT何をいまさら(95) ATSAMD51、TRNG発EVSYS経由PORT行 へ戻る

IoT何をいまさら(97) ATSAMD51、DACを使う#1、Wio Terminal へ進む

実験に使用したソース全文
#include <Arduino.h>

// GPIO27 (WIO:A0/D0, SAMD51:PB08, Arduino:0) 
// GPIO22 (WIO:A1/D1, SAMD51:PB09, Arduino:1) 
#define GPIO27  (0)
#define GPIO22  (1)
// TC7 period value
//  17 ... 44.118kHz
//  75 ... 10kHz
// 255 ... 2.94kHz
#define PERIOD (75)

uint32_t counter;
volatile uint32_t flag;
volatile int xx;

// TC7: 8bit interval timer
void setupTC7(uint8_t periodValue) {
  MCLK->APBDMASK.bit.TC7_ = 1;      // Enable CLK_TC7_APB
  GCLK->PCHCTRL[39].reg = 0x41;     // GCLK_TC7 <= Generator 1 (48MHz)
  NVIC->ISER[3] |= 0x40000;         // Enable TC7 Interrupt #114 [3] bit 18
  TC7->COUNT8.CTRLA.bit.ENABLE = 0; // Disable TC7 (Some TC7 registers are enable-protected.)
  while(TC7->COUNT8.SYNCBUSY.bit.ENABLE == 1) {}
  TC7->COUNT8.CTRLA.bit.SWRST = 1;  // TC7 Software reset for initialize
  while(TC7->COUNT8.SYNCBUSY.bit.SWRST == 1) {}
  TC7->COUNT8.CTRLA.reg = 0x00000504; // DIV64, COUNT8
  TC7->COUNT8.INTFLAG.reg = 0x33;     // Clear all interrupt flags
  TC7->COUNT8.INTENSET.bit.OVF = 1; // Enable TC7 OVF interrupt
  TC7->COUNT8.PERBUF.reg = periodValue;
  TC7->COUNT8.CTRLA.bit.ENABLE = 1; // Enable TC7
}

void TC7_Handler(void) {
  xx = TC7->COUNT8.INTFLAG.reg;
  TC7->COUNT8.INTFLAG.reg = 0x33;
  flag = (flag == 0) ? 1 : 0;
  digitalWrite(GPIO27, flag);
}

uint8_t TC7_readSYNC() {
  TC7->COUNT8.CTRLBSET.bit.CMD = 0x4; // READSYNC
  while(TC7->COUNT8.SYNCBUSY.bit.COUNT == 1) {}
  return TC7->COUNT8.COUNT.reg;
}

void setup() {
  Serial.begin(9600);
  while(!Serial);
  Serial.printf("ATSAMD51 TC7 8bt interval timer.\r\n");
  pinMode(GPIO27, OUTPUT);
  pinMode(GPIO22, OUTPUT);
  flag = 1;
  digitalWrite(GPIO27, flag);
  digitalWrite(GPIO22, 1);
  setupTC7(PERIOD);
  counter = 0;
  xx = 0;
}

void loop() {
  if ((counter++ % 2) == 0) {
    digitalWrite(GPIO22, 0); // monitor LED ON 
  } else {
    digitalWrite(GPIO22, 1); // monitor LED OFF
  }
  Serial.printf("%d CNT=%02x flag=%d, xx=%02x\r\n",counter, TC7_readSYNC(), flag, xx);
  delay(1000);
}