マイコンの気持ちを理解してチャタリングを防止!! タイマー割り込みを使いこなそうマイクロマウスで始める組み込み開発入門(8)(2/3 ページ)

» 2012年12月05日 10時00分 公開
[三月兎MONOist]

組み込み開発に重要な「割り込み処理」

 マイコンは、記述されたプログラムを順番通りに処理していきます。しかし、全ての処理をあらかじめ順序よく記述できるわけではありません。

 例えば、ケータイ(携帯電話端末)でメールの文章作成中に、電話の着信があった時のことを思い出してみてください。電話に出て会話を終えた後、メール作成画面に戻ることができますよね。この場合、メール作成中に「電話の着信」という割り込み処理が入ったということです。

 もしも、この処理をソースコード内に、「電話着信があったら?/なかったら?」と条件分岐で記述していたらどうなるでしょう? 常に“if文”が必要な煩雑なプログラムになることが容易に想像できますよね。おまけに携帯電話には、着信以外にもさまざまな機能があります。このように、いつ実行するのか/いつ終了するのかが分からない処理を、常時監視するプログラムを記述していては、煩雑な記述だらけのソースコードになってしまいます。

 こうした問題を解決するために、あらかじめ用意しておいたサブルーチンを、外部信号によって起動する仕組みを用います。そう、それが割り込み処理です。割り込みのためのサブルーチンのことを「割り込みハンドラ」、もしくは「割り込みサービスルーチン」と呼びます。

 割り込み処理を使えば、実行中の処理(=メール作成)を中断して、優先順位の高い処理(=着信および通話)を実行し、それが終わったら、再度、中断した処理を再開できるのです。組み込み開発において、割り込み処理は重要な役割を果たします。

割り込み処理の流れ 図2 割り込み処理の流れ。矢印aで「戻りアドレス(n+4)」をスタックに保存して、割り込みハンドラへ分岐。割り込みハンドラの処理を終えたら、スタックから「戻りアドレス(n+4)」を取り出して、そのアドレスへ戻ってメイン処理を続ける

北上

タイマーと割り込み処理について、大体分かったかな?


えみ

マイコンを制御するのに、タイマーと割り込み処理が重要な役割を担っていることはよく分かりました。でも、「きちんと分かるように説明して」と言われたら……できません(ガクッ)。


北上

うん、今はそれでOKだよ。

プログラムは、書いて動かして実感して覚えるというのが大事なんだ。まずは、割り込み処理のプログラムを実際に書いて、動かしてみよう!


えみ

はいっ!



割り込み処理プログラムの作成

 それでは、割り込み処理プログラムを作成していきましょう。

 割り込み処理を行わせるには、開発環境の「HEW」が自動生成した表1のソースコードに編集を加える必要があります。

ファイル名 内容
intprg.c 割り込みベクターテーブルに登録されている割り込みハンドラが記述されたソースコード
resetprg.c リセット時の処理が記述されたソースコード
表1 割り込み処理に必要なソースコード

 「割り込みベクターテーブル」は、割り込み要求信号が発生したときに、要求を出している要因に応じて、正しい割り込みハンドラを呼び出すために用いられます。この割り込みベクターテーブルは、HEWが自動生成する「vecttbl.c」に記述されています。

 「vecttbl.c」を参照すると、「CMT0」の割り込み発生時に呼び出される関数名が「INT_CMT0_CMI0」であることが分かります(ソースコード1)。

void *INT_Vectors[] = {
// 4 Illegal code
    (void*) INT_Illegal_code,
 
……(中略)……
 
// 184 CMT0 CMI0
    (void*) INT_CMT0_CMI0,
 
……(以下略)……
 
}
ソースコード1 「vecttbl.c」に割り込みベクターテーブルが記述されている。今回は「CMT0」を使用する。(CMTは2チャンネル用意されており、もう1つ「CMT1」がある)

 この「INT_CMT0_CMI0」関数を記述してあるのが、「intprg.c」ファイルです。初期状態は何もしない関数なので、ここに割り込み時に実行したい処理を追記します(ソースコード2)。これで「CMT0」のコンペアマッチ割り込みが発生すると、「int_cmt0」関数が実行されます。

// 184 CMT0 CMI0
//void INT_CMT0_CMI0(void){/* sleep(); */}  <= コメントアウト
//CMT0割り込み
extern void int_cmt0(void);                // 割り込み発生時にINT_CMT0_CMI0関数から呼ばれる関数の宣言
void INT_CMT0_CMI0(void){
    int_cmt0();                            // 割り込み発生時に処理する関数の呼び出し(intrpt.cに記述)
}
ソースコード2 「intprg.c」の自動生成ソースコードをコメントアウトし、これから作るタイマー割り込み関数「int_cmt0」を宣言する

 次に「resetprg.c」を修正します。「SR_Init」には、CPUの基本動作を決定するSR(ステータスレジスタ)の初期値が設定されています。初期状態の「0x000000F0」を「0x00000000」に書き換えます。「F0」を「00」に書き換えることにより、割り込みマスクレベルを「0」に設定します。割り込み優先レベルが1以上に設定されていれば、その割り込みは有効となります(ソースコード3)。

//#define SR_Init    0x000000F0          <=コメントアウト
#define SR_Init    0x00000000            //割り込み優先レベルの設定
ソースコード3 「resetprg.c」内で割り込み優先レベルの設定を変更する

 「Mouse2012.h」に1msごとにカウントアップされるグローバル変数「G_TimerCount」を宣言します。このとき、必ず“volatile”修飾子を付けます(ソースコード4)。volatile修飾子を忘れた状態でビルドすると、コンパイラがプログラムを最適化してしまい、その結果、思わぬエラーを引き起こしてしまいます。

//グローバル変数
volatile int    G_TimerCount;                //1msごとにカウントアップされる変数
ソースコード4 「Mouse2012.h」に、グローバル変数「G_TimerCount」を追加

 これで割り込み処理の準備ができました。割り込みに必要なヘッダファイル「intrpt.h」(ソースコード5)と、「intrpt.c」(ソースコード6)を作ります。

void int_cmt0(void);                                //タイマー割り込み(1ms)
//外部変数
extern volatile int    G_TimerCount;                //1msごとにカウントアップされる変数
//割り込みフラグ
#define CMT0_INT_F     CMT0.CMCSR.BIT.CMF           //タイマー0割り込みフラグ
ソースコード5 割り込み処理用ヘッダファイル「intrpt.h」を記述

#include    "iodefine.h"
#include    "common.h"
#include    "intrpt.h"
 
void int_cmt0(void)
{
    //タイマー割り込み(1ms)
    CMT0_INT_F = 0;            //フラグクリア
    //タイマーカウント
    G_TimerCount++;            //1msごとにカウントアップ
}
ソースコード6 「intrpt.c」内にタイマー割り込み関数「int_cmt0」を記述する

 CMTを使うために、「init.c」にCMT初期化関数を追加します。CMTのスタンバイモードを解除すると、タイマーが使えるようになります。まずこれをやらないと、次行以下の初期化が行えません。

 初期化では、以下の設定を行います(ソースコード7)。なお、フラグクリアは必須です。というのも、フラグをクリアしないとコンペアマッチの動作が起きないからです。

  • 割り込みの許可
  • カウンタクロック
  • フラグクリア
  • CMCOR
  • 割り込み優先順位

void init_cmt(void)
{
    //CMTスタンバイモード解除
    STB.CR4.BIT._CMT = 0;
    //
    CMT.CMSTR.BIT.STR0 = 0;            //CMT0カウント停止
    //CMT0は制御用タイマー
    CMT0.CMCSR.BIT.CMIE = 1;           //割り込みを許可
    CMT0.CMCSR.BIT.CKS = 0;            //カウントクロックPφ/8 = 3MHz
    CMT0.CMCSR.BIT.CMF = 0;            //フラグクリア
    CMT0.CMCOR = 3000-1;               //1msごとに割り込み
    //
    INTC.IPRJ.BIT._CMT0 = 0x0f;        //割り込み優先度を最高に設定
    //
    CMT.CMSTR.BIT.STR0 = 1;            //CMT0カウント開始
}
ソースコード7 「init.c」内にCMT初期化関数「init_cmt」を追加する

Copyright © ITmedia, Inc. All Rights Reserved.