忍者ブログ

Fグループ電子工作講座

秋月電子SH7125ボードで始めるマイコン開発

シリアル通信の設定(4)受信割り込み

関連:シリアル通信関連記事一覧

やっと受信処理に入れます。
ここでは割り込みを使った受信処理を設定します。
※割り込みを使わない1文字づつの受信はシリアル通信の設定(1)

プログラムはシリアル通信の設定(3)まで済んでいる事を前提とします。
プロジェクトはシリアル通信の設定(3)の物をそのまま使用します。

受信割り込みの設定

ここまで順に読んできた方であれば割り込みの設定自体はさほど難しくありません。
受信割り込み要求を許可すれば、受信割り込みが発生するようになるのでそこに受信処理を書けばいいだけです。

SCI1を初期化する際に
    SCI1.SCSCR.BIT.RIE = 1;
とすれば受信割り込みが許可されます。
割り込みの優先順位は送信、受信で共通です。

<用語>
RIE:レシーブインタラプトイネーブル
受信割り込み許可。
これを1に設定すると、RDRFが1になった時に割り込み処理が発生するようになります。
設定(1)のプログラムでif(SCI1.SCSSR.BIT.RDRF)で待機させていた部分を割り込み処理で自動実行できるようになります。
ベクタ番号は221でvecttbl.cintprg.cINT_SCI1_RXI1関数がこれに該当します。

注意点として、受信割り込み許可を設定すると受信割り込みRXI1以外に受信エラー割り込みERI1も同時に発生するようになります。
受信エラー割り込みのベクタ番号は220でvecttbl.cintprg.cINT_SCI1_ERI1関数がこれに該当します。

まず受信エラー割り込みの処理を作成します。
<設定>
void SCI1_INT_ERI1(void){
    if(SCI1.SCSSR.BIT.ORER==1)    //オーバーランエラーあり
        SCI1.SCSSR.BIT.ORER=0;    //オーバーランエラーフラグを解除
    if(SCI1.SCSSR.BIT.FER==1)    //フレーミングエラーあり
        SCI1.SCSSR.BIT.FER=0;    //フレーミングエラーフラグを解除
    if(SCI1.SCSSR.BIT.PER==1)    //パリティーエラーあり
        SCI1.SCSSR.BIT.PER=0;    //パリティーエラーフラグを解除
}
<解説>
この関数はintprg.cINT_SCI1_ERI1へ登録する必要があります。
void INT_SCI1_ERI1(void){SCI1_INT_ERI1();}

ORERFERPERの何れかが1になっていると受信エラー割り込みが発生します。
電源が入った状態でRS232Cのコネクタを抜き差しするとゴミを受信してエラーになります。
受信エラーの各フラグがどれか1つでも立っていると受信処理を継続することができません。
とりあえずエラーが出ているフラグを解除だけしておきます。


受信割り込み処理

とりあえず受信バッファ(受信した文字を格納する配列)を用意します。
受信した文字は順次受信バッファに格納していきます。しかし、受信処理中のバッファにメイン関数からアクセスするとよろしくありません。そこで、受信完了したバッファと、受信中のバッファを分けておきます。

<設定>
//受信中に使用
char _sci1_rx_temp[255];    //受信中文字バッファー(最大255文字)
int _sci1_rx_now;            //受信中の文字数

//受信完了時に使用
char _sci1_rx_data[255];    //受信完了文字バッファー(最大255文字)
int _sci1_rx_num;            //受信完了した文字数
int _sci1_rx_fin_f;            //受信完了フラグ

<解説>
受信完了フラグ_sci1_rx_fin_fはメイン関数等で受信完了したか否かを確認するために使用します。
_sci1_rx_now
_sci1_rx_num
_sci1_rx_fin_f
は初期化関数内で0に初期化します。


やっと受信割り込み関数を設定できます。
前回の設定に基づき、[で受信開始し、]で受信を完了する仕様として作成します。
また汎用性のため0x02で受信開始し、0x03でも受信を完了する仕様にしておきます。
おまけで、改行文字'\r'または'\n'でも受信を完了する仕様にしました。
なお、前回
#define STX0 (0x02)
#define ETX0 (0x03)
#define STX1 '['
#define ETX1 ']'
としてヘッダファイルに記述してあります。

<設定>
#define TYPE_STX    1    //開始文字
#define TYPE_ETX    2    //終了文字
#define TYPE_STD    3    //通常文字
#define TYPE_BRK    4    //改行文字

void SCI1_INT_RXI1(void){
    char c;
    int code_type=0;
    if(SCI1.SCSSR.BIT.RDRF){    //何か受信している
        SCI1.SCSSR.BIT.RDRF=0;    //受信フラグを解除
        c=SCI1.SCRDR;    //受信文字を格納

        switch(c){        //文字の種別判定
            case STX0:    //開始文字を受信
            case STX1:
                code_type=TYPE_STX;
                break;
            case ETX0:    //終了文字を受信
            case ETX1:
                code_type=TYPE_ETX;
                break;
            case '\n':    //改行文字を受信
            case '\r':
                code_type=TYPE_BRK;
                break;           
            default:    //それ以外の文字
                code_type=TYPE_STD;
                break;
        }//end switch

        if(code_type==TYPE_STX){
            _sci1_rx_now=0;        //受信中の文字数をリセット
        }

        if(code_type!=TYPE_BRK){    //改行文字以外
            _sci1_rx_temp[_sci1_rx_now]=c;    //受信した文字をバッファへ格納
            _sci1_rx_now++;            //受信した文字数をカウントアップ
        }
       
        if(code_type==TYPE_ETX || (_sci1_rx_now>=1 && code_type==TYPE_BRK)){    //終了文字または1文字以上受信後の改行文字
            _sci1_rx_temp[_sci1_rx_now]='\0';            //null文字を追加
            sprintf(_sci1_rx_data,"%s",_sci1_rx_temp);    //受信中文字から受信完了文字へコピー
            _sci1_rx_num=_sci1_rx_now;    //受信文字数を格納
            _sci1_rx_fin_f=1;            //受信完了フラグを立てる
            _sci1_rx_now=0;                //受信中の文字数をリセット
        }//end TYPE_ETX       
    }//end RDRF
}//SCI1_INT_RXI1

<解説>
この関数はintprg.cINT_SCI1_RXI1へ登録する必要があります。
void INT_SCI1_RXI1(void){SCI1_INT_RXI1();}

受信した文字を一度cへ格納します。
で、受信した文字が開始文字終了文字通常文字改行文字のどれに該当するのかを判定します。

<switch case文の動作について>
switch case文はif文を並べたような処理になります。
switch(入力)に対して、入力に一致するcaseがあれば、そこへ飛んでそこから処理が始まり下に向かって順次処理が実行されます。
で、break;が出てくるまで処理が続き、break;が出てきた時点でswitch文が終了します。
cがSTX1の場合、case STX1:から処理が始まりcode_type=TYPE_STX;を実行してbreak;で終了します。
cがSTX0の場合、case STX0:から処理が始まり、case STX1:を通り過ぎてcode_type=TYPE_STX;を実行してbreak;で終了します。
結局同じ処理になります。
該当するcaseが無い場合、defaultへ飛んで処理されます。

判定したコードの種類に応じて処理が変化します。

受信した文字が開始文字TYPE_STXの場合、受信文字数をリセットします。
受信した文字が改行文字TYPE_BRK以外であれば受信した文字をバッファへ格納します。
受信した文字が終了文字TYPE_ETXの場合、バッファをコピーして受信完了となります。
1文字以上受信した状態で改行文字TYPE_BRKが来た場合も同様です。

ここで重要なのが_sci1_rx_temp[_sci1_rx_now]='\0';によるnull文字の追加です。
sprintfを使って文字列をコピーする場合、先頭アドレスから始まって'\0'が出るまでコピーが継続されます。ところが、受信文字を1文字ずつ配列に入れていった場合'\0'が配列の中に無いので処理が終了しません。配列を宣言した時点で配列の中身が0になるかよくわからない状態になるかはコンパイラの仕様で変わってきます。しかし、長い文字列を受信した後に短い文字列を受信した場合は同じ動作になります。
文字列”[HELLO WORLD]”を受信した後に文字列"[RS232C]"を受信する状況を考えてみます。
コンパイラの仕様で配列を宣言した時点で0埋めされているとします。

ここで”[HELLO WORLD]”を格納すると以下の様になります。

これをコピーすると'\0'が出てきた1218番地で処理が終了し、”[HELLO WORLD]”となります。

つづいて"[RS232C]"を格納します。

これをコピーすると先ほどと同じ、'\0'が出てくる1218番地で処理が終了するため”[RS232C]ORLD]”となってしまいます。

そこで受信完了時点で最後に'\0'を追加することで正常に"[RS232C]"がコピーできるようになります。



受信文字の取得関数

受信文字が_sci1_rx_dataに格納されているので、これをメイン関数から呼び出せるようにします。また、呼び出そうと思った時点で受信完了しているか確認するために、_sci1_rx_fin_fを取得します。

_sci1_rx_data_sci1_rx_fin_fintSCI.c内ではどこでも使えるグローバル変数ですが、他のソースファイルからは読み出すことができません。externすればプロジェクト全体で使えるグローバル変数扱いになり他のソースから読み出すことができるようになります。その代り、(間違って)書き換えることも可能になります。
これを防止するために重要な変数はグローバル化しない方法でプログラムを作成するべきです。
そこで、_sci1_rx_fin_f_sci1_rx_data中身を取得する関数を作成します。

<設定>
//受信フラグを確認
int get_recive_f(int channel){
    int value=0;
    switch (channel){
        case 1:
            value=_sci1_rx_fin_f;
            break;
        default:
            value=0;
            break;
    }
    return value;
}
//受信フラグをクリア
void clr_recive_f(int channel){
    switch (channel){
        case 1:
            _sci1_rx_fin_f=0;
            break;
        default:
            break;
    }
}

//受信データをコピー
void get_recive_buf(int channel,char* buf){
    switch (channel){
        case 1:
            sprintf(buf,"%s",_sci1_rx_data);
            break;
        default:
            sprintf(buf,"");
            break;
    }
}

おまけで受信フラグをクリアする関数も用意しました。
<解説>
現在はSCI1しか設定していませんが、今後はSCI0とSCI2も使えるように設定します。
そこで、データの呼び出し関数は同じ関数でポート番号を指定できるようにしました。
これで、内部変数に直接アクセスすることなくデータを取得できます。
ポインタchar*の挙動がよくわからない人はポインタの解説ページを参照してください。

ここまでの関数はすべてintSCI.hプロトタイプ宣言をしておいてください。


メイン処理

あとはメイン関数を変更すれば受信処理が行えるようになります。
<設定>
#include "iodefine.h"
#include <stdio.h>
#include "io_setup.h"
#include "intSCI.h"

void main(){
    char recive_buf[255];                   //受信バッファ
    hardware_setup();                    //汎用IOの初期化
    init_SCI1();                        //シリアル通信1の初期化

    write_sci1_sum("HELLO WORLD");

    while(1){                            //無限ループ開始
        if(get_recive_f(1)){        //SCI1で新しく受信したか確認
            get_recive_buf(1,recive_buf);    //受信データをrecive_bufにコピー
            clr_recive_f(1);                //受信完了フラグをリセット
            write_sci1_sum(recive_buf);        //受信した文字をSUM付きで送り返す
        }//end get_recive_f
    }//end while
}//end main

<解説>
受信完了確認を行い、
受信データをrecive_bufにコピーし、
受信完了フラグをリセットし、
受信データをSUM付きで送り返しています。

TMZで
[HELLO WORLD]
を送信したら
[[HELLO WORLD]D4]
が戻ってきました。

長くなったのでSUMの確認関数は次回実装します。
PR

コメント

プロフィール

HN:
ぼんどF博士
性別:
男性
自己紹介:

最新コメント