LPC1114 付き LPCXpresso アプリケーションを作成しよう

 NXPセミコンダクタ社(以下NXP社)の LPCXpresso 基板の被評価マイコン・モジュールには, LED が搭載されており, LPC1114 マイコンの PIO0_7 出力で制御されるようになっています.手始めに,この LED を点滅させるプログラムを作成します.

LED をチカチカさせるシステム

 LED の点滅間隔を制御するには,通常はタイマなどの機能を使用します.今回は, SysTick タイマを使用しています.

LED チカチカのシステム構成

 SysTick タイマは, ARM コアのクロックを分周して,一定間隔ごとに例外を発生させます.ここでは,10ミリ秒ごとに例外を発生させています.この例外が50回発生するたびに,汎用入出力(General Purpose I/O: GPIO)の一つである PIO0_7 出力端子の状態を反転させます.すると,この出力端子に接続されている LED が1秒ごとに点滅するという仕組みです.

SysTick タイマは,リアルタイム・オペレーティングシステム(Real Time Operating System: RTOS)などの時間管理を行うために装備されているシステムに近いタイマです.そのため,通常は,このような使い方はしないはずです.

今回は,簡単に使用できるということで,普通のタイマ割り込みと同じ感覚で使うアプリケーションにしてみました.ほかの一般的なタイマを使用した例は,別の機会に紹介します.

main 関数を書き換える

 このアプリケーションは,すべて main.c ファイルの中に記述されます.


main.c ファイルを開く

 まずは, main.c ファイルをエディタで開きます.プロジェクト・エクスプローラ・ビューの中で, proj01 プロジェクト, src ディレクトリの順に開きます.そして, src ディレクトリの中にある main.c ファイルを右クリックして,コンテキスト・メニューを呼び出し, Open を選択します.

テキストエディタ

 すると,エディタ・ビューに main.c ファイルが開きます.最初は, LPCXpresso 開発環境が生成した中身のないプログラムが入っています.まずは, main 関数を書き換えます.

int main(void) {
    volatile static uint32_t period;    // Assigned to a variable for debug
    volatile static int i = 0 ;         // dummy counter

    // Configure PORT PIO0_7 as output
    LPC_GPIO0->DIR  |= 0x080;           // Configure PIO0_7 as output

    // Configure System tick timer in 10msec period
    period = SystemCoreClock / 100;     // Period for 10msec SYSTICK
    SysTick_Config(period);             // Configuration

    // Enter an infinite loop
    while(1) {                          // Infinite loop
        i++;                            // dummy count.
    }
    return 0 ;
}
main関数を変更する

 このアプリケーションは,タイマと例外によって処理されているので, main 関数では,単にタイマと汎用出力の初期化を行っています.

    // Configure PORT PIO0_7 as output
    LPC_GPIO0->DIR  |= 0x080;           // Configure PIO0_7 as output

 最初にGPIOの設定を行います.PIO0_7 を出力に設定するためには, GPIO0 モジュールの DIR レジスタの7ビット目に "1" を書きこみます. LPC_GPIO0 というポインタを使用してレジスタを指し示すのが, CMSIS で定められた記法です.そのため, CMSIS に従う限りは,このようにモジュールからのポインタを使用する書き方になってしまいます.

    // Configure System tick timer in 10msec period
    period = SystemCoreClock / 100;     // Period for 10msec SYSTICK
    SysTick_Config(period);             // Configuration

 次に行っているのは, SysTick タイマの初期化です.このタイマを初期化するための関数 SysTick_Config は, CMSIS のライブラリで定義されています.そのため,使用する前に特別に宣言する必要もありません.

 この関数に引数として与えている値は, SysTick モジュールでの分周比です. SystemCoreClock という変数には,現在使用中のコア・クロックの周波数が入っています.この値を100で割った値を与えることで100分の1秒,つまり10ミリ秒ごとに例外が発生します.

 main 関数を書き換えた状態から,再びビルドしても,正常にバイナリ・ファイルが生成されます.

period が,わざわざ volatile 変数になっているのは,デバッガを使用して,例外発生周期を変更したかったためです. SysTick_Config 関数を実行する直前にブレークポイントを仕掛けて, period 変数を書き換えてから実行を再開させると,例外発生周期を変更することができます.

SysTick_Handler ハンドラを作成する

 周辺モジュールの初期化が終わったので,あとは,例外処理ハンドラに PIO0_7 出力を0.5秒ごとに反転させるプログラムを書くだけです. main 関数の下に以下の SysTick_Handler ハンドラを追加します.

void SysTick_Handler(void) {
    static uint8_t count = 0;           // Software counter
    if (++count >= 50) {                // wait for 50 system ticks
        // Activated every 500msec
        LPC_GPIO0->DATA ^= 0x80;        // Toggle PIO0_7
        count = 0;                      // Reload counter
    }
}
例外処理ハンドラを追加する

 この例外処理ハンドラは, static 変数 count を使って,例外が50回発生するごとに if 文内のコードを実行します.

        LPC_GPIO0->DATA ^= 0x80;        // Toggle PIO0_7

 この行で PIO0_7 出力端子が反転します.

例外処理ハンドラと普通の関数の違い

 ほかのマイコンで例外処理ハンドラを書いたことがある方は,この例外処理ハンドラを妙に思われたのではないでしょうか.この関数が「例外処理ハンドラ」である事を示すキーワード,たとえば __interrupt などが,一切出てこないのです.もちろん,ほかの場所で SysTick_Handler が例外処理ハンドラであると宣言されているわけでもありません.

 コンパイラの規約により,関数内部で作業領域として使用してもよい(破壊してもよい)レジスタが定められています.呼び出された関数では,作業領域以外の保護を必要とするレジスタだけをスタックに保存して処理を始めます.

 これに対して,例外は,アプリケーションの処理とは完全に無関係に発生します.そのため,関数呼び出しの場合とは異なり,作業領域を含むすべてのレジスタを保護する必要があります.例外処理ハンドラに特別な処理が必要になるのは,関数よりも多くのレジスタを保護する必要があるからです.


例外発生時のスタックフレーム

 これら,関数と例外ハンドラのギャップを埋めるために, Cortex-M0 では,例外発生時に「作業領域として使われる可能性のあるレジスタ」を自動的にスタックに保存しています.これらのレジスタを作業領域として使うことが,コンパイラの規約で定められていれば,例外処理ハンドラも関数も区別なく扱うことができるというわけです.

 さらに,例外発生時には, LR レジスタに EXC_RETURN という特殊な値が格納されて,スタックに積まれます.このため,関数または例外処理から戻る時には,スタックに積まれた値によって,関数から戻ろうとしているのか,例外処理ハンドラから戻ろうとしているのかを判断し,例外発生時にのみ,作業領域レジスタも復旧させています.

例外処理ハンドラは二重宣言にならないのか?

 main.c ファイルに記述した SysTick_Handler 例外処理ハンドラは,あってもなくても,問題なくコンパイルが通りました.なくてもコンパイルが通るということは,どこかで,この例外処理ハンドラが宣言されていたということです.では,なぜ, main.c ファイルに例外処理ハンドラを付け加えたときに,例外処理ハンドラが二重に宣言されたと判断されないのでしょうか.その答えは, proj01 プロジェクトの cr_startup_lpc11.c というファイルにありました.

#define WEAK __attribute__ ((weak))
:
WEAK void SysTick_Handler(void);
:
void SysTick_Handler(void)
{
    while(1)
    {
    }
}

 この例外処理ハンドラは,確かに cr_startup_lpc11.c で宣言されています.また,本体も存在します.しかし,ひとつだけ,通常の宣言とは違いがあります.それは,キーワード WEAK です.

 このキーワードを付けると, __attribute__ が追加され,「ほかの場所で同じ名前の関数(例外処理ハンドラ)が宣言されていたら遠慮する.」という機能をもつようになります.そのため, main.c ファイルで定義された SysTick_Handler に遠慮して,リンク対象から除外されたのでした.

 逆に言うと,例外処理ハンドラを書き忘れた,あるいは,例外処理ハンドラの名前を打ち間違えた場合でもコンパイラはエラーとはしません.そういった場合, cr_startup_lpc11.c で定義されたデフォルトの例外処理ハンドラが使用され,システムは,無限ループに陥ってしまいます. WEAK は,便利だけれど,気をつけなくてはならない機能だと思います.

バイナリ・コードを作成する

 これで,ソース・コードの準備がすべて整いました.


プロジェクトをビルドする

 以前のビルドから変更したのは, proj01 プロジェクトの main.c ファイルだけです.プロジェクトをビルドするためには,クイック・スタート・ビューの Start here タブにある Build 'proj01' (Debug) をクリックします.もちろん, Build all projects (Debug) をクリックしても,同じようにビルドできます.


再びビルド完了

 ビルドが完了すると,コンソール・ビューにバイナリ・ファイルの情報が表示されます.プログラムは,1172バイトになりました.

 次回は,作成したアプリケーションをマイコンに書き込み,実行します.

田中範明(noritan.org)


トラックバック(0)

このブログ記事を参照しているブログ一覧: LPCXpressoで,LEDチカチカ (6)

このブログ記事に対するトラックバックURL: http://www.eleki-jack.com/mt/mt-tb.cgi/3890





カテゴリ


Copyright (C) 2006-2015 CQ Publishing Co.,Ltd. All Rights Reserved.