質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

91.37%

  • C++

    2409questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

  • Boost

    25questions

    Boost (ブースト)は、C++の先駆的な開発者のコミュニティ、 またそのコミュニティによって公開されているオープンソースライブラリのことを指します。

boost::Asioのシリアル通信にて、バッファの削除が正常に動作しない

受付中

回答 1

投稿 2017/12/07 04:09

  • 評価
  • クリップ 0
  • VIEW 69

Unity-chan

score 12

前提・実現したいこと

Arduinoと、双方向シリアル通信を、非同期で行いたい。
ArduinoとC++プログラムを同時に開発していくと、正しく動作しない場合にどちらが原因か分からなくなるため、今回は「com0com」で仮想的に二つのCOMポートを繋げつつ「RS232Cテストツール」にデータの送受信を行わせた。

発生している問題・エラーメッセージ

COM側から前回受信した文字列よりも短い文字列を受信すると、短い文字列の後に前回受信した文字列が残る。(前回のデータに上書きされた状態で表示される)

イメージ説明
1.COM側から「22」を受信 →「22」 が出力される
2.COM側から「555」を受信 →「555」が出力される
3.COM側から「1」を受信    →「155」が出力される(ここで1が出力して欲しかった)
☆文字列長によらず、前回の文字列が出力されないようにしたい

該当のソースコード

SerialCommunication.h

#ifndef __SerialCommunication_h__
#define __SerialCommunication_h__


// Description:
//  同期・非同期を指定する
//
// Remarks:
//  InitSerialComの引数として用いる
//  
enum SyncAsync {
    COM_SYNC = 1,
    COM_ASYNC = 0,
};


class SerialCommunication {
public:
    //-----------------------------------------------------------------------------
    //
    // Name: SerialCommunication()
    //
    // Description:
    //   シリアル通信を行うための初期化を行う.
    //
    // Arguments:
    //   comPort - 占有するシリアルポートを指定する
    //
    // See Also:
    //   ~SerialCommunication()
    //
    SerialCommunication(char* comPort);

    //-----------------------------------------------------------------------------
    //
    // Name: ~SerialCommunication()
    //
    // Description:
    //   確保したメモリを解放する.
    //
    // See Also:
    //   SerialCommunication()
    //
    ~SerialCommunication();


    //-----------------------------------------------------------------------------
    //
    // Name: InitSerialCom()
    //
    // Description:
    //   シリアル通信を行う際に必要な初期設定を行う.
    //
    // Arguments:
    //   isSyncro - 同期or非同期を指定する(SyncAsyncを使用)
    //   baudRate - シリアル通信時のボーレートを指定する
    //   byteSize - シリアル通信時のバイト数を指定する
    //
    // Remarks:
    //   現在非同期のみ実装済
    //   baudRateのデフォルト「9600」
    //   byteSizeのデフォルト「8」
    //
    void InitSerialCom(SyncAsync isSyncro, int baudRate, int byteSize);
    void InitSerialCom(SyncAsync isSyncro);

    //-----------------------------------------------------------------------------
    //
    // Name: ReceiveSetting()
    //
    // Description:
    //   シリアル通信を行う際に必要な追加設定を行う(主に受信).
    //
    // Arguments:
    //   isLoop      - 受信処理を繰り返し行うか設定する(繰り返す=true)
    //   delimPhrase - 指定文字を受信するまでバッファに貯める
    //
    // Remarks:
    //   いつかフロー制御,パリティの設定を追加したい
    //
    void ReceiveSetting(bool isLoop, char* delimPhrase);
    void ReceiveSetting(bool isLoop);


    //-----------------------------------------------------------------------------
    //
    // Name: Receive()
    //
    // Description:
    //   受信待機状態に入る.
    //
    // Remarks:
    //   ReceiveSettingに応じて挙動が変化する
    //    - isLoop = true :データの受信が完了したらもう一度受信待機状態に入る
    //             = false:データの受信が完了したら処理を終了する
    //    - delimPhrase   :指定文字列を受信するまで受信バッファに貯め続ける
    //                      指定文字列を受信したら受信が完了する
    //
    void Receive();

    //-----------------------------------------------------------------------------
    //
    // Name: GetReceivedData()
    //
    // Description:
    //   受信完了した,最新の文字列を取得する.
    //
    // Return:
    //   受信した文字列の先頭ポインタを返す
    //
    char* GetReceivedData();


    //-----------------------------------------------------------------------------
    //
    // Name: Send()
    //
    // Description:
    //   指定文字列を送信する.
    //
    // Arguments:
    //   sendData - 送信する文字列の先頭ポインタを指定する
    //
    void Send(char* sendData);


    //-----------------------------------------------------------------------------
    //
    // Name: StopLoop()
    //
    // Description:
    //   受信待機状態を解除する.
    //
    // Remarks:
    //   ループ状態も解除される
    //
    void StopLoop();





private:
    class SerialCommunication_impl* scmImpl;
};

#endif

SerialCommunication.cpp

#define PIMP(x) scmImpl->x    // Pimplイディオムにアクセスするためのマクロ

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <boost/array.hpp>
#include <iostream>
#include "SerialCommunication.h"

using namespace boost::asio;
using std::cout;
using std::endl;
using std::string;


class SerialCommunication_impl {
public:
    io_service    io;
    serial_port    sPort;
    streambuf    buf;
    SyncAsync    syncFlag;
    boost::thread thrReceive;
    string        writeData;
    string        delim;
    bool        loopFlag;
    string        receiveData;

    // 変数を初期化するコンストラクタ
    SerialCommunication_impl(char* comPort) :
        sPort(io, comPort),
        thrReceive(boost::bind(&io_service::run, &io)) {}


    // [同期/非同期]、ボーレート、通信バイトサイズを設定する
    void Initialize(SyncAsync isSync, int baudRate, int byteSize) {
        syncFlag = isSync;

        sPort.set_option(serial_port_base::baud_rate(baudRate));
        sPort.set_option(serial_port_base::character_size(byteSize));
    }

    // 通信(主に受信)における設定を変更する
    void ReceiveSetting(bool isLoop, char* delimPhrase) {
        //sPort.set_option(serial_port_base::flow_control(serial_port_base::flow_control::none));
        //sPort.set_option(serial_port_base::parity(serial_port_base::parity::none));
        //sPort.set_option(serial_port_base::stop_bits(serial_port_base::stop_bits::one));

        loopFlag = isLoop;
        delim = delimPhrase;
    }


    // 非同期受信
    void AsyncReceive() {
        // コールバック関数をbindする
        auto cbOnSend(
            boost::bind(&SerialCommunication_impl::ReadCallBack, this,
                placeholders::error,
                placeholders::bytes_transferred));


        // 区切り文字が設定されていたらその文字が現れるまで受信バッファにため込む、未設定の場合は通信1回ごとに受信データをバッファから取得する(設定は"ReceiveSetting")
        if(delim == "")
            async_read(sPort, buf, transfer_at_least(1), cbOnSend);
        else
            async_read_until(sPort, buf, delim.c_str(), cbOnSend);
    }

    // 受信におけるコールバック
    void ReadCallBack(const boost::system::error_code& ec, std::size_t size) {
        // 
        if (!ec) {
            // バッファは次の受信で使うので、違う変数に入れてバッファを空にする
            receiveData = const_cast<char*>(buffer_cast<const char*>(buf.data()));
            buf.consume(size);

            // 受信データの確認用
            //cout << receiveData << size  << endl;

            if (loopFlag)
                AsyncReceive();
        }
        else {
            // std::cout << "Read Error." << ec.message() << std::endl;
        }
    }


    // 非同期送信
    void AsyncWrite(char* sendData) {
        // 送信するデータをstringに変換
        writeData = sendData;

        // コールバック関数をbindする
        auto cbOnSend(
            boost::bind(&SerialCommunication_impl::WriteCallback, this,
                placeholders::error,
                placeholders::bytes_transferred));

        // 送信する
        async_write(sPort, buffer(writeData), cbOnSend);
    }

    // 送信におけるコールバック
    void WriteCallback(const boost::system::error_code& ec, std::size_t size) 
    {
        if (!ec);
//            cout << "送信したぞ" << endl;
        else
            cout << "送信失敗" << endl;
    }

    void ThreadStop() 
    {
        // スレッドを中断する
        thrReceive.interrupt();

        // async_readが終了後までinterruptは適応されないため、手動で停止。
        sPort.cancel();                
    }
};


// コンストラクタ
SerialCommunication::SerialCommunication(char* comPort) {
    scmImpl = new SerialCommunication_impl(comPort);
}
// デストラクタ
SerialCommunication::~SerialCommunication() {
    delete scmImpl;

    cout << "Finalize SerialCommunication." << endl;
}


// [同期/非同期]、ボーレート、通信バイトサイズを設定する
void SerialCommunication::InitSerialCom(SyncAsync isSyncro, int baudRate, int byteSize) {
    PIMP(Initialize(isSyncro, baudRate, byteSize));

}
void SerialCommunication::InitSerialCom(SyncAsync isSyncro) {
    InitSerialCom(isSyncro, 9600, 8);
}


// 受信のループ設定、区切り文字の指定
void SerialCommunication::ReceiveSetting(bool isLoop, char* delimPhrase) {
    PIMP(ReceiveSetting(isLoop, delimPhrase));
}
void SerialCommunication::ReceiveSetting(bool isLoop) {
    PIMP(ReceiveSetting(isLoop, ""));
}


// 受信状態に移行する
void SerialCommunication::Receive() {
    if (PIMP(syncFlag))
        ;
    else
        PIMP(AsyncReceive());
}

// 受信したデータを取得する
char* SerialCommunication::GetReceivedData() {
    // 「string -> const char* -> char*」にキャストした
    return const_cast<char*>(PIMP(receiveData).c_str());
}

// 値を送信する
void SerialCommunication::Send(char* sendData) {
    if (PIMP(syncFlag))
        ;
    else
        PIMP(AsyncWrite(sendData));
}

// 受信のループを止める
void SerialCommunication::StopLoop() {
    PIMP(ThreadStop());
}

Sample.cpp

#include <iostream>
#include <Windows.h>
#include "SerialCommunication.h"

using std::cout;
using std::endl;

int main(void) {
    // COM5を初期化
    SerialCommunication serial("COM5");

    serial.InitSerialCom( COM_ASYNC , 9600 , 8 );
    serial.ReceiveSetting(true);
    serial.Receive();


    int i = 0;
    while (true) {
        // 経過秒数のカウント
        cout << i++ << "秒経過" << endl;

        // 10秒経ったら終わり
        Sleep(1000);
        if (i > 10) {
            serial.StopLoop();
            break;
        }


        // 送信テスト
        serial.Send("test123テスト");

        // 受信したデータの確認
        cout << "受信データ:"<<serial.GetReceivedData() << endl;
    }
    getchar();


    return 0;
}

試したこと

〇ReadCallBackにある"buf.consume(size)"によってbufの中身が正常に消費されていることを確認した。
buf.consume(size)の前後におけるバッファのデータ変化を確認することで、バッファの消費が正常に実行されているか検証した。

    void ReadCallBack(const boost::system::error_code& ec, std::size_t size) {
        // 
        if (!ec) {
            // バッファは次の受信で使うので、違う変数に入れてバッファを空にする
            receiveData = const_cast<char*>(buffer_cast<const char*>(buf.data()));
            cout << receiveData << size << endl;

            buf.consume(size);

            cout << receiveData << size  << endl;

            if (loopFlag)
                AsyncReceive();
        }
        else {
            // std::cout << "Read Error." << ec.message() << std::endl;
        }
    }

補足情報(言語/FW/ツール等のバージョンなど)

Visual Studio 2017
Boost 1.65.1

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

0

こんにちは。

C言語文字列を使っているとよくやらかすのですが、最後のNULL終端を入れ忘れているということはないでしょうか? バッファは最初に0クリアしていて長い文字列は自動的にNULL終端されているとか。

ソース見てないので外れているかも。その時はごめんなさい。

投稿 2017/12/07 12:26

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

ただいまの回答率

91.37%

関連した質問

同じタグがついた質問を見る

  • C++

    2409questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

  • Boost

    25questions

    Boost (ブースト)は、C++の先駆的な開発者のコミュニティ、 またそのコミュニティによって公開されているオープンソースライブラリのことを指します。