忍者ブログ

Fグループ電子工作講座

秋月電子SH7125ボードで始めるマイコン開発

外部入力割込み(IRQ)の設定 実用編

前回は外部入力割り込みの基本設定を行いました。
外部割込みは入力される信号に可能な限り高速で反応しようとするため、ノイズに対しても強烈に反応してしまいます。また、外部入力割り込みは高速で入力される信号を検出できるため、時間の情報と連携してしようすることが多くなります。
そこで、時間情報(コンペアマッチタイマー)と連携できるように改造を行います。

誤反応防止

ブザー振動を取得する場合を例とします。
ブザーがピピピピピと鳴っているときにピの数をカウントしたい場合を考えます。
ブザーからピーと音が出ているとき、PWMの様に高速でON-OFF信号が出ています。
ピピピピピと音が出ているときは下図の様に高速でON-OFF信号が出ている状態と信号が出ていない状態が繰り返されます。

この信号を単純に割り込みとしてカウントしてしまうとピが3回出たことをカウントしたいのに、実際にはカウント値が25回になってしまいます。合計カウント数をピ1回のON-OFF数で割れば、ピの数を計算できそうですが、ピ1回のON-OFF数が一定でないと計算が合わなくなります。

そこで、一旦立ち上がりを検出したら割り込み回数をカウントアップし、一定時間経過するまでカウントアップを実行しないように設定してしまいます。また、一定時間が経過するまでに次の割り込みを検出したらカウントアップせず、受付時間だけ逐次更新します。

これでしっかりとピの回数だけカウントできるようになります。
この方法だと音に限らず、信号が不安定な場合にもノイズ対策として使用できます。

割り込み時刻の取得

今度はミニ四駆の移動速度を測定したい場合を考えてみます。
ミニ四駆のコースにセンサー2つを少し離して配置し、1つ目のセンサーが反応してから2つ目のセンサーが反応するまでの時間を測れば距離と時間で速度が計算できます。
1つ目のセンサーが反応した時刻と2つ目のセンサーが反応した時刻を記録し、差を計算すれば時間は求まります。
反応した時刻を記録するのは割り込みが発生した時点でシステム時間を取得してどこかに保存するだけです。
あとは任意のタイミングで記録した時間を読み出すだけです。

プログラム変更

コンペアマッチタイマの改造

タイマ割り込みの回で設定したコンペアマッチタイマの設定可能時刻は最短1msですが、割り込み時刻として使用するのはもう少し分解能が欲しいところです。
そこで、システム時刻を100倍の分解能で取得できるように無理矢理改造してみます。

CMTの初期化関数を以下の様に変更します。

unsigned long _time_div=0;    //システムタイマー分解能

void init_CMT0(int T)    //ミリ秒単位で割り込みを設定する
{
    long int LN;    //目標値

    //    CMT0の割り込みレベル(優先順位)を設定
    INTC.IPRJ.BIT._CMT0 = 0xA;    // ※(0x0:最低~0xF:最高)

    STB.CR4.BIT._CMT=0;            // CMTモジュールのスタンバイ解除

    //設定中なのでカウンター一時停止
    CMT.CMSTR.BIT.STR0=0;    // 0:カウント停止, 1:カウント開始

    CMT0.CMCSR.BIT.CMIE=1;    // コンペアマッチ割り込み許可 ※1で割り込みを行う
    CMT0.CMCSR.BIT.CKS=2;    // クロックセレクト[1,0]  10=>2:Pφ/128

    //CMCORの値を算出    Tミリ秒→LNカウント
    LN = 195312 *  T/1000;        // 25MHz/128=195312Hz(1カウント5.12μs)
    if(LN < 1) LN = 1;
    else if(LN > 0xffff) LN = 0xffff;//335msに相当
    _time_div=LN;

    CMT0.CMCNT = 0;            // カウンタリセット
    CMT0.CMCOR = (int)LN;    // カウント目標値セット
    CMT.CMSTR.BIT.STR0=1;    // CMT0カウント開始
}   

CMTの設定何回クロックを検出したら割り込みを発生させるか設定しました、この何回を新規に作成した変数に記録しただけです。

続いて100倍分解能の時刻取得方法

//システム時刻を100倍分解能で取得
unsigned long getsystime2(void){
    return (_systimer*100+(CMT0.CMCNT*100/_time_div));
}

とりあえず本来のシステム時刻を100倍しておきます。
これだけでは分解能は変わりません。
これにクロックを何回検出したかカウントしている内部変数の値を足し合わせました。
これでおおよそ100倍の分解能になります。
割り込みが発生するタイミグが悪いとカウント値がおかしな事になるはずでなので、真面目に分解能を上げたい場合は、きちんとvoid init_CMT0(int T)を編集してください。

外部割込み関数の変更

まず、受信時刻を格納するための変数を用意します。

static unsigned long _intrupt_time[4];        //有効割り込み発生時刻
static unsigned long _intrupt_time_last[4];    //最終割り込み発生時刻
static long _intrupt_time_sub[4];    //割り込み発生間隔

_intrupt_timeはピの最初の割り込みでカウントアップするときのみ更新します。
_intrupt_time_lastは上の図で25回発生する割り込み毎に更新します。
_intrupt_time_subはピから次のピまでの時間を記録するのに使用します。

これら変数はinit_IRQ()内で_intrupt_countと一緒に初期化しておきます。
    for(i=0;i<4;i++){
        _intrupt_count[i]=0;    // カウント値を初期化
        _intrupt_time[i]=0;
        _intrupt_time_last[i]=0;
        _intrupt_time_sub[i]=0;
    }

外部割込み関数を以下の様に変更します。
void IRQ_INT_IRQ0(void)
{
    unsigned long time;
    unsigned long time_o;
    time=getsystime2();            //現在の時刻を取得(100倍分解能)
    time_o=_intrupt_time_last[0];    //最終割り込み発生時刻を読み出し
    _intrupt_time_last[0]=time;        //最終割り込み発生時刻を更新
    if((time-time_o)>100){        //最終割り込み発生時刻から1ms以上経過
        _intrupt_time_sub[0]=(long)(time-_intrupt_time[0]);//差分を計算
        _intrupt_time[0]=time;    //有効割り込み発生時刻を更新

        _intrupt_f|=1;
        _intrupt_count[0]++;
    }
    INTC.IRQSR.BIT.IRQ0F=0;
}

とりあえず現在の時刻を取得し、最終割り込み発生時刻_intrupt_time_lastを更新します
今回割り込みが発生した時刻timeと前回割り込みが発生した時刻time_oを比較します。
差が1ms(100)以下であれば計算もカウントもしません。
差が1ms(100)以上であれば色々計算します。
ピを検出し始めた時刻_intrupt_timeも更新します。
IRQ_INT_IRQ3(void)についても同様に変更します。

最後に記録した各時間を取得する関数を追加します。


//割り込み時刻を取得する
unsigned long get_irq_time(int port){
    if(port<0||port>3)
        return 0;
    return _intrupt_time[port];
}

//差分時間を取得する
long get_irq_subtime(int port){
    if(port<0||port>3)
        return 0;
    return _intrupt_time_sub[port];
}

//2端子の差分時間を取得する
long get_irq_subtime2(int portA,int portB){
    if(portA<0||portA>3)
        return 0;
    if(portB<0||portB>3)
        return 0;
    return (long)(_intrupt_time[portB]-_intrupt_time[portA]);
}

2端子の差分時間は先の説明のミニ四駆の速度計算に使うやつです。
最終的なファイルは以下となります。
タイマー設定(intCMT.cintCMT.h
割り込み設定(intIRQ.cintIRQ.h

メインプログラム

#include "iodefine.h"
#include <stdio.h>
#include "io_setup.h"
#include "intCMT.h"
#include "intSCI.h"
#include "intIRQ.h"

void main(void){
    //変数定義
    char trans_buf[100];
    unsigned long timer;    //ループ制御用タイマー
    int i;

    //各機能の初期化
    hardware_setup();    //汎用IOの初期化
    init_CMT0(1);        //タイマーの初期化
    init_SCI1();        //シリアル通信の初期化
    init_IRQ();

    timer=getsystime();    //現在時刻の取得(intCMT)

    write_sci1("sh7125 connect\n");

    while(1){    //無限ループ開始
        if(timer<getsystime()){            //タイマー設定時刻を経過
            timer=getsystime()+50;        //50ms後に設定
            if(get_intrupt_f()){        //外部割込みが発生していた
                clr_intrupt_f();        //フラグを解除
                //sprintf(trans_buf,"%d,%d\n",get_irq_cnt(0),get_irq_cnt(3));    //割り込み回数を文字列に格納
                sprintf(trans_buf,"%d,%d\n",get_irq_time(0),get_irq_time(3));    //割り込み時刻を文字列に格納
                //sprintf(trans_buf,"%d,%d\n",get_irq_subtime(0),get_irq_subtime(3));    //割り込み間隔を文字列に格納
                //sprintf(trans_buf,"%d\n",get_irq_subtime2(0,3));    //割り込み間隔を文字列に格納
                write_sci1(trans_buf);    //通信で出力
            }//intrupt_f
        }//50ms
    }//end while
}

今回修正した割り込み設定(intIRQ.cintIRQ.h)タイマー設定(intCMT.cintCMT.h)の他に前回に引き続き汎用IO設定(io_setup.cio_setup.h)とシリアル通信一式(intSCI.cintSCI.h)を使用しています。
これで色々遊べます。
PR

コメント

プロフィール

HN:
ぼんどF博士
性別:
男性
自己紹介:

最新コメント