前回SAMD51マイコンの周辺回路に直接アクセスするための「定型句」を調べました。そして制御レジスタへのアクセスの門番的存在、PACを覗いてみました。今回はクロック関係の設定の一部を調べてみます。そして使っていないと思われる端子を使い、内蔵クロックを外部へ出力させて観察するところまでやってみたいと思います。
※「IoT何をいまさら」投稿順Indexはこちら
周辺回路を直接制御していくためにはデータシートを読む必要があります(いつもの事ながらちゃんと読んでないです、自分。)SAMD51でウエブ検索すると、似ているけれども別な型番の品種がヒットしたりしてちょっと混乱します。つらつら拝見するに「周辺回路的にはほとんど同じような」感じではあるのです。なお、対象のWio Terminalに搭載されているマイコンは以下です(リンク先はMicroChip社の製品ページ。)
どういうわけか、MicroChip社のWebページはATSAMD51とATから始まっているのに、データシートの本文中で型番説明しているところでは、ATを除いてSAMから始まっています。ATはアトメルからきているのかな?この辺のネーミングの背景、ちょっと気になります。ちなみにSAM=SMART ARM Microcontrollerだそうです。
SAMという型名の語幹は共通ですが、後ろにDがついたりEが付いたりで品種が分かれてきます。Wio Terminal搭載品は、
D51なので、Cortex-M4FコアとAdvanced Feature Set
の意味だそうです。M4Fはハードウエアの浮動小数点計算ができる組み込み用マイコンとしてはちょっと高級なコア。Advanced Feature Setは、アドバンスなのかね~と思いましたが、SAMD51/SAME51ではほぼ共通の機能セットのようです。BASIC Setと呼ばないのは、他のもっと小さ目の品種SAMD21などとの比較でしょうか。
ともかくSAMD51はこの「51」シリーズの基本形的な存在であるようです。その後の記号Pは120ピンもしくは128ピンの品種を意味し、番号19は512KB Flash品の意味だそうです。このシリーズ使うにあたってはお名前の付け方もある程度理解しておく方が良いでしょう。すぐ忘れるけど。
オシレータとクロックジェネレータ周り
何と言っても発振回路周りはマイコンの根幹部分、これ無しには手も足もでません。最近のマイクロコントローラの通例にもれず、SAMD51のオシレータとクロックジェネレータもかなり充実しているというか複雑というかです。細かく、使用部位毎に適切なクロックを配布できるようにして無駄な消費電力を抑え、性能が出るようにしているようです。
オシレータはPLLなど内蔵している高速クロック系統のブロックと32k発振の低速クロックの系統のブロックに分かれます。さらに、それぞれのブロック内に複数のクロック源があり、応用により適切なものを組み合わせて用いることができるようになってます。
発振器からの元クロックを選択し、必要に応じて分周し、そして駆動するクロックジェネレータは、大きくGCLKとMCLKと呼ばれる2系統のブロックに分かれます。ここで覚えておかないとならないのは以下です。
-
- GCLKという方がより発振器に近いクロック源であり、複数の「非同期」なクロックを生成し、各周辺回路に供給する役割を持つ。
- MCLKは、マイコンコアに与えるクロックをGCLKの0番から生成している。「同期」と呼ばれるのはコア・クロックや内部バスのクロックに「同期」したクロックだからである。ソフトウエアはこちらのクロックで動作する。
- 多くの周辺回路はGCLKからのそれぞれ固有の「非同期」クロックで動作しているため、ソフトウエアの動作とは「同期」していない。よってマイコン側からレジスタに読み書きするときにハードウエアによる「同期」を必要とする場合がある。これにはそれなりのクロック数がかかる。
3つ目を忘れていて、今回もついやらかしました。後で説明します。
今回は、Wio TerminalをArduino環境(VS Code + PlatformIO)でデフォのままビルドしたとき、
-
- GCLKの内部にある12個のクロックジェネレータの使用状況を確認
- 「空いていそうな」クロックジェネレータを見つける
ところから調べたいと思います。以下のコードは、GCLKの中の12個のクロックジェネレータに1本づつ存在するGENCTRLというレジスタの一部フィールドを標準出力にダンプするためのものです。これのソースフィールドを見れば、どの発振器に接続されているのかわかります。
void dumpGENCTRL() { Gclk* gclk = (Gclk*)GCLK; for (int idx = 0; idx < 12; idx++) { uint16_t src = (uint16_t)gclk->GENCTRL[idx].bit.SRC; uint16_t oe = (uint16_t)gclk->GENCTRL[idx].bit.OE; Serial.print("GENCTRL["); Serial.print(idx); Serial.print("].src = "); Serial.print(src); Serial.print(" oe = "); Serial.println(oe); } }
調べた結果は以下のとおりです。3番が外部の32kHzオシレータをソースとしている他は、高速クロック側のDPLLとかDFLL(回路詳細は未調査)をクロック源として設定されていました。また各ジェネレータがどの周辺回路に接続されているのかも知りたいところ(おいおい調査の予定)です。
ジェネレータ番号 | 発振器 |
---|---|
0 | DPLL0 |
1 | DFLL |
2 | DPLL1 |
3 | XOSC32K |
4 | DFLL |
5 | DFLL |
6 | — |
7 | — |
8 | — |
9 | — |
10 | — |
11 | — |
12本のうち、後半6本はデフォルト状態では使っていないみたいです。勝手に使うためにはそちらとなります。しかし今回は、生成したクロックを外部端子に出力するという「野望」があるのでどれでも良いというわけにはいきません。
GCLKブロックでは、外部端子からクロック入力を得たり、生成したクロックを外部端子に出力する機能も含んでいるのです。これに使える端子は8本しかないようです。そしてそれら端子はジェネレータ前半の0番から7番までに対応しているみたい。結局、2つの条件のANDをとると以下の2候補に絞られます。
ジェネレータの6番か、7番
今回はこのうちジェネレータの7番の方を使ってみることにいたします。以下ちょっと煩雑な記述ですが、間違えると動作しないので、忘れないように列挙しておきます。
-
- ジェネレータの7番に接続可能な内部のIO信号は GCLK/IO[7]
- GCLK/IO[7]は、SAMD51チップのPort Bのビット13(PB13)に接続できる
- PB13端子は、Wio Terminalの回路図上、GPCLK2という信号名でラズパイ互換の端子配列の背面40ピン拡張端子に出力されている。
- 40ピン拡張端子での名前はGPIO6(ラズパイでのお名前)
- なお、Arduino環境での端子番号は 38番(Arduino環境の関数に引数として渡すときはこちら)
この端子に出力するクロックはチップ内蔵の低速発振器OSCULP32Kの「ほぼ32kHz」クロックといたしました。このクロックは、ある意味このチップでもっとも始原的なクロックです。
-
- 何時でも動いている(停止しない)
- ウオッチドッグタイマ(番犬タイマ)に直結している
- 割り込み受付回路にも直結している
設定をどう変更しようが、必ず動いていることが期待できるクロックです。反面32kHzといいつつ、一般的な時計に使えるような精度は無いです。
ジェネレータの7番でOSCULP32Kを源としてクロックを作る
特にクロックディバイダを噛ませることなく、ストレートでジェネレータ7番でOSCULP32Kからクロックを生成(1対1)し、先ほどのPB13端子に向けてクロックを生成するには、GCLK7番目のGENCTRLレジスタの
-
- SRCフィールドにOSCULP32K選択するために 0x04を書き込み
- IO端子へのクロック出力のために OEフィールドに1を書き込み
- ジェネレータそのものをイネーブルするためにGENENフィールドに1を書き込み
が必要です。ここで先ほどの件にハマリました。最初、ビットフィールドを個別に使い(見やすいと思ったので)、
gclk->GENCTRL[7].bit.SRC = 0x04; //OSCULP32K gclk->GENCTRL[7].bit.OE = 1; //OUTPUT Enable
などと書いたら、SRCフィールドには書き込まれているのに、OEフィールドには値が書き込まれません。よく考えたら、このレジスタはCPU側クロックとの「同期」が必要なレジスタでした。そのようなレジスタに値を書き込んだ場合、数クロック~数十クロックくらいかかる筈の同期中はソフトウエアから次の書き込みを行っても書き込みが無視されてしまうのです。連続書き込みしたければ、別にある同期中を示すステータスビットを監視して完了を待たねばなりません。でもメンドイ。
レジスタ全体にRead Modify Writeもせず直接値を書き込んでしまうのは、良い子はやらない不作法です。が、ここではそれで動く筈(元がオール0だし。)3つのフィールドに32ビット1発書き込みすれば、同期の件は見えなくなってOK。
GCLK/IO[7]信号をPB13に出力する設定
上記で、GCLK回路からGCLK/IO[7]信号にのってOSCULP32がやってくる筈ですが、これをポートから外部に出力する設定も必要です。
-
- SAMD51の場合、Portは0,1,2… と番号でアクセスされるポートグループというものに組織化されている
- 1グループは最大32端子(32ビット)
- 0番グループはポートA、1番グループがポートB、以下同文。
- 出力のためには端子を出力設定する必要がある(デフォルトは入力)
- ソフトウエアから見える端子への出力でなく、周辺回路からの直接出力であることを選択する必要がある。
- さらに、複数の周辺回路のうち、どの周辺回路からの出力か選択する必要がある。
ポート操作の説明をダラダラ書けば面倒ですが、コードで書けば高々3行。Portへの出力設定と、GCLKでのクロック設定の両方を行い、Wio Terminal裏側のGPIO6番端子にOSCULP32波形を出力させるコードはこちら。案ずるより生むがやすし?
void setGCLK7Out() { Port* port = (Port*)PORT; port->Group[1].PMUX[6].bit.PMUXO = 0xC; //PB13 Function M port->Group[1].PINCFG[13].bit.PMUXEN = 1; //PB13 PMUX enable port->Group[1].DIRSET.reg = 0x2000; //PB13 output Gclk* gclk = (Gclk*)GCLK; gclk->GENCTRL[7].reg = 0x904; //OUTPUT Enable, GENERATOR Enable, OSCULP32K }
それなりの波形が現れました。ただ、予想どおりというか、ちょっと周波数大きめ(32.768kHzを1.6%くらい超過。)まあ、外部発振器なしに、いつでもこのクロックが当てにできるというのは安心。
周辺回路への直接アクセスの実験も軌道に乗った?感じです。次はタイマ機能でも使ってみますか?