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つしかない。

 


2023年3月7日火曜日

赤外線で通信する(その4)ソフトウェア(受信処理)編

受信部は、赤外線で送られてきたデータを赤外受信モジュール(OSRB38C9AA)で電気信号に変換しPIC16F15325のEUSART1で受信する。受信するだけでは何にもならないので受信したデータはEUSART2を使って送信し、これをPCで受けてTeraTermで内容を確認する。

メインプログラムは先ずEUSART1の受信割込みを起動する。EUSART1がデータを1バイト受信し割込みが発生すると受信レジスタから受信データを取出し、受信バッファに格納する。受信バッファが満杯になるか改行コードを受信すると受信を停止し受信完了をメインプログラムに通知する。

メインプログラムは受信状況を監視しており、受信が完了すると受信バッファを送信処理に渡し(これを送信バッファとする)EUSART2の送信割込みを起動する。USART2の送信割込みが発生すると送信バッファからデータを1バイト取出し送信レジスタに書き込む。このとき最後のデータを書き込んだ場合は送信割込みを停止し送信処理完了をメインプログラムに通知する。しかし実際にはこのとき最大2バイトが物理的に未送信で送信完了迄には更に最大8msが必要である事に注意が必要である。

 メインプログラムは3つのバッファを用意し、それらを順に使って処理を行う。つまり最初のバッファの受信が完了すると直ぐ送信に回し、同時に2つ目のバッファで受信処理を起動する。これを全3つのバッファを使って繰り返すことで疑似マルチタスク処理を行う。

送信スピードより受信スピードが速ければいずれバッファが不足し受信の取りこぼしが発生するが、受信赤外線回線は赤外線受信モジュールの特性で休止時間があるため最大効率75%だから今回は送受同速の通信でもデータが溢れることは無い筈である。

次図はPIC16F18326の割り込みロジックで、送信部で使ったPIC16F18326と同じと考えてよい。

受信部では2つのEUSARTを(同時に)使うため複数の要因で割込みが起こるが、割込みが発生した場合、1つのエントリーポイントが入口となるため、割込み処理ではどの要因で割込みが発生した識別してそれぞれ対応する処理を呼び出す必要がある。そのため割込みは次の様にする。

void __interrupt() isr(){    //割込み処理の入り口
    if(PIR1bits.RX1IF && PIE3bits.RC1IE)
                recvISR();  //UART1が受信済で割込み可なら受信処理

    if(PIR1bits.TX2IF && PIE3bits.TX2IE)
                sendISR();  //UART2が送信可
で割込み可なら送信処理
}

今回の様なフロー制御の効かない通信方式の場合、送信より受信を優先して処理するのは通信の基本である。また割込み処理中の割込み(多重割込み)は禁止されるが、送信処理の割込みでも受信データの有無が先にチェックされ、もし受信データが存在すると望まない受信処理が実行される事になるので割込み可かどうかのチェックは必須である(最初はこれが無くて上手く行かなかった)。

割り込み処理では次のような状態(state)を導入する。RxSTOPは待機状態、RxBUSYは受信中、RxCMPLTは受信完了を表す。

 typedef enum{ RxSTOP, RxBUSY, RxCMPLT }    rxstate;
 

作業領域は次の様にする。

rxstate     r1state = RxSTOP;   // RX1 state
char        *r1bufp;            // RX Buffer
int         r1buf_siz;          // remaining RX buffer size

上位プログラムは受信部がRxIDLEにある時受信を起動する事が出来る。この時パラメータとして受信データを格納するバッファの場所とその大きさを指定する。そして状態を受信中(RxBUSY)とし、割込みを許可する。

 void recvUART1(char *buf, int siz){
    r1bufp      = buf;
    r1buf_siz   = siz;
    r1state     = RxBUSY;
    PIE3bits.RC1IE  = ON;
}

データが受信され割込みが起こると次の受信処理が実行される。

 void recvISR1(void){
    byte st = RC1STA;            // read status 1st
    char c  = RC1REG;            // read data
    if(st & 0x02){               // if overrun error
        RC1STAbits.CREN = OFF;     // clear CREN bit once
        RC1STAbits.CREN = ON;
    }
    *r1bufp++ = (st & 0x06) ? '?' : c; //if any error chg char to '?'
    r1buf_siz--;
    if((r1buf_siz<=0)||(c=='\n')){  // completion
        PIE3bits.RC1IE  = OFF;
        *r1bufp = 0;
        r1state = RxCMPLT;
        LED1(OFF);
    }
}
先ず受信状態(RxBUSY)である事を確認した後、受信データと受信ステータスを取り込み、エラーがあれば然るべく回復処理を行い、正常であればバッファにデータを格納する(エラーの場合は'?'を入れる)。もし改行を受信するかバッファが満杯になれば割り込みを停止しバッファに文字列の終端(0)を書き込み、状態を受信完了(RxCMPLT)として上位プログラムに知らせる。

プログラムを頻繁にいじってPICに書き込むが、外から中身が分からないのでスタート時にバージョン等を出力するようにした。次は先に受信部を起動し、次に送信部を起動したときのTeraTermの表示である。


次の写真は赤外通信状態にある送信部(左)、受信部(右)である。受信部では4つのLEDで内部状態を示すようにしてデバッグを行った。

    LED1 - 受信がBUSY状態にある
   
LED2 - 割り込み処理を実施中
   
LED3 - 2つ以上のバッファに受信済データが入っている
   
LED4 - 送信がBUSY状態にある


  参考までに3月7日時点のソースコード(約300行)を以下に示す。

 /*
 * File:   main.c
 * Author: MYcrosLip
 * Created on 2023/02/24
 *
 * Recieve Message on IR using interrupt
 *
 * * Programmed for PIC16F15325
 */
/*
 * PIC16F15325 pin configuration
 * PIN & PORT Allocation
 *  5:RC5 - RX1 in
 *  6:RC4 - Tx1 oout
 *  2:RA5 - Rx2 in
 *  3:RA4 - Tx2 out
 * 10:RC0 - LED1
 *  9:RC1 - LED2
 *  8:RC2 - LED3
 *  7:RC3 - LED4
 */

#include <stdio.h>
#include <stdlib.h>
#include <xc.h>

#define _XTAL_FREQ 32000000

// config word 1
#pragma config RSTOSC   = HFINT32   //** Power-up Default Value for COSC
#pragma config FEXTOSC  = OFF       // FEXTOSC External Oscillator Mode Selection
#pragma config FCMEN    = OFF       // Fail-Safe Clock Monitor Enable
#pragma config CSWEN    = ON        // Clock Switch Enable
#pragma config CLKOUTEN = OFF       // Clock Out Enable
#pragma config PWRTE = OFF  // Power-up Timer Enable
#pragma config WDTE  = OFF  //** Watchdog Timer Enable
#pragma config LVP = OFF    //** Low Voltage Programming
#pragma config CP  = OFF    // Program Memory Code Protection

#define MYNAME          "IR-Rx-Intrpt-Basic"
#define VERSION         " 0.03"
#define DATE            " 2023/03/07"
#define COPYRIGHT1      " Copyright(C)2023"
#define COPYRIGHT2      " by MYcrosLip"
#define NL              "\n"

#define NBLINK          2       // startup blink sign

#define BUFLEN          30      // Buffer Length in byte
#define NBUF            3       // Number of Buffer

#define     ON      1
#define     OFF     0

typedef unsigned char         byte;
typedef unsigned int          uint;
typedef enum{TRUE=1, FALSE=0} boolean;


// Syntax Sugar; Dialect Adaptation Codes
#define RX1PPS          RX1DTPPS
#define RX2PPS          RX2DTPPS

// LED Macros
#define LED4(x)         LATCbits.LATC3 = x
#define LED3(x)         LATCbits.LATC2 = x
#define LED2(x)         LATCbits.LATC1 = x
#define LED1(x)         LATCbits.LATC0 = x

typedef enum{ RxSTOP, RxBUSY, RxCMPLT }    rxstate;
typedef enum{ TxSTOP, TxBUSY, TxCMPLT }    txstate;

// PROTOTYPES
void Init_Clk(void);
void Initialize(void);
void blink(int);
void LEDs(byte);

void __interrupt() isr(void);
void resetUART1(void);
void recvUART1(char*, int);
void recvISR1(void);
void resetUART2(void);
void sendUART2(char*);
void sendISR2(void);

//========================================================
// Interrupt Dispatcher
//========================================================
void __interrupt() isr(){
    LED2(ON);
    if(PIR3bits.RC1IF && PIE3bits.RC1IE) recvISR1();
    if(PIR3bits.TX2IF && PIE3bits.TX2IE) sendISR2();
    LED2(OFF);
}

//========================================================
//      UART1 Handler
//========================================================
rxstate     r1state = RxSTOP;   // RX1 state
char        *r1bufp;            // RX Buffer
int         r1buf_siz;          // remaining RX buffer size
txstate     t1state = TxSTOP;   // TX1 state
char        *t1bufp;            // TX Buffer

void resetUART1(){
    byte    b;
    while(PIR3bits.RC1IF) b = RC1REG;
    RC1STAbits.CREN = OFF;     // clear CREN bit once
    RC1STAbits.CREN = ON;
    r1state = RxSTOP;
}
//========================================================
void recvUART1(char *buf, int siz){
    r1bufp      = buf;
    r1buf_siz   = siz;
    r1state     = RxBUSY;
    LED1(ON);
    PIE3bits.RC1IE  = ON;
}
//========================================================
void recvISR1(void){
    byte st = RC1STA;            // read status 1st
    char c  = RC1REG;            // read data
    if(st & 0x02){               // if overrun error
        RC1STAbits.CREN = OFF;     // clear CREN bit once
        RC1STAbits.CREN = ON;
    }
    *r1bufp++ = (st & 0x06) ? '?' : c ; //if any error chg char to '?'
    r1buf_siz--;
    if((r1buf_siz<=0)||(c=='\n')){  // completion
        PIE3bits.RC1IE  = OFF;
        *r1bufp = 0;
        r1state = RxCMPLT;
        LED1(OFF);
    }
}

//========================================================
//      UART2 Handler
//========================================================
rxstate     r2state = RxSTOP;   // RX2 state
char        *r2bufp;            // Recieve Buffer
int         r2buf_siz;          // remaining buffer size
txstate     t2state = TxSTOP;   // TX2 state
char        *t2bufp;            // Send Buffer

void resetUART2(){
    byte    b;
    while(PIR3bits.RC2IF) b = RC2REG;
    RC2STAbits.CREN = OFF;     // clear CREN bit once
    RC2STAbits.CREN = ON;
    t2state = TxSTOP;
}
//========================================================
// Initiator
void sendUART2(char *bufr){
    t2bufp            = bufr;
    t2state           = TxBUSY;
    LED4(ON);
    PIE3bits.TX2IE   = ON;
}
//========================================================
// Interrupt Service
void sendISR2(void){
    TX2REG = *t2bufp++;
    if(*t2bufp == 0){
        LED4(OFF);      
        PIE3bits.TX2IE = OFF;   // Dsiable Interrupt
        t2state = TxCMPLT;      // Mission Completed
    }
}

//=======================================================
// CONSTANTS
static char id[]   = NL NL MYNAME VERSION DATE COPYRIGHT1 COPYRIGHT2 NL;

// VARIABLES
char    buf[NBUF][BUFLEN+1];
int     rbufn = 0, tbufn = 0;   // recieve buffer & send buffer
int     nbuf = 0;               // number of filled buffers

//========================================================
// MAIN
//
int main() {
    __delay_ms(1000);   // 1 sec delay
    Init_Clk();
#undef  _XTAL_FREQ  
#define _XTAL_FREQ   8000000     //chg Fosc to 8MHz dor __delay_ms()

    Initialize();
    blink(NBLINK);      // Startup Salute
    
    // Reset serial ports
    resetUART1();
    resetUART2();
    
    rbufn = 0; tbufn = 0; nbuf = 0;

    PIE3            = 0;
    INTCONbits.PEIE = ON;
    INTCONbits.GIE  = ON;
    
    sendUART2(id);
    while(t2state!=TxCMPLT){};
    t2state = TxSTOP;
    
//MAIN LOOP
    while(TRUE){
        if((r1state==RxSTOP)&&(nbuf < NBUF)){
            recvUART1(buf[rbufn],BUFLEN);
        }
        
        if(r1state==RxCMPLT){
            nbuf++;
            rbufn++;  rbufn %= NBUF;    // next buffer
            r1state = RxSTOP;
        }
        
        LED3( nbuf>1 );
        
        if((t2state==TxSTOP) && (nbuf>0)){
            sendUART2(buf[tbufn]);
        }
        
        if(t2state==TxCMPLT){
            tbufn++; tbufn %= NBUF;
            nbuf--;
            t2state = TxSTOP;
        }
    }
    return (EXIT_SUCCESS);
}

void Init_Clk(){
// OSC: HFINTOSC=16MHz, Fosc=8MHz
    OSCCON1     = 0x61;     // New ClockSource is HIFNTOSC, Div by 2
    OSCEN       = 0x40;     // HFINTOSC OSC Manual Req Enable
    OSCFRQ      = 0x05;     // HFINTOSC is set to 16MHz
}

void Initialize(void){
// PIN-Periphral Selection
    // 5:RC5 - RX1 in
    TRISCbits.TRISC5 = 1;   // set Cbit5 to intput
    ANSELCbits.ANSC5 = 0;   // set Cbit5 to digital
    RX1PPS          = 0x15; // RC5 EUSART1
    // 6:RC4 - Tx1 oout
    TRISCbits.TRISC4 = 0;   // set Cbit4 to output
    ANSELCbits.ANSC4 = 0;   // set Cbit4 to digital
    RC4PPS          = 0x0F;  // TX1 EUSART

    // 2:RA5 - Rx2 in
    TRISAbits.TRISA5 = 1;   // set Abit5 to intput
    ANSELAbits.ANSA5 = 0;   // set Abit5 to digital
    RX2PPS          = 0x05; // RA5 EUSART2
    // 3:RA4 - Tx2 out
    TRISAbits.TRISA4 = 0;   // set Abit4 to output
    ANSELAbits.ANSA4 = 0;   // set Abit4 to digital
    RA4PPS          = 0x11;  // TX2 UASRT2

    //10:RC0 - LED1
    TRISCbits.TRISC0 = 0;   // set Cbit0 to output
    ANSELCbits.ANSC0 = 0;   // set Cbit0 to digital
    // 9:RC1 - LED2
    TRISCbits.TRISC1 = 0;   // set Cbit1 to output
    ANSELCbits.ANSC1 = 0;   // set Cbit1 to digital
    // 9:RC1 - LED3
    TRISCbits.TRISC2 = 0;   // set Cbit2 to output
    ANSELCbits.ANSC2 = 0;   // set Cbit2 to digital
    // 7:RC3 - LED4
    TRISCbits.TRISC3 = 0;   // set Cbit3 to output
    ANSELCbits.ANSC3 = 0;   // set Cbit3 to digital
    //
//EUART1 Tx : Fosc=8MHz
    TX1STA      = 0x24;     // Async mode, Tx enable, high speed
    RC1STA      = 0x90;     //  
    // Baud Rate Generator
    BAUD1CON    = 0x08;     // BRG16
    SP1BRGH     = 0x03;     // 1200: 1666(682) 2400:832(340) on SYNC=0,BRGH=1,BRG16=1
    SP1BRGL     = 0x40;
    
//EUART2 Tx : Fosc=8MHz
    TX2STA      = 0x24;     // Async mode, Tx enable, high speed
    RC2STA      = 0x90;     //  
    // Baud Rate Generator
    BAUD2CON    = 0x08;     // BRG16
    SP2BRGH     = 0x03;     // 1200: 1666(682) 2400:832(340) on SYNC=0,BRGH=1,BRG16=1
    SP2BRGL     = 0x40;
}

void blink(int n){
    for(int i=n; i>0; i--){
        LEDs(ON);   __delay_ms(300);
        LEDs(OFF);  __delay_ms(300);
    }
    __delay_ms(300);
}

void LEDs(byte f){
    LED1(f);    LED2(f);   LED3(f);    LED4(f);
}


 


 

 

 

 

2023年3月3日金曜日

赤外線で通信する(その3)ソフトウェア(送信処理)編

送信部、受信部の機能が確認出来たところでソフトウェアに挑む。

これまでPICやハードウェアの機能確認のために簡単なソフトを組んだが、次はPICの性能を引き出すべく割り込みで処理を行う。次図はPIC16F18326の割り込みロジック。

最初に作るのは送信部だ。

送信部は外部条件に左右されずひたすらデータを送るだけのタスクなので敢えて割り込みを使わなくても作る事が出来る。つまりEUARTの送信バッファが空になるまで待って次のデータを書き込む、ひたすらこれを繰り返せばよい。それだけである。これを割り込みを使うようにするには(PIC16F18326の初期化部分を除いて)次の3つのプロルラムに分ける。

1)起動処理 - 送信データが発生すれば割り込みを起動する。
2)割り込み処理 -
今回使うPIC16デバイスはベクター割り込みが使えないので、割り込みが発生したら先ずその要因を調べ、それがUARTの送信だった場合は 2.5)へ進む。
2.5)データ送信 - データを1バイト送信バッファに書き込む。

3)完了処理 - 送信すべきデータが無くなれば割り込みを停止させ完了をデータ送信を依頼したプログラムに知らせる。

0)作業用データ 

typedef enum{TRUE=1, FALSE=0} boolean;  //真理値型の定義
char        *bufp;             //送信データポインタ
boolean     cmplt = TRUE;          //完了フラグ

1)起動処理

void sendUART(char *buf){
    bufp            = buf;     // バッファポインタをセット
    cmplt           = FALSE;   // 完了フラグをOFF
    PIE1bits.TXIE   = ON;      // TX割り込みON
}

2)割り込み処理

void __interrupt() isr(){
    if(PIR1bits.TXIF) sendISR(); //UART送信可なら送信処理
}

この割り込み処理ルーチンに入る時にGIEはOFFにされ多重の割り込みは起こらない。また割り込み処理ルーチンからリターンするときGIEは自動的にONにされる。この処理時間を調べたが2.5)3)を含めて15~18μs程度であった。

2.5) データ送信 & 3)完了処理

 void sendISR(){
    TX1REG = *bufp++;           // データ1文字送信
    if(*bufp == 0){             // もし最終文字なら
        PIE1bits.TXIE = OFF;    //
TX割り込みOFF
        cmplt = TRUE;           // 完了フラグをON
    }
}

これを呼び出すメインルーチン側は次の様に記述する。

INTCONbits.PEIE = 1;        // ペリフェラル割り込み可
INTCONbits.GIE  = 1;        // グローバル割り込み可

while(1){
    sendUART("Test Data");      // 送信要求(起動)
    while(!cmplt){};            // 完了待ち
    __delay_ms(33);             // 33ms(=25+8)の休止が必要
}

受信モジュールの規格上、sendUART()に渡す文字列(一度に送信できる量)は18文字以下に収め、そして送信完了後は25ms以上の休止時間を設ける必要がある(直後に次の送信がある場合)。ここで注意が必要なのは、ソフトウェア上で送信が完了しても送信バッファには最大2文字分の未送信データが残っており、それらの送出にはその後約8ms必要なので、足して33msの休止時間の確保が必要な事である。

 以上ページを間延びさせたくなかったので細かい部分は省いて骨格だけを書いた。


プログラムを作っている時気付いたが、何故か次の割り込み処理ルーチンのプロトタイプ宣言がコンパイルエラーとなる。

void interrupt isr(void);

xc8コンパイラはvoidやinterruptに文句を言ってくる。まさか文法がANSIからK&Rに戻った訳でもあるまいに。色々調べると、xc8コンパイラのあるバージョン以降は次の書き方をするよう変更になったようで、これに書き換える事でコンパイルはできるようになった。 

void __interrupt() isr(void);

しかしこれは、これまで見た事も無い訳の分からない文法だ。