前回はGPIO設定をいじって基本的なLチカを実施しました.
今回はクロック周波数の設定を行っていきます.
STM32C011のデータシートとリファレンスマニュアルを手元に置いておいてください.
クロック設定
前回のLチカではA0ピンにLEDが繋がれていると仮定し,以下のようなコードを使いました.
#include <stm32c0xx.h>
int main (void){
RCC->IOPENR |= RCC_IOPENR_GPIOAEN; // GPIOAにクロックを供給
GPIOA->MODER &= ~GPIO_MODER_MODE0_Msk;
GPIOA->MODER |= (GPIO_MODE_OUTPUT_PP << GPIO_MODER_MODE0_Pos); // A0をOutputに
while (1)
{
GPIOA->BSRR |= GPIO_BSRR_BS0;
for(int i = 0; i<20000; i++);
GPIOA->BRR |= GPIO_BRR_BR0;
for(int i = 0; i<20000; i++);
}
}
ですがこのLEDがどれくらいの周期で点滅するかはわかりません.
それはクロックが設定されていないためです.
マイコンのクロックとは,マイコンの動作スピードを決めるパルスのことです.
このパルスがON-OFFされるタイミングを基準にマイコン内部での処理が進んでいきます.
クロックが早いと同じ時間で沢山の計算ができますが,一方で電力消費も増えていきます.
ロボット製作等ではクロックが早いに越したことはないと思いますが,長時間電池駆動するようなデバイスであれば,必要な程度までクロックを落としておくと良いでしょう.
この後PWMやタイマ,SPIといった機能を使う上では,大本であるシステムクロックが正しく設定されている必要があります.
最終的に今回書くコードは以下です.
#include <stm32c0xx.h>
void RCC_Configuration(void);
void GPIO_Configuration(void);
int main (void){
RCC_Configuration();
GPIO_Configuration();
while (1)
{
GPIOA->BSRR |= GPIO_BSRR_BS0;
for(int i = 0; i<20000; i++);
GPIOA->BRR |= GPIO_BRR_BR0;
for(int i = 0; i<20000; i++);
}
}
void RCC_Configuration(void){
// 48MHz
RCC->CR |= RCC_CR_HSION; // HSI始動
while(!(RCC->CR&RCC_CR_HSIRDY)); // クロック安定化を待機
RCC->CR &= ~RCC_CR_HSIDIV_Msk; // クロック分周設定
RCC->CR |= 0x0UL<<RCC_CR_HSIDIV_Pos; // 分周なし
RCC->CFGR &= ~RCC_CFGR_SW_Msk; // HSIをクロック減に設定
}
void GPIO_Configuration(void){
RCC->IOPENR |= RCC_IOPENR_GPIOAEN; // GPIOAにクロックを供給
GPIOA->MODER &= ~GPIO_MODER_MODE0_Msk;
GPIOA->MODER |= (GPIO_MODE_OUTPUT_PP << GPIO_MODER_MODE0_Pos); // A0をOutputに
}
GPIO_configuration関数には前回設定したGPIO周りの諸々がそのまま書いてあります.
RCC_Configuration関数の中身を今回は説明していきます.
RCCは Reset and clock controlの略で,システムの根幹部分の設定を行うと言ってもいいでしょう.
クロックについてはリファレンスマニュアルの123ページ,Figure 9に図が載っています.
色々と書いてありますが,中央近くのSYSCLKを何Hzにするか,というのが重要です.
SYSCLCからタイマ(TIMx)やUSARTといった聞き覚えのある機能にクロックを供給できることがわかります.
今回はHSI48という内部クロックを使って,STM32C011の上限である48MHzのクロックをSYSCLKに供給していきます.
とはいえ,いじるレジスタは RCC_CRとRCC_CFGRの2つです.
クロックの起動(RCC_CRレジスタ)
まずRCC_CRレジスタを確認します.
使うのは HSION, HSIRDY, HSIONの三項目です.
まずはHSI48クロックを起動します.HSIONビットをセットすればOKです.
RCC->CR |= RCC_CR_HSION; // HSI始動
起動した直後はパルスが不安定になるので,安定するまで待つ必要があります.
大変便利なことに,HSI48が安定するとハードウェア側でHSIRDYビットをセットして教えてくれます.
なのでHSIRDYビットがセットされるまで待つことで,クロックが安定化してから次の処理に進むことができます.
while(!(RCC->CR&RCC_CR_HSIRDY)); // クロック安定化を待機
クロック周波数の設定(RCC_CRレジスタ)
先ほど確認したClock treeのFigure 9では,いたるところに /1,2,4,...のようなブロックがありました.
これが分周器です.必要に応じてここで周波数を落とします.
周波数を落とすことで,電力消費を低減できます(自分で検証したことないので違ったらすみません).
また,今回使うような32bitマイコンなら問題ない可能性が高いですが,低周期のタイマを使うときにはクロックを落とさないといけないことがあります.
例えば 1Hzのタイマを48MHzのクロックで実現する場合,480万回クロックをカウントする必要があり,最大カウント数が256しかない8bitのタイマではまるで対応できません.
その場合,事前に周波数を落としておくことで,タイマでカウントできる範囲に持ち込みます.
32bitフルに使えるカウンタなら40億回カウントできるので,800秒くらいなら作れそうですね.でも15分の周期は作れません.注意が必要です.
話を戻しましょう.HSI48の最初の分周はRCC_CRレジスタのHSIDIVで行います.
初期値で4分周されている,つまり12MHzが出力されていることがわかります.分周を1にしたいので
RCC->CR &= ~RCC_CR_HSIDIV_Msk; // クロック分周設定
RCC->CR |= 0x0UL<<RCC_CR_HSIDIV_Pos; // 分周なし
とすればOKです.OR演算で値を設定する前に,RCC_CR_HSIDIV_Mskマクロを使って値をクリアしています.
Figure 9を見ると,この分周器を通過したクロックがHSISYSと呼ばれていることがわかります.
システムクロック源の設定(RCC_CFGRレジスタ)
それではいよいよシステムクロック源の設定を行います.RCC_CFGRレジスタを操作します.
使うのはSWという部分です.HSISYSを選ぶためには000とすれば良さそうです.
RCC->CFGR &= ~RCC_CFGR_SW_Msk; // HSIをクロック減に設定
ちょっと手間を省いてビットマスクを使ったクリアだけで書いてしまいました.
この記述でSWのビットエリアを000に設定できます.
これでクロックまわりの設定が終わりました.
ちょっといじって遊んでみる
プログラムを書き込むとLEDが点滅するはずです.周期を変えるには当然forループの最大カウントをいじればいいのですが,折角なのでクロックの周波数をいじってみましょう.
例えば分周の部分を
RCC->CR &= ~RCC_CR_HSIDIV_Msk; // クロック分周設定
RCC->CR |= 0x1UL<<RCC_CR_HSIDIV_Pos; // 2分周
とすればLEDの点滅周期は半分になるはずです.111と設定すれば128分周(SYSCLKが400kHzくらい?)まで落とすことができます.
forループのカウントを変えていないのに点滅周期を変えられると,クロック周りが少しだけわかった気になりますね.
初期状態ではどうだったのか?
前回は一切クロック設定を書いていませんでした.しかしLEDは点滅していて,何等かクロックが供給されていたはずです.
RCC_CRレジスタの初期値を見ると,0x0000 1540と書いてあります.すなわち,HSIDIV = 010, HSION = 1だった,つまりHSI48が有効化されていて,4分周されていたことになります.
またRCC_CFGRレジスタの初期値は0x000 0000ですので最初からHSI48がシステムクロックとして設定されていたようです.
つまりHSI48を使った12MHzのクロックが初期状態では供給されるようです.
もし初期状態を知っているなら,HSIDIVの部分だけ触ればHSI48による48MHzのクロックを供給できます.
クロック設定は重要なので,48MHzの内部クロックを使うと明示的に書くのが良いと私は思います.あまりにもメモリが苦しくなったら理解の上で消してもいいコードということです.
まとめ
クロック設定は地味ですがとても重要です.この辺を理解しながら書けるのはレジスタ叩きの醍醐味でもあります.次回はタイマを使って行こうと思います.