PIC24FJ64GB002のTimer

PICのタイマー処理について、たくさん投稿があるけど、MCC(Microchip Code Configuration)を利用した実装方法があまりないので、学習ついでに備忘録をのこしておく。

まず、PIC24FJ64GB002には5個(Timer1~5)のタイマーがあるとデータシートに記載がある。MCC上でも確かに5個あることがわかる。

pic24ftimer01

ただ、設定できる内容が微妙に違っていて3種類ある。

Inkedpic24ftimer02_LI

Inkedpic24ftimer03_LI

pic24ftimer04

あとは、Timer4はTimer2と同じで、Timer5はTimer3と同じ設定内容になる。

  • 基本的には、Timer1~5すべてのタイマーは最低でも16bitで、独立してカウントできる。
  • Timer1~5すべてにGateというオプションがある。
  • Timer2とTimer4は、32bitでカウントすることもできるが、その場合Timer3(Timer2とペア)とTimer5(Timer4とペア)を使用する。つまり32bitタイマーを使う場合は、それぞれペアになるTimer3またはTimer5(またはその両方)は利用不可になる。
  • ADコンバーターの変換タイミングをTimer3とTimer5を利用してカウントすることもできる(任意)

Timer1を使ってLEDを点滅させる

今までは、_delay()関数を使ってLEDをメインループ内で点滅させていたが、これだとほかの処理のタイミングにも影響するので、タイマーイベント内で点滅させるように変更してみる。

まずはTimer1を追加して、0.5s周期でタイマーが発動するように設定する。

pic24ftimer05

Generateすると、tmr1.ctmr1.hが追加される。

pic24ftimer06

// system.c
void SYSTEM_Initialize(void)
{
    :
    TMR1_Initialize();
}

// interrupt_manager.c
void INTERRUPT_Initialize (void)
{
    :
    //    TI: T1 - Timer1
    //    Priority: 1
    IPC0bits.T1IP = 1;
}

tmr1ソースを見ると、タイマー発動したときの割り込み関数が実装されている。TMR1_CallBack()という関数をコールしている。TMR1_CallBack()の中身は空になっているので、ここに直接実装せよということになる。

// tmr1.c
void __attribute__ ( ( interrupt, no_auto_psv ) ) _T1Interrupt (  )
{
    /* Check if the Timer Interrupt/Status is set */
    // ticker function call;
    // ticker is 1 -> Callback function gets called everytime this ISR executes
    TMR1_CallBack();

    tmr1_obj.count++;
    tmr1_obj.timerElapsed = true;
    IFS0bits.T1IF = false;
}

void __attribute__ ((weak)) TMR1_CallBack(void)
{
    // Add your custom callback code here
}

void TMR1_Start( void )
{
    /* Reset the status information */
    tmr1_obj.timerElapsed = false;
    /*Enable the interrupt*/
    IEC0bits.T1IE = true;
    /* Start the Timer */
    T1CONbits.TON = 1;
}

とりあえず、メインループ内のLED点滅処理をTMR1_CallBack()移動してみる。そして初期化後にTMR1_Start()をコールするように修正して、PICに書き込むと指定した間隔でLEDが点滅するようになる。

// --------------- main.c
int main(void)
{
    // initialize the device
    SYSTEM_Initialize();
    
    IO_RB14_SetLow();
    IO_RB15_SetHigh();
    
    TMR1_Start();
    
    while (1)
    {
    }

// --------------- tmr1.c
#include "pin_manager.h"
void __attribute__ ((weak)) TMR1_CallBack(void)
{
    // Add your custom callback code here
    IO_RB14_Toggle();
    IO_RB15_Toggle();
}

割り込みベクタテーブルについて

さきほどのタイマーのソースを見ると、カウンタが規定数を超えると、以下関数が自動的にコールされる。

void __attribute__ ( ( interrupt, no_auto_psv ) ) _T1Interrupt ()

付近のプログラムソースをみても誰もこの関数をコールしてない。この_T1Interruptは、Interrupt Vectorsという割り込み関数のテーブルに登録されている関数名で、各PICの機種ごとに定義されている。XC16コンパイラのドキュメントにもHTMLファイルで提供されている。

C:\Program Files (x86)\Microchip\xc16\v1.35\docs\vector_docs

上記の関数は、「Timer1の割り込み時には、_T1Interruptをコールする」というような定義になっているので、この関数名を変更すると、コンパイルは成功するが、実行してもコールされないので名称を変更してはいけない。別の見方をすると、この名称で割り込み関数として実装すれば、どこで記述しても動作するはずである。

インターバルを変更してみる。

MCCでやればいい話だが、プログラム内でも動的にカウンタ周期を変更できるのでやってみる。ついでにカウンタの値の計算方法をおさらいしておく。

タイマーカウンタ(TMR1)は、1サイクル毎(PIC24Fは2クロック1サイクル)に1ずつ増えていく。設定した周期(PR1)に到達すると、割り込みイベントが発生する。

現在クロック周波数が32MHzで動作しているので、

FOSC/2 = 16000000
1サイクル = 1/16000000 = 62.5 ns

となり、62.5nsで1カウントする。カウンタは16bitなので、最長のインターバルが、

62.5 ns x 65535 = 4.095 ms

4msしかない。このため、MCCではプリスケーラの値を1:256に設定して256倍の長さにしている。

4.095 ms x 256 = 1048 ms = 1.05 s

でようやく1秒ぐらいまでカウントできるようになる。プリスケーラを加味すると、1カウントにかかる時間は、以下のようになる。

62.5 ns x 256 = 16 us

インターバルの値を変更するAPIは、tmr1.cに用意されている。16bit長(0 - 65535)のカウンタ数を指定して変更する。

void TMR1_Period16BitSet( uint16_t value )

例えば、タイマーのインターバルを300msにする場合、以下のように求めてAPIに指定する。

main(){
  :
  // period : 300000 us / 16 us = 18750
  TMR1_Period16BitSet( 18750 );
  TMR1_Start();
  while(1){
    :
  }
  return -1;
}

元々500msで指定していたので、実行すると確かにちょっと早く点滅するようになる。