関連:
シリアル通信関連記事一覧やっと受信処理に入れます。
ここでは割り込みを使った受信処理を設定します。
※割り込みを使わない1文字づつの受信は
シリアル通信の設定(1)プログラムは
シリアル通信の設定(3)まで済んでいる事を前提とします。
プロジェクトは
シリアル通信の設定(3)の物をそのまま使用します。
受信割り込みの設定
ここまで順に読んできた方であれば割り込みの設定自体はさほど難しくありません。
受信割り込み要求を許可すれば、
受信割り込みが発生するようになるのでそこに受信処理を書けばいいだけです。
SCI1を初期化する際に
SCI1.SCSCR.BIT.RIE = 1;とすれば受信割り込みが許可されます。
割り込みの優先順位は送信、受信で共通です。
<用語>RIE:レシーブインタラプトイネーブル
受信割り込み許可。
これを1に設定すると、RDRFが1になった時に割り込み処理が発生するようになります。
設定(1)のプログラムで
if(SCI1.SCSSR.BIT.RDRF)で待機させていた部分を割り込み処理で自動実行できるようになります。
ベクタ番号は221で
vecttbl.cと
intprg.cの
INT_SCI1_RXI1関数がこれに該当します。
注意点として、
受信割り込み許可を設定すると
受信割り込みRXI1以外に
受信エラー割り込みERI1も同時に発生するようになります。
受信エラー割り込みのベクタ番号は220で
vecttbl.cと
intprg.cの
INT_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.cの
INT_SCI1_ERI1へ登録する必要があります。
void INT_SCI1_ERI1(
void){
SCI1_INT_ERI1();}
ORER、
FER、
PERの何れかが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.cの
INT_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_fは
intSCI.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の確認関数は次回実装します。