IoT何をいまさら(87) Wio Terminal、SAMD51タイマの外部クロック駆動

Joseph Halfmoon

MicroChip社のArmコアマイコン、SAMD51の周辺を直接プログラミングしてみています。前回はクロックジェネレータGCLK部分を調べてみました。そして内蔵発振器のクロックを外部端子に出力させて観察しました。今度はタイマ/カウンタを調べてみます。初回は外部端子に与えたクロックでカウンタ動作を行わせたいと思います。

SAMD51(ATSAMD51)にはタイマ/カウンタに分類できるものが複数存在します。列挙すれば以下のとおりです。

  1. TC (Timer/Counter)
  2. TCC (Timer/Counter for Control Applications)
  3. WDT (Watchdog Timer)
  4. RTC (Real-Time Counter)
  5. FREQM (Frequency Meter)

後ろの3つは特定用途向けのタイマ/カウンタで、前の2つが一般的なタイマ/カウンタです。TCとTCCを比べると、TCがベーシックな機能、TCCはTCのスーパーセットで機能強化されているものに見えます。TCCは制御用途のための外部入出力機能が充実しています。しかしTCも入力キャプチャ、出力コンペア、PWMなどの基本的な入出力機能を備えています。TCでも制御用途にも使えるケースは多いのではないかと思います。

Wio Terminalに搭載されている SAMD51P19Aの場合、TCだけでも8個搭載(32ビット動作の場合は2個を組み合わせて使うので4個相当)しています。今回は、TCのうち、使われていないと思われる1本を見つけて、そのカウンタを外部端子から与えたクロックで動作させてみたいと思います。

TCとAPB

合計8個のTCが搭載されていますが、接続されている内部バスは異なります。SAMD51の場合、周辺装置用のAPBバスは4本もあり、それぞれブリッジA,B,C,Dと呼ばれるバスブリッジでより高速なバスマトリックスに接続されています。TCのインスタンスはA,B,C,Dのブリッジに2個づつ接続されています。

CPUがAPBバス経由で周辺装置のレジスタに読み書きする場合、各装置へのAPBバスクロックが供給されていないと読み書きできない筈です。SAMD51のデータシートをみると、RESET時初期値でAPBバスクロックが供給される装置もあれば、供給されない装置もあります。TCをみると後者のようです。しかし、RESET直後はディセーブルでも、今回のプラットフォームであるSAMD51用のArduino環境の初期化ルーチンにより、メインプログラムが呼び出されたときには使えるようになっているのかも知れません。そこで以下のような関数を作ってAPBバスクロックの分配状況を確認してみました。

void dumpAPB_MASK() {
  Mclk* mclk = (Mclk*)MCLK;
  uint32_t maska = (uint32_t)mclk->APBAMASK.reg;
  uint32_t maskb = (uint32_t)mclk->APBBMASK.reg;
  uint32_t maskc = (uint32_t)mclk->APBCMASK.reg;
  uint32_t maskd = (uint32_t)mclk->APBDMASK.reg;
  Serial.printf("MSKA=%08x MSKB=%08x MSKC=%08x MSKD=%08x\r\n", maska, maskb, maskc, maskd);
}

バスクロックを司っているのは、前回ちらっと出ましたが MCLK という名の回路です。ここで前回テーマのGCLKからCPUクロックを生成しています。バスクロックはCPUクロックと「同期」しているのでMCLKの支配下にあるわけです。4本あるAPBバス毎にマスクレジスタというものがあり、各周辺回路へのバスクロックを許可/不許可しています。

上記関数でマスクレジスタの値を確認したところ、以下の結果を得ました。

MSKA=0000f7ff MSKB=0001fe57 MSKC=00002078 MSKD=000003ff
  • TC/TCCについてはAPBバスクロックはすべて許可状態にあった
  • それ以外の周辺は許可状態も不許可状態もある

まあ、自分で使うときに必ずイネーブルにして、使い終わったら戻すというのが良いお作法でしょうが、初期化ルーチンでタイマは全部OKにしてくれている(多分PWM出力などArduino関数を使うとタイマが使用される)ので、特に操作せず、そのまま使ってしまうことにいたします。

TCの使用状況

上に述べたとおり、PWM出力(Arduino的にはアナログ出力)などを使わなければタイマ類は空いているだろうと予想がつきます。一応、以下の関数を使って8個のTCの使用状況を確認いたしました(勿論、PWMなどは使わない状態。)

void dumpTC_CTRLA() {
  Tc* TCaddr[] = TC_INSTS;
  for (int idx = 0; idx < TC_INST_NUM; idx++) {
    uint8_t en = (uint8_t)TCaddr[idx]->COUNT16.CTRLA.bit.ENABLE;
    uint8_t md = (uint8_t)TCaddr[idx]->COUNT16.CTRLA.bit.MODE;
    uint8_t ev = (uint8_t)TCaddr[idx]->COUNT16.EVCTRL.bit.EVACT;
    uint8_t st = (uint8_t)TCaddr[idx]->COUNT16.STATUS.bit.STOP;
    Serial.printf("TC[%d] EN=%d, md=%d, evact=%d, stop=%d\r\n", idx, en, md, ev, st);
  }
}

EN=0は該当TCがディセーブルであることを示しています。結果は以下のとおり、すべて空いていました。動作モードはすべて16ビットモードです。なお、イネーブル状態で実際にカウンタが動いているとstopが0になります。

TC[0] EN=0, md=0, evact=0, stop=1
TC[1] EN=0, md=0, evact=0, stop=1
TC[2] EN=0, md=0, evact=0, stop=1
TC[3] EN=0, md=0, evact=0, stop=1
TC[4] EN=0, md=0, evact=0, stop=1
TC[5] EN=0, md=0, evact=0, stop=1
TC[6] EN=0, md=0, evact=0, stop=1
TC[7] EN=0, md=0, evact=0, stop=1

Timerを使うArduino関数を使う場合にリソース競合に気をつけさえすれば、どこを使っても良いと判断しました。今回は末尾のTC7を使用して実験することにいたします。

外部からのクロック入力

タイマを動作させるクロックはGCLK内に12本あるクロックジェネレータのどれかによって生成された信号でないとなりません。前回調査で、外部端子と入出力可能なGCLK内のクロックジェネレータは8本。そのうち6本までは既に使用されていました。空いているのは6番と7番のみです。前回7番をクロックの外部出力用に使ったので、今回は6番を外部クロックの入力用に使ってみます。設定はこんな感じ。

void setGCLK6In() {
  Port* port = (Port*)PORT;
  port->Group[1].PMUX[6].bit.PMUXE = 0xC; //PB12 Function M
  port->Group[1].PINCFG[12].bit.INEN  = 1; //PB12 INPUT ENABLE
  port->Group[1].DIRCLR.reg &= 0x1000; //PB12 input
  Gclk* gclk = (Gclk*)GCLK;
  uint32_t temp = gclk->GENCTRL[6].reg;
  temp = (temp & 0xFFFFF6E0) | 0x102; //GCLK_IN
  gclk->GENCTRL[6].reg  = temp;       //GENERATOR Enable, GCLK_IN
  port->Group[1].PINCFG[12].bit.PMUXEN  = 1; //PB12 PMUX enable
}

ジェネレータ6番への外部クロック入力に関係する信号名をまとめると以下のとおりです。

  • 内部のGCLK信号名: GCLK/IO[6]
  • SAMD51の端子番号: PB12
  • Wio Terminalのボード上の信号名: GPCLK1
  • Raspberry Pi 互換端子の信号名: GPIO5

入力なので、ポートのDIRECTIONを入力に設定するだけで良いかと思っていたのですが、そんなことはありませんでした。PMUXENなどもGCLK/IOとして設定してやらないと入力できるようになりませんでした。

なお、Wio Terminalの背面のRaspberry Pi互換配列のピンソケット端子にWio Terminalを上に向けたままアクセスするために、末尾に写真を掲げた「お手製拡張ボード」を作りました。

TC7の設定とカウンタの読み出し

TC7はデフォルトで16ビットモードになっているので、そのままでカウンタのクロックソースをGCLKのジェネレータ6番(さきほど外部入力に接続したクロックジェネレータ)に向けてやります。そしてカウントを許可。

void enableT7() {
  Gclk* gclk = (Gclk*)GCLK;
  gclk->PCHCTRL[39].reg = 0x46; //GCLK_TC7 <= Generator 6(PB12 input)
  Tc* tc = (Tc*)TC7;
  tc->COUNT16.CTRLA.bit.ENABLE = 1;
}

これでTC7のカウンタが動作を始めます。なお、GCLKからTCへのクロックの分配は同一のバスブリッジに接続しているTC2個づつ共用となっています。

  • APB-Aバスに接続する TC0とTC1が、GCLK_TC0/1を共有
  • APB-Bバスに接続する TC2とTC3が、GCLK_TC2/3を共有
  • APB-Cバスに接続する TC4とTC5が、GCLK_TC4/5を共有
  • APB-Dバスに接続する TC6とTC7が、GCLK_TC6/7を共有

カウンタ値そのものは、何時でも読めるのですが、CPUクロックとは非同期なクロックで動作しているので、「同期」の手順を踏む必要があります。一応タイムアウト付きで作ってみたカウンタ読み出し関数が以下です。

int readT7Counter() {
  int timeoutCount = 0;
  Tc* tc = (Tc*)TC7;
  tc->COUNT16.CTRLBSET.bit.CMD = TC_CTRLBSET_CMD_READSYNC_Val;
  while(tc->COUNT16.SYNCBUSY.bit.COUNT == 1) {
    if (timeoutCount++ > 100) break;
  }
  return (int)tc->COUNT16.COUNT.bit.COUNT;
}

読み出す前にトリガかけないと読み出しレジスタの内容が更新されません。同期動作を忘れるとカウンタ値の更新が見えないので注意です。

アイキャッチ画像に掲げたように、外部からテスト用に遅いクロック信号(1kHz)を与えてカウントさせてみました。そして以下のコードで読み取ってみました。

measuredV = readT7Counter();
Serial.printf("T7=%d\r\n", measuredV);

当然、読み取る度に値は更新されます。一例がこちら。

T7=59906

外部クロックを入力して、カウントできているのでないかい。

<おまけ>

Wio Terminalは、背面にRaspberry3/4などと互換配列の信号がピンソケットで出ています。今回もその端子にアクセスする必要があります。Wioをひっくり返してジャンパ線を接続してしまうと折角のLCDやボタンなどが使えません。

そこで過去投稿で、2列の拡張端子のうち「外側」の信号を取り出す治具を作りました(以下写真の下側)

しかし、今回/前回使用したGPIO5/6は、2列の「内側」なので作成済の下の治具ではとりだせませぬ。そこで、泥縄でまた治具を作りました(以下写真上側。)

本当は、両列一度に取り出せるといいのだけれど。。。

HandMadeExtBoardsForWioTerminal

IoT何をいまさら(86) Wio Terminal、SAMD51内蔵OSC信号を取り出す に戻る

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