2023年3月22日水曜日

赤外線で通信する(その5)眠りながら仕事をさせる

 その3,その4の処理ではメインプログラムの主要部分は、

     while(TRUE){
        if((r1state==RxSTOP)&&(nbuf < NBUF)){
            recvUART1(buf[rbufn],BUFLEN);
        }
        
    //途中省略
   
        if(t2state==TxCMPLT){
            tbufn++; tbufn %= NBUF;
            nbuf--;
            t2state = TxSTOP;
        }
    }

等のようにひたすらぐるぐる走り回って無駄に計算資源を消費する構造になっている。 しかし仕事が無いときはここでSLEEP()を実行してCPUを眠らせ省エネに努めたい。つまりOSのカーネルのアイドルプセスみたいに、

    while(TRUE){
       SLEEP();
    }

というメインプログラムとして、全ての処理を割込みの中で行いたい。 

 そう思ってメインループでいきなりSLEEP()を実行したがSleepモードに入るとPICは止まってしまって動かない。マニュアルを読むと、SleepモードにはSLEEPとIDLEの2種類があることが分かる。SLEEPではCPUのほか周辺装置(ペリフェラル)も止まり、IDLEではCPUは止まるがペリフェラルは動く(いずれの場合も割込みが発生するとSleepは解除される)。つまり何か入出力を行いながらCPUを眠らせるにはIDLEにする必要があるのだ。これを制御するのがCPUDOZEのIDLENビットだ。

 今回はEUSARTを使うのでSLEEP()を実行したときモードをIDLEにする必要があり、そのためには予め、

 CPUDOZEbits.IDLEN   = ON;     //IDLE mode when SLEEP()

としておく必要がある。ここで注意が必要なのはSleepモードに入りCPUが止まるときCPUクロック(Fosc)も止まる事である(実際にはCPUクロックを止める事でCPUを止めているのであろう)。ペリフェラルの中にはFosc或いはそれを四分周したFosc/4を同期をとるために使っているものがあり、その場合たとえSleepモードでIDLEになってもペリフェラルは止まってしまう。これを纏めると次表になる。

 EUSARTもFosc/4を使うペリフェラルの1つであり、これを止めないためにはEUSARTはFoscに依存しない非同期モードで動かす必要がある。

 今回のプログラムは、その4で作ったプログラムの一部を次の様に改修したものになる。先ずメインのループは上で述べた通り、

     while(TRUE){
       SLEEP();
    }

とする。そうするとCPUは割り込みが発生するまで何もせず眠る。割り込みが発生して一旦目覚めてもまたすぐSLEEP()を実行するので再び眠りにつく。つまり初期設定とペリフェラルの起動を終えたら何もしないメインプログラムである。

 次に元々メインループ内でやっていた部分をどう実行するかであるが、今回はタイマーで一定周期で割込みをかけてその処理の中で行う。2400bpsのシリアル通信を使うから1バイト送受するのに4ms必要である。ただし1バイト毎にメインルーチンで処理する必要はないのでとりあえず5ms毎にタイマー割込みをかけて、その割込み処理の中でメインループでやっていた処理を実行することとする(もっと短い時間間隔でも可能)。

PIC16F15325はTimer0~2の3つのタイマーを内蔵しており、各タイマーは各々性格が少し異なっている。ここではシンプルなTimer0を8ビット非同期モードで使って約5ms毎に定期的に割り込みを発生させる


タイマーの入力には31KHz(T=32.26μs)のLFINTOSC発振器を使い、156分周して5ms毎の割込みを発生させる。 ソフト的には次のように記述する。

    T0CON0          = 0x80;     //Enable, 8bit, postscaler=1:1
    T0CON1          = 0x90;     //LFINTOSC, Async, Prescaler=1:1
    TMR0H           = 156;      // tick for every 5ms
    PIR0bits.TMR0IF = OFF;
    PIE0bits.TMR0IE = ON;

割込み処理ルーチンにはタイマー割込みチェックを追加する。タイマーの割込みフラグは自動的にクリヤーされないので処理が終了した後ソフト的にクリヤーする必要がある。

 void __interrupt() isr(){
    if(PIR3bits.RC1IF && PIE3bits.RC1IE) recvISR1();
    if(PIR3bits.TX2IF && PIE3bits.TX2IE) sendISR2();
    if(PIE0bits.TMR0IE && PIR0bits.TMR0IF){
        tickISR();    PIR0bits.TMR0IF = 0;
    }
}

最後にメインループに相当する処理を5ms毎の割込みの中で処理するプログラムを作る(メインループ内から移植する)。

 void tickISR(){
    if((r1state==RxSTOP)&&(nbuf < NBUF)){
        recvUART1(buf[rbufn],BUFLEN);
    }

    if(r1state==RxCMPLT){
        nbuf++;
        rbufn++;  rbufn %= NBUF;    // next buffer
        r1state = RxSTOP;
    }

    if((t2state==TxSTOP) && (nbuf>0)){
        sendUART2(buf[tbufn]);
    }

    if(t2state==TxCMPLT){
        tbufn++; tbufn %= NBUF;
        nbuf--;
        t2state = TxSTOP;
    }

これでプログラムは完全割込みで動き始める。つまりCPUは殆ど眠っておりタイマーの5ms毎、及びEUSARTの割込みが発生したときだけ動くようになる。送信部のPIC16F18326も同じように使えるTimer0を内蔵しており、同じやり方に変更できる。

これを発展させるとOSのカーネルを作る事が出来るが、小さいPICでそこまでやるメリットも無いのでここまでに留めておく。

 

【参考】タイマーについて
 今回タイマーを扱った。PIC16F15325は(
ウオッチドッグタイマーなど特殊なものを除き)3つのタイマーを持っているが、各々性格が少し異なっている。いずれのタイマーも8or16ビットカウンタの前後にプエリスケーラとポストスケーラを持っている。プリスケーラは1/2^n形式の分周を行い、ポストスケーラは1/n形式の分周を行う。

Timer0は最も基本的なタイマーで全てのPICマイコンに実装され、8ビットモード時は設定した周期でフリーランのタイマーとして動作する。16ビットモードの時はタイムアップ毎にカウント値を設定しなおす必要がある。

Timer1は16ビットのタイマー/カウンタでクロックに対するゲート機能を備えておりゲート幅を調整することで複雑な動きが出来るようである。

Timer2は 8ビットのカウンタで、何らかの信号とトリガーとしてワンショット、或いはモノステーブルのタイマーとしても動作できるし、単なるフリーランのタイマーとしても使える

PIC16F18326はもっと多くのタイマーを内蔵しており、Timer0はPIC16F15325のTimer0と同じ、奇数番のタイマーはPIC16F15325のTimer1と、偶数番のタイマーはPIC16F15325のTimer2と似ているが機能が制約されている。例えば偶数番のタイマーはクロックソースがFosc/4に固定されているのでSleep状態では作動しないし、一方奇数番のタイマーはクロックソースやゲート信号の選択肢が4つしかない。

 


0 件のコメント:

コメントを投稿