動作周波数を168Mhzに変更する

さて、前回実はクロックが内部クロックになっていて、しかもどうやら168Mhzではなさそうということがわかりました。
今回は168Mhzで動かすための設定です。

クロック図

何回か前に紹介したこのマイコンのクロックですがもう一度見てみましょう

stm32f4_clock0
出典
: ReferenceManual

これを見ても何が何やらですが、スタートアップスクリプトからどういう設定になっているのかというと。

stm32f4_clock1
出典
: ReferenceManual

こうなっています。
基本的に左が源で右がどこに使われているかなのですが、
この「HCL to AHB bus,core,memory and DMA」というのがマイコンのベースとなるクロックです。1+1がどのぐらいの速度で計算されるかはここで決まるわけです。
そしてスタートアップスクリプトの設定を見るに、ここのクリックが何から来ているかというと16MhzのHSIRCというマイコン内蔵の16Mhzのクロックになっています。
もちろん使えるクロックですが、精度が微妙で正確な何かを作るときにはちょっと使えません。
では、せっかくつながっている制度の良い外部の8Mhzをつかって168Mhzで動かしてみましょう。

じゃあPLLを使わなければ

168Mhzで動かすのに一番簡単なのは168Mhzのクリスタルを買ってきてマイコンにつなぐことです。ただ、168Mhzというクリスタルクロックはありません(あっても入手が難しいし、つなぐのも大変)
そこでこのマイコンに内蔵されているPLLという周波数を増やす回路を使います。つまりプログラム絵設定しなきゃいけない手順としてはこんな感じになるんです

  1. 外部クロックにクロックを切り替える
  2. PLLをONにする
  3. PLLで正しい周波数を設定する
  4. その他プリスケーラなどを設定する

さて、正解を先に見てみましょう。

[c]

#include "stm32f4xx.h"

static void SetSysClock(void);

int main(void)
{
SetSysClock();
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;

GPIOA->MODER = 0;
GPIOD->MODER = 0x40000000;
while(1) {
GPIOD->ODR = GPIOA->IDR << 15;
};
}

/************************* PLL Parameters *************************************/
/* PLL_VCO = (HSE_VALUE or HSI_VALUE / PLL_M) * PLL_N */
#define PLL_M 8
#define PLL_N 336

/* SYSCLK = PLL_VCO / PLL_P */
#define PLL_P 2

/* USB OTG FS, SDIO and RNG Clock = PLL_VCO / PLLQ */
#define PLL_Q 7

/* PLLI2S_VCO = (HSE_VALUE Or HSI_VALUE / PLL_M) * PLLI2S_N
I2SCLK = PLLI2S_VCO / PLLI2S_R */
#define PLLI2S_N 258
#define PLLI2S_R 3

static void SetSysClock(void)
{
/******************************************************************************/
/* PLL (clocked by HSE) used as System clock source */
/******************************************************************************/
__IO uint32_t StartUpCounter = 0, HSEStatus = 0;

/* Enable HSE */
RCC->CR |= ((uint32_t)RCC_CR_HSEON);

/* Wait till HSE is ready and if Time out is reached exit */
do
{
HSEStatus = RCC->CR & RCC_CR_HSERDY;
StartUpCounter++;
} while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));

if ((RCC->CR & RCC_CR_HSERDY) != RESET)
{
HSEStatus = (uint32_t)0x01;
}
else
{
HSEStatus = (uint32_t)0x00;
}

if (HSEStatus == (uint32_t)0x01)
{
/* Select regulator voltage output Scale 1 mode, System frequency up to 168 MHz */
RCC->APB1ENR |= RCC_APB1ENR_PWREN;
PWR->CR |= PWR_CR_VOS;

/* HCLK = SYSCLK / 1*/
RCC->CFGR |= RCC_CFGR_HPRE_DIV1;

/* PCLK2 = HCLK / 2*/
RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; // APB2は84Mhz

/* PCLK1 = HCLK / 4*/
RCC->CFGR |= RCC_CFGR_PPRE1_DIV4; // APB1は 42Mhz

/* Configure the main PLL */
RCC->PLLCFGR = PLL_M | (PLL_N << 6) | (((PLL_P >> 1) -1) << 16) |
(RCC_PLLCFGR_PLLSRC_HSE) | (PLL_Q << 24);

/* Enable the main PLL */
RCC->CR |= RCC_CR_PLLON;

/* Wait till the main PLL is ready */
while((RCC->CR & RCC_CR_PLLRDY) == 0)
{
}

/* Configure Flash prefetch, Instruction cache, Data cache and wait state */
FLASH->ACR = FLASH_ACR_ICEN |FLASH_ACR_DCEN |FLASH_ACR_LATENCY_5WS;

/* Select the main PLL as system clock source */
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
RCC->CFGR |= RCC_CFGR_SW_PLL;

/* Wait till the main PLL is used as system clock source */
while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS ) != RCC_CFGR_SWS_PLL);
{
}
}
else
{ /* If HSE fails to start-up, the application will have wrong clock
configuration. User can add here some code to deal with this error */
GPIOD->ODR |= 1<<14;
}

/******************************************************************************/
/* I2S clock configuration */
/******************************************************************************/
/* PLLI2S clock used as I2S clock source */
RCC->CFGR &= ~RCC_CFGR_I2SSRC;

/* Configure PLLI2S */
RCC->PLLI2SCFGR = (PLLI2S_N << 6) | (PLLI2S_R << 28);

/* Enable PLLI2S */
RCC->CR |= ((uint32_t)RCC_CR_PLLI2SON);

/* Wait till PLLI2S is ready */
while((RCC->CR & RCC_CR_PLLI2SRDY) == 0)
{
}
}

[/c]

SetSysClock(void)というのはSTMが他の場所で公開していた168Mhzにするための関数です。僕らのプロジェクトには入ってないのですが、他の場所から引っ張ってきました。
何で先に正解を用意したかというと、さっきのPLLをONにして〜という手順には今はまだ知らなくていいであろういろいろなレジスタが関係してくるのでとりあえず今は168Mhzに出来るようにしておいて、説明を軽くしようと思いました。
この関数を呼べば168Mhzに切り替わるのでmain関数の最初に書いておけばOKです。このまま便利に使いましょう。

どういう流れなのか

とはいえ、ちょっと中身を知っておきましょう。
今回のこのプログラムで設定されている流れはこうなっています。

stm32f4_clock2
出典
: ReferenceManual

8Mhzの入力がHCLKに行くまでにどうなっていっているかというと

  1. 最初のプリスケーラ(分周器)でM分の1にして1Mhzを作る
  2. 1MhzをN倍して366MHzを作る
  3. 分周器でN分の1にして168Mhzを作る
  4. AHBプリスケーラをそのまま1倍で通過してHCLKとして使う。

という感じです。図で表すと

IMG_0673

何故こんなややこしいことになっているかというと、いくつか制限があります。
まず回路的な制限として

  1. VCOの入力は1~2Mhz
  2. VCOの出力は100~432Mhz(100以下も可能ではあるが注意が必要)
  3. Pの出力は180Mhzが上限

そして、それぞれ M N Pという値がありますが、それらも設定できる値が決まっていて

  1. M = 2〜63
  2. N = 2〜432
  3. P = 2,4,6,8

この条件で考えると、8Mhzの入力に対して M=8, N=366,P=2の設定で168Mhzを作るのが良さそうとなるわけです。
ちなみに168Mhzでなく180Mhzでも動くわけですが、168Mhzにしているのはいつか使うであろうUSBの48Mhzを作るためにVCOを336Mhzにしています。(USBのクロックはVCOの出力を別のQという分周器で作っています。Qの値を7にすると48Mhzになります。これはUSBの通信速度です)

深いことは考えない

これでPLLでどうやって168Mhzを作っているのかわかりました。
今回はここまででこの関数やレジスタは深追いはしません、流れだけ書いておきますね。

  1. HSE(外部クロック)を有効にする
  2. HSEが正常に立ち上がって、かつある程度クロックを出すまで待つ(クリスタルの起動後は不アンテナ周波数なため待つのが普通)
  3. VCOの準備
  4. APB2はHCLKの1/2となるようにする
  5. APB1はHCLKの1/4となるようにする
  6. PLLに各値の設定
  7. PLLをONにする
  8. PLLが安定になるまで待機
  9. クロック源をHSIからPLLに切り替える
  10. 切替後正常になるまで待機
  11. I2Cに関する別のPLLの設定

ただ、1つあなたが気にしなきゃいけないのはAPB1とAPB2のクロックです。
これらはAHBのクロック(168Mhz)を分周したものになっています。
前回まで使ってきたGPIOはAPB2のクロックを使っていましたね。
APB2は1/4分周されているので168/4=42Mhzとなります。
つまりCPUの実行速度とは違ってだいぶ遅いわけです。
なんでかというと実はAPB1とAPB2は上限速度があって、それもだいぶ遅くて

  1. APB2は90Mhzが上限
  2. APB1は45Mhzが上限

となっているからです。

このプログラムを動かすと前回と何の違いもなくLEDがボタンに合わせてON,OFFします。
速くなったかどうかわからないですね。。。
それを確かめるためにも次回はタイマーを使って時間が経ったら何かをするというプログラムを書いてみましょう。
これで本当に168Mhzになっているかを確認できますね。

コメントを残す