SNSへはこちら

AVR32マイコンやってみよう(5) - クロックを速くする

続いて、クロックに移りたいと思います。
前回で無事Lチカが済んだので、次にやることと言ったらクロック設定です。地味に面倒くさいけど大事ですね。

致命的なエラーも起きずにちゃんと書き込んだプログラムが動いているのが確認できたし、何よりLED が点滅するということで、この点滅周期によって目視でクロックが切り替わったと知ることができるというのが心強いですよね。

まずは、LED を点滅させるプログラムのベースを示します。これを見ながら、「あっ、速くなった」とか確認していました。とりあえず wait 関数内のカウント値(この場合は 0xFFFF>>2)がありますが、大体同じ周期で LED が点滅するように以後編集していきます。つまり、この値が増えるほど CPU クロックが速くなっているといえます。

クロックの種類

データシートから分かる、このマイコンの搭載するクロックを見ていきましょう。
Configuration Summary によると、今回用いているマイコンは AT32UC3B064 なので、左から2列目が該当すると分かります。

なので、積んでいる(接続できる)クロックは以下の通り。

  • PLL0 PLL1
    • OSC0 or OSC1 をクロックソースとしてそれぞれ用いることのできるクロック逓倍器。
    • 内部 VCO の出力は 80MHz 〜 240MHz の範囲でなければならない。
  • OSC0 OSC1
    • 400kHz 〜 20MHz のクリスタルを接続できる。
    • 表右側のように、OSC1 が付いていないものもある。
  • OSC32K
    • RTC 用。
  • RCSYS
    • 内蔵 RC オシレータ。動作スピードは驚愕の 115kHz

実はデフォで動いているクロックは滅茶苦茶遅いんですね。普通遅くとも 1MHz 用意するだろ、って思いますが、そう贅沢ばっか言ってられません。

ポイントは PLL ですね。VCO が何かはさておき、とにかく出力可能な周波数範囲が決まっているので、合わせてやらねばなりません。
また、CPU の最大周波数は 60MHz なので、PLL の出力周波数範囲に入りません。この点については下で述べています。

もちろん、限界まで CPU クロックを上げたいということですから、そのやり方について軽く触れます。

OSC0

USB DFU を使用するために既に取り付けてあると思いますが、外部クリスタルを取り付けるものです。僕は面倒くさいので 12MHz のセラロックを接続しています。よい子の皆はきちっとクリスタルと負荷容量を取り付けましょう。まあぶっちゃけホビーユースならそこまで精度いらないですね。

設定レジスタは PM->OSCCTRL0 で、STARTUPMODE を設定すれば十分です。MODE は周波数的に当然 7 を書き込むとして、正直 STARTUP に何を書き込むのが適切なのかわかりません。とりあえず適当な値にします。

あとは適切なレジスタのビットが立ってクロックが準備完了になるまで待つだけです。そのために実装したコードと wait 関数の初期値を示しておきます。

.type clock_osc0_init, %function // {{{
clock_osc0_init:
    # tune the gain in accord to XTAL frequency
    load r0 PM_OSCCTRL0
    lda.w r1, 7 | (1 << 8)
    st.w r0, r1

    # start OSC0
    load r0 PM_MCCTRL
    lda.w r1, 1<<2
    st.w r0, r1

1: # wait for OSC0 to be ready
    load r0 PM_POSCSR
    ld.w r1, r0[0]
    lda.w r2, 1<<7
    and r1, r2
    brne 2f
    rjmp 1b
2:

    # select OSC0 as main clock
    load r0 PM_MCCTRL
    ld.w r1, r0[0]
    load r2, 0xFFFFFFFC
    and r1, r2
    lda.w r2, 1 # main clock == OSC0
    or r1, r2
    st.w r0, r1


    ret 0
// }}}

# waitのカウンタ値は0xFFFFF

PLL0

OSC0 の準備ができたところで、続いてお待ちかねの PLL です。まず大事なのは、逓倍率です。すなわち計算式が大事ってわけですね。

さらに、PLL ブロックは以下のような構造になっていて、VCO は 80〜240 MHz しか出せないという制約があります。そして、そのために**あるビット(図の PLLOPT[1])を立てると、PLL の外側で出力周波数を 1/2 倍してくれる部分があります。なのでこれを活用しようということです。

以上を鑑みて、PLLMUL == 10 - 1PLLDIV == 1 にするとちょうど CPU クロックが Max の 60MHz になるということで、設定するコードを以下に示します。メインルーチンの方では、上の OSC0 用の設定関数を呼んだ直後にこちらをコールしてください。

.type clock_pll_init, %function // {{{
clock_pll_init:
# 12MHz * 10 / 1 / 2 == 60MHz

    # tune PLLMUL & PLLDIV and start
    load r0 PM_PLL0
    load r1, (((10-1)<<16)|(1<<8)| (1 << 3) | 1)
    st.w r0, r1

1: # wait for PLL0 to be locked
    load r0 PM_POSCSR
    ld.w r1, r0[0]
    lda.w r2, 1
    and r1, r2
    brne 2f
    rjmp 1b
2:

    # selece PLL0 as main clock
    load r0 PM_MCCTRL
    ld.w r1, r0[0]
    load r2, 0xFFFFFFFC
    and r1, r2
    lda.w r2, 2 # main clock == PLL0
    or r1, r2
    st.w r0, r1

    ret 0
// }}}

# waitのカウンタ値は0xFFFFF<<2

無駄に縦に長くなりましたが、とりあえずクロックの設定はできたと思います。
まあここまで来るとアセンブリでは長ったらしくてやっていけないので、次回は gcc をビルドしちゃおうかと思います。多分一筋縄では行かない気がしますが...