PIC16F19155を使ってみる(その5)

前回は、RTCCを使った割り込み処理でスリープからの復帰をテストしました。
alasixosaka.hatenablog.com

今回は、EUSARTを使ったシリアル通信のテストを行います。
前回のブログで、シリアル通信は行わないと書いてしまったのに、その舌の根も乾かぬうちにという感じですが、よくよく考えたら、TWE-LITEと通信するときにシリアルで通信する必要がありました。タイムサーバーからのデータをTWE-LITEで経由で受信し時刻合わせをする機能を搭載する予定なので、シリアル通信が必要です。この機能は受信だけでOKですが、RTCCから読みだしたデータをシリアル通信でPCに送るテストをしてみたいので、送信側も実装する必要があり、送信、受信のテストを行いました。

配線です。

右上のピンヘッダはUSBーシリアル変換器との接続のためです。左からGND、ひとつ飛んでVCC、TXD、RXDの順です。
PIC側のピンはRC6がRXD(黄色の線)でUSB-シリアル変換器のTXDと接続、RC7がTXD(緑の線)でUSB-シリアル変換器のRXDと接続します。USB-シリアル変換器を接続した場合は、電源はUSB-シリアル変換器から供給することとし、ブレッドボード上のVCCとGNDのラインにそれぞれ接続しています。今回の追加はこれだけです。

送信テスト

まず、PIC側からの送信のテストを行います。受け手はPCで、USBシリアル変換器を繋いで、TeraTermでモニタしました。
プログラムです。PIC16F19155にはEUSARTが2つあります。今回は1番の方を使いました。

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

// CONFIG1
#pragma config FEXTOSC = OFF    // External Oscillator mode selection bits->Oscillator not enabled
#pragma config RSTOSC = HFINT1    // Power-up default value for COSC bits->HFINTOSC (1MHz)
#pragma config CLKOUTEN = OFF    // Clock Out Enable bit->CLKOUT function is disabled; i/o or oscillator function on OSC2
#pragma config VBATEN = OFF    // VBAT Pin Enable bit->VBAT functionality is disabled
#pragma config LCDPEN = OFF    // LCD Charge Pump Mode bit->LCD Charge Pump is disabled.
#pragma config CSWEN = ON    // Clock Switch Enable bit->Writing to NOSC and NDIV is allowed
#pragma config FCMEN = ON    // Fail-Safe Clock Monitor Enable bit->FSCM timer enabled

// CONFIG2
#pragma config MCLRE = ON    // Master Clear Enable bit->MCLR pin is Master Clear function
#pragma config PWRTE = OFF    // Power-up Timer selection bits->PWRT disable
#pragma config LPBOREN = OFF    // Low-Power BOR enable bit->ULPBOR disabled
#pragma config BOREN = ON    // Brown-out reset enable bits->Brown-out Reset Enabled, SBOREN bit is ignored
#pragma config BORV = LO    // Brown-out Reset Voltage Selection->Brown-out Reset Voltage (VBOR) set to 1.9V on LF, and 2.45V on F Devices
#pragma config ZCD = OFF    // Zero-cross detect disable->Zero-cross detect circuit is disabled at POR.
#pragma config PPS1WAY = OFF    // Peripheral Pin Select one-way control->The PPSLOCK bit can be set and cleared repeatedly by software
#pragma config STVREN = ON    // Stack Overflow/Underflow Reset Enable bit->Stack Overflow or Underflow will cause a reset

// CONFIG3
#pragma config WDTCPS = WDTCPS_31    // WDT Period Select bits->Divider ratio 1:65536; software control of WDTPS
#pragma config WDTE = OFF    // WDT operating mode->WDT Disabled, SWDTEN is ignored
#pragma config WDTCWS = WDTCWS_7    // WDT Window Select bits->window always open (100%); software control; keyed access not required
#pragma config WDTCCS = SC    // WDT input clock selector->Software Control

// CONFIG4
#pragma config BBSIZE = 512    // Boot Block Size Selection bits->Boot Block Size (Words) 512
#pragma config BBEN = OFF    // Boot Block Enable bit->Boot Block disabled
#pragma config SAFEN = OFF    // SAF Enable bit->SAF disabled
#pragma config WRTAPP = OFF    // Application Block Write Protection bit->Application Block NOT write-protected
#pragma config WRTB = OFF    // Boot Block Write Protection bit->Boot Block NOT write-protected
#pragma config WRTC = OFF    // Configuration Register Write Protection bit->Configuration Words NOT write-protected
#pragma config WRTD = OFF    // Data EEPROM Write Protection bit->Data EEPROM NOT write-protected
#pragma config WRTSAF = OFF    // Storage Area Flash Write Protection bit->SAF NOT write-protected
#pragma config LVP = OFF    // Low Voltage Programming Enable bit->High Voltage on MCLR/Vpp must be used for programming

// CONFIG5
#pragma config CP = OFF    // UserNVM Program memory code protection bit->UserNVM code protection disabled


// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

//int LEDflg ;

#define _XTAL_FREQ 1000000

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


void main(void) {
    char num = 10;

    ANSELA = 0b00000000;  
    ANSELB = 0b00000000;
    ANSELC = 0b00000000;
    TRISA  = 0b00001000;  
    TRISB = 0x00;
    TRISC = 0x00;
    
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 0;
    RC6PPS = 0x0D;
    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;
    
    
    // SYSCMD SYSCLK enabled; ACTMD ACT enabled; FVRMD FVR enabled; IOCMD IOC enabled; NVMMD NVM enabled; 
    PMD0 = 0x00;
    // TMR0MD TMR0 enabled; TMR1MD TMR1 enabled; TMR4MD TMR4 enabled; TMR2MD TMR2 enabled; 
    PMD1 = 0x00;
    // ZCDMD ZCD enabled; DACMD DAC enabled; CMP1MD CMP1 enabled; ADCMD ADC enabled; CMP2MD CMP2 enabled; RTCCMD RTCC enabled; 
    PMD2 = 0x00;
    // CCP2MD CCP2 enabled; CCP1MD CCP1 enabled; CCP4MD PWM4 enabled; CCP3MD PWM3 enabled; 
    PMD3 = 0x00;
    // CWG1MD CWG1 enabled; UART2MD EUSART2 enabled; MSSP1MD MSSP1 enabled; UART1MD EUSART enabled; 
    PMD4 = 0x00;
    // CLC3MD CLC3 enabled; CLC4MD CLC4 enabled; SMT1MD SMT1 enabled; LCDMD LCD enabled; CLC1MD CLC1 enabled; CLC2MD CLC2 enabled; 
    PMD5 = 0x00;
    
    RC1STA   = 0b10000000;      // 非同期送信 9600baud
    TX1STA   = 0b00100100;
    BAUD1CON = 0b00001000;
    SP1BRGH  = 0;
    SP1BRG   = 25;
    
    RA0 = 0;
    
    while(1){
      
        RA0 = 0;
        __delay_ms(950);            
        RA0 = 1;
        __delay_ms(50);
        printf("%d\r\n",num++); // 0123・・・と送信
    }
    return;
}

シリアルポートに0,1,2,3と順番にキャラクタを送信するだけのプログラムです。
前回からの変更点だけを記載します。

#include <stdio.h>

ます、stdio.hをインクルードして、printf関数を使えるようにしています。
そして、putch関数を定義します。

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

ここでは、TX1IFビットを読んでビットが立っている間はループを繰り返して送信の終了を待ち、送信が終了していたら、TX1REGに出力するデータを書き込みます。
メイン関数の中ですが、まず

 char num = 10;

として、出力するデータをnumという変数で定義しています。これをカウントアップして順次出力することになります。
それからPPSによるピンの設定です。

    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 0;
    RC6PPS = 0x0D;
    RX1PPS = 0x17;     //RC7をRXにする
    PPSLOCK = 0x55;
    PPSLOCK = 0xAA;
    PPSLOCKbits.PPSLOCKED = 1;

データシートを見ると、EUSARTのピンはPPSで設定しなくてもデフォルト設定で使えそうな感じがしたのですが、PPSで設定しないと動きませんでした。
まずPPSのロック解除を行います。RTCCの時と違って、こちらは解除用のおまじないが必要でした。PPSLOCKに0x55と0xAAを順番に書き込みます。
そして、PPSLOCKビットをクリアして書き込みを許可して、RC6をTXDに設定します。PIC16F19155ではTXDは0x0Dになっています。
そして、ついでと言ってなんですが、受信用のRXDをRC7に設定します。こちらは、出力端子の設定と逆で、RX1PPS = 0x17のように、周辺装置側を左に書いて、ピンを右に書きます。RC7は0x17です。
書き込んだら、PPSLOCビットを立てて、またPPSをロックします。
EUSARTの設定の部分です。

    RC1STA   = 0b10000000;      // 非同期送信 9600baud
    TX1STA   = 0b00100100;
    BAUD1CON = 0b00001000;
    SP1BRGH  = 0;
    SP1BRG   = 25;

RC1STAとTX1STAで非同期、ノンパリティ、8ビットに設定しています。TX1STAのビット2を立てているのは、高速モードで動作するという意味です。メインクロックを1MHzと低速にしているので、高速モードにしてやらないとまともな速度で通信ができません。
そして、BAUD1CONレジスタの第3ビットを立てて、ボーレート設定用のレジスタを16ビットで使うことを指示します。ボーレート設定用レジスタはSP1BRGHとSP1BRGで、ここに25を書き込むと約9600ボーで通信します。ちなみに12を書き込むと19200ボーでの通信になります。メインクロックが1MHzの場合は実用的なのはこの2つくらいです。

PIC16F19155のボーレート設定、非同期でハイスピード、16ビットの場合

whileループの中ですが、Lチカの後に一行加えています。

    while(1){
      
        RA0 = 0;
        __delay_ms(950);            
        RA0 = 1;
        __delay_ms(50);
        printf("%d\r\n",num++); // 0123・・・と送信
    }

printf("%d\r\n",num++);として、冒頭で定義した変数numをインクリメントしながらprintfで出力しています。これで、シリアルポートから順番に数字の0、1と送信されます。

TeraTermで受信したところ

受信テスト

こんどはPCからデータを送って、PICで受信してみます。
受信テスト用のプログラムです。
変更した点だけ記載します。
まず、受信用の変数RxDataと、受信を検知した際に立てるフラグ変数Rflgを定義します。

unsigned char RxData, Rflg;

次に、受信用の割り込み関数を定義します。

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

ここでは、まず割り込みフラグRC1IFをクリアし、エラーチェックを行い、エラーならRC1STAレジスタを再設定してリターンします。エラーがなければ、データをRxDataに読み込み、受信用フラグを立ててリターンします。
次にメインルーチンです。RC7を入力に設定する必要があるので、TRISCの設定を下記のように変更します。

    TRISC  = 0b10000000;

それから、受信割り込みを許可するため、下記の4行を追記します。

    PIR3bits.RC1IF = 0;
    PIE3bits.RC1IE = 1;
    INTCONbits.PEIE   = 1 ;             // 周辺装置割り込み有効
    INTCONbits.GIE    = 1 ;             // 全割込み処理を許可する

まず、割り込みフラグRC1IFビットをクリアします、次に、RC1IEフラグを立てて受信割り込みを許可します。それから、作法に従って周辺装置割り込み、全体割り込みの順に許可します。
ループ関数の中ですが、

    while(1){
        for (int i=0; i<10; i++){
            RA0 = 0;
            __delay_ms(950);            
            RA0 = 1;
            __delay_ms(50);
            printf("%d\r\n",num++); // 0123・・・と送信
        }
        Rflg = 0;
        while(1){
            if (Rflg==1){
                Rflg=0;
                RA0 = 0;
                break;
            }
        }
        printf("%d\r\n",RxData);    //受信したデータをそのまま返す
    }
    return;
}

まず、送信時と同じ動作をforループを使って10回繰り返します。そのあと、受信検知用フラグ関数Rflgをクリアして、whileで無限ループに入り受信を待ちます。シリアルポートへ何もデータが送られてこないと永遠に待ち続けます。
ループ内では、Rflgが立っていたら、Rflgをクリアし、無限ループを抜けて、受信したデータをそのまま送信し返します。RA0=0は受信が成立しているかどうかをチェックするためのコマンドです。受信待ちに入る前にLEDは点灯しっぱなしになるので、ちゃんと受信ができていればここでLEDが消灯し、受信できたことが確認できます。
下の写真では、1を送った後に49が返ってきて149と表示されています。これは、1のアスキーコードが49なので、1をteratermから送るとコードの49が送られるためです。2を送ると同様に50を返します。

10回カウントしたら受信を待ち、受信したらそのまま返して、再びカウントアップを続けます。