2010年1月23日土曜日

サンプルコードにストップウォッチ機能を実装してみる

今回は、サンプルコードを変更して、ストップウォッチを作ってみることにしました。
そして、今回もしょぼいミスのおかげでメチャ苦労しました(^^;

サンプルコードの1つに「LCD_Test」というLCDに時計が表示されるものがあります。時計は”時:分:秒”の表示で、secオーダで時計が進んでいきます。
コード解析したところ、1sec周期のタイマ割り込みのタイミングでグローバルなカウンタ変数がインクリメントされ、そのカウンタを参照してLCDに時計を表示するという実装になっていました。
実際にサンプルコードを動かしてみると、時計と同じ間隔で秒が更新されているようでした。32.768kHzの発振子を取り付けたおかげでしょうね。
さて、このサンプルコードをちょっと変更して、ストップウォッチを作ってみます。

液晶付きマイコンボード用に提供されているサンプルコードは、以下のサイトで配布されています。 
http://www.cqpub.co.jp/interface/contents/special0912/ 


(1)実現方法
・基板にキーボタン(2ヶ)が付いているので、これをトリガにして時計の停止・再開を行う。
※サンプルコードは、実行すると自動的に時計が動き出すので、「開始」は無しとする。
・右キー押下で停止、左キー押下で再開する。
・タイマは常時動作させておいて、停止中はカウンタ変数をインクリメントさせない。
・キー入力は割り込みで検出する。(エッジ検出、右キー左キーどちらもポート0入力)
・キーのチャタリングはマイコンの「チャタリング除去機能」で対応する。

ちなみに、マイコンのデータシートに、「ストップウォッチタイマ(SWT)」というのを見つけたのですが、時計を停止させるのに関連しているのでしょうか?概要を読んでもこれが何なのかよくわからなかったです(^^;


(2)変更内容
・時計の状態を保持するグローバルな状態変数(StopFlg)を追加する。
→ 初期値:StopFlag = 0(再開)
・ポート0用の割り込みハンドラ本体を実装する。
・swFlgは、ポート0の割り込みハンドラ内で停止・再開情報を更新する。
→ ポート0(6番):StopFlag = 1(停止)
→ ポート0(7番):StopFlag = 0(再開)
・タイマハンドラ本体に、StopFlag値によるカウンタインクリメント有無の分岐を追加する。
→ StopFlag(1):カウンタインクリメントしない
→ StopFlag(0):カウンタインクリメントする
・割り込みベクタテーブルに、ポート0用の割り込みハンドラを登録する。
・main関数の時計表示処理(無限ループ)に入る前に、ポート0の初期設定を行う。
→ ポート方向(入出力)設定
→ 割り込みレベル設定
→ エッジ検出設定
→ チャタリング除去設定
→ 割り込み有効設定

ポート入力のサンプルコードもあるので、ポート処理はほとんど流用できます。
こんな感じで、実装は比較的簡単。


(3)問題点
実装完了後、試しに動かしてみたのですが、どうも挙動がおかしいのです。
停止キー入力後、時計は止まりました。でも、再開キーで時計は進み出しません。
デバッガで確認したところ、最初のキー入力による割り込み処理以降、タイマ割り込みや次のキーの割り込みが入って来ませんでした。

というわけで、調査していくことにします・・・。


(4)調査
最初のキー入力による割り込み処理終了後、デバッガでPSR(Processor Status Register)の値を確認してみたところ・・・、
・IEが0(割り込み受付無効)
・ILが011(ポート0の割り込みレベルを3にしているため)
となっていました。

IEは、割り込み処理終了後は1に戻っていないと、次の割り込みを受け付けられません。
0のまんまというのはおかしいです。ILも割り込みレベル000に戻っていないですね・・・。
次の割り込みを受け付けるためには、IEが1になっていて、かつ現在のIL値よりも高いレベルの割り込みである必要があります。

なんでかなー?と、スタートアップマニュアルC言語版を見てみると・・・、

「割り込み処理ルーチンからのリターンにはPCとPSRの内容を復帰するreti命令を使用します。」
「reti命令は割り込み処理ルーチン用のリターン命令です。割り込み処理ではリターンアドレスとともにPSRもスタックにセーブされますので、reti命令によってPSRの内容を復帰させる必要があります。」

おー、なるほど!
でも、サンプルコードにはretiを使ってる箇所が見当らないんですけどねー。
とりあえず、割り込みハンドラの処理終了時のところにasm(“reti”)を入れてみました。

すると今度は・・・、
最初のキー入力による割り込み処理終了後、デバッガがハングしてしまいました。

んー、よくわからん!スタックポインタの問題か?
というわけで、割り込みハンドラ内で別の関数をコールしているので、これをやめてみました。

すると・・・、無事に動作しました!なるほど、つまりこういう事か。
・割り込みハンドラを抜けるときはreti命令を使用する。
・割り込みハンドラ内では関数コールをしない。するならinline関数にする。

ん?まてまて、まだ納得できないんです!
というのも、サンプルコードには、
・コード上にretiが存在しない。アセンブラレベルで見るとなぜかreti命令が入っている。
・割り込みハンドラ内で別の関数をコールしている、でも問題なく動作している。

思いっきり矛盾してます。
再度、スタートアップマニュアルC言語版を見てみると・・・、

割り込み処理ルーチンは次の形式でプロトタイプ宣言します。
<型><関数名>__attribute__((interrupt_handler));

このようにプロトタイプ宣言することにより、割り込み処理終了時にハードウェアが退避してくれたPSR(プロセスステータスレジスタ)とPC(プログラムカウンタ)の内容を復帰する処理を自動的に組み込んでくれます。

あ!しまった・・・。プロトタイプ宣言が間違ってる!
たしかに、サンプルコードはこれで宣言されている。

ということで、プロトタイプ宣言を上記のように修正し、コードを元に戻してみたところ(asm(“reti”)削除、関数コール復活)、無事に動きました\(^▽^)/

こんなことに気づかないなんてダメですね(^^; まったくしょぼいミスです。


念のためPSRのIEとILを確認してみると、思ったとおりの動きをしていますw
そして、プロトタイプ宣言に「__attribute__((interrupt_handler))」の有無でどう違うのかアセンブラレベルで見てみました。

■__attribute__((interrupt_handler))有り
image image

■__attribute__((interrupt_handler))無し

image image

reti命令の有無と、スタックポインタの取り方が違っていました。プロトタイプ宣言を間違うとこうなるんですね(^^;
スタックポインタの取り方の違いで何が起こるのかをもう少し調べた方が良さそうですが、今回はこの辺で終わっておきますw


(5)結論 
結論と言うほどでもないですが、割り込みハンドラ関数のプロトタイプ宣言には気をつけます。
で、ストップウォッチ機能については、無事に動作しました。成功です!
今回は、2つのキーボタンを使用して、時計の停止・再開を行いましたが、1つのキーをトグルのように停止・再開を行い、もう1つのキーで時計のクリアを行うことも出来そうですね。


(6)備考
今回変更した主な部分を記載しておきます。

■キー割り込み

/* プロトタイプ宣言 */
void p0xInt(void)__attribute__ ((interrupt_handler));

/* 割り込みハンドラ関数(ポート0) */
void p0xInt(void)
{
    /* interrupt flag*/
    char intFlag = 0;

    /* Read Interrupt status */
    intFlag = P0_IFLG;

    /* Check Interrupt status */
    if(isP06Int() == P_INT_OCCURED) {
//    if((intFlag & P_BIT_X6) == P_BIT_X6) {
        //P06 intterupt occured
        intFlag |= P_BIT_X6;
        StopFlag = 1;                        // 時計停止
    }
    if(isP07Int() == P_INT_OCCURED) {
//    if((intFlag & P_BIT_X7) == P_BIT_X7) {
        //P07 intterupt occured
        intFlag |= P_BIT_X7;
        StopFlag = 0;                        // 時計再開
    }
    /* Interrupt Flag clear */
    resetP0IF((unsigned char) intFlag);
//    P0_IFLG = intFlag;

    /* 割り込み処理終了 */
//    asm("reti");
}



■タイマ(1sec)割り込み

/* 割り込みハンドラ(1秒周期タイマ) */
void ctInt(void)
{
    CT_IFLG = 1;
    if(StopFlag == 0){
        onesec++;
    }
}

関連記事:


0 件のコメント:

AddThis