TWELITEとPICでシリアル通信する

電子工作の疑似電波時計制作の続きです。
前回は、TWELITEの消費電流を測ってみました。
alasixosaka.hatenablog.com

そして、DOZEモードに入らず、スリープしない限り結構な電力を消費していることがわかってしまいました。
そこで、TWELITEが時刻サーバーからのデータの受信に失敗したとき(時刻サーバーが何らかの理由で止まってしまった場合など)にPIC側から命令を出してTWELITEにスリープするように指示することを考えました。まあ、いずれにしても時刻を受信したらそれをPIC側に送ってやる必要があって、それもシリアル通信になるので、TWELITEとPICはシリアルで通信する必要があります。ただ、以前の構想では、TWELITEからPIC側に時刻データを送るだけでよかったのですが、PIC側から指示を出す必要があるので双方向に通信する必要があります。そして、PICは通常はスリープしている(タイミングによっては起きている場合もある)ので、シリアル通信でスリープから起こしてやる必要があります。なので、今回は、TWELITEからシリアルデータを送信してスリープ中のPICを起こしてデータをうまく受信できるのかというのをやってみました。

TWELITEのSerial1が使えない

TWELITEにはシリアルポートが2つ搭載されいて、TWELITEのMWXライブラリではSerialという名称で使われるUART0とSerial1という名称で使われるUART1があり、MWXライブラリでは通常はSerialの方はセットアップが自動で行われ、Srrial1の方は、プログラムで明示的にセットアップをする必要があるようです。
Serial1を使ったサンプルアクトは、Unit_UART1というフォルダに入っています。これを参考に、もともとのTWELITE親機のプログラムにSerial1を使う処理を追加して子機からの送信をモニタリングするのを通常のSerialで行い、PICとの通信をSerial1で行おうとしました。Serial1を使うには、Setup関数内で、

// initialize the object. (allocate Tx/Rx buffer, and etc..)
Serial1.setup(64, 192);

// start the peripheral with 115200bps.
Serial1.begin(115200);

のように宣言すればよい。
以前書いたように、PICをクロック1MHzで動かすとシリアル通信で115200bpsで通信することができないので、速度の部分を9600bpsに書き換えてPICとの通信を試みました。
結論から言うと、115200bpsでも9600bpsでも通信することができませんでした。これはPIC側の問題ではなく、TWELITE側の問題です。私が使っているチップが古いためなのかもしれませんが、UART1の動作は確認できませんでした。サンプルアクトのUnit_UART1を動かして、USBシリアル変換器と繋いでみましたがそれでも全く通信ができず、Serial1を使うことは諦めることにしました。
そうすると、Serialの通信速度をどうするかという問題にぶち当たります。Serialの設定を変えて9600bpsで動かすのか、PICのクロックを4MHzくらいに上げて115200bpsで通信するのかという選択になります。できればPIC側は低いクロックで動かしたいのでTWELITE側で設定を変えて9600bpsを試してみました。
上に書いたSetup関数内の設定のSerial1を下のようにSerialに変更することで、問題なく動作することがわかりました。

// initialize the object. (allocate Tx/Rx buffer, and etc..)
Serial.setup(64, 192);

// start the peripheral with 9600bps.
Serial.begin(9600);

TWELITEをプログラムするときに、TWELITERとの通信は115200bpsで行われるので、ここの設定でSerialを9600bpsに変えてしまうと、次からプログラムができないのではないかと心配したのですが、TWELITERを繋いでプログラムを書き込むときには問題なく115200bpsで通信して、プログラムが書き込み終えた段階でTWELITEのシリアルポートは9600bpsで動くようになるようです。

スリープ中のPICをTWELITEで起こす

配線は次のようにします。TWELITEとPICをシリアルでつないで、PICはさらにFT232RLとも繋いでいます。PICはシリアルポートが2つあるので、その一つをTWELITEとの通信用に、もう一つはPCに繋いで通信の様子をモニタします。

PICをスリープ状態でシリアル通信で割り込みにより起動するという例があまりなかったのですが、こちらのサイトを参考にしました。
ameblo.jp

PIC側のプログラムです。コンフィクレーションの設定は省略しています。PPSを使ってシリアルポートのTX1,RX1をそれぞれ、RC6,RC7に割り当て、TX2,RX2をそれぞれRB0,RB1に割り当てています。TWELITEからは時刻データが送られてきますので、データは順番に配列のstr1[]に格納されます。データに含まれる”CR"(ASCIIコードの13)を検出して、その一つ前が曜日、その前が年、というように順番にそれぞれの変数に値を代入します。それをprintf関数を使ってシリアルポート2に出力しています。出力が終わったらスリープします。シリアルデータが送られてきたらスリープからウェイクアップして割り込み処理により受信を行います。
スリープからの起動でシリアル通信するとはじめの何ビットかはデータの取りこぼしが発生します。データシートによれば13bitの0をまず送信することを推奨しています。ですので、データの始めから秒、分、時というようにせず、データのお尻から変数に取り込んでいます。また、PICは常にスリープ状態にあるわけではないので、時計の時刻を更新するタイミングでは起きています。その場合にシリアル受信をすると、ダミーデータをきっちり読み込んでしまいますので、データ長が一定せず、頭から読んだときにうまく時刻データがずれてしまうという問題もあるので、お尻から読むようにしています。
また、スリープする前に、BAUD1CONbits.WUE = 1; という1行を入れています。これは、スリープからシリアル通信で起動するときのお作法のようなもので、参考にしたブログにも書いてありました。でも、プログラムを書く段になってすっかり忘れてしまって初めはこの文を入れずにうまくいかないと思って悩んでしまいました。

#include <xc.h>
#include <PIC16f19155.h>
#include <stdio.h>
#include <string.h>

#define _XTAL_FREQ 1000000
char RxData;
char Rflg;
int x;

void __interrupt() isr(void)
{
    PIR3bits.RC1IF = 0;
    if((RC1STAbits.OERR)||(RC1STAbits.FERR)){
        RC1STA = 0;
        RC1STA = 0x90;
    }
    else{
        RxData = RC1REG;
        Rflg = 1;
    }
}

void putch(unsigned char ch) {
    while (!PIR3bits.TX2IF); 
                      //送信終了待ち
                             
    TX2REG = ch;
}


void main(void) {
    char num = 10;
    
    ANSELA = 0b00000000;  
    ANSELB = 0b00000000;
    ANSELC = 0b00000000;
    TRISA  = 0b00001000;  
    TRISB = 0x00;
    TRISC  = 0b10000000;
    
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 0;
    RC6PPS = 0x0D;     //RC6をTXにする
    RX1PPS = 0x17;     //RC7をRXにする
    RB0PPS = 0x0F;     //RB0をTX2にする
    RX2PPS = 0x09;     //RB1をRX2にする
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 1;
    
     // NOSC HFINTOSC; NDIV 1; 
    OSCCON1 = 0x60;
    // CSWHOLD may proceed; SOSCPWR Low power; 
    OSCCON3 = 0x00;
    // MFOEN disabled; LFOEN disabled; ADOEN disabled; SOSCEN enabled; EXTOEN disabled; HFOEN disabled; 
    OSCEN = 0x08;
    // HFFRQ 1_MHz; 
    OSCFRQ = 0x00;
    // MFOR not ready; 
    OSCSTAT = 0x00;
    // TUN 0; 
    OSCTUNE = 0x00;
    // ACTUD enabled; ACTEN disabled; 
    ACTCON = 0x00;
    
    //__builtin_write_RTCWREN();
    RC1STA   = 0b10010000;      // 非同期送信 9600baud
    TX1STA   = 0b00100100;
    BAUD1CON = 0b00001000;
    SP1BRGH  = 0;
    SP1BRG   = 25;
    
    RC2STA   = 0b10010000;      // 非同期送信 9600baud
    TX2STA   = 0b00100100;
    BAUD2CON = 0b00001000;
    SP2BRGH  = 0;
    SP2BRG   = 25;
    
    PIR3bits.RC1IF = 0;
    PIE3bits.RC1IE = 1;
    INTCONbits.PEIE   = 1 ;             // 周辺装置割り込み有効
    INTCONbits.GIE    = 1 ;             // 全割込み処理を許可する
    
    while(1){
        
        Rflg = 0;
        char str1[30]="";
        x = 0;
        while(1){
            if (Rflg==1){
                Rflg=0;
                RA0 = 0;
                str1[x]=RxData;
                x++;
                if (RxData ==13){                 
                    break;
                }
            }
        }
        int i = 0;
        while(1){
            if (str1[i] == 13){
                break;
            }else{
                i = i + 1;
            }
        }
        char wday = str1[i-1];
        char year = str1[i-2];
        char month = str1[i-3];
        char day = str1[i-4];
        printf("%x/%x/%x/%x",year,month,day,wday);
        printf("\n\r");
        char hour = str1[i-5];
        char min = str1[i-6];
        char sec = str1[i-7];
        printf("%x:%x:%x",hour,min,sec);
        printf("\n\r");
        __delay_ms(10);
        //printf("%s\r\n",str1);    //受信したデータをそのまま返す
        //printf("go to sleep");
        BAUD1CONbits.WUE = 1;
        SLEEP();
        NOP();
    }
    return;
}

TWELITE側のプログラムです。基本は前回と変わっていません。変更点だけを下記します。
時刻を無線で受信した後、シリアルで送信するところをまず、Serial << "\0"としてダミーデータを送っています。これは参考サイトのやり方そのままですがこれでうまくいきました。また、最後のcrlfをコメントアウトしていますが、これはループから抜ける時にcrlfを送信しているので、重複を避けるためにコメントアウトしました。

Serial  << "\0"
<< u8sec
<< u8min
<< u8hour
<< u8day
<< u8month
<< u8year
<< u8wday
//<< crlf
;

これで、TWELITEからのシリアル出力で、スリープ状態のPICに時刻データを送信できることを確認しました。
今度は、逆にPICからのシリアル送信でTWELITEをスリープさせることをやってみます。