TinyGoを使ってArm Cortex-M0+コア搭載Microchip社ATSAMD21マイコンの周辺回路を探っております。前回9本あるクロックジェネレータのうち5本が空いてることに気づきました。今回はそのうちの2本を使って、内蔵のオシレータのクロック信号を外部に出力しオシロで周波数を測定してみました。
※「AT SAMの部屋」 投稿順indexはこちら
今回測定対象としましたのは、3つある内蔵オシレータのうち以下の2つです。
-
- OSC32K
- OSC8M
2つにしたのは、ターゲットボードであるSeeduino Xiaoは超小型なので端子数が少なくちょうど良い塩梅の端子が2本だったという事情であります。OSC32Kはその名のとおり「ほぼ」32.768kHzのクロックを生成するもの。OSC8Mは8M基本ですが、設定により4/2/1MHzにもできるもの。どちらも工場出荷時にキャリブレーション値が記録されているみたいですが、多分RC発振器みたいなので精度はそれなりかと。
※32kHzクロックとそのRTCでの歩度調整機能については第15回ご参照ください。(2022/06/07 追記)
先に測定結果
Go言語のプログラムの説明をする前に、先に測定結果を示しておきます。以下C1(黄色)の信号がOSC32Kを外部出力したもので、C2(青色)がOSC8Mの外部出力です。右側の Measurements 部分に測定した周波数が表示されてます。
OSC32Kは「ほぼ」32.768kHzといいつつ、約0.9%近く誤差がありますな。一方青のC2の方は速度が速いので波形がよく分かりませぬ。C1の測定を外して時間DIVを調整したものが以下に。ほぼ1MHzの信号が出力されております。
OSC8Mの設定値は読み取っていないのですが、TinyGoのデフォルトでは1MHzを選択して動作させているようです。
なお、冒頭のアイキャッチ画像に測定中の写真を掲げました。OSC32K出力をしている端子には赤のLED、OSC8M出力端子には青のLEDがもともと接続してあったので人間の目には分かりませんが高速Lチカしている筈。
測定に使用したGoのソース
以下は前回GCLKの設定値読み取りに使用したプログラムを大幅改造して作成したGCLKのクロックジェネレータ信号の外部出力用のプログラムです。クロックジェネレータの番号と外部端子はハードウエアで1対1対応になっています。
-
- PA10端子(XIAOボードのD2端子)はジェネレータの4番
- PA11端子(XIAOボードのD3端子)はジェネレータの5番
setGGENxout(xには4か5が入る)関数にクロックジェネレータのソースとなる発振器の番号をあたえるとその信号が対応端子に出力されます。今回はソース4番(OSC32K)とソース6番(OSC8M)を指定していますが、他の発振器を指定しても外部出力できるでしょう、多分。実際には確かめてないデス。
package main import ( "fmt" "time" "runtime/volatile" "unsafe" ) func setGGEN4out(src uint32) { PMUX8 := (*uint8)(unsafe.Pointer(uintptr(0x41004435))) PCFG8 := (*uint8)(unsafe.Pointer(uintptr(0x4100444A))) PDIR32 := (*uint32)(unsafe.Pointer(uintptr(0x41004408))) GGENSTATUS8 := (*uint8)(unsafe.Pointer(uintptr(0x40000c01))) GENCTRL32 := (*uint32)(unsafe.Pointer(uintptr(0x40000c04))) GENDIV32 := (*uint32)(unsafe.Pointer(uintptr(0x40000c08))) //set PA10 Function H(7) tmp8 := volatile.LoadUint8(PMUX8) volatile.StoreUint8(PMUX8, (tmp8 & 0xF0) | 0x7) //set PMUXEN, clear other bits volatile.StoreUint8(PCFG8, 0x01) //set PA10 OUTPUT tmp32 := volatile.LoadUint32(PDIR32) volatile.StoreUint32(PDIR32, tmp32 | 0x00000400) //set GEN4 DIV=0 volatile.StoreUint32(GENDIV32, 0x00000004) //set GEN4 GENCTRL tmp32 = 0x00090004 | ((src & 0x1F) << 8) volatile.StoreUint32(GENCTRL32, tmp32) for { if (volatile.LoadUint8(GGENSTATUS8) & 0x80) == 0 { break } } } func setGGEN5out(src uint32) { PMUX8 := (*uint8)(unsafe.Pointer(uintptr(0x41004435))) PCFG8 := (*uint8)(unsafe.Pointer(uintptr(0x4100444B))) PDIR32 := (*uint32)(unsafe.Pointer(uintptr(0x41004408))) GGENSTATUS8 := (*uint8)(unsafe.Pointer(uintptr(0x40000c01))) GENCTRL32 := (*uint32)(unsafe.Pointer(uintptr(0x40000c04))) GENDIV32 := (*uint32)(unsafe.Pointer(uintptr(0x40000c08))) //set PA11 Function H(7) tmp8 := volatile.LoadUint8(PMUX8) volatile.StoreUint8(PMUX8, (tmp8 & 0x0F) | 0x70) //set PMUXEN, clear other bits volatile.StoreUint8(PCFG8, 0x01) //set PA11 OUTPUT tmp32 := volatile.LoadUint32(PDIR32) volatile.StoreUint32(PDIR32, tmp32 | 0x00000800) //set GEN5 DIV=0 volatile.StoreUint32(GENDIV32, 0x00000005) //set GEN4 GENCTRL tmp32 = 0x00090005 | ((src & 0x1F) << 8) volatile.StoreUint32(GENCTRL32, tmp32) for { if (volatile.LoadUint8(GGENSTATUS8) & 0x80) == 0 { break } } } func main() { var counter int = 0 setGGEN4out(4) //SRC=0x04 OSC32K setGGEN5out(6) //SRC=0x06 OSC8M for { fmt.Println(counter) time.Sleep(5 * time.Second) } }
上記ソースの設定の説明
前回に続き、不作法にも危ない unsafe ポインタを活用?し操作しております。これができるTinyGoは良いですな。この手の操作を行うときは、Microchip社のATSAMD21G18マイコンのデータシートを良く読んで行ってくだされ。上のコードに間違いあっても知りませんよ。
Portの設定
信号を外部出力するためにはPortを出力に設定しないとなりませんが、通常のソフトウエアによるGPIO操作と異なり、周辺装置からのダイレクトなアクセスをPortに導かないとなりません。
どの周辺装置からの信号を端子に導くかを制御するレジスタ群がPMUXです。1本のPMUX(8ビット)で2端子づつ、4つの32ビットポートグループの全ての端子にPMUXが存在するので合計64本のPMUXが存在します。それぞれにオフセット・アドレスが振られています。今回使用するのはPA10とPA11で隣り合ったピンなのでたまたま同じPMUXアドレスをシェアしている関係です。
下4ビットがPA10、上4ビットがPA11です。ここにGCLKからのクロック出力を接続する Peripheral function H (0x7) を書き込んでいます。
しかしPMUXに機能設定しただけでは信号が出てこないのがチトややこしいです。PINCFGレジスタの中のPMUXENというビットに1を立てないとPMUXはアクティブになりません。PINCFGは1端子に1レジスタ存在するので合計128本もあります。PA10とPA11に対応するレジスタのPMUXENを立てます。ここにはドライブストレングスとかプルアップとかピン毎のハードウエア設定がいろいろ入ってますが、他の設定は今回オール0にしてます。
そして最後にピンの出力方向の設定です。AT SAMマイコンは近代的で、方向設定レジスタにはSET専用、CLEAR専用のアクセス経路が設けられています。わざわざリード・モディファイ・ライトせずともSET専用レジスタの該当ビットに1を書き込めばその端子を出力に設定できます(CLEAR専用に1をかけば入力となります。)
GCLKの設定
ようやくクロックジェネレータ側の設定です。既に前回 GENCTRLレジスタをリードしているので、そこに書き込んで設定します。書き込みの場合はリードと違って1回で済むのでよっぽど楽です。
GENCTRLレジスタにそれぞれ以下の設定を書き込みます。
-
- ID=4 SRC=0x04 (OSC32K)OE=1, GENEN=1
- ID=5 SRC=0x06 (OSC8M)OE=1, GENEN=1
OEはビット19、GENENはビット16です。GENENによりクロックジェネレータがイネーブルとなるとともに、OEにより外部出力(PMUX)へ信号が送られるようになります。
なお、前回はリードだったので端折ってしまったSYNCBUSY待ちも今回はライトなので一応行ってます。GCLKのSTATUSレジスタの最上位ビットを読み出して0になるまで待ちます。
また念のため、クロックジェネレータの分周器をクリアし、元の信号がそのまま出るようにしています。GENDIVレジスタに以下の値を書き込んでます。
-
- 0x00000004
- 0x00000005
なおOSC8Mを1MHzにするのはオシレータ側の分周器で上記のクロックジェネレータ側の分周器ではありません。
まあ、こんな程度の設定で(初回はメンドイですが、マイコン使うときは皆さんフツーにやっている操作じゃないかと思います。いや今時そんな事書いているのはベアメタル派だけ?昔はRESETベクタから書いていたのですよ、トホホ。)周辺装置を直接制御できることが分かりました。後は端から動かしてみるってもんですかい?