PICマイコン(PIC12F675)のタイマーの管理の仕方の一例

組み込みエンジニア

こんにちは、ENGかぴです。

マイコンのソフトを開発しているとタイマーを多く使います。例えばDOから任意の期間信号を出力したり、DIに一定期間入力されている信号の判定したいときタイマーを使います。PIC12F675を使ったタイマーの管理の一例をまとめました。

この考え方はマイコンの種類を問わず使えます。例えばベースタイマを10ms間隔でカウントするようにしておくと100回カウントアップしたとき1000ms経過するタイマーを作ることができます。以下ではタイマー1をTMR1と記述します。

PIC12F675を使ってマイコンの動きを勉強するためにPIC12F675の機能でできることについてまとめています。

PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ

マイコンのタイマーの時限について

タイマーの分解能は10msを前提とします。タイマーの考え方としては、__delay_ms(数値)や__delay_us(数値)によって管理してもよいのですが、これらはソフトウェアウェイトであるためメイン関数を素早く周回させたい場合においては適した方法ではありません。

タイマーの分解能が10msと言っているのは10ms刻みの管理となるので10ms以下の管理はできないということになります。

例えば5msをカウントしたい場合などにおいては使用できません。5msなどをカウントしたい場合はタイマーの分解能を1ms若しくはそれよりも低い時間でオーバーフローさせなければなりません。

動作クロックが高速にできるマイコンであればタイマーの分解能を細かくしてもよいのですが、今回使用するPICマイコンにおいては内部クロックを使用すると内部クロックが4MHzとなり動作クロックは1MHzになるので1命令1usになります。

分解能を細かくするほど割り込みの回数が増えてしまうので割り込みの渋滞の問題も考えていく必要がありますが、今回は10msを分解能とするので問題にはなりません。

タイマー管理の考え方

PIC12F675においてTMR1をオーバーフロー時間を10msとします。例えば10ms経過後にカウントアップしてメイン関数内で5回以上になったときにタイマーを更新したとすると50msを分解能とするタイマーが作れます。10回以上になったときに更新したとすると100msを分解能とするタイマーが作れます。

TMR0でも同様の構成で管理はできますが、WDTを使用する設定でプリスケーラを使用しているため最大で256us毎の割り込みとなり割り込みが増えてしまうことからTMR1での説明としています。

タイマー管理のイメージ

タイマー管理の説明図
タイマー管理の説明図

説明図のタイマー管理部分の5を1に変えると10msの分解能のタイマーになります。10に変えた場合は100msの分解能のタイマーになります。

割り込みとメイン関数の関係を見ると値を比較しているときに割り込みとなっても次の行で-5するだけなので影響はないため割り込みを禁止する必要はありません。

欠点はメイン関数の1周の時間に左右されることですが、通常メイン関数のループは1ms以下になることが多いことから問題ないと考えています。

メイン関数の1周に時間を要している場合は誤差が大きくなる可能性もありますので誤差が問題になるような場合は注意が必要です。

タイマー管理に必要な定義と変数

タイマー管理を行う際に準備しておきたい定義と変数について説明します。定義は#defineで行います。TIME_OFFとTIME_UPについて準備を行いあとは任意のタイマーについて時限を決めるために値を定義しておきます。

//--------------定数定義----------------------------
#define	TIME_START_H 0xD8	//タイマスタート位置	65536- 55536 = 10000
#define	TIME_START_L 0xF0	//タイマスタート位置	65536- 55536 = 10000
#define TIME_OFF -1         //タイマーを使用しない場合
#define TIME_UP 0           //タイムアップ
#define TIME_START_H 0xD8   //TMR1Hの初期値
#define TIME_START_L 0xF0   //TMR1Lの初期値
#define TIME_LED_CNT_MAX 5  //250ms経過
#define TIME_LED_CNT2_MAX 15    //750ms経過
#define TIME_BASE_MAX 5     //ベースタイマカウント値
//--------------変数定義----------------------------
unsigned char cnt10ms;
char TimerLed1;
char TimerLed2;

次に変数を宣言します。定義をみると-1を扱っていることから符号なしではなく符号ありで定義する必要があります。

タイマーの時限を長くしたい場合は状況によってcharやshortを使い分けます。今回はcharで宣言します。cnt10msは10msが経過した時のカウントなので符号なしで宣言します。

初期化をするときにTIME_OFFをセットするとタイマーを使用しない設定になります。セットしない場合は0となるのでタイムアップがセットされている状態になります。

TimerLed1 = TIME_LED_CNT_MAX;   //TimerLed1の初期値セット
TimerLed2 = TIME_OFF;

初期化時に使用しないタイマーはTIME_OFFをセットしておくとよいでしょう

回路図とタイマー管理する処理を考える

タイマ管理の動作確認の回路図
タイマ管理の動作確認の回路図

タイマーの管理をしながら250msでGP0とGP2を操作してLED1とLED2が点灯/消灯するようにします。250ms経過後に750msのタイマを動作させてタイプアップ後にGP0とGP2を切り替えるソフトを開発します。SW1を押すとLED3が点灯します。

変数の準備と各種初期化

void main(void) {
    //各種初期化    
    ADCON0 = 0x00;  //ADC使用しない 
    ANSEL = 0x00;   //アナログモードは使用しない
    CMCON = 0x07;   //コンパレータ使用しない
    TRISIO = 0x08;  //GP3はDI・その他はDO
    GPIO = 0x00;    //ポートの設定 1:High 0:Low
    OPTION_REG = 0x0F;  //プリスケーラ128 WDTで使用
    //割り込み設定
    INTCON = 0xC0;  //割り込み許可とPIE1を許可
    PIE1 = 0x01;    //タイマ1割り込みを許可
    //タイマー1初期化
    T1CON = 0x00;
    TMR1H = TIME_START_H;//タイマ初期化
    TMR1L = TIME_START_L;//タイマ初期化
    TMR1IF = 0;//割り込みクリア 
    TMR1ON = 1;//タイマ1ON
    //タイマー1初期化 END
    cnt10ms = 0;
    TimerLed1 = TIME_LED_CNT_MAX;   //TimerLed1の初期値セット
    TimerLed2 = TIME_OFF;
    //各種初期化 END

繰り返し使う値においては#defineで定義しておくと#define内で値を修正することでプログラム内の値をすべて修正することができます。TimerLed1を250ms後にタイムアップしたいので初期値としてTIME_LED_CNT_MAXをセットしています。

タイマー管理と動作を考える

    while(1){
        CLRWDT();
        
        if( cnt10ms >= TIME_BASE_MAX){  //タイマー管理
            cnt10ms-= TIME_BASE_MAX;
            
            if( TimerLed1 > TIME_UP ){ //50ms毎に値が更新される
                --TimerLed1;
            }
            if( TimerLed2 > TIME_UP){ //50ms毎に値が更新される
                --TimerLed2;
            }
        }
        
        if( TimerLed1 == TIME_UP ){
            TimerLed1 = TIME_OFF;   //タイムアップしたのでOFFにする
            GPIO0 = 1;
            GPIO2 = 1;
            TimerLed2 = TIME_LED_CNT2_MAX; //750msタイマをセット
        }
        if( TimerLed2 == TIME_UP){
            TimerLed2 = TIME_OFF;   //タイムアップしたのでOFFにする
            GPIO0 = 0;
            GPIO2 = 0;
            TimerLed1 = TIME_LED_CNT_MAX;   //250msタイマをセット
        }
        
        if( GPIO3 == 1){
            GPIO5 = 1;
        }
        else{
            GPIO5 = 0;
        }
    }
//-----------------割り込み関数-------------------------
void __interrupt() intr( void){
    
    if( TMR1IE == 1 && TMR1IF == 1){ //タイマ1の割り込みであるか
        TMR1IF = 0;//割り込みフラグをクリア
        ++cnt10ms;
        TMR1ON = 0;
        TMR1H = TIME_START_H;//タイマ初期化
        TMR1L = TIME_START_L;//タイマ初期化
        TMR1ON = 1;//タイマ1ON
    }
}

タイマー管理処理はTMR1割り込みでカウントしているcnt10msの回数が規定回数以上になったときにタイマーを更新する処理を行います。

最小のタイマーの分解能はTMR1のオーバーフロー時間になるので10msを何分周するかを決めているのがTIME_BASE_MAXになります。今回は5を設定しているので50msになります。

TimerLed1やTimerLed2をカウントスタートしたいときは変数に値をセットするとタイマー管理処理によってタイマー値が更新されていきタイムアップします。

タイムアップした時に必要な処理を実装することでタイムアップ後の動作を作ることができます。

今回の例ではTimerLed1とTimerLed2を交互にセットしながらGP0とGP2を操作してしています。これを応用することで様々な時間に対応した時間管理ができるようになります。

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。チェックはしておりますがコメントなど細かな部分で間違っている可能性があります。参考としてお使いいただければと思います。

// CONFIG
#pragma config FOSC = INTRCIO 
#pragma config WDTE = ON    
#pragma config PWRTE = OFF   
#pragma config MCLRE = OFF  
#pragma config BOREN = OFF  
#pragma config CP = OFF     
#pragma config CPD = OFF   

#include <xc.h>
//--------------定数定義----------------------------
#define	TIME_START_H 0xD8  //タイマスタート位置 65536- 55536 = 10000
#define	TIME_START_L 0xF0   //タイマスタート位置 65536- 55536 = 10000
#define TIME_OFF -1         //タイマーを使用しない場合
#define TIME_UP 0           //タイムアップ
#define TIME_START_H 0xD8   //TMR1Hの初期値
#define TIME_START_L 0xF0   //TMR1Lの初期値
#define TIME_LED_CNT_MAX 5  //250ms経過
#define TIME_LED_CNT2_MAX 15    //750ms経過
#define TIME_BASE_MAX 5     //ベースタイマカウント値
//--------------変数定義----------------------------
unsigned char cnt10ms;
char TimerLed1;
char TimerLed2;

void main(void) {
    //各種初期化    
    ADCON0 = 0x00;  //ADC使用しない 
    ANSEL = 0x00;   //アナログモードは使用しない
    CMCON = 0x07;   //コンパレータ使用しない
    TRISIO = 0x08;  //GP3はDI・その他はDO
    GPIO = 0x00;    //ポートの設定 1:High 0:Low
    OPTION_REG = 0x0F;  //プリスケーラ128 WDTで使用
    //割り込み設定
    INTCON = 0xC0;  //割り込み許可とPIE1を許可
    PIE1 = 0x01;    //タイマ1割り込みを許可
    //タイマー1初期化
    T1CON = 0x00;
    TMR1H = TIME_START_H;//タイマ初期化
    TMR1L = TIME_START_L;//タイマ初期化
    TMR1IF = 0;//割り込みクリア 
    TMR1ON = 1;//タイマ1ON
    //タイマー1初期化 END
    cnt10ms = 0;
    TimerLed1 = TIME_LED_CNT_MAX;   //TimerLed1の初期値セット
    TimerLed2 = TIME_OFF;
    //各種初期化 END
    
    while(1){
        CLRWDT();
        
        if( cnt10ms >= TIME_BASE_MAX){  //タイマー管理
            cnt10ms-= TIME_BASE_MAX;
            
            if( TimerLed1 > TIME_UP ){ //50ms毎に値が更新される
                --TimerLed1;
            }
            if( TimerLed2 > TIME_UP){ //50ms毎に値が更新される
                --TimerLed2;
            }
        }
        
        if( TimerLed1 == TIME_UP ){
            TimerLed1 = TIME_OFF;   //タイムアップしたのでOFFにする
            GPIO0 = 1;
            GPIO2 = 1;
            TimerLed2 = TIME_LED_CNT2_MAX; //750msタイマをセット
        }
        if( TimerLed2 == TIME_UP){
            TimerLed2 = TIME_OFF;   //タイムアップしたのでOFFにする
            GPIO0 = 0;
            GPIO2 = 0;
            TimerLed1 = TIME_LED_CNT_MAX;   //250msタイマをセット
        }
        
        if( GPIO3 == 1){
            GPIO5 = 1;
        }
        else{
            GPIO5 = 0;
        }
    }
}
//-----------------割り込み関数-------------------------
void __interrupt() intr( void){
    
    if( TMR1IE == 1 && TMR1IF == 1){ //タイマ1の割り込みであるか
        TMR1IF = 0;//割り込みフラグをクリア
        ++cnt10ms;
        TMR1ON = 0;
        TMR1H = TIME_START_H;//タイマ初期化
        TMR1L = TIME_START_L;//タイマ初期化
        TMR1ON = 1;//タイマ1ON
    }
}
//---------------------end file----------------------------

関連リンク

PICマイコンを使ってマイコンのレジスタの設定やMPLAB X IDEのプラグインであるMCCを使用して動作確認したことについてまとめています。

PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ

PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ

GEEKJOB-未経験からITエンジニアに【オンライン無料体験】

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました