続いて、クロックに移りたいと思います。
前回で無事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
で、STARTUP と MODE を設定すれば十分です。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 - 1
、PLLDIV == 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 をビルドしちゃおうかと思います。多分一筋縄では行かない気がしますが...