シリアル通信の基本実装
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);
}