QSerialProtクラスを使ってQtとArduinoでシリアル通信する


背景

Arduinoからシリアルで送信したデータをQtを使って処理したいときに
どうやっていいか調べたので残します。
その都度調べればいいんだけど、例によってQtの情報は英語ばかりで
毎回英語読むのは日本語より疲れる&時間がかかるので、
忘れないうちに日本語にして残しておきます。

環境

Qt 5.9.1

コード

おもにこちらこちらを参考にさせて頂きました。
シリアル通信で取得した文字列をテキスト編集エリアに追加していきます。

mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , _serialPort( new QSerialPort( this ) )
{
    ui->setupUi(this);

    // ここで取得した情報一覧をメニューとかコンボボックスとかに登録できる
    foreach( const QSerialPortInfo info, QSerialPortInfo::availablePorts() )
    {
        qDebug() << "Name        :" << info.portName();
        qDebug() << "Description :" << info.description();
        qDebug() << "Manufacturer:" << info.manufacturer() << "\n";
    }

    // でも今回は直書きする
    // portName: デバイスマネージャで確認する
    _serialPort->setPortName( QString( "COM4" ) );

    // baudRate,dataBits,parity,stopBits:
    // Arduinoのスケッチで指定したものと同じ数値を指定する
    _serialPort->setBaudRate( QSerialPort::Baud115200 );
    // ちなみに以下はArduinoのスケッチでSerial.beginの第2引数を省略した時の値
    _serialPort->setDataBits( QSerialPort::Data8 );
    _serialPort->setParity(   QSerialPort::NoParity );
    _serialPort->setStopBits( QSerialPort::OneStop );

    if ( _serialPort->open( QIODevice::ReadOnly ) )
    {
        connect( _serialPort, &QSerialPort::readyRead,
                 this,        &MainWindow::printData );
    }
    else
    {
        qDebug() << "Couldn't open the port [COM4].";
    }
}

MainWindow::~MainWindow()
{
    _serialPort->close();
    disconnect( _serialPort, &QSerialPort::readyRead,
                this,        &MainWindow::printData );
    delete _serialPort;
    delete ui;
}

void MainWindow::printData()
{
    if ( sender() == _serialPort )
    {
        QString data = QString( _serialPort->readAll() );
        ui->textEdit->append( data );
    }
}

Arduinoのスケッチはこちら。
電圧を読み取って出力するだけです。

voltage.ino
#define GET_VOLTAGE()  analogRead( A0 ) * 5.0 / 1024

float _voltage;

void setup()
{
    _voltage = 0.0;
    Serial.begin( 115200 );
    // Serial.begin( 11520, SERIAL_8N1 );と同じ
    // SERIAL_7E2(データ7ビット,偶数パリティ,ストップビット2ビット)とか
    // SERIAL_8O1(データ8ビット,奇数パリティ,ストップビット1ビット)とかがある。
}

void loop()
{
    _voltage = GET_VOLTAGE();
    Serial.print( "voltage : " );
    Serial.println( _voltage );
    delay( 1000 );
}

うごかしてみた

たまに改行がうまくいってない箇所があります。受け取った側のデータ処理が悪いのでしょうか。
ともあれ、ひとまず通信はできました。


(2017/09/12 追記)
どうやらスロットの処理の仕方が悪いようです。
readyRead()シグナルは、送信側の1回のコマンドに相当するとは限らないようで、1回分の送信データの途中までしか取得できなくても通知されるものみたいです。
これに対応したのが以下。

mainwindow.cpp
void MainWindow::printData()
{
    if ( sender() == _serialPort )
    {
        // とりあえずメンバのバッファにためる
        _buffer.append( _serialPort->readAll() );
        if ( _buffer.contains( "\r\n" ) )
        {

            // ArduinoのSerial.println()は末尾に<CR><LF>が付加される
            // 区切り文字が含まれてたら表示してバッファクリア        
            QString data = QString( _buffer );
            ui->textEdit->append( data );
            _buffer.clear();
        }
    }
}

でもこれだと文中に<CR><LF>があるときに修正が必要になるな。
EOLを何にするかをあらかじめ送信側と決めておけばよいのかな。

その他

readyRead()シグナルをハンドルするって、QIODeviceを使うときの常套句なんですね。
QFileしか使ったことなかったので勉強になりました。

送信はまた今度


(おわり)