PICマイコン(PIC12F675)タイマのカウント値を計算する

組み込みエンジニア

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

マイコンのソフトを開発するときタイマーを使うことが良くあります。PIC12F675の16ビットタイマであるTMR1を使いますが、タイマ値は1バイトずつの異なるレジスタで管理しているためカウント値を確認する場合は計算が必要です。

カウント値の計算はビットをシフトさせて計算しています。この考え方はマイコンを問わずタイマだけでなく様々な分野で応用することができます。TMR1の使い方は下記記事を参考にしてください。

PICマイコン(PIC12F675)のタイマー1の設定と使い方

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

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

TMR1の初期設定

TMR1を割り込みを500ms毎に発生するように初期設定を行い、TMR1のカウント値を確認しながらLEDの点灯/消灯を切り替えることを目的とします。

プリスケーラの設定

TMR1のプリスケーラを1:8にしてオーバーフローまでの時間を長くします。初期値を設定しない場合は524.3msでオーバフローになります。プリスケーラはT1CON内のビットを変更する必要があります。

引用:PIC12F675のデータシート(T1CONレジスタ)
引用:PIC12F675のデータシート(T1CONレジスタ)

TICONレジスタのbit5-4に11をセットしてプリスケーラを1:8にします。これより動作開始前のTICONの値は0x30となります。

TMR1で500msタイマを作る

TMR1のプリスケーラを1:8に設定したのでオーバフローまでの時間は524.3msになっています。まずは初期値を設定して500msのタイマーを作ります。

動作クロックに対するプリスケーラなのでTMR1のカウント値が8us毎に1上がることになります。作りたいタイマーは500000usなので8で割ると62500になります。

TMR1で500msタイマの作る例

オーバーフローまでのカウント数は65536なので初期値は65536-62500=3036になります。

3036を16進数に置き換えると0x0BDCになるので初期値にセットします。

次にLEDの点灯/消灯を切り替えるタイミングである250ms経過時のカウント値を計算します。

500msタイマの250ms経過時のカウント値の説明図

62500の半分は31250となるので最初に求めた初期値分を加えて250msが経過するときのカウント値は31250+3036=34286(0x85EE)となります。

メイン関数内で現在のTMR1のカウント値を計算しながら0x85EEと比較してLEDの点灯/消灯を切り替えるソフトを作っていきます。

TMR1のカウント値を計算する

TMR1は16ビットタイマーなので計算値は16ビットの変数を準備して計算して求めます。メイン関数の前にunsigned shortの型でtmr1cntを宣言しています。unsignedは符号なしの変数として扱うという意味です。shortは2バイトを扱う変数になります。unsigned shortは符号なしの2バイトの変数になります。

//--------------変数定義----------------------------
unsigned short    tmr1cnt;

XC8コンパイラーのバージョンによっては16ビット値をそのまま利用することができます。

シフト演算する方法は1バイトデータを2バイトデータなどに拡張する場合に使える方法でマイコンを問わず応用できる考え方です。

PIC12F675のTMR1値の作り方

PIC12F675はTMR1のタイマー値は8ビットずつにTMR1HとTMR1Lに分けて管理しているのでこれらを考慮してtmr1cntに値を代入する必要があります。

TMR1のカウント値の管理
TMR1のカウント値の管理

tmr1cntにTMR1Lの代入するならビットの位置がbit0からbit7であるため加算するだけでよいのですが、TMR1Hを代入する場合には注意が必要です。TMR1Hはtmr1cntの8ビット目から代入する必要があります。この計算はシフト演算を行うと簡単に実現できます。

シフト演算について

シフト演算とは値を2進数で考えたときに右にシフトするか左にシフトするかで値を変化させることです。2進数でのシフトになるので1/2倍若しくは2倍になります。

値を右にシフトする場合: 値 >> 1 (1/2倍)
値を左にシフトする場合: 値 << 1 (2倍)

のように記述します。このシフトを使うことでtmr1cntにTMR1Hを8ビットずらして代入することができます。THR1Hをシフトしていく様子を見てみましょう。

シフト演算の例
シフト演算の例

上の例では左に1回、4回、8回の例をあげています。シフトするごとに値が2倍、4倍、8倍になっていることが分かります。

tmr1cntにTMR1Hを8回左にシフトして代入し最後にTMR1Lを加算することでtmr1cntによるタイマー値の算出ができます。ソースコードを示すと以下のようになります。

tmr1cnt = TMR1H;
tmr1cnt = (tmr1cnt << 8) + TMR1L;

回路図とTMR1の値を計算する処理

TMR1のカウント値を使ってLEDを切り替える動作確認の回路図
TMR1のカウント値を使ってLEDを切り替える動作確認の回路図

GP0とGP2を操作してLED1とLED2が点灯/消灯するようにタイマを実装します。SW1を押すとLED3が点灯するようにします。

変数の準備と各種初期化をしよう

#include <xc.h>
//--------------定数定義----------------------------
#define LED_ONOFF_CHG_CNT   0x85EE
//--------------変数定義----------------------------
unsigned short    tmr1cnt;

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 = 0x30;
    TMR1H = 0x0B;//タイマ初期化
    TMR1L = 0xDC;//タイマ初期化
    TMR1IF = 0;//割り込みクリア 
    TMR1ON = 1;//タイマ1ON
    //タイマー1初期化 END
    tmr1cnt = 0;
    //各種初期化 END

#defineでLED_ONOFF_CHG_CNTを0x85EEとして定義をしています。比較する際はLED_ONOFF_CHG_CNTと比較するようにソフトを作っていきます。

値を変更するときはLED_ONOFF_CHG_CNTの数値のみを変更するだけでソース内のすべての値が変更できます。個別に値を管理していた場合の更新ミスを防ぐことができます。

tmr1cntの変数はメイン関数の外で宣言しています。関数の外で宣言した変数はソースプログラムにおいて共通の変数になります。メイン関数内で宣言した場合は、その関数内でのみ有効な値となります。

TMR1のカウント値で比較する

    while(1){
        CLRWDT();
        di();
     	TMR1ON = 0;	//タイマー1を読み込むときは一時的に止めておく。
	tmr1cnt = TMR1H;
	tmr1cnt = (tmr1cnt << 8) + TMR1L;
	TMR1ON = 1;	//タイマー1を動かす。   
        ei();
        
        if( tmr1cnt > LED_ONOFF_CHG_CNT){ //250ms経過後の処理
            GPIO0 = 1;
            GPIO2 = 1; 
        }
        else{ //250ms未満の処理
            GPIO0 = 0;
            GPIO2 = 0;           
        }
        
        if( GPIO3 == 1){
            GPIO5 = 1;
        }
        else{
            GPIO5 = 0;
        }
    }
}
//------------------割り込み関数--------------------------
//void interrupt intr( void ){}    XC8 コンパイラが2.1以前の表記
void __interrupt() intr( void){
    
    if( TMR1IE == 1 && TMR1IF == 1){ //タイマ1の割り込みであるか
        TMR1IF = 0;//割り込みフラグをクリア
        TMR1ON = 0;//タイマ1ON
        TMR1H = 0x0B;//タイマ初期化
        TMR1L = 0xDC;//タイマ初期化
        TMR1ON = 1;//タイマ1ON
    }
}

メイン関数内でTMR1のカウントを計算するソフトを実装します。TMR1Hをシフト演算する際は、レジスタの値をシフトできないことから一旦変数に代入してからシフト演算を行う必要があります。

TMR1のカウントを計算する際は、計算の途中でレジスタの値が変化しないように一時的にタイマーを止めておくとよいでしょう。データシートにも止めることが推奨されています。

TMR1のカウント値を計算しLED_ONOFF_CHG_CNT(0x85EE)と比較して大きければGP0とGP2に1をセットします。カウント数がLED_ONOFF_CHG_CNT 未満であればGP0とGP2に0をセットします。

ソースコード全体

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

// 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 LED_ONOFF_CHG_CNT   0x85EE
//--------------変数定義----------------------------
unsigned short    tmr1cnt;

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 = 0x30;
    TMR1H = 0x0B;//タイマ初期化
    TMR1L = 0xDC;//タイマ初期化
    TMR1IF = 0;//割り込みクリア 
    TMR1ON = 1;//タイマ1ON
    //タイマー1初期化 END
    tmr1cnt = 0;
    //各種初期化 END
    
    while(1){
        CLRWDT();
        di();
     	TMR1ON = 0;	//タイマー1を読み込むときは一時的に止めておく。
	tmr1cnt = TMR1H;
	tmr1cnt = (tmr1cnt << 8) + TMR1L;
	TMR1ON = 1;	//タイマー1を動かす。   
        ei();
        
        if( tmr1cnt > LED_ONOFF_CHG_CNT){ //250ms経過後の処理
            GPIO0 = 1;
            GPIO2 = 1; 
        }
        else{ //250ms未満の処理
            GPIO0 = 0;
            GPIO2 = 0;           
        }
        
        if( GPIO3 == 1){
            GPIO5 = 1;
        }
        else{
            GPIO5 = 0;
        }
    }
}
//------------------割り込み関数---------------------------
//void interrupt intr( void ){}    XC8 コンパイラが2.1以前の表記
void __interrupt() intr( void){
    
    if( TMR1IE == 1 && TMR1IF == 1){ //タイマ1の割り込みであるか
        TMR1IF = 0;//割り込みフラグをクリア
        TMR1ON = 0;//タイマ1ON
        TMR1H = 0x0B;//タイマ初期化
        TMR1L = 0xDC;//タイマ初期化
        TMR1ON = 1;//タイマ1ON
    }
}
//---------------------end file----------------------------

関連リンク

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

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

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

TECH::CAMPプログラミング教養【無料体験会】

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

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