PIC32のタイマー1割り込みによるLチカ

LED blinking with timer interrupt
 
PIC32マイコンのタイマーは指定したクロック(分周比)ごとにタイマーレジスタの値が増えていき、一定の値になると0に戻るとともに割り込みフラグレジスタ(IFS0)がセットされます。ループ中に加算されたタイマー値を計算して経過を時間調べることもできますが、もっぱら割り込みによって呼び出された関数(割り込みサービスルーチン:ISR)で個別の処理をするのが一般的です。

PIC32ではすべての割り込み処理を同じ関数で呼び出すシングルベクタと、割り込みごとに異なる関数で呼び出すマルチベクタがあります。シングルベクタよりもマルチベクタの方が処理が早く、また、関数内で条件分岐させるよりも関数ごとに分けた方がコードの可読性が高いので、シングルベクタが使われることはあまりありません。

PIC32MXにはタイマー1からタイマー5まで用意されています。タイマー1は16bitが上限で副発振回路でのカウントができます。一方で2~5は副発振回路は使えませんが2と3、もしくは4と5を組み合わせることで32bit値でカウントすることができます。

では、タイマー関連の設定をプログラムしてみましょう。タイマーの速度(クロック数)はPBCLKの分周比で決まるため、外部発振器を使用した場合は、FPBDIVの値も定義する必要があります。
div.c
#pragma config FPBDIV = DIV_1 //周辺モジュールクロック倍数 : x1/1
この場合はタイマーもCPUクロックと同じ周波数で動作しますが、これをDIV_2にするなどして周波数を落とすと消費電力の削減を図ることができます。

タイマー1の設定はT1CONレジスタに書き込みます。
t1con.c
T1CONbits.TCS   : 対象クロック (0=内部,1=外部)
T1CONbits.TSYNC : 外部クロック時の同期 (0=非同期,1=同期)
T1CONbits.TCKPS : プリスケーラ設定 (0=1,1=8,2=64,3=256)
T1CONbits.TGATE : 内部クロック時のゲート[T1CKピンがHighのときだけカウントするか] (0=無効,1=有効)
T1CONbits.TWIP  : 非同期書き込み中かどうかを調べる (0=完了している,1=処理中である)
T1CONbits.TWDIS : 非同期時の書き込みの許可 (0=許可,1=許可しない)
T1CONbits.SIDL  : アイドル中の動作 (0=動作を続行,1=停止させる)
T1CONbits.ON    : タイマーの実行 (0=中断,1=実行)
「T1CONbits.ON = 1」でタイマーが稼働し、TMR1の値がPR1に設定した値までカウントが増えるとTMR1が0にリセットされるとともに割り込みが発生します。TCKPS値を増やすと、1カウントされるまでの間隔が倍増するので、例えばモジュールクロック40MHzの状況で100msごとに発生させたい場合は「250ns(1/40MHz) * 256(T1CONbits.TCKPS = 3) * 15625(PR1 = 0x3D09) = 100ms」と計算します。

タイマー1による割り込みを有効にするには、プログラム全体での割り込み処理を使用する関数「__builtin_enable_interrupts()」を記述して「IEC0bits.T1IE = 1」をセットします。この状態でマイコン内部の処理によって「IFS0bits.T1IF」の0だった値が1になるとタイマー1モジュール(ベクタ番号4)を指定した関数が呼び出されます。タイマーを繰り返したい場合、T1IFは手動(プログラム内)で0にして再び受け入れられるようにします。

割り込みの優先度は「IPC1bits.T1IP(7で最優先、0で割り込みをしない)」および「IPC1bits.T1IS(副優先。先の優先が同じレベルならこの値の大きい方[0~3]が優先される)」で指定します。

割り込み関数を定義するにはXC32コンパイラの場合、関数名の前に「割り込み優先度○の○番ベクタ」であることを宣言します。今回は「割り込みレベル4、4番ベクタ(_TIMER_1_VECTOR)」なので下記のようになります。
attr.c
void __attribute__((interrupt(IPL4AUTO))) __attribute__((vector(4))) OnTimer1(void){}
「#include <sys/attribs.h>」をインクルードすれば、__ISRマクロで簡潔に記述することができます。
isr.c
void __ISR(_TIMER_1_VECTOR, IPL4AUTO) OnTimer1(void){}
以上を集約したプログラム例がこちらになります。回路図は「PIC32でシンプルなLチカをやってみる」と同じです。
main.c
#include <p32xxxx.h>
#include <sys/attribs.h>

// 外部の8MHz水晶発振器から40MHzのクロックを生成するための設定
#pragma config FNOSC = PRIPLL // 発信源 = 主外部発振器
#pragma config POSCMOD = XT // 主発振の方法 = 外部高精度発振器を使う
#pragma config FPLLIDIV = DIV_2 // PLL入力分数 = x1/2
#pragma config FPLLMUL = MUL_20 // PLL逓倍比 = x20
#pragma config FPLLODIV = DIV_2 // PLL出力分数 = x1/2
#pragma config FPBDIV = DIV_1 //周辺モジュールクロック倍数 : x1/1

unsigned char toggle = 0, count = 0;

// 割り込みレベル4、4番ベクタ(Timer1に該当)
void __ISR(_TIMER_1_VECTOR, IPL4AUTO) OnTimer1(void) 
//void __attribute__((interrupt(IPL4AUTO))) __attribute__((vector(4))) OnTimer1(void)
{
    IFS0bits.T1IF = 0; // 割り込みフラグをリセット

    count++;
    if(count >= 10){
        toggle = !toggle;
        count = 0;
        PORTBbits.RB0 = toggle; // RB0のスイッチを切り替える      
    }
}

void initTimer1()
{
    T1CONbits.ON = 0;    // 設定のため、タイマー1を無効にする
    TMR1 = 0;            // タイマー1をリセット
     
    // 100ms周期で割り込み <- 250ns(1 / 40MHz) * 256 * 15625(0x3D09) = 100ms
    T1CONbits.TCKPS = 3; // プリスケーラ : x1/256
    PR1 = 0x3D09;        // カウンター
    
    INTCONbits.MVEC = 1; // マルチベクタ割り込みを有効にする
    
    IPC1bits.T1IP = 4;   // 割り込み優先度(0-7)
    IPC1bits.T1IS = 3;   // 副優先度(0-3)
    IFS0bits.T1IF = 0;   // 割り込みフラグをリセット
    IEC0bits.T1IE = 1;   // タイマー1の割り込みを許可
    
    __builtin_enable_interrupts();  // マイコンにおける割り込みの有効。「asm volatile("ei")」と同義
    T1CONbits.ON = 1;    // タイマー1有効
}

int main(void)
{
    TRISBbits.TRISB0 = 0; // B0を出力にする
    PORTBbits.RB0 = 0;    // B0 = off
     
    initTimer1();
    while(1) ;
}

プログラムの値を変えて、任意の間隔でLEDを点滅できるようにしてみましょう。
2018/09/24