PICマイコン(PIC16F1827)のシリアル通信を実装する

組み込みエンジニア

こんにちは、ENGかぴです。

PIC16F1827はシリアル通信機能(EUSART)を持っているため外部機器と通信を行うことができます。MCCを使用してEUSARTの設定を行いArudino UNOとシリアル通信を行いシリアルモニタで動作確認を行いました。

応用編として電文を作成して外部機器からPIC16F1287を操作させています。

PICマイコン(PIC16F1827)のシリアル通信の応用

EUSARTを実装する

PIC16F1827のEUSARTの設定はMCCを使って行っています。MCCの使い方については下記記事にまとめています。

MPLAB Code Configurator(MCC)の追加と使い方

今回はMCCを使用してEUSART及びTMR0の設定を行っています。

System Moduleの設定

最初に共通事項であるSystem Moduleの設定を行います。MPLAB X IDEを起動しMCCのアイコンをクリックしてMCCを有効にします。

System Moduleの設定(MCC)
System Moduleの設定(MCC)

内部クロックを使用するためINTOSCを選択しています。クロック周波数は任意でもよいですが4MHz_Hzを選択しています。クロックを高速にするほど消費電流が増えてしまいます。他にも設定項目はありますがクロックの設定のみとしています。

EUSARTを設定する

PICマイコンのシリアル通信はEUSARTと表現されています。シリアル通信はMSSPの設定によりSPI通信やI2C通信も実現することができます。EUSARTはDevice Resources内のPeripherals欄のEUSARTを選択して追加します。

EUSARTの設定(MCC)
EUSARTの設定(MCC)

EUSARTを非同期で使用するためにModeでasynchronousを選択しEnable Transmit及びEnable Receiveにチェックを入れます。Baud Rateは19200bpsとしていますが任意でも構いません。

Baud Rateを高くするとErrorが大きくなることがあるためクロック設定を高速にする必要や外部機器が高速通信に対応できるかの確認が必要です。

送信や受信時に割り込みを使用してデータの格納を行う方が確実なデータ取得がしやすいためEnable EUSART Interruptsをチェックし割り込みを使用しています。

データの取得が遅れたりするとオーバーランが発生することがあるため可能な限り受信時にレジスタからデータを獲得しておくことが推奨されます。

パリティを使用しないためTransmission Bits及びReception Bitsを8ビット構成にしています。

Software Settings欄にはTransmit Buffer SizeやReceive Buffer Sizeで割り込み時にデータを一時保存しておくバッファのサイズを選択できますがサイズを大きくするとデータ容量が増えてしまい使用できるRAMサイズを圧迫するため少なめの値にしておく方が良いでしょう。

TMR0を設定する

TMR0はDevice Resources内のPeripherals欄のTimerを選択しTMR0をクリックして追加します。

TMR0の設定(MCC)
TMR0の設定(MCC)

Timer Clock内でクロックに関する設定を行います。Clock Sourceは内部クロックを使用しているためFOSC(FOSC/4)を指定します。PrescalerはClock Sourceに対してTMR0のカウントアップする分周比を指定します。

Timer PeriodはTMR0がオーバーフローするタイミングを設定します。1ms毎にオーバーフロー割り込みが発生しますがCallback Function Rateを0x0A(10)を指定しているため10ms毎にコールバック関数が呼び出されます。

void mainTimer(void); //タイマー0割り込みのコールバック関数
//コールバック関数の使用例
TMR0_SetInterruptHandler(mainTimer); //コールバック関数を指定

上記例ではmainTimer()が10ms毎にコールされます。

動作確認

PIC16F1827とArduino UNOを接続してシリアル通信の動作確認を行います。通信条件はボーレートが19200bps、パリティなしとします。

動作確認用の回路

PIC16F1827とArduino UNOによるシリアル通信の確認回路
PIC16F1827とArduino UNOによるシリアル通信の確認回路

Arduino UNOではソフトウェアシリアルのライブラリを使用してPIC16F1827とシリアル通信を行います。

Arduino UNOはシリアルモニタ用にTXとRXを兼用しているためTXをPIC16F1827と接続するとシリアルモニタに表示する際にTXからデータが送出されてしまいPIC16F1827は不要なデータを受信してしまいます。

対策としてソフトウェアシリアルのライブラリを使用しています。Arudino UNOのTXはシリアルモニタの表示のみで使用しデータの送信をソフトウェアシリアルのTXである11ピンから送信するようにしています。Arudino UNOのRXはシリアルモニタに表示するために使用しています。

Arduino UNOからはシリアルモニタを起動したときに一度だけ文字列を送信しPIC16F1827は送信した文字列をそのまま返信してエコーバックするようにします。受信処理が発生するとLED2が点灯/消灯を切り替えます。

PIC16F1827からはSW1を押したときArduino UNOに対して「PIC-OK」の文字列を送信するようにします。LED1は送信処理が発生すると点灯/消灯を切り替えます。

PIC16F1827のピン設定(MCC)
PIC16F1827のピン設定(MCC)

RB0はウィークプルアップしているため信号レベルが浮くことはありません。SW1を押すとLED3が点灯し押さない場合はLED3が消灯するようにします。

PB0はINPUTでウィークプルアップ(WPU)設定しています。WPUを有効にするためにはRegistersのnWPUENをenabledにする必要があります。

Easy SetupでWPUにチェックするのみでは有効にならないので注意が必要です。Notificationsにワーニングで通知されます。

受信データを送信する

Arduino UNOから受信したデータの取得と送信をMCCが生成した関数を使用して実装します。

void mainApp(void){
    
    if(EUSART_is_rx_ready()){ //受信したデータがあるか
        rxData = EUSART_Read(); //受信データを取得
        picRcv.buf[picRcv.wp] = rxData;

        if( ++picRcv.wp >= sizeof(picRcv.buf)){
            picRcv.wp = 0;
        }
            
        if(EUSART_is_tx_ready()){ //送信可能であるか
            EUSART_Write(rxData); //データを送信
        }
        IO_RB6_Toggle();
    }
}

MCCが生成したeusart.hに実装例がありますがEUSART.Read()を使用する際は受信データがあるかを確認するEUSART_is_rx_ready()を先に使用する必要があります。

同様にEUSART_Write()を使用する際は送信時も送信可能であるか(送信ビジーでないか)を確認するEUSART_is_tx_ready()を先に使用する必要があります。

変数rxDataにReadしたデータを格納しておきWriteにセットすることで受信データをそのまま送信することができます。

EUSART_is_rx_ready()及びEUSART_is_tx_ready()を使用せずにEUSART_Read()またはEUSART_Write()を使用すると無限ループに入ってしまうため注意が必要です。

任意のデータを送信する

任意のデータを送信する例を示します。送信したいデータを変数に格納しておき順番にセットしていきます。

void TxDataSet(void){
    
    Txdata.cnt = 0;
    Txdata.data[0] = 'P';
    Txdata.data[1] = 'I';
    Txdata.data[2] = 'C';
    Txdata.data[3] = '-';
    Txdata.data[4] = 'O';
    Txdata.data[5] = 'K';
    timTxWait = TIME_TX_MAX; //送信のタイムアウトをセット
} 
/* 以下はメイン処理内に実装   */
if( timTxWait > TIME_UP ){ 
    if(EUSART_is_tx_ready()){
        if( Txdata.cnt < sizeof(Txdata.data)){ //送信データ数に達したか
            EUSART_Write(Txdata.data[Txdata.cnt]); //次の送信データを選択
            ++Txdata.cnt; //次の送信データを選択
        }
        else{ //送信完了
            timTxWait = TIME_OFF;
        }
    }     
}
else if( timTxWait == TIME_UP){ //送信タイムアウト
    timTxWait = TIME_OFF;
} 

Txdata[]の配列に送信したいデータをセットしTxdata.cntで次の送信データを示すカウントを管理します。timTxWaitは最初の送信から一定期間経過以内に送信が完了するかを確認するためのタイムアウトとして使用します。

TxDataSet()を送信開始したい箇所で一度だけコールして任意のデータをセットしメイン処理で周回させながらデータの送信を行います。動作確認用の回路図においてSW1を押したときにTxDataSet()をコールして送信を開始しています。(ソースコード全体参照)

動作結果

シリアルモニタによる動作確認の結果
シリアルモニタによる動作確認の結果

Arduinoのシリアルモニタを使用して動作確認を行うとシリアルモニタ起動後にArudino UNOから送信した文字列がPIC16F1827を介して受信できていました。またSW1を押すとPIC-OKが受信できていました。

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。チェックはしておりますがコメントなど細かな部分で間違っている可能性があります。参考としてお使いいただければと思います。

PIC16F1827のソースコード:

#include "mcc_generated_files/mcc.h"

#define TIME_OFF -1       //タイマーを使用しない場合
#define TIME_UP 0         //タイムアップ
#define TIME_FILTER_MAX 1 //ベースタイマカウント値
#define TIME_TX_MAX 10
#define	CNT_INIT_MAX 10    //10ms×10 = 100ms
#define RING_SZ 64
#define SZ_MASK 0x3F
#define DI_NUM 4           //DIフィルタのサンプリング数
#define TX_SZ 6

typedef struct{
    uint8_t wp;
    uint8_t rp;
    uint8_t buf[RING_SZ];
}RING_MNG;

typedef struct{
    uint8_t    wp;
    uint8_t    buf[ DI_NUM ];
    uint8_t    di;
}FILT_DATA;

typedef struct{
    uint8_t cnt;
    uint8_t data[TX_SZ];
}TX_MNG;

//--------------変数定義----------------------------
int8_t  timfilter;
int8_t  timTxWait; 
RING_MNG picRcv;
uint8_t CntInit; //初期化時のみ使用
int8_t rxData;
FILT_DATA DiData;
bool btn1hold;
TX_MNG  Txdata;
//--------------関数プロトタイプ宣言----------------------------
void mainApp(void);
void mainTimer(void); //タイマー0割り込みでの処理
void DiFilter(void);
void DiFilterInit(void);
void DiFilter(void);
void TxDataSet(void);
/* Main application */
void main(void)
{
    SYSTEM_Initialize();
    TMR0_SetInterruptHandler(mainTimer); //コールバック関数を指定
    INTERRUPT_GlobalInterruptEnable(); //割り込みの許可
    INTERRUPT_PeripheralInterruptEnable(); //ペリフェラル割り込みの許可
    DiFilterInit(); //メイン処理前にDIを確定する

    while (1)
    {
        mainApp();
        DiFilter();
    }
}
/* main処理  */
void mainApp(void){
    
    if(EUSART_is_rx_ready()){ //受信したデータがあるか
        rxData = EUSART_Read(); //受信データを取得
        picRcv.buf[picRcv.wp] = rxData;

        if( ++picRcv.wp >= sizeof(picRcv.buf)){
            picRcv.wp = 0;
        }
            
        if(EUSART_is_tx_ready()){ //送信可能であるか
            EUSART_Write(rxData); //データを送信
        }
        IO_RB6_Toggle();
    }
    
    if( DiData.di == 0 ){
        if( btn1hold == false ){
            btn1hold = true;
            TxDataSet(); //送信準備
        }
    }else{
        btn1hold = false; 
    }
    
    if( timTxWait > TIME_UP ){
        if(EUSART_is_tx_ready()){
            if( Txdata.cnt < sizeof(Txdata.data)){ //送信データ数に達したか
                EUSART_Write(Txdata.data[Txdata.cnt]); //送信
                ++Txdata.cnt; //次の送信データを選択
            }
            else{ //送信完了
                timTxWait = TIME_OFF;
            }
            IO_RA7_Toggle();
        }     
    }
    else if( timTxWait == TIME_UP){ //送信タイムアウト
        timTxWait = TIME_OFF;
    }   
}
/* 送信準備関数 */
void TxDataSet(void){
    
    Txdata.cnt = 0;
    Txdata.data[0] = 'P';
    Txdata.data[1] = 'I';
    Txdata.data[2] = 'C';
    Txdata.data[3] = '-';
    Txdata.data[4] = 'O';
    Txdata.data[5] = 'K';
    timTxWait = TIME_TX_MAX; //送信のタイムアウトをセット
}
/* タイマ管理関数(コールバック) */
void mainTimer(void){
 
    if( timfilter > TIME_UP ){
        --timfilter; //タイマを更新
    }
    
    if( timTxWait > TIME_UP){
        --timTxWait; 
    }
}
// DIフィルタの初期化 //
void DiFilterInit(void){
	
    CntInit = CNT_INIT_MAX;
    while(CntInit > 0){  //0になるまでフィルタを実施
	DiFilter();         //DIフィルタ処理
	__delay_ms(10);     //10ms遅延させてDIフィルタ処理
        CntInit--;
	timfilter = TIME_UP;
    }    
}
//--------モード切替部のDIフィルタ----------------------------
void DiFilter(void){
    uint8_t i;
    bool boo = true;

    if( timfilter == TIME_UP ){
        timfilter = TIME_FILTER_MAX;

        DiData.buf[ DiData.wp ] = IO_RB0_GetValue();

        for( i = 1; i < DI_NUM; i++){
            if( DiData.buf[i-1] != DiData.buf[i] ){
                boo = false;
                break;
            }
        }

        if( boo ){ //比較して一致したら値を採用
            DiData.di = DiData.buf[ 0 ]; 
            IO_RA3_LAT = DiData.di;
        }

        if( ++DiData.wp >= DI_NUM){
            DiData.wp = 0;
        }
    }
}

本ソースコードはMPLAB X IDEにMCCのプラグインをインストールしていないと使用できません。

Arduino UNOのソースコード:

#include <SoftwareSerial.h>
SoftwareSerial mySerial(10, 11); // RX, TX

void setup() {
    Serial.begin(19200);
    mySerial.begin(19200);
    mySerial.println("Hello, world?"); //PICに一度だけ送信
}

void loop() {

    while( Serial.available()){ //受信データがあるか
        Serial.write(Serial.read()); //受信データを取得してシリアルモニタに表示
    }
}

関連リンク

PICマイコンを使ってマイコンのレジスタの設定やMPLAB X IDEのプラグインであるMCCを使用して動作確認したことについてまとめています。

PICマイコン(PIC12F675)で実現できる機能と解説リンクまとめ

PICマイコン(PIC16F1827)で実現できる機能と解説リンクまとめ

TECH::CAMPプログラミング教養【無料体験会】

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました