読者です 読者をやめる 読者になる 読者になる

LPCXpressoとLPC800-MAXでマイコンを学ぶ(その4:スタートアップルーチン)

スタートアップルーチンを見てみる

Blinkyのサンプルアプリはmai.cのmain関数から始まっているのですが、ここでここまでに至る起動時のスタートアップルーチンを見てみようと思います。スタートアップルーチンでは、起動時の重要な初期化やデフォルト設定を行っています

main関数はどこから呼ばれるか?

main.cのmain関数はどこからコールされているか調べてみますと、cr_startup_lpc8xx.cからコールされていることがわかります。 正確には"_REDLIB_"が有効なので、Redlib内の__mainの後にmainがコールされています。またmain関数を実行し、万が一抜けてきた場合にはwhileの永久ループに入るようになっていることがわかります。

(cr_startup_lpc8xx.c)

#if defined (__REDLIB__)
    // Call the Redlib library, which in turn calls main()
    __main() ;
#else
    main();
#endif

    //
    // main() shouldn't return, but if it does, we'll just enter an infinite loop
    //
    while (1) {
        ;
    }
}

このmain関数をコールしている関数はResetISRという関数です。これはリセットハンドラで、リセットされるとここへジャンプしてきます。つまりこのRestISRがプログラムの開始となります。

SystemInit関数

ResetISRではSystemInit関数をコールしてシステムの初期化を行い、その後main関数を実行するようになっています。ResetISRの最初ではベクターテーブルをFlashからSRAMへ移動したり(Cortex-M0+の機能)、data領域の初期化などを行っています。

SystemInitの中を見ていきましょう。ifdefで無効になっている部分を取り除くと以下のようになります。

(system_LPC8xx.c)

void SystemInit (void) {
  volatile uint32_t i;

  /* System clock to the IOCON & the SWM need to be enabled or
  most of the I/O related peripherals won't work. */
  LPC_SYSCON->SYSAHBCLKCTRL |= ( (0x1 << 7) | (0x1 << 18) );

  LPC_SYSCON->SYSPLLCLKSEL  = SYSPLLCLKSEL_Val;   /* Select PLL Input         */
  LPC_SYSCON->SYSPLLCLKUEN  = 0x01;          /* Update Clock Source      */
  while (!(LPC_SYSCON->SYSPLLCLKUEN & 0x01));     /* Wait Until Updated       */

  LPC_SYSCON->MAINCLKSEL    = MAINCLKSEL_Val;     /* Select PLL Clock Output  */
  LPC_SYSCON->MAINCLKUEN    = 0x01;          /* Update MCLK Clock Source */
  while (!(LPC_SYSCON->MAINCLKUEN & 0x01));       /* Wait Until Updated       */

  LPC_SYSCON->SYSAHBCLKDIV  = SYSAHBCLKDIV_Val;
}

各行を解説していきます。これはユーザーズマニュアルと突き合わせて確認できます。

SWM(Switch Matrix)、IOCON(I/O configuration)のブロックにクロックを供給して有効にする。
LPC_SYSCON->SYSAHBCLKCTRL |= ( (0x1 << 7) | (0x1 << 18) );

#define SYSPLLCLKSEL_Val      0x00000000 // Reset: 0x000
:
System PLLのクロックソース選択。上記の値からIRCを選択
LPC_SYSCON->SYSPLLCLKSEL  = SYSPLLCLKSEL_Val; <----- 1
System PLLのアップデート
LPC_SYSCON->SYSPLLCLKUEN  = 0x01;
while (!(LPC_SYSCON->SYSPLLCLKUEN & 0x01)); 

#define SYSPLLCLKSEL_Val      0x00000000 // Reset: 0x000
:
メインクロックソース選択。上記の値からIRC Oscillator(内部発振器を使用)
LPC_SYSCON->MAINCLKSEL    = MAINCLKSEL_Val; <----- 2
メインクロックのアップデート
LPC_SYSCON->MAINCLKUEN    = 0x01;
while (!(LPC_SYSCON->MAINCLKUEN & 0x01));

#define SYSAHBCLKDIV_Val      0x00000001 // Reset: 0x001
:
システムクロックの分周比の設定。上記の値から1
LPC_SYSCON->SYSAHBCLKDIV  = SYSAHBCLKDIV_Val; <----- 3

動作クロックの設定

さて何をやってるんでしょうね。なんとなくクロック周りの初期設定を行っていることはわかります。上記の設定は下記の図の設定を行っています。下記はクロックジェネレータのブロック図ですが、前述の1〜3は①〜③の設定を行っています。

f:id:tomo_watanabe:20131012211624p:plain

この図との対応は、SYPLLCLKSELではIRC oscillatorを選択、次段のMAINCLKSELでは前段のPLL出力ではなく、IRC oscillatorを選択、それをSYSAHBCLKDIVで分周してシステムクロックとしています。つまり、

  • main clock : IRC oscillator
  • system clock : main clock / 1

となります。システムクロック=メインクロック=内部発振器のクロック数です。では内部発振器のクロックはいくつになっているでしょうか。ユーザーズマニュアルp5に書いてあります。

f:id:tomo_watanabe:20131012212651p:plain

したがって、システムクロックは12MHzで動作することになります。LPC812は30MHzで動作可能なのですが、初期値は抑えていますね。では、実際に12MHzで動作していることを確認してみます。main関数の最初のSystemCoreClockUpdate関数でそれを確認できます。SystemCoreClockUpdate関数の最後にSystemCoreClockへの代入箇所があるので、その後にブレークポイントを張ってブレークしたところで確認します。※下記スクリーンショットスクリーンショットの作成の都合上、代入前になっているので注意。実際に確認するのは戻り出口の場所で

f:id:tomo_watanabe:20131012213916p:plain

確かに12MHzになっています。実際のコード上での設定はどこでやっているかというとSystemCoreClockUpdate関数の下記になります。

  switch (LPC_SYSCON->MAINCLKSEL & 0x03) {
    case 0: /* Internal RC oscillator */
      SystemCoreClock = __IRC_OSC_CLK;  <-------- ここで一旦決定される
      break;
    case 1: /* Input Clock to System PLL */
      switch (LPC_SYSCON->SYSPLLCLKSEL & 0x03) {
          case 0: /* Internal RC oscillator */
            SystemCoreClock = __IRC_OSC_CLK;
            break;
          case 1: /* System oscillator */
            SystemCoreClock = __SYS_OSC_CLK;
            break;
          case 2: /* Reserved */
            SystemCoreClock = 0;
            break;
          case 3: /* CLKIN pin */
            SystemCoreClock = __CLKIN_CLK;
            break;
      }
      break;
    case 2:  /* WDT Oscillator  */

:
:
    下記でLPC_SYSCON->SYSAHBCLKDIV = 1
    SystemCoreClock /= LPC_SYSCON->SYSAHBCLKDIV;

SystemInitでLPC_SYSCON->MAINCLKSEL = 0と設定しているので、一旦システムクロックは_IRC_OSC_CLKになります。その後LPC_SYSCON->SYSAHBCLKDIVの値で分周しますが、設定では1なのでシステムクロックはそのまま_IRC_OSC_CLKとなります。

/*----------------------------------------------------------------------------
  Define clocks
 *----------------------------------------------------------------------------*/
#define __XTAL            (12000000UL)    /* Oscillator frequency             */
#define __SYS_OSC_CLK     (    __XTAL)    /* Main oscillator frequency        */
#define __IRC_OSC_CLK     (12000000UL)    /* Internal RC oscillator frequency */
#define __CLKIN_CLK       (12000000UL)    /* CLKIN pin frequency              */

__IRC_OSC_CLKは定義から12MHzとなっているので、システムクロックが12MHzということになります。以上がスタートアップルーチンです。スタータップで主に必要なことは下記となります。

  • 動作クロックの設定
  • クロックを供給するブロックの設定