TinyGoを使ってATSAMD21マイコンの周辺回路を制御しています。前から気になっていたのがRTCです。TinyGoのデフォルトを調べていると動いているっぽいです。この際調べておこうと。その過程でこのMCUにはRTCの精度をあげられる仕組みがあることに気づきました。TinyGoは使ってないみたいですが。
※「AT SAMの部屋」 投稿順indexはこちら
最初にデータシートを全部よく読んでおけばいいのですが、泥縄で必要なところだけ拾い読みなので見落としてました。不埒にも「内蔵OSC32Kの精度がイマイチ」だからとか「外付けクリスタルはまだましだけれども」みたいなことを書いてしまいました。しかし、MicroChip社の中の人はそんなことお見通しだったみたいです。RTC内部に以下のレジスタがありました。
FREQCORR
Frequency Correction機能です。ATSAMD21のRTCを標準的な設定で実時間時計(カレンダー・クロック)として使用する場合、約1ppm精度で歩度調整できる機構です。これを使えば時計精度を上げられそうです。でも、今回TinyGoのRTCのデフォルト設定を確認してみて、この機構は使わない(使えない)設定値で動かしていることを確認しました。
RTCの動作状況を調べるGoコード
私は RTC = Real Time Clock の頭文字だと思っていましたが、手元のATSAMD21のデータシートをみると Real Time Counter と書いてあります。私のイメージの年月日時分秒のある「カレンダークロック」はモード2という設定のときでした。それ以外の設定、モード0とモード1では単純「カウンタ」です。
さて、Go言語のソースを各種マイコン(Arm、RISC-Vその他)向けにビルドできる「クロス開発環境」TinyGoで、Arm Cortex-M0+コアのMicroChip社ATSAMD21G18マイコン搭載の超小型ボードSeeeduino Xiao向けのオブジェクトを作ったとき、デフォルトでRTCは動いています。
動作状況を確認するためのGoコードが以下に。前回とは異なり再び unsafeなポインタだらけの不作法なコードに逆戻りです。すんません。
package main import ( "fmt" "time" "runtime/volatile" "unsafe" ) func dumpGCLKRTC() { //CLKCTRL CLKCTRL_DIR8 := (*uint8)(unsafe.Pointer(uintptr(0x40000c02))) CLKCTRL_DIR16 := (*uint16)(unsafe.Pointer(uintptr(0x40000c02))) volatile.StoreUint8(CLKCTRL_DIR8, 0x04) // GCLK_RTC work16 := volatile.LoadUint16(CLKCTRL_DIR16) genID := uint8((work16 & 0x0F00)>>8) fmt.Printf("dstID=0x%02X ", (work16 & 0x3F)) fmt.Printf("genID=%d ", genID) fmt.Printf("CLKEN=%d ", (work16 & 0x4000)>>14) fmt.Printf("WRTLOCK=%d\n", (work16 & 0x8000)>>15) //GENCTRL GENCTRL_DIR8 := (*uint8)(unsafe.Pointer(uintptr(0x40000c04))) GENCTRL_DIR32 := (*uint32)(unsafe.Pointer(uintptr(0x40000c04))) volatile.StoreUint8(GENCTRL_DIR8, genID) work := volatile.LoadUint32(GENCTRL_DIR32) fmt.Printf("genID=%d ", (work & 0xF)) src := uint8((work & 0x1F00)>>8) fmt.Printf("SRC=%d ", src) fmt.Printf("GENEN=%d ", (work & 0x10000)>>16) fmt.Printf("DIVSEL=%d\n", (work & 0x100000)>>20) //GENDIV GENDIV_DIR8 := (*uint8)(unsafe.Pointer(uintptr(0x40000c08))) GENDIV_DIR32 := (*uint32)(unsafe.Pointer(uintptr(0x40000c08))) volatile.StoreUint8(GENDIV_DIR8, src) work = volatile.LoadUint32(GENDIV_DIR32) fmt.Printf("DIV=0x%04X\n", (work & 0xFFFF00)>>8) } func dumpRTCsetting() { RTCCTRL_16 := (*uint16)(unsafe.Pointer(uintptr(0x40001400))) EVCTRL_16 := (*uint16)(unsafe.Pointer(uintptr(0x40001404))) FREQCORR_8 := (*uint8)(unsafe.Pointer(uintptr(0x4000140C))) work := volatile.LoadUint16(RTCCTRL_16) fmt.Printf("ENABLE=%d ", (work & 0x2)>>1) fmt.Printf("MODE=%d ", (work & 0xC)>>2) fmt.Printf("CLKREP=%d ", (work & 0x40)>>6) fmt.Printf("PRESCALER=%d\n", (work & 0xF00)>>8) work = volatile.LoadUint16(EVCTRL_16) fmt.Printf("OVFEO=%d ", (work & 0x8000)>>15) fmt.Printf("CMPEO0=%d ", (work & 0x100)>>8) fmt.Printf("PREEOx=%02X\n", (work & 0xFF)) work8 := volatile.LoadUint8(FREQCORR_8) fmt.Printf("SIGN=%d ", (work8 & 0x80)>>7) fmt.Printf("CORR VALUE=%d\n", (work8 & 0x7F)) } func readRTCcounter32() { READREQ_16 := (*uint16)(unsafe.Pointer(uintptr(0x40001402))) STATUS_8 := (*uint8)(unsafe.Pointer(uintptr(0x4000140A))) COUNT_32 := (*uint32)(unsafe.Pointer(uintptr(0x40001410))) volatile.StoreUint16(READREQ_16, 0x8010) for { if (volatile.LoadUint8(STATUS_8) & 0x80) == 0 { break } } fmt.Printf("COUNT32=0X%08X\n", volatile.LoadUint32(COUNT_32)) } func main() { for { fmt.Println("GCLK_RTC Setting:") dumpGCLKRTC() fmt.Println("RTC Settings:") dumpRTCsetting() fmt.Println("RTC Counter Value:") readRTCcounter32() fmt.Println("-------------------------------") time.Sleep(5 * time.Second) } }
上記コードを実機上で走らせた結果
上記コードを実機上で走らせ、USBシリアルに接続した仮想端末ソフトで送られてくる情報を眺めたものが以下です。
まず、GCLK_RTC Setting: 以下にクロックジェネレータ側でのRTC向けの設定がダンプされています。
-
- dstID=0x04というのが、RTCにクロックを供給しているクロックジェネレータの設定で、genID=2と書かれていることからクロックジェネレータの2番のクロックがRTCに接続されていることが分かります。CLKEN=1なのでクロック供給は許可されています。書き込みロックはかかっていません。
- genID=2から始まる次の行をみるとSRC=4とあり、OSC32K、内蔵32KHzオシレータがその源となっています。GENEN=1とあるのでクロックジェネレータは動作してます。またDIVSEL=0なので、次の行の分周器設定値で素直に分周されるようになっています。
- しかし次の行ではDIV=0x0000なので分周されておらず、OSC32Kの周波数のままダイレクトにドライブされているみたいです。
続いてRTC Settingsです。
-
- ENABLE=1の行で、RTCはイネーブル(動作中)であることが分かります。MODE=0であることから、カレンダ・クロックではなく、32ビットのカウンタモードでの動作です。RTCのプリスケーラも使っていないようです。
- 次の行のOVFE0以下はカウンタになにかイベントを仕掛けてあればどこかにビットが立つのですが、デフォルトではなにも仕掛けてないことが分かります。
- その次のSIGN以下の行は先ほど出てきた歩度調整のためのレジスタですが、これは使ってません。というかプリスケーラを使わないとこの機能は使えません。
ここまで「解読」するとRTCはOSC32Kの周波数(32.768kHz)をそのままカウントアップする設定でRUNしているようです。最後のRTC Counter Valueということでカウンタを読み出してみてます。
上記の実機ダンプは5秒に1回の割合で発生するようにしかけてあります。2回分のカウンタ値の差分を計算してみたものが以下に。
0x001B8BD0 -)0x00190B30 ------------ 0x280A0 0x280A0 = 164000 = 32768 * 5
おお、ピッタンコで5秒相当のカウント値ですな。
どうもTinyGo処理系の生成するオブジェクトはモード0のRTCの値を当てにしている感じなので上記の設定を変更するのは躊躇われます。まあそういうカウンタモードで動いているのは覚えておきます(直ぐに忘れそうだが、自分。)