STM32いじってみた(4) PLLクロック編

皆さん問題です。今回いじっているこの STM32F3 は、周波数何 MHz で動作しているでしょうか!?

…正解は、8MHz です。遅っ!!
そう、何も設定しないとこの速度なのです。確か僕の使っている LPC1114 は標準で 48MHz なので、相当遅いですね…

ということで、高速動作を目指してまずはクロック周波数の変更をしたいと思います。そしてその後、ほぼ 1ms を計ってくれるタイマーをサクッと作成しましょう。

システムクロックの種類

このマイコンがサポートしているクロックはそれぞれ、

  • HSI RC クロック
  • HSEクロック
  • PLLクロック

です。略称なのですが、おそらく HSI は High Speed Internal、HSE は High Speed External でしょう[要出典]。
これとは別に 32.768kHz の低速な LSI もあるのですが、どうやらこれはペリフェラルの1つであるリアルタイムクロック用らしいですね。

HSI は 8MHz、HSE は 外部クロック入力、 PLL は皆さんご存知、周波数の逓倍を行ってくれる機構です。

このマイコンは最大 72MHz のクロックに対応しているようですが(LPC1114 は 50MHz)、今回は 64MHz を生成し使っていきましょう。

HSI・PLL の設定

外部からクロック源を用意するのはまぁ面倒なので、今回は HSI→PLL ということで高速クロックを得ましょう。デフォルトで HSI は有効なので、PLL の設定さえしてしまえば OK です!(実際はまた別の設定が必要になるのですが…)

ところでどのマイコンもそうなのですが、クロックと言ってもたくさん種類があります。クロック源は単一であることがほとんどなのですが、そこから分周したものをなんとかクロック、さらにそこから引っ張ってきて、4分周したものをxx機能のクロックとして用いる…等、数段に渡る構成になっているのです。
当ブログでも説明しようと思いましたがすっごく面倒くさい複雑になり分かりづらいと思うので、例の CubeMX のクロックの画面を下に記します。この設定は今回レジスタによって設定する値と同等です。

まずは PLL に入力する周波数です。直接 HSI を入れることができれば良いのですが、実際は2分周以上したものしか入力できません。よって2分周したものを使うことにします。これは RCC_CFGRPLLSRC ビットをいじれば良さそうです。RCCについてはマニュアル第9章参照。

RCC->CFGR |= RCC_CFGR_PLLSRC_HSI_DIV2;

このことから、PLL は16倍まで出来るとのことらしいので最大 4 * 16 = 64MHz となります。
ちょっとここで注意なのですが、APB1 クロックの扱える最大周波数は36MHzだとユーザーマニュアルに書いてあります。ですから色々する前にまず分周して置かなければなりません。まぁ 64 / 2 = 32MHz とすればいいですかね。2分周です。

RCC->CFGR |= RCC_CFGR_PPRE1_DIV2;

続いて逓倍値を設定します。同じく CFGR レジスタの PLLMUL を見てください。ちょっと変則的ですが、対応する値を入れておけばいいっぽいです。

初期状態では PLL は無効なのでこのまま書き込んじゃいましょう。

RCC->CFGR |= ( (16 - 1) << RCC_CFGR_PLLMUL_Pos); // 16x -> 64MHz

…さて、これで出来たかな??って思うかもしれませんが違います。僕は最初はPLLの設定自体は完了したと思いこんでいました。そんでプログラムを書き込んでみるとなんと! Hardware Falut という忌々しいエラーによりマイコンが落ちてしまい、プログラムが実行できなくなってしまいました。実行コードをトレースしてみると、なにかいけないことがあった時に割り込みが起こるらしく、今回はその割り込み関数内で無限ループして止まっていた、という感じでした。

そのため以下の設定を追加で行います。

フラッシュ領域アクセスレイテンシーを変更する

なんだこりゃ??って感じですよねきっと。僕もなんだこれ!?って感じですから安心してください(?)。どうやらメモリ上のフラッシュ領域にデータを読みに行く際の遅延時間をクロックに応じて調整する必要がある、とのことです。とりあえずマニュアルに従って設定します。と言っても1行だけですけど。ユーザーマニュアルをご覧の方は第4章へ行きましょう。

このレジスタです。設定箇所は最下位3ビットにありますね。それでは今回の周波数は 74MHz なので1ビット目に1を立てます。ここで便利なのが FLASH_ACR_LATENCY_1 というマクロ。FLASH_ACR_LATENCY_ までは良いのですが、一番後ろの 1 があることでやや便利です。このようなマクロは複数ビットで1つの設定項目をなしている場合に大概存在します。ということでこのマクロは「FLASH_ACR レジスタの LATENCY ビット(列)に対して1ビット目を立てよ」と言うものになりますね。

FLASH->ACR |= FLASH_ACR_LATENCY_1;

僕はこの設定項目に気づかず、1週間使いました。

いよいよPLLクロックに切り替え

さてここまで設定できたので、いよいよクロックを動作させ切り替えてきたいと思います。それではまたマニュアル第9章に戻ってください。さっきすっ飛ばした RCC_CR を見てください。PLL イネーブルがありますね。ついでに PLL が動いているか確認できるビット(PLLRDY)がありますね。これを使ってとりあえず PLL を動かしてみましょう。

RCC->CR |= RCC_CR_PLLON;
while(!(RCC->CR & RCC_CR_PLLRDY)); // wait until PLL is ready

このコードの2行目ではこのあとにシステムクロックとして割り当てるため、ちゃんと動くまで待っています。

続いて、また CFGR レジスタを見てください。こんな感じでシステムクロックを変更できますね。

それじゃ、PLL にしてみましょうか。

RCC->CFGR |= RCC_CFGR_SW_PLL; // PLL as system clock
while( (RCC->CFGR & RCC_CFGR_SWS_Msk) != RCC_CFGR_SWS_PLL ); // wait until PLL clock supply starts

ここでも RCC_CFGR_SW_PLL というお前よく分かってんじゃん的マクロの登場です。そして最後の最後に、PLLのクロック出力がシステムクロックまで来てるかを確認し、完了です。以下がコード全景になります。

このコードの注意点ですが、途中途中で無限ループを使っています。このようなコードではいつどこで無限ループから抜けられずに動作を停止してしまうか分かりません(まぁ滅多にないが)。ですのでより安定した電子工作を楽しみたい方はフェイルセーフをすると良いでしょう。
例えばですが、「無限ループの回数をカウントし、規定回数のループを越えたら元のHSIクロックに戻してしまう」等の処置が考えられますね。実際 PLL が不安定になると自動的に HSI になるようですが、対策なしではそれでも無限ループは抜けられないでしょう。よってタイムアウトを設けるのが適切かと思います。

次は1msタイマーを作ろうとしていたのですが、記事が長くなったので次に続く…