HOME  >>  LED電子工作集 > PICマイコンとC言語の勉強  P-8 時間を計測しよう

PICマイコンとC言語の勉強  P-8 時間を計測しよう

■ はじめに

 今、PICマイコンとC言語の勉強に挑戦している。 そのいきさつは、鉄道模型工作実験室 の別室ブログ 「レイアウトに速度計を設置しよう」 ( 2019/2/22 )にて紹介しているので参照ください。

 学習の第2ステップも最後の難関に差し掛かった。 ゲートを通過する時間を測定測定するため方法を習得しておくことにする。

 

■ 測定の要件

 まず、どのような時間を測定するのかまとめておこう。 二つのゲートを通過する時の通過時間を計算しておきます。

    

 測定ゲートの間隔を L = 120 mm とすると、スケールスピード 30Km/h で通過する時間は、

       120 mm / 55.6 = 2.16 sec

となります。 これ以上遅く走らせる事は無いので、最長でも 2 sec は測定できるタイマーが必要なことが分かる。

 ちなみに、スケールスピード 300 Km/h で駆け抜けた場合は、216 msec かかることになります。

 

■ 通過時間を計測する方法

 時間計測に関しては、Arduino を使用し方法として以前に「レイアウトに速度計を設置する」で報告しています。 その方法は、

 if (slit1 == LOW) {
     t1 = millis();
     while (slit2 == HIGH) {
         slit2 = digitalRead(SLIT2_PIN) ;
     }
     t2 = millis();
     tt = t2 - t1 ;
     V =108000 / tt ;

 スリット1を通過した時点で時刻を計測し、スリット2を通過するまで待ちます。 そして、スリット2を通過したらその時の時刻を計測してその時間差を計算し、その値から車速を計算しています。

 これだけの記述で、二つのスリットを通過する時間を計測していました。 今回使用するPICマイコンにはこのような便利な方法がないようなので、何か工夫が必要です。 さらに、スリット2を通過するまでマイコンはウエイト状態です。 今回、この待ち時間のある計測方法では、7セグメントLEDの表示が消えてしまうのです。 測定中は表示が消えても良いとするなら別ですが、このダイナミック点灯方式では別の方法が必要となります。

 考えられる方法として、スリットの通過を割込み方式で検出して、タイマーを使って時間を計測する方法です。 この方法の使い方について参考本やネットであれこれ調べました。 例えば、

  1. タイマー割込みを使ってLEDを点滅させる方法(一定時間毎に点滅させる)
  2. 超音波センサーで物体の距離を測る方法(音の反射時間を測っているから)
  3. キャプチャ機能でパルスを計測する方法 (ハルスの幅を測定する)
  4. ストップウオッチ(ボタンを押して時間を測る)

等で、割り込みやタイマーを使った方法が紹介されていました。

 しかし、どれもややこしい説明がいっぱいで、超初心者にはチンプンカンプンの事がいっぱい出てきました。 さらに、コンパイラの違いやC++言語での使用による新しい用語を理解するのにウロウロです。 それでも、タイマーやキャプチャ機能の使い方を何とか理解できたと思っていましたが、ふと、測定精度に関して思い当たることが有りました。

   自分が求める時間計測の測定精度はどれくらい必要なのか?

 何を考えたかと言いますと、LEDのダイナミック点灯ループ内で、スリットのON/OFF を検知できれば、通過信号のややこしい割込み処理は必要ないのではとひらめいたのです。 測定誤差を 5% 程度とするなら、216 msecの場合では 10msec となる。 

    LEDのダイナミック点灯ループのサイクル内で充分ではないのか?

 ループの中では、LED点灯時間を設定している __delay_ms(5) が一番効いているのでと狙いをつけているからです。

/*****************************************
*     7segLED Test 4
*           2019/3/6
*           PIC16F1827   MPLAB X   XC8
******************************************/
 ( 途中省略 )
long val;      //表示する数の値
long mstimer;  //ミリ秒タイマ
int digit;     //表示する桁位置
int flg;       //計測中を示すフラグ
int segment_data[]= [0xDE,0x42,0x5D,0x57,0xC3,0x97,0x9F,0xD2,0xDF,0xD7];
               //表示する数字のパターンの行列
char st[3];    //表示する桁の数字、3個の行列

void interrupt MStimer(void)  //TIMER2 の割込み
{
    if(TMR2IF==1){     //TIMER2の割込みフラグをチェック
        mstimer++;     //ミリ秒タイマをカウントアップ
        TMR2IF=0;      //フラグを消す
    }
}

void main()
{
    OSCCON = 0b01110010;  //内部クロックを8MHzに設定
    ANSELA = 0b00000000;  //全てデジタルI/O
    ANSELB = 0b00000000;  //全てデジタルI/O
    TRISA  = 0b00000000;  //全て出力
    TRISB  = 0b00010001;  //入力ポートを設定
    PORTA  = 0b00000000;  //出力ピンをすべてLOWにする
    PORTB  = 0b00000000;  //出力ピンをすべてLOWにする
    T2CON  = 0b00111100;  //TIMER2のプリ&ポストを設定
    TMR2  = 0;     //TIMER2の初期化
    TMR2IF =0;     //TIMER2の割込みフラグを0にする
    TMR2IE =1;     //TIMER2割込み許可
    PEIE  =1;      //周辺機器の割込み許可
    GIE  =1;       //全体の割込み許可

    digit=0;      //初期値の設定
    flg=0;
    val=0;
    stimer=0;
    
    while(1){     //ループ処理
    if(RB4==0){           //INゲートがONなら
            if(flg==0){       //測定中でないこと
                mstimer=0;    //ミリ秒タイマを0
                flg=1;        //測定中の表示
            }
        }
        if(RB0==0){           //OUTゲートがONなら
            if(flg==1){       //測定中であれば
                val=mstimer;   //カウンタ値をVOLに
                flg=0;            //測定待機中の表示
            }
        }
       
        st[2]=val/100;           //100の位の数字
        st[1]=val/10-st[2]*10;   //10の位の数字
        st[0]=val%10;            //1の位の数字
        
        if(digit==2){         //100の位の数字を表示させる
            PORTA = segment_data[st[2]]; //出力ポートに指示
            RB1 = 1;
            __delay_ms(5);            //5msec表示
            RB1 = 0;                  //表示を消す
            __delay_us(100);
        }
        
        if(digit==1){        //10の位の数字を表示させる
            PORTA = segment_data[st[1]];
            RB2= 1;
            __delay_ms(5);
            RB2 = 0;
            __delay_us(100);
        }
        
        if(digit==0){         //1の位の数字を表示させる
            PORTA = segment_data[st[0]];
            RB3 = 1;
            __delay_ms(5);
            RB3 = 0;
            __delay_us(100);
        }
        
        digit ++;           //表示する桁をカウントアップ
        if(digit ==3)  digit=0;   //3になったら0に戻す
    }
}

 

● ループ時間の測定

 そこで、先回の実験装置を使って、このサイクル時間を計測してみました。

 7segLED-Test3 のプログラムを走らせ、100毎のラップタイムを測定した結果、10回の平均で、10.48 sec であった。 これは 1カウントでは 0.105 sec になります。 さらの、LEDへの表示は 20 ループ毎に実施していましたから、1ループには 5.25msec 掛かっていることが分かりました。

     これはいけますね!

 と言うことで、キャプチャ機能などを使用せずに実施することにしました。

 

● ミリ秒タイマの工夫

 時間を計測するための道具として、ミリ秒タイマを作ることにしました。 これには多くの参考資料が有りましたので勉強になりました。 まず、内部クロックを設定してプリスケーラとポストスケーラの倍率を決め、8ビットカウンターがオーバーフローする時間を計算した。 このオーバーフローのフラグを利用して、設定した変数、「ミリ秒タイマ」をカウントアップさせようとするのである。

 内部クロックを 8MHzとすると、Fosc/4 からは 2MHz のクロックが出てくる。 即ち、0.5μsec のクロックとなる。 これをポストスケーラで8倍にすると、4μsec となり、8ビットカウンターがオーバーフローする時間は 256倍となるので、1.024msec となるのだ。

 タイマーは、TIMER2 を使えば充分である。 設定は OSCCON = 0b01110010 で8Mhz を指定し、T2CON = 0b00111100 にて TIMER2 のプリスケーラを 1:1 に、ポストスケーラを 1:8 に設定する。 そして、TMR2IE = 1 としてTIMER2 の割込みを許可し、PEIE = 1GIE = 1 にして周辺割込みと全体割り込みを許可する。

 あとは、カウントアップする「ミリ秒タイマ」を設定しておけばタイマー設定は完了である。

 

■ プログラムの作成

 全体のプログラムは先回の 7segLED Test 3 を修正しする。 通過時間から計算したスケールスピードをLEDに表示させるのだ。

 通過ゲートからのINとOUTの信号は、INゲートは RB6 ポートに、OUTゲートは RB0 ポートに入力させるよう設定している。 もし、うまく行かなかった場合には、CCPキャプチャ機能を使う事も考えたからである。

 入出力ポート設定は、 TRISB = 0b00010001 となる。

 

● 割り込み設定

 TIMER2 からの割込みによって「ミリ秒タイマ」をカウントアップする方法は、割込み設定を使って、1.024msec 毎にカウントアップするように設定した。

 void interrupt MStimer(void)  
 {
   if(TMR2IF==1){  //TIMER2の割込みチェック
      mstimer++;   //ミリ秒タイマをカウントアップ
      TMR2IF=0;    //フラグを消す
  }
 }

● 通過時間の計測と表示

 後は、LEDダイナミック点灯ループの中で、入力ポートの信号を取り入れ、カウンタの値をクリアーしたり読み出したりして、通過時間を求め、それを表示させれば良いのである。 今回は、スケールスピードの計算は無しにして、通過時間だけを表示させることにする。

 測定中は flg を設定しておくと、ダブル動作による誤作動をなくせるし、チャタリングも防止できるのだ。 今回の回路では通過信号を負論理で取り入れる様にしているのでノイズにも強いと思っている。

 

 作成したプログラムを右に示す。 コンフィグ等の設定は先回と同じなので省略しています。

◆ お詫びと訂正

 プログラムの一部に間違いがありました。 点灯させるLEDの指定方法がトライ時に実施したままのデータを流用してしまいました。 実際には右上の様に修正ずみです。 お詫びして訂正いたします。

 

● コンパイラのトラブル

 今回もコンパイラのトラブルに襲わられてしまいました。 自信をもって作ったプログラムですが、コンパイラが通らないのです。 どうやら void interruput の部分が怪しいのですが、 原因が分からず、これも半日ほどウロウロしてしまいました。

 スタンドアロンで使っているVISTA まで疑ったのですが、“ XC8 interruput エラー ” にて検索してみました。 すると一発で出てきましたね!   6時間位立ち往生された方の貴重なブログが参考になりました。 XC8 Compiler の Project Properties のなかで、C Standard という設定項目を C99 -> C90 に変更すればよいとのことでしたので、早速修正すると、見事にコンパイルを通すことが出来ました。 初心者が通らなければならない関門の一つですかね。
  コンパイルにはハンマーのアイコンをクリックするのでビルドするとも言うらしいですね。 素人にはどちらでもいいのですが・・・・・。

 

■ 動作の確認

 実験回路に、通過ゲートの代わりとして二つのタクトスイッチを追加して信号の入力を実施するようにした。

 二つのスイッチを使って、計測された時間の検証を実施しました。 さすが、”実験室”ですね・・・・・・・!

 ただし、ミリ秒単位の計測は無理なので、表示時間を 1/10 になるように修正してストップウオッチで計測してみました。 上右の写真に示す様に、ストップウオッチは 5.78 秒を示し、LEDには 575 と表示されています。 測定結果を下に示す。

StopW. LED 差(ms) 勾配 遅れ(ms)
msec 表示×10 1.031122
4220 4200 -20 4331 111
8000 7890 -110 8136 136
1730 1880 150 1939 209
1300 1350 50 1392 92
870 930 60 959 89
4210 4210 0 4341 131
2430 2510 80 2588 158
6140 6050 -90 6238 98
5410 5320 -90 5486 76
3470 3400 -70 3506 36
680 790 110 815 135
5060 5020 -40 5176 116
8890 8740 -150 9012 122
9110 8970 -140 9249 139
平均値
118

 このミリ秒タイマは 1.024msec を 1msec としてカウントしていますので少し修正する必要があります。 そこで全体のプロット点から近似直線式をもとめ、その勾配値をその修正値としました。 そして、この値から想定される実時間とストップウオッチの計測時間の差を求めたのが遅れと示した欄の値です。

 本当は、ゲート通過の計測タイミングのずれ ( 5msec 程度を予想していた ) を検証したかったのですが、ストップウオッチとタクトスイッチの動作タイミングのズレが、およそ 100msec もあるので、参考にはなりませんでした。 でも、勾配値を求める事が出来たので、スケールスピードの計算にはこの修正値を生かすことにします。

 表示結果を10倍して、計測時間の msec と比較したグラフです。 Y切片が -118 になっているのは上記の遅れ時間を示しています。

  尚、表示桁が3個しかありませんのでこれを超える場合は表示がクシャクシャになってしまいます。 表示桁を増やすとか、表示ロジックを変えるなどの工夫が必要です。

     ***************************************************

 今回の実験で、計測結果を表示させる方法の目途が付きました。 これから実際の測定装置の工作を始めることにします。