Qtでシリアル通信と非同期データ処理を実装する

シリアル通信の基本実装

QtのQSerialPortクラスを用いて、ハードウェアとの直列通信を制御します。このクラスは、ポートのオープン・クローズ、ボーレート・データビット・ストップビット・パリティ・フローコントロールの設定を簡易に実現します。通信は非同期で行われるため、UIスレッドの応答性を保つために受信データの処理を別スレッドに委譲します。

// シリアルポートの初期化と接続
void SerialTerminal::onConnectButtonClicked()
{
    serialPort = new QSerialPort(this);
    serialPort->setPortName("COM3");
    serialPort->setBaudRate(QSerialPort::Baud115200);
    serialPort->setDataBits(QSerialPort::Data8);
    serialPort->setParity(QSerialPort::NoParity);
    serialPort->setStopBits(QSerialPort::OneStop);
    serialPort->setFlowControl(QSerialPort::NoFlowControl);

    if (serialPort->open(QIODevice::ReadWrite)) {
        connect(serialPort, &QSerialPort::readyRead, this, &SerialTerminal::onDataReceived);
        qDebug() << "シリアルポートを開きました";
    } else {
        QMessageBox::critical(this, "エラー", serialPort->errorString());
    }
}

// 受信データの自動処理(信号でトリガー)
void SerialTerminal::onDataReceived()
{
    QByteArray received = serialPort->readAll();
    dataBuffer.append(received);

    // バッファをUIに反映(非同期)
    QMetaObject::invokeMethod(this, "updateDisplay", Qt::QueuedConnection);
}

// 送信データの送信
void SerialTerminal::onSendButtonClicked()
{
    QString message = inputField->toPlainText();
    if (!message.isEmpty()) {
        serialPort->write(message.toUtf8());
        inputField->clear();
    }
}

// シリアルポートの切断
void SerialTerminal::onDisconnectButtonClicked()
{
    if (serialPort && serialPort->isOpen()) {
        serialPort->close();
        delete serialPort;
        serialPort = nullptr;
        qDebug() << "シリアルポートを閉じました";
    }
}

バックグラウンドデータ処理スレッドの設計

受信したバイナリデータをリアルタイムで解析するため、QThreadを継承した専用スレッドを構築します。ここでは、16進数文字列のパターンを抽出し、有効なデータフレームを検出する処理を実装します。ファイルを介したデータ共有は非推奨であるため、スレッド間通信には信号・スロットを活用します。

// WorkerThread.h
class WorkerThread : public QThread
{
    Q_OBJECT

public:
    explicit WorkerThread(QObject *parent = nullptr);
    void setDataBuffer(QByteArray *buffer);
    void stop();

signals:
    void parsedData(const QString &);

protected:
    void run() override;

private:
    QByteArray *dataBuffer;
    volatile bool running;
    QRegularExpression hexPattern;
};

// WorkerThread.cpp
WorkerThread::WorkerThread(QObject *parent)
    : QThread(parent), running(true), hexPattern(QRegularExpression("[0-9A-Fa-f]{2}"))
{
}

void WorkerThread::setDataBuffer(QByteArray *buffer)
{
    dataBuffer = buffer;
}

void WorkerThread::stop()
{
    running = false;
}

void WorkerThread::run()
{
    while (running && dataBuffer) {
        if (dataBuffer->isEmpty()) {
            msleep(10); // データ待ち
            continue;
        }

        // バッファから16進数ペアを抽出
        for (int i = 0; i < dataBuffer->size() - 1; ++i) {
            char b1 = (*dataBuffer)[i];
            char b2 = (*dataBuffer)[i + 1];

            QString candidate = QString("%1%2").arg(b1, 0, 16).arg(b2, 0, 16);
            if (hexPattern.match(candidate).hasMatch()) {
                emit parsedData(candidate.toUpper());
                i++; // 次のバイトは既に処理済み
            }
        }

        // 処理済みデータを削除(簡易版)
        dataBuffer->clear();
        msleep(5);
    }
}

スレッド連携とUI更新

メインスレッドでは、WorkerThreadのパース結果を受信し、リアルタイムでテキストエリアに表示します。これにより、UIの処理負荷を分散し、通信の遅延を最小限に抑えます。

// MainWindowの初期化
void MainWindow::setupWorkerThread()
{
    worker = new WorkerThread(this);
    connect(worker, &WorkerThread::parsedData, this, &MainWindow::onParsedHex);
    worker->start();
}

void MainWindow::onParsedHex(const QString &hex)
{
    outputArea->append("[HEX]" + hex);
}

タグ: Qt QSerialPort qthread 多スレッド シリアル通信

6月8日 17:41 投稿