MPLAB® Code Configurator(Timer編)

前回に引き続き今回はMCCを使ったTimer割り込みプログラムの説明です。前回コードで書いていたwait(LEDを点滅させる間隔)をTimer割り込みで行う事を目指します。は設定する項目が多いので前回のGPIO編と違いMCCの有り難さが分かりました。前回と同じ様にPIC24FJ64GA002用のプロジェクトを作成して下さい。プロダクトネームは、”timer00″とします。

Timerの設定はシステムクロックの設定から

Timerはシステムクロックを基準に動作します。先ずはシステムクロックの確認設定から始めます。IDEには空のプロダクト、”timer00″がロードされているとして、MCCを起動して下さい。MCCは既にインストールされているとします。まだインストールされていない方は前回を参照してインストールして下さい。

  1. IDEツールバーに有るMCCアイコンをクリック。これでMCCが起動します。
  2. プロダクトを作成して最初に起動するとこの画面が出ます。矢印のボタンを押して
  3. ここでも矢印のボタンを押すと下記が表示されます。
  1. MCCのSystem欄で System Module を選ぶと
  2. 中央にこの画面が出ます。これがシステムクロックの設定画面です。Postscaler PLL Enable等を選択すればクロックの値を変更出来ますが、今回はデフォルトをそのまま使用します。システムクロックは4MHz。

次はタイマーの設定です。PIC24FJ64GA002は5つのタイマーを持っていますが、今回はTimer1を使います。

  1. Device Resoueces欄で下にスクロースしTimerフォルダーを見つけ、横の三角をクリックするとタイマー一覧が現れます。
  2. ここで、Timer1の左に有る緑のプラスマークをクリックすると
  3. 上の Project Resource欄に新たに Peripheralsフォルダーが作成されTimer1が追加されます。これを選ぶと右側にTimer1の設定画面が表示されます。一秒間隔で割り込みが発生する様Timerを設定して行きます。
  4. 先ずは、ここをクリックしてタイマーを有効にします。
  5. ここで、クロックの分周を指定します。今回は間隔が1秒と長いので分周を最大の256と指定します。
  6. ここでタイマーの時間を設定します。設定枠の両側に有る数字は設定出来る時間の最小値と最大値です。今回のクロックで256分周を行うと256us から 8.38s の間でタイマーの時間を設定する事ができるようです。今回は間隔を1秒としたいので、”1s”と入力して下さい。
  7. ここをクリックしてタイマー割り込みを有効にします。
  8. ここは多分、希望するタイマーの時間が11でタイマーの時間より長い時に指定するものと思います。今回は11で十分なので、”1”を入力しました。

これでTimer1の設定は完了です。次にLED点滅用のGPIOの設定を行いコードを作成します。

  1. Project Resource欄 SystemフォルダーのPin Moduleをクリック
  2. 右側のPin Manager欄のPin Module GPIO output行, Port B 15列をクリック。(鍵がロックに変わる)
  3. 上の段いRB15が追加され、Outputにチェックが入っている事を確認。
  4. 最後にGenarate ボタンを押すとコードが作成されます。作成の状況が右側の画面に表示されます。最後に、”Generation complete.”と表示されて作成完了です。

今回のメインは、”tmr1.c”です。MCCが作成したコードは各関数を外部からコールするとタイマー1を操作出来る様なっています。下記は関数の例です。

機能関数
初期設定TMR1_Initialize (void)
タイマー開始TMR1_Start( void )
タイマー停止TMR1_Stop( void )
カウンターの書き込みTMR1_Period16BitSet( uint16_t value )
カウンターの読み込みTMR1_Period16BitGet( void )

ただ特徴的なのが、割り込み作業の実行(定義?)方法です。

tmr1.c

/**
  Section: Included Files
*/

#include <stdio.h>
#include "tmr1.h"

/**
 Section: File specific functions
*/
void (*TMR1_InterruptHandler)(void) = NULL;
void TMR1_CallBack(void);

/**
  Section: Data Type Definitions
*/

/** TMR Driver Hardware Instance Object

  @Summary
    Defines the object required for the maintenance of the hardware instance.

  @Description
    This defines the object required for the maintenance of the hardware
    instance. This object exists once per hardware instance of the peripheral.

  Remarks:
    None.
*/

typedef struct _TMR_OBJ_STRUCT
{
    /* Timer Elapsed */
    volatile bool           timerElapsed;
    /*Software Counter value*/
    volatile uint8_t        count;

} TMR_OBJ;

static TMR_OBJ tmr1_obj;

/**
  Section: Driver Interface
*/

void TMR1_Initialize (void)
{
    //TMR1 0; 
    TMR1 = 0x00;
    //Period = 1.000064 s; Frequency = 2000000 Hz; PR1 7812; 
    PR1 = 0x1E84;
    //TCKPS 1:256; TON enabled; TSIDL disabled; TCS FOSC/2; TSYNC disabled; TGATE disabled; 
    T1CON = 0x8030;

    if(TMR1_InterruptHandler == NULL)
    {
        TMR1_SetInterruptHandler(&TMR1_CallBack);
    }

    IFS0bits.T1IF = false;
    IEC0bits.T1IE = true;
	
    tmr1_obj.timerElapsed = false;

}


void __attribute__ ( ( interrupt, no_auto_psv ) ) _T1Interrupt (  )
{
    /* Check if the Timer Interrupt/Status is set */

    //***User Area Begin

    // ticker function call;
    // ticker is 1 -> Callback function gets called everytime this ISR executes
    if(TMR1_InterruptHandler) 
    { 
           TMR1_InterruptHandler(); 
    }

    //***User Area End

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

void TMR1_Period16BitSet( uint16_t value )
{
    /* Update the counter values */
    PR1 = value;
    /* Reset the status information */
    tmr1_obj.timerElapsed = false;
}

uint16_t TMR1_Period16BitGet( void )
{
    return( PR1 );
}

void TMR1_Counter16BitSet ( uint16_t value )
{
    /* Update the counter values */
    TMR1 = value;
    /* Reset the status information */
    tmr1_obj.timerElapsed = false;
}

uint16_t TMR1_Counter16BitGet( void )
{
    return( TMR1 );
}


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

void  TMR1_SetInterruptHandler(void (* InterruptHandler)(void))
{ 
    IEC0bits.T1IE = false;
    TMR1_InterruptHandler = InterruptHandler; 
    IEC0bits.T1IE = true;
}

void TMR1_Start( void )
{
    /* Reset the status information */
    tmr1_obj.timerElapsed = false;

    /*Enable the interrupt*/
    IEC0bits.T1IE = true;

    /* Start the Timer */
    T1CONbits.TON = 1;
}

void TMR1_Stop( void )
{
    /* Stop the Timer */
    T1CONbits.TON = false;

    /*Disable the interrupt*/
    IEC0bits.T1IE = false;
}

bool TMR1_GetElapsedThenClear(void)
{
    bool status;
    
    status = tmr1_obj.timerElapsed;

    if(status == true)
    {
        tmr1_obj.timerElapsed = false;
    }
    return status;
}

int TMR1_SoftwareCounterGet(void)
{
    return tmr1_obj.count;
}

void TMR1_SoftwareCounterClear(void)
{
    tmr1_obj.count = 0; 
}

/**
 End of File
*/

  • タイマー割り込みが発生すると、ISRとして定義された68行目の void attribute ( ( interrupt, no_auto_psv ) ) _T1Interrupt ( ) が実行されます。
  • この中でユーザーの定義する割り込み処理はTMR1_InterruptHandler()関数によって実行されます。しかし関数は、 if(TMR1_InterruptHandler)によって真の時に実行する様定義されています。
  • TMR1_InterruptHandler は実行したい関数のポインターで上記の”if”文は、”実行したい関数が定義されていたら、それを実行しなさい”となります。それでは、TMR1_InterruptHandler は何処で定義されているか。それは、120行の void TMR1_SetInterruptHandler(void (* InterruptHandler)(void)) です。この関数がMCCが作成したコードのキー関数と思います。
  • この関数の引数 void (* InterruptHandler)(void) は実行したい割り込み処理用関数のポインターで、この関数内で、TMR1_InterruptHandler = InterruptHandler; としてTMR1_InterruptHandlerを定義しています。
  • としてここで実行したい関数のポインターを、TMR1_InterruptHandlerにセットしています。

このコーディングの利点は、”tmr1.c” を変更せずに、ISRをコーディング出来る点です。

例えば、

  • タイマー割り込みで実行したい関数を、”main.c”にコーディング
  • そこから、引数をその関数のポインターとして、関数TMR1_SetInterruptHandler(void (* InterruptHandler)(void))をコールする。
  • これにより、TMR1_InterruptHandler に実行したい関数が登録される。
  • その状態でタイマー割り込みがかかれば、以下の様になる。
    • 68行目の void attribute ( ( interrupt, no_auto_psv ) ) _T1Interrupt ( ) が実行され
    • if(TMR1_InterruptHandler) に到達する。
    • TMR1_InterruptHandlerに実行したい関数のポインターが登録されいるので
    • 次の行、TMR1_InterruptHandler()が実行される。この関数は実は、”main.c”でコーディングしたISR。

これを元に、”main.c”をコーディングすると以下の様になります。

main.c

/**
  Section: Included Files
*/
#include "mcc_generated_files/system.h"

int fl;

 void myTimerISR(void)
 {
    fl = !fl; 
}
/*
                         Main application
 */
int main(void)
{
     // initialize the device
    SYSTEM_Initialize();
    TMR1_SetInterruptHandler (myTimerISR);     
    TMR1_Start();
   
    fl = 1;
    while (1)
    {
        LATB = 0xFFFF;
        while(fl) ;
        LATB = 0x0000;
        while(!fl) ;
  
       // Add your application code
    }

    return 1;
}
/**
 End of File
*/
  • 6行:外部変数 ”fl”を定義。割り込みが発生するとこの値を変更します。
  • 8行:割り込み関数、void myTimerISR(void) の定義。割り込みの度にflの値を反転します。
  • 19行:ここで割り込み関数を TMR1_SetInterruptHandler (myTimerISR); の様に定義しています。
  • 20行:タイマーの開始
  • 25行:LED点灯
  • 26行:fl=0になるまで待ちます。タイマー割り込みがかかり、flが0になると
  • 27行:LED消灯
  • 28行:fl=1になるまで待ちます。タイマー割り込みがかかり、flが1になると 20行へ

これでLEDが1秒に1回点滅します。この様に、”trm1.c”を全く変更無くコーディング出来ます。ちなみに使用したPIC側の回路は前回と同じ。

次回は

今回はMCCの便利さを痛感しました。タイマー設定に関し殆どユーザーのコーディング無しです。PICには他にも、I2C SPI 外部割り込み等色々な機能が有ります。次回はこれらの機能に付いてMCCがどの様に働くか確かめて行きたいと思います。