[PIC]HI-TECH Cで、タイマー割り込みを行う(PIC16F84A)

PIC16F84Aには、TMRというタイマーモジュールがあり、これを利用と一定周期でタイマ割り込みをかけることが出来ます。

今回は、HI-TECH Cでタイマ割り込み処理を作成してみます。


※アセンブラ(MPASM)でタイマー割り込み処理を行いたい場合は、こちらの記事を参考にして下さい。
[PIC]TMRモジュールを使用する(タイマー割り込み)


TMRを使用したサンプルコードです。
これは,8Mhzで動作しているPICに対して,約10ミリ秒周期でタイマー割り込みをかけています。

#include <stdio.h>
#include <htc.h>
 
#define TMR0_INTERVAL (255-150) // TMRモジュールのカウント初期値
                                // 8Mhz, PS=6, TMR0=150サイクル ≒ 10msec
 
int intr_counter = 0;
int data = 0;
 
//--------------------------------
// メイン処理
//--------------------------------
void main( void )
{
    __CONFIG ( FOSC_HS & WDTE_OFF & PWRTE_ON & CP_OFF );
 
    // PortBを出力モードにする
    TRISB = 0;
 
 
    TMR0   = TMR0_INTERVAL;     // カウントアップタイマー値を初期化
 
    INTCONbits.T0IE   = 1;      // TMR0   interruptを有効にする
    INTCONbits.GIE    = 1;      // Grobal interruptを有効にする
 
    OPTION_REGbits.T0CS  = 0;   // タイマモードとして実行
    OPTION_REGbits.PS    = 6;   // プリスケーラ値(6:128倍)
    OPTION_REGbits.PSA   = 0;   // プリスケーラをTMRモジュールで使用する
 
    // 無限ループに入る
    while ( 1 ) {
        NOP();
    }
}
 
//--------------------------------
// 割り込み処理(10msec周期)
//--------------------------------
static void interrupt intr( void )
{
    intr_counter++;
    if ( intr_counter >= 10 ) {
        intr_counter = 0;
 
        //-------------------------------
        // 10回割り込みが来た
        //  -> 100msec周期で値を加算する
        //-------------------------------
        data++;
    }
 
    // 加算値をPortBに出力
    PORTB = data;
 
    //-------------------
    // 割り込みの再設定
    //-------------------
    TMR0 = TMR0_INTERVAL;
    T0IF = 0;
}



サンプルコードを元に、タイマー割り込みの使用方法を順に見ていきます。


割り込み処理を有効にする


まずはmain関数側で、main()では大きく3つの事を行っています。

まずは、タイマー値を初期化します。
タイマーはTMR0に指定した値からカウントアップが行われ、オーバーフローすると割り込み処理が走ります。レジスタ値は8bitなので、オーバーフローは値が255を超えたときに発生します。

    TMR0   = TMR0_INTERVAL;     // カウントアップタイマー値を初期化



今回のタイマー値はdefineされており、150回カウントアップすると割り込みが走るようにしました。
150を設定した理由は後で説明します。

#define TMR0_INTERVAL (255-150)





次に、タイマー割り込み処理を有効にします。タイマー割り込みはINTCONレジスタのT0IE(Tmr0 Interuupt Enabled)ビットを立てることで有効になります。割り込みを起こすには、T0IEを有効にした上で、さらにGIE(Grobal Interrupt Enabled)ビットを立てる必要があります。

    INTCONbits.T0IE   = 1;      // TMR0   interruptを有効にする
    INTCONbits.GIE    = 1;      // Grobal interruptを有効にする



INTCONレジスタの値を変更するには、INTCONbits共用体のT0IEフィールドを操作します。このフィールドはビットフィールドで実装されているので、メンバを指定する事でレジスタ内の該当ビットが操作できます。今回はpic15f84aを使用しているので、この構造体はHI-TECH Cをインストールした先のinclude\pic15f84a.hに定義されています。

ヘッダファイルを見ると分かるのですが、INTCONレジスタについては以下の定義がなされています。

// Register: INTCON
volatile unsigned char           INTCON              @ 0x00B;
// bit and bitfield definitions
volatile bit RBIF                @ ((unsigned)&INTCON*8)+0;
volatile bit INTF                @ ((unsigned)&INTCON*8)+1;
volatile bit T0IF                @ ((unsigned)&INTCON*8)+2;
volatile bit RBIE                @ ((unsigned)&INTCON*8)+3;
volatile bit INTE                @ ((unsigned)&INTCON*8)+4;
volatile bit T0IE                @ ((unsigned)&INTCON*8)+5;
volatile bit EEIE                @ ((unsigned)&INTCON*8)+6;
volatile bit GIE                 @ ((unsigned)&INTCON*8)+7;
volatile bit TMR0IF              @ ((unsigned)&INTCON*8)+2;
volatile bit TMR0IE              @ ((unsigned)&INTCON*8)+5;
#ifndef _LIB_BUILD
volatile union {
    struct {
        unsigned    RBIF                : 1;
        unsigned    INTF                : 1;
        unsigned    T0IF                : 1;
        unsigned    RBIE                : 1;
        unsigned    INTE                : 1;
        unsigned    T0IE                : 1;
        unsigned    EEIE                : 1;
        unsigned    GIE                 : 1;
    };
    struct {
        unsigned                        : 2;
        unsigned    TMR0IF              : 1;
        unsigned    : 2;
        unsigned    TMR0IE              : 1;
    };
} INTCONbits @ 0x00B;
#endif




INTCONbits構造体以外にも、各ビットを指すT0IE,GIE変数も定義されています。
ですので、下記のコードで指定する事も可能です。

    T0IE   = 1;
    GIE    = 1;



また、INTCONという変数自体も定義されている為、下記のコードでもOKです。

    INTCON |= 0x20;  // T0IEを有効にする
    INTCON |= 0x80;  // GIEを有効にする




割り込みを指定したら、OPTION_REGレジスタでTMRモジュールの加算トリガを決定します。
T0CSビットを0にするとクロックサイクルを元にタイマーが加算されるようになります。
結果として、アセンブラ命令が1つ実行する毎にTMR0レジスタが1づつ加算されます。

    OPTION_REGbits.T0CS  = 0;   // タイマモードとして実行



1クロックサイクル単位で1つ加算されると、仮にTMR0=0としても255命令毎に割り込みが掛かってしまうため、非常に短周期での割り込みとなってしまいます。

この周期を長くするためには、プリスケーラを使用します。プリスケーラを有効にした上で,プリスケーラ値を1~7の値にすることで、TMR0が加算されるタイミングを変更する事が出来ます。
サンプルでは、プリスケーラ値を6にしています。

    OPTION_REGbits.PS    = 6;   // プリスケーラ値(6:128倍)
    OPTION_REGbits.PSA   = 0;   // プリスケーラをTMRモジュールで使用する



プリスケーラ値が1増える毎にTMR0が加算されるタイミングが2倍遅くなります。
例えば2を指定すると、アセンブラを4命令実行する毎にTMR0が加算されます。


割り込み周期を算出する

今回は、プリスケーラ=6、TMR0=255-150で設定したので、タイマー割り込みは以下の周期となります。

150*128=19200クロックサイクル



PICは4クロックで1命令実行するので、以下のクロック周期で割り込みが起きます。

19200 * 4 = 76800クロック



今回のサンプルは、PICを8Mhzのセラロックで動作させているので、約10mSec周期での割り込みとなります。

76800 / 8000000 = 0.0096(秒)
                = 9.6(ミリ秒)



これで、割り込みの指定は完了です。



割り込みルーチン

割り込みルーチンですが、”static void interrupt”な関数を定義します。関数名は何でも良く、この修飾子を持つ関数を用意しておくと、HI-TECH Cはタイマー割り込みのハンドラ関数と見なしてくれます。

static void interrupt intr( void )




割り込み処理は、今回のサンプルでは以下のロジックになっています。
プリスケーラを使用して10ミリ秒周期まで落としましたが、これでも人間にとってはまだ速いので、さらに10倍して100ミリ秒単位で値を加算し、その値をPortBに出力しています。これで、PortBにLEDをつないでおけば割り込みが周期的に入っている事が目視で確認できます。

    intr_counter++;
    if ( intr_counter >= 10 ) {
        intr_counter = 0;
 
        //-------------------------------
        // 10回割り込みが来た
        //  -> 100msec周期で値を加算する
        //-------------------------------
        data++;
    }
 
    // 加算値をPortBに出力
    PORTB = data;



割り込み処理が終わったら、再度TMR0に値をセットした後、T0IFをクリアします。
T0IFは”TMR0 Overflow Interrupt Flag bit”で、割り込みのクリア用フラグです。これをクリアし忘れると、割り込み関数が終わっても元の処理に戻ってくれず割り込み関数が無限ループで動作する事になってしまいます。

    //-------------------
    // 割り込みの再設定
    //-------------------
    TMR0 = TMR0_INTERVAL;
    T0IF = 0;


関連記事

コメントを残す

メールアドレスが公開されることはありません。