忍者ブログ

Fグループ電子工作講座

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

シリアル通信の設定(7)受信データ解析

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

いよいよ最後の設定です。
前回のフォーマットで送信されたデータを受信し、これを解析して数値に戻します。

段階的に機能を作っていきましょう。

文字列を数字に変換する関数

とりあえず指定した文字列に対して、指定した場所から、指定した文字数分、数字に変換する関数を作ります。機能的にはSUMチェックでやった事と同じです。

<設定>
short str_to_num(char *buf,int start,int num){
    char sub_buf[5];    //部分切り出し用
    int len;
    short value;

    len=strlen(buf);    //文字数を取得
    if(num>4)            return -1;    //指定数が多すぎる
    if(start<0)            return -1;    //変換できない範囲を指定している
    if(start+num>len)    return -1;    //変換できない範囲を指定している

    strncpy( sub_buf, buf+start, num );    //変換したい文字部分を切り出し
    sub_buf[num]='\0';
    value=strto0x(sub_buf);                //文字を数値に変換

    return value;
}
<解説>
16進表記の最大4文字まで変換します(short型の範囲)
変換できない状況に対応するため、いくつか保護が入れてあります。
strto0xはSUMチェックの際に作成した関数です。

メイン処理で動作を確認してみます。
<設定>
#include "iodefine.h"
#include <stdio.h>
#include "io_setup.h"
#include "intSCI.h"

void main(){
    short data[16]={0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F};
    char recive_buf[255];
    char trans_buf[255];
    short ID;
    short DLC;

    hardware_setup();                    //汎用IOの初期化
    init_SCI1();                        //シリアル通信1の初期化
    send_data_sci1(0x10,10,data);        //専用フォーマットでデータ送信

    while(1){                                //無限ループ開始
        if(get_recive_f(1)){                //SCI1で新しく受信したか確認
            get_recive_buf(1,recive_buf);    //受信データをrecive_bufにコピー
            clr_recive_f(1);                //受信完了フラグをリセット
            if(check_sum(recive_buf)==1){    //SUMチェック合格
                ID=str_to_num(recive_buf,1,2);    //IDを取得
                DLC=str_to_num(recive_buf,3,2);    //DLCを取得
                sprintf(trans_buf,"%s sum check OK ID:%02X DLC:%02X\n",recive_buf,ID,DLC);
            }else{                            //SUMチェック不合格
                sprintf(trans_buf,"%s sum check NG\n",recive_buf);
            }
            write_sci1(trans_buf);            //受信した文字を送り返す
        }//end get_recive_f
    }//end while
}//end main

<解説>
フォーマットから外れているデータを解析すると危険なので、SUMチェックが合格した
もののみ解析を行います。

IDのある場所とDLCのある場所は決まっているので直接指定してあります。

TMZで動作確認してみます。
[100A00010203040506070809BF]
を送信しすると
[100A00010203040506070809BF] sum check OK ID:10 DLC:0A
が返ってきて正常に変換できたことが分かります。

試しに
[HELLO WORLD1C]
を送信すると
[HELLO WORLD1C] sum check OK ID:00 DLC:00
が返ってきました。
16進数以外の文字は変換できずに変換結果は0になります。


データ保存用変数

受信した文字データを数値化しますが、数値化したデータを保存する変数を作っておきます。

<設定>
#define MAX_RECIVE_DATA    10    //とりあえず10 ID分のデータを記録する
static unsigned char _recive_dlc[MAX_RECIVE_DATA];        //DLC
static unsigned char _recive_data[MAX_RECIVE_DATA][20];    //受信データ 20個
static unsigned long _recive_time[MAX_RECIVE_DATA];        //最終受信時刻
static unsigned char _recive_timeout[MAX_RECIVE_DATA];    //受信タイムアウト

<解説>
受信データ_recive_data以外にも最終受信時刻_recive_timeを記録します。
また、最終受信から一定時間(500ms)経過して次の受信が無い場合はデータをリセットするようにします。
最終受信時刻から一定時間経過した場合はタイムアウトフラグ_recive_timeout1にします。
本当は構造体で作りたかったのですが、構造体の説明を用意していなかったので、グローバル変数で作成しておきます。

これらの変数の初期化関数を以下とします。

<設定>
//受信データ1セット分を初期化
void init_recive_data(int data_num){
    int i;
    _recive_dlc[data_num]=0;
    _recive_time[data_num]=0;
    _recive_timeout[data_num]=1;

    for(i=0;i<20;i++){
        _recive_data[data_num][i]=0;
    }
}

//受信データ全部を初期化
void init_recive_data_all(void){
    int i;
    for(i=0;i<MAX_RECIVE_DATA;i++){
        init_recive_data(i);
    }
}


また、タイムアウトのチェックにはタイマ割り込み(intCMT.cintCMT.h)にて作成した関数getsystime()を使用します。

<設定>
//各データごとのタイムアウトチェック
void check_timeout(int data_num){
    if(_recive_timeout[data_num]==1)    //元々タイムアウトしている場合
        return;

    if(_recive_time[data_num]+500<getsystime()){
        init_recive_data(data_num);
        _recive_timeout[data_num]=1;
    }
}

//全データのタイムアウトチェック
void check_timeout_all(void){
    int i;
    for(i=0;i<MAX_RECIVE_DATA;i++){
        check_timeout(i);
    }
}

_recive_data_recive_dlc_recive_timeoutをそれぞれ呼び出す関数も作成します。

//受信データを指定して取得
unsigned char get_recive_data(int data_num,int data_pos){
    if(_recive_dlc[data_num]<data_pos)
        return -1;    //ありえない位置を参照した

    return _recive_data[data_num][data_pos];
}

//受信データ長を取得
unsigned char get_recive_dlc(int data_num){
    return _recive_dlc[data_num];
}

//タイムアウト状態を取得
unsigned char get_recive_timeout(int data_num){
    return _recive_timeout[data_num];
}
これでデータ保存先の準備ができました。

解析関数

冒頭でテストしたif(get_recive_f(1)){}の中身はmain関数内で見える必要はありません
そこでSUMチェックやら数値変換やらをひとまとめに実行する関数を作成します。

先ほど通信に使ったIDをdefineしておきます。
<設定>
#define ID_OPE        0x10    //操作情報用通信ID
#define DATA_NUM_OPE    0        //操作データ保管No.

<設定>
//受信データの解析(定期的に実行されることが前提)
short analysys_sci(short port){
    char recive_buf[255];
    short len;    //文字列長
    short rtn;    //関数の戻り値
    short ID;
    short DLC;

    check_timeout_all();    //タイムアウトをチェックする

    if(get_recive_f(port)){                //SCI1で新しく受信したか確認
        get_recive_buf(port,recive_buf);    //受信データをrecive_bufにコピー
        clr_recive_f(port);                //受信完了フラグをリセット
        if(check_sum(recive_buf)==1){    //SUMチェック合格
            len=strlen(recive_buf);

            ID=str_to_num(recive_buf,1,2);    //IDを取得
            DLC=str_to_num(recive_buf,3,2);    //DLCを取得

            if(ID==0x00)
                return 0;    //文字列を受信した

            if(len!=(1+2+2+DLC*2+2+1))    //文字数が一致しないSTX+ID+DLC+データ+SUM+ETX
                return -2;

            switch(ID){        //この中に各IDにおける解析処理を追加していく
                case ID_OPE:
                    rtn=analysys_0x10(recive_buf,ID,DLC); //ID:0x10に対する解析処理
                    break;
                default:
                    rtn=ID;
                    break;
            }//end ID

            return rtn;
        }//end check_sum
        return -1;    //受信したけどSUMチェックが不合格
    }//end get_recive_f
    return 0;    //受信していない
}

メインの解析処理を以下とします。

<設定>
short analysys_0x10(char *buf,int id,int dlc){
    int i;
    int data_num=DATA_NUM_OPE;

    if(id!=ID_OPE)    return -3;    //一致しないはずがないのでプログラムミス

    _recive_dlc[data_num]=dlc;                //DLCを格納
    _recive_time[data_num]=getsystime();    //最終受信時刻を更新
    _recive_timeout[data_num]=0;            //タイムアウトフラグを解除

    for(i=0;i<dlc;i++){
        if(i>20)
            return ID_OPE;    //多すぎたら終了

        _recive_data[data_num][i]=str_to_num(buf,1+2+2+i*2,2);    //受信データを順次格納
    }
    return ID_OPE;
}

<解説>
この関数を実行できた時点で正常受信が成功しているので、最終受信時刻を更新して、タイムアウトフラグを解除します。

send_data_sci1と対になる関数

(2017/08/16追記)
send_data_sci1で送信したデータをrecv_data_sci1で取得できるようにしてみます。

//専用フォーマットでデータをまるごと取得
void recv_data_sci1(int id,int dlc,short*data){
    int data_num;
    int i;

    if(id>255)    return;    //IDがおかしいので終了
    if(id<0)    return;
    if(dlc<0)    return; //DLCがおかしいので終了
    if(dlc>100)    return;

    switch(id){        //この中に各IDに対するデータ番号を追記していく
        case ID_OPE:
            data_num=DATA_NUM_OPE;
            break;
        default:    //登録が無いので終了
            return;
            break;
    }//end ID

    if(get_recive_timeout(data_num)==0){    //操作情報がタイムアウトしていない
        for(i=0;i<dlc;i++){
            data[i]=get_recive_data(data_num,i);    //受信したデータを取得0-255
        }//end for
    }else{        //操作情報がタイムアウトしている
        for(i=0;i<dlc;i++){
            data[i]=0;    //0に設定する
        }//end for
    }//end get_recive_timeout
}
<解説>
名前にsci1が入っていますが、あまり関係ありません。送信側と名前を統一したかっただけです。


ここまで作成した関数はすべてintSCI.hでプロトタイプ宣言しておく必要があります。

かなり時間がかかりましたが、

以上でシリアル通信を使ったデータ送受信の設定は終わります。

最終的にできたファイルは以下となります。
シリアル通信ソースファイル:intSCI.c
シリアル通信ヘッダファイル:intSCI.h

動作確認

とりあえず正常に受信データの解析ができるか試してみます。
<設定>
#include "iodefine.h"
#include <stdio.h>
#include "io_setup.h"
#include "intSCI.h"
#include "intCMT.h"
void main(){
    short data[16]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F};
    char transbuf[255];

    int rtn;   

    hardware_setup();                    //汎用IOの初期化
    init_SCI1();                        //シリアル通信1の初期化

    send_data_sci1(ID_OPE,10,data);        //専用フォーマットでデータ送信

    while(1){                            //無限ループ開始
        rtn=analysys_sci(1);            //受信チェック
        if(rtn==ID_OPE){
            sprintf(transbuf,"recive ID:0xX%02X DLC:%d %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X"
                ,rtn
                ,get_recive_dlc(DATA_NUM_OPE)
                ,get_recive_data(DATA_NUM_OPE,0)
                ,get_recive_data(DATA_NUM_OPE,1)
                ,get_recive_data(DATA_NUM_OPE,2)
                ,get_recive_data(DATA_NUM_OPE,3)
                ,get_recive_data(DATA_NUM_OPE,4)
                ,get_recive_data(DATA_NUM_OPE,5)
                ,get_recive_data(DATA_NUM_OPE,6)
                ,get_recive_data(DATA_NUM_OPE,7)
                ,get_recive_data(DATA_NUM_OPE,8)
                ,get_recive_data(DATA_NUM_OPE,9)
            );
            write_sci1(transbuf);        //解析結果を送り返す
        }//end ID_OPE
    }//end while
}//end main

起動時に送られてくるデータ[100A00010203040506070809BF]はフォーマット通りに設定された電文です。
これをTMZからSH7125へ送り返せば解析ができます。
recive ID:0x10 DLC:10 00 01 02 03 04 05 06 07 08 09
正常に解析できていれば成功です。

次回はマイコン同士で通信を行い、遠隔操作をやってみます。
PR

コメント

プロフィール

HN:
ぼんどF博士
性別:
男性

最新コメント