Qtの基礎 - シリアル通信
概要
QtSerialPortライブラリは、 Qtライブラリのアドオンモジュールであり、ハードウェアシリアルポートとバーチャルシリアルポートの両方に単一のインターフェースを提供する。
シリアルインターフェースは、そのシンプルさと信頼性から、組み込みシステムやロボット開発等の業界ではよく使用されている。
QtSerialPortライブラリを使用することにより、開発者はシリアルインターフェイスへのアクセスが必要なQtアプリケーションの実装に必要な時間を大幅に短縮することができる。
データの送信
Qtのシリアルポートモジュールをプロジェクトファイルに追加する。
# QMake
QT += core serialport
# CMake
find_package(Qt6 REQUIRED COMPONENTS SerialPort)
target_link_libraries(mytarget PRIVATE Qt6::SerialPort)
以下の例では、ボーレートは9600[bps]、データビットは8[ビット]、パリティなし、ストップビットは1[ビット]の8N1で、シリアル通信を行っている。
#include <QCoreApplication>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QSerialPort serial;
serial.setPortName("/dev/ttyUSB0"); // シリアルポート名 (環境に合わせて変更すること)
serial.setBaudRate(QSerialPort::Baud9600); // ボーレート 9600[bps]
serial.setDataBits(QSerialPort::Data8); // データビット 8[ビット]
serial.setParity(QSerialPort::NoParity); // パリティなし
serial.setStopBits(QSerialPort::OneStop); // ストップビット 1[ビット]
serial.setFlowControl(QSerialPort::NoFlowControl); // フロー制御なし
if (!serial.open(QIODevice::ReadWrite)) {
qDebug() << "Could not open serial port : " << serial.errorString();
return -1;
}
// データの送信
QByteArray dataToSend = "Hello, Serial Port!";
serial.write(dataToSend);
serial.close();
return a.exec();
}
データの受信
同期式
以下の例では、同期的にデータを受信している。
実用的な設計を行う場合は、信頼性と応答性を高めるために、シグナルおよびスロットを使用した非同期でデータを受信する方法が推奨される。
#include <QCoreApplication>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QSerialPort serial;
serial.setPortName("/dev/ttyUSB0"); // シリアルポート名 (環境に合わせて変更すること)
serial.setBaudRate(QSerialPort::Baud9600); // ボーレート 9600[bps]
serial.setDataBits(QSerialPort::Data8); // データビット 8[ビット]
serial.setParity(QSerialPort::NoParity); // パリティなし
serial.setStopBits(QSerialPort::OneStop); // ストップビット 1[ビット]
serial.setFlowControl(QSerialPort::NoFlowControl); // フロー制御なし
if (!serial.open(QIODevice::ReadWrite)) {
qDebug() << "Could not open serial port : " << serial.errorString();
return -1;
}
// データの受信 (同期式)
// 非同期でデータを受信する方法を推奨する
// 1[Sec]待機
if (serial.waitForReadyRead(1000)) {
QByteArray responseData = serial.readAll();
while (serial.waitForReadyRead(10))
responseData += serial.readAll();
qDebug() << "Recieved data : " << responseData;
}
else {
qDebug() << "Failed to recieve data";
}
serial.close();
return a.exec();
}
非同期
非同期でデータを受信する場合、QSerialPort
クラスのシグナルとスロットのメカニズムを使用する。
これにより、データが受信された時に自動的に通知されて、アプリケーションのメインループをブロックすることなくデータを受信することができる。
まず、QObject
クラスを継承したクラスを定義する。
シリアルポートからのデータの受信を管理するためのスロットを用意する。
以下の例では、シリアルポートがデータを受信したことを検出するために、readyRead
シグナルを使用している。
// Sample.hファイル
#include <QCoreApplication>
#include <QSerialPort>
#include <QDebug>
class SerialPortReader : public QObject
{
Q_OBJECT
public:
SerialPortReader(QSerialPort *serialPort, QObject *parent = nullptr) : QObject(parent), m_serialPort(serialPort)
{
connect(m_serialPort, &QSerialPort::readyRead, this, &SerialPortReader::handleReadyRead);
}
private slots:
void handleReadyRead()
{
const QByteArray data = m_serialPort->readAll();
qDebug() << "Recieved data : " << data;
}
private:
QSerialPort *m_serialPort;
};
// Sample.cppファイル
#include <QCoreApplication>
#include <QSerialPort>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QSerialPort serial;
serial.setPortName("/dev/ttyUSB0"); // シリアルポート名 (環境に合わせて変更すること)
serial.setBaudRate(QSerialPort::Baud9600); // ボーレート 9600[bps]
serial.setDataBits(QSerialPort::Data8); // データビット 8[ビット]
serial.setParity(QSerialPort::NoParity); // パリティなし
serial.setStopBits(QSerialPort::OneStop); // ストップビット 1[ビット]
serial.setFlowControl(QSerialPort::NoFlowControl); // フロー制御なし
if (!serial.open(QIODevice::ReadWrite)) {
qDebug() << "Could not open serial port : " << serial.errorString();
return -1;
}
SerialPortReader reader(&serial);
return app.exec();
}
データの送受信
非同期
非同期でデータを受信する場合、QSerialPort
クラスのシグナルとスロットのメカニズムを使用する。
これにより、データが受信された時に自動的に通知されて、アプリケーションのメインループをブロックすることなくデータを受信することができる。
まず、QObject
クラスを継承したクラスを定義する。
シリアルポートからのデータの送受信を管理するためのスロットを用意する。
- シリアルポートがデータを受信したことを検出するために、
readyRead
シグナルを使用する。 - シリアルポートがデータを送信完了したことを検出するために、
QIODevice::bytesWritten
シグナルを使用する。QIODevice::bytesWritten
シグナルは、データがデバイスのバッファに書き込まれた時に発生する。- これは必ずしも物理的な送信完了を意味するわけではない。
- 大量のデータを送信する場合、
QIODevice::bytesWritten
シグナルは複数回発生する可能性がある。 - そのため、送信データのサイズを記録して、
QIODevice::bytesWritten
シグナルで受信したバイト数の合計がそのサイズに達した時に送信完了とみなす。
以下の例では、8N1設定の非同期シリアル通信を行っている。
// SerialCommunication.hファイル
#include <QCoreApplication>
#include <QSerialPort>
#include <QDebug>
class SerialCommunication : public QObject
{
Q_OBJECT
private:
QSerialPort *m_serialPort;
qint64 m_bytesWritten;
qint64 m_totalBytesToWrite;
public:
explicit SerialCommunication(QObject *parent = nullptr) : QObject(parent), m_bytesWritten(0), m_totalBytesToWrite(0)
{
m_serialPort = new QSerialPort(this);
connect(m_serialPort, &QSerialPort::readyRead, this, &SerialCommunication::handleReadyRead);
connect(m_serialPort, &QSerialPort::errorOccurred, this, &SerialCommunication::handleError);
connect(m_serialPort, &QSerialPort::bytesWritten, this, &SerialCommunication::handleBytesWritten);
}
bool openPort(const QString &portName)
{
m_serialPort->setPortName(portName);
m_serialPort->setBaudRate(QSerialPort::Baud9600);
m_serialPort->setDataBits(QSerialPort::Data8);
m_serialPort->setParity(QSerialPort::NoParity);
m_serialPort->setStopBits(QSerialPort::OneStop);
m_serialPort->setFlowControl(QSerialPort::NoFlowControl);
if (m_serialPort->open(QIODevice::ReadWrite)) {
qDebug() << "シリアルポートのオープンに成功";
return true;
}
else {
qDebug() << "シリアルポートのオープンに失敗: " << m_serialPort->errorString();
return false;
}
}
void sendData(const QByteArray &data)
{
if (m_serialPort->isOpen()) {
m_bytesWritten = 0;
m_totalBytesToWrite = data.size();
qint64 bytesWritten = m_serialPort->write(data);
if (bytesWritten == -1) {
qDebug() << "ポートへのデータの書き込みに失敗: " << m_serialPort->errorString();
}
else if (bytesWritten != data.size()) {
qDebug() << "ポートへの全データの書き込みに失敗 - 書き込まれたバイト数: " << bytesWritten;
}
m_serialPort->flush();
}
else {
qDebug() << "データの送信に失敗 : シリアルポートがオープンされていない";
}
}
signals:
void transmissionComplete();
private slots:
void handleReadyRead()
{
QByteArray data = m_serialPort->readAll();
qDebug() << "受信データ: " << data;
// ここで受信したデータを処理する
// ...略
}
void handleBytesWritten(qint64 bytes)
{
m_bytesWritten += bytes;
qDebug() << "Bytes written:" << bytes << "Total:" << m_bytesWritten << "of" << m_totalBytesToWrite;
if (m_bytesWritten >= m_totalBytesToWrite) {
qDebug() << "Transmission complete!";
emit transmissionComplete();
// ここで送信完了後の処理を行う
// ...略
}
}
void handleError(QSerialPort::SerialPortError error)
{
if (error == QSerialPort::NoError) {
return;
}
qDebug() << "予期せぬエラー: " << m_serialPort->errorString();
switch (error) {
case QSerialPort::DeviceNotFoundError:
qDebug() << "デバイスが見つからない : 接続を確認すること";
break;
case QSerialPort::PermissionError:
qDebug() << "パーミッションエラー : 他のアプリケーションがそのポートを使用していないか確認すること";
break;
case QSerialPort::OpenError:
qDebug() << "ポートのオープンに失敗 : 既に使用されていないか確認すること";
break;
case QSerialPort::WriteError:
qDebug() << "送信エラー : 接続を確認して、再度送信できるかどうかを試すこと";
break;
case QSerialPort::ReadError:
qDebug() << "受信エラー : 接続を確認して、再度受信できるかどうかを試すこと";
break;
case QSerialPort::ResourceError:
qDebug() << "The port has been unexpectedly removed. Check the connection.";
m_serialPort->close();
break;
default:
qDebug() << "不明なエラー : 設定を確認すること";
}
}
};
// main.cppファイル
#include "SerialCommunication.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
SerialCommunication serialComm;
QObject::connect(&serialComm, &SerialCommunication::transmissionComplete, []() {
qDebug() << "送信完了のシグナルを受信した時";
// ここで送信完了後の処理を行う
// ...略
});
// 任意のポート名を指定すること
if (serialComm.openPort("COM1")) {
serialComm.sendData("Hello, Serial Port!");
}
return a.exec();
}