MicroPython的午睡(98) STM32版、Nucleoのクロック設定を見直す

Joseph Halfmoon

前回前々回とRTCを触りながら、いったい私はどのクロックを使っているのだろうかと不安になりました。以前の第81回でタイマを触る時に高速のシステムクロックは内蔵PLLからのクロックということは確かめてあったのです。しかしRTCなどのクロック設定は未調査でした。今回は「成り行き」で使っていた部分を調査。

※「MicroPython的午睡」投稿順 Indexはこちら

STM32F401REのクロック系統

ベアメタルでプログラムを作っていれば自前で全部設定しなければならない(ということは全部把握できる、ホントか?)のクロックですが、MicroPythonを使っていると、よきに計らってくれるので、調べもせぬままテキトーに使って、動いた動いたと喜んでました。今回、RTCを使っていてクロックソースに疑念が生じ、一度、調べなおそうと発案いたしました。

まずはターゲットマイコンSTM32F401REのクロック系統からです。おおもとの発振源は以下の4系統が存在します。分かりやすいお名前デス。

    1. HSI
    2. HSE
    3. LSI
    4. LSE

HSIとHSEは高速なクロック源となるもので、HSIが内蔵のRCオシレータ(16MHz)、HSEが外付け振動子を使った発振回路(4MHz~26MHz)です。一方LSIとLSEは低速なクロック源となるもので、LSIが内蔵のRCオシレータ(約32kHz)、LSEが外付け振動子を使った発振回路(32.768kHz)です。HSEとLSEは外付け振動子だけでなく、外部からのクロック信号を受け入れるために使うことも可能です。

上記の高速クロックHSI、HSEのどちらかを基準として内蔵のPLLを使って逓倍クロック生成することができます。PLLも2つ搭載していますが、PLLへの入力クロックはHSIかHSEのどちらか片方を選択せねばならず、これは2つのPLLで共通です。単にPLLと呼ばれる最初のPLLからはSYSCLKに使える最大84MHzのクロックと、48MHz固定のペリフェラルクロックが出力されます。第2のPLLは主としてI2Sバスを制御するためのもののようです。

Nucleo-F401REのクロック関係ハードウエア

上記のようなクロックソースがあるSTM32F401REですが、ボードの設定により使える機能と使えない機能がありえます。Nucleoボードは汎用性が高く、半田ジャンパの設定で、いろいろな構成が可能です。手元のNucleo-F401REボードの設定がどうなっているのだか確認してみました。

以下のようにLSEに外付けオシレータ(下のX2)は実装されていますが、HSEの外付けオシレータ(下のX3)は実装されていないです。手元のNucleoのデータシートを読むとどちらも実装せずのような書きかたでした。自分で実装したわけではないので、手元のボードはデータシートより盛っている?サービス?X2

LSEの外部には外付けの振動子(上の黒いデバイス)があります。一方HSEには振動子がみあたりませぬ。ではHSEは使えないのかというとそうでもないみたいです。まず、上記の写真で

R35とR37はオープン(OFF)

この2か所はX3とマイコンを接続するための経路です。パターンに振動子が実装されていないだけでなくHSEに接続する端子とパターンも切り離されてます。

一方、以下のジャンパの中で、SBs

HSEの入力端子である PD0/PH0/PF0はボード表面のMorphoコネクタへ至る経路からも切り離されてます。

SB55がオープン(OFF)

こうして余計なところにHSEの入力端子が接続されないように切り離した上で以下のジャンパはONになってます。

SB50はショート(ON)

これはHSEの入力端子である PD0/PH0/PF0をMCOという名の信号に接続するジャンパ線です。そしてこのMCOという信号は、Nucleo-F401REボードのUSBコネクタ側にあるデバッグI/F ST-LINKへ伸びてます。

ST-LINK側のSB16を見ると以下のように、MCOと書かれておりそこはゼロΩ抵抗でジャンパしてます。SBs2

このMCO信号はST-LINK側のマイコンのクロック出力から到来してます。ST-LINK側には8MHzの外部振動子が鎮座しており、元はその発振器みたいです。つまりHSEにはST-LINK側からクロックをもらえるような経路が存在する、です。

MicroPythonがデフォルトで使っている低速クロックはどれ?

RTCのクロック源となる低速クロックには以下の選択肢がありえます。

    1. LSI
    2. LSE
    3. HSEからの入力を分周して1MHzとしたもの

第1のLSIはチップ内蔵なので必ず使えます。上記のハード調査からは、第2のLSEには外部の振動子が実装済、第3のHSEにはST-LINK側からのクロック信号が接続されているということでハード的にはどれも使えるようです。

MicroPythonのSTMモジュールの定数を使ってRTCのクロック源の選択をのぞき見したところ結論は1のLSIでした。内蔵RCオシレータなのね。内蔵RCオシレータの精度は?なので、RTC刻む時刻がかなり不正確だった、という実感にはあってます。

LSEを起動、その波形を確かめる

そこで第2のLSEを使ってみるべしと思い立ったのですが、これがチョイ面倒です。というのもSTM32系のマイコンは立派な作りをしていて、RTCなどバッテリ・バックアップ・ドメインに属するペリフェラルは「保護」されているのです。何か誤動作したりしてレジスタが書き換えられないようにいくつかの鍵がかけられるようになっています。調べたところSTM32版のMicroPythonはソフトウエアでバッテリ・バックアップ・ドメインのレジスタにアクセス制限はかけていなかったです。しかし、マイコンのハード的にバッテリ・バックアップ・ドメインに属するクロックソースをいじるときは局所的なリセット手順が必要みたいでした。メンドイけれども立派な機能デス。暴走許すまじ?

また、発振クロック信号の確認用に端子からクロックを出力する機能もありました。

そこでLSEで外付け発振器をONにして発振させ、外部にクロックを出力してみました。使用する端子はPA8、ArduinoコネクタのD7端子です。MicroPythonのソースが以下に。

#STM32F401RE: LSE clock output
import stm
import time

def readRCC_BDCR():
    return stm.mem32[stm.RCC + stm.RCC_BDCR]

def readRCC_CFGR():
    return stm.mem32[stm.RCC + stm.RCC_CFGR]

def readPWR_CR():
    return stm.mem32[stm.PWR + stm.PWR_CR]

def selectLSE():
    stm.mem32[stm.RCC + stm.RCC_BDCR] = 0x00010000 #RESET
    stm.mem32[stm.RCC + stm.RCC_BDCR] = 0x00008101

def setPA8ALT():
    tmp = stm.mem32[stm.RCC + stm.RCC_AHB1ENR] & 0xFFFFFFFE #GPIOA EN
    stm.mem32[stm.RCC + stm.RCC_AHB1ENR] = tmp | 0x00000001   
    tmp = stm.mem32[stm.GPIOA + stm.GPIO_PUPDR] & 0xFFFCFFFF #pullup/pulldown off
    stm.mem32[stm.GPIOA + stm.GPIO_PUPDR] = tmp | 0x00000000
    tmp = stm.mem16[stm.GPIOA + stm.GPIO_OTYPER] & 0xFEFF #push/pull
    stm.mem16[stm.GPIOA + stm.GPIO_OTYPER] = tmp | 0x0000
    tmp = stm.mem32[stm.GPIOA + stm.GPIO_AFR1] & 0xFFFFFFF0 #AF0(SYS_AF=MCO_1)
    stm.mem32[stm.GPIOA + stm.GPIO_AFR1] = tmp | 0x00000000
    tmp = stm.mem32[stm.GPIOA + stm.GPIO_MODER] & 0xFFFCFFFF #ALT mode
    stm.mem32[stm.GPIOA + stm.GPIO_MODER] = tmp | 0x00020000
    tmp = stm.mem32[stm.RCC + stm.RCC_CFGR] & 0xF89FFFFF #MCO1PRE = 000 /MCO1 = 01(LSE)
    stm.mem32[stm.RCC + stm.RCC_CFGR] = tmp | 0x00200000 #MCO1PRE = 000 /MCO1 = 01(LSE)   

def main():
    print("STM32F401RE output LSE.")
    setPA8ALT()
    selectLSE()
    time.sleep(1)
    print("stm.RCC_BDCR: 0x{0:08x}".format(readRCC_BDCR()))
    print("stm.RCC_CFGR: 0x{0:08x}".format(readRCC_CFGR()))
    # Normal End

if __name__ == "__main__":
    main()

信号を観察している様子が以下に。measureCLKDUT

 

観察したLSEの波形が以下に。

LSEclock

ま、だいたい32.768kHzの信号が見えてますな。

なお、上記のソースは以下2行を変更すれば、HSIの波形を4分周した波形を観察することもできます。

tmp = stm.mem32[stm.RCC + stm.RCC_CFGR] & 0xF89FFFFF #MCO1PRE = 110 /MCO1 = 00(HSI)
stm.mem32[stm.RCC + stm.RCC_CFGR] = tmp | 0x06000000 #MCO1PRE = 110 /MCO1 = 00(HSI)

波形が以下に。大分汚いけれどもx4すると約16MHzとな。予定どおりじゃ。HSIclockDIV4

またまた、以下のように変更すればPLLの出力を観察できます。

tmp = stm.mem32[stm.RCC + stm.RCC_CFGR] & 0xF89FFFFF #MCO1PRE = 110 /MCO1 = 11(PLL)
stm.mem32[stm.RCC + stm.RCC_CFGR] = tmp | 0x06600000 #MCO1PRE = 110 /MCO1 = 11(PLL)

波形が以下に。PLLclockDIV4

上記の測定周波数はかなり揺らいでいて、上記はたまたま17MHzとか低めの数字のところが読み取れてますが、20MHz以上の数字が読めることもあります(x4倍すると84MHz。)測定に使っている「お手軽ツール」Analog Discovery2のほぼほぼ限界をこえちょる上、低速クロックを観察するつもりだったのでマイコンのクロック出力ポートも高速設定にしてない(STM32はドライバビリティを4段階で変更できるのだ!)のも失敗かと。後で外付け分周器を挟んでちゃんと周波数を観察してみますか。

なお、別設定すればHSEの出力も見える筈なのですが、設定しても波形は見えずでした。HSEは、使えるように設定されてないみたいっす。

Pythonの筈なのにクロックの沼にハマっていく。。。

MicroPython的午睡(97) STM32版、RTC、日曜は7、月曜は1 へ戻る

MicroPython的午睡(99) STM32版、Nuceloのクロック速度続き へ進む