What Is QSerialPort?
QSerialPort is Qt’s cross-platform serial port API. It supports RS-232, RS-485, UART on Linux (/dev/ttyUSB0), Windows (COM3), macOS (/dev/tty.usbserial), and embedded Linux.
┌─────────────────────────────────────────────────────┐
│ Qt Application │
│ │
│ QSerialPort │
│ ┌──────────────────────────────────────┐ │
│ │ open() → configure → read/write │ │
│ └──────────────────┬───────────────────┘ │
│ │ │
└───────────────────────┼─────────────────────────────┘
│
┌─────────▼──────────┐
│ /dev/ttyUSB0 │ (Linux)
│ COM3 │ (Windows)
└────────────────────┘
│
┌─────────▼──────────┐
│ Microcontroller │
│ Sensor / Modem │
└────────────────────┘
CMakeLists.txt Setup
find_package(Qt6 REQUIRED COMPONENTS SerialPort)
target_link_libraries(MyApp PRIVATE Qt6::SerialPort)
List Available Ports
#include <QSerialPortInfo>
const auto ports = QSerialPortInfo::availablePorts();
for (const QSerialPortInfo &info : ports) {
qDebug() << "Port:" << info.portName();
qDebug() << "Description:" << info.description();
qDebug() << "Manufacturer:"<< info.manufacturer();
qDebug() << "VID:PID:" << info.vendorIdentifier()
<< info.productIdentifier();
qDebug() << "---";
}
Opening & Configuring a Port
#include <QSerialPort>
QSerialPort serial;
serial.setPortName("/dev/ttyUSB0"); // Linux; "COM3" on Windows
serial.setBaudRate(QSerialPort::Baud115200);
serial.setDataBits(QSerialPort::Data8);
serial.setParity(QSerialPort::NoParity);
serial.setStopBits(QSerialPort::OneStop);
serial.setFlowControl(QSerialPort::NoFlowControl);
if (!serial.open(QIODevice::ReadWrite)) {
qWarning() << "Cannot open:" << serial.errorString();
return;
}
qDebug() << "Port opened successfully";
Baud Rate Quick Reference:
| Constant | BPS |
|---|---|
Baud9600 | 9600 |
Baud19200 | 19200 |
Baud38400 | 38400 |
Baud115200 | 115200 |
Baud1000000 | 1 Mbps |
Async Reading (Event-Driven)
// In a QObject-based class
connect(&serial, &QSerialPort::readyRead,
this, &MyClass::onDataReceived);
void MyClass::onDataReceived() {
QByteArray data = serial.readAll();
qDebug() << "Received:" << data.toHex(' ');
// OR for text:
qDebug() << "Text:" << QString::fromUtf8(data);
}
Writing Data
// Write raw bytes
QByteArray command = QByteArray::fromHex("020103FF03");
serial.write(command);
serial.waitForBytesWritten(1000); // wait up to 1s
// Write text
serial.write("AT+VERSION\r\n");
// Flush
serial.flush();
Synchronous Read (Blocking, for simple cases)
serial.write("STATUS?\r\n");
if (serial.waitForReadyRead(2000)) { // wait up to 2s
QByteArray response = serial.readAll();
// Wait for more data
while (serial.waitForReadyRead(100))
response += serial.readAll();
qDebug() << "Response:" << response;
} else {
qWarning() << "Timeout waiting for response";
}
Framing — Read Until Newline
// In-class buffer for partial reads
QByteArray m_rxBuffer;
void onDataReceived() {
m_rxBuffer += serial.readAll();
while (true) {
int idx = m_rxBuffer.indexOf('\n');
if (idx < 0) break; // no complete line yet
QByteArray line = m_rxBuffer.left(idx).trimmed();
m_rxBuffer.remove(0, idx + 1);
qDebug() << "Line:" << line;
parseFrame(line);
}
}
Fixed-Length Frame Parser
const int FRAME_SIZE = 8; // [STX][LEN][CMD][D0][D1][D2][CRC][ETX]
void onDataReceived() {
m_rxBuffer += serial.readAll();
while (m_rxBuffer.size() >= FRAME_SIZE) {
// Find STX
int stx = m_rxBuffer.indexOf('\x02');
if (stx < 0) { m_rxBuffer.clear(); break; }
if (stx > 0) m_rxBuffer.remove(0, stx); // discard garbage
if (m_rxBuffer.size() < FRAME_SIZE) break;
QByteArray frame = m_rxBuffer.left(FRAME_SIZE);
if (frame.last() == '\x03') {
processFrame(frame);
}
m_rxBuffer.remove(0, FRAME_SIZE);
}
}
Error Handling
connect(&serial, &QSerialPort::errorOccurred,
[](QSerialPort::SerialPortError error) {
if (error == QSerialPort::ResourceError) {
qWarning() << "Device disconnected!";
// Attempt reconnect
} else if (error != QSerialPort::NoError) {
qWarning() << "Serial error:" << error;
}
});
Visual: RS-232 Frame
START DATA BITS (8) PARITY STOP
│ ┌──┬──┬──┬──┬──┬──┬──┬──┐ │ │
└────┤b0│b1│b2│b3│b4│b5│b6│b7├───┴──────┘
└──┴──┴──┴──┴──┴──┴──┴──┘
0 LSB MSB P 1
Protocol frame on top of UART:
[STX 0x02][LEN][CMD][PAYLOAD...][CRC][ETX 0x03]
Lab 1 — UART Terminal
class UartTerminal : public QWidget {
Q_OBJECT
QSerialPort *m_serial;
QTextEdit *m_log;
QLineEdit *m_input;
QByteArray m_rxBuf;
public:
UartTerminal(QWidget *p = nullptr) : QWidget(p) {
auto *lay = new QVBoxLayout(this);
// Port selector
auto *hbox = new QHBoxLayout;
auto *portBox = new QComboBox;
for (auto &pi : QSerialPortInfo::availablePorts())
portBox->addItem(pi.portName());
auto *connectBtn = new QPushButton("Connect");
hbox->addWidget(new QLabel("Port:"));
hbox->addWidget(portBox);
hbox->addWidget(connectBtn);
lay->addLayout(hbox);
// Log
m_log = new QTextEdit;
m_log->setReadOnly(true);
m_log->setFont(QFont("Courier", 10));
lay->addWidget(m_log);
// Input
auto *inputRow = new QHBoxLayout;
m_input = new QLineEdit;
auto *sendBtn = new QPushButton("Send");
inputRow->addWidget(m_input);
inputRow->addWidget(sendBtn);
lay->addLayout(inputRow);
m_serial = new QSerialPort(this);
m_serial->setBaudRate(QSerialPort::Baud115200);
connect(connectBtn, &QPushButton::clicked, [=]() {
m_serial->setPortName(portBox->currentText());
if (m_serial->open(QIODevice::ReadWrite))
m_log->append("<b>Connected to " + portBox->currentText() + "</b>");
else
m_log->append("<font color='red'>Error: " + m_serial->errorString() + "</font>");
});
connect(sendBtn, &QPushButton::clicked, this, &UartTerminal::sendData);
connect(m_input, &QLineEdit::returnPressed, this, &UartTerminal::sendData);
connect(m_serial, &QSerialPort::readyRead, this, &UartTerminal::onRead);
}
private slots:
void sendData() {
if (!m_serial->isOpen()) return;
QString cmd = m_input->text() + "\r\n";
m_serial->write(cmd.toUtf8());
m_log->append("<font color='#89b4fa'>TX: " + m_input->text() + "</font>");
m_input->clear();
}
void onRead() {
m_rxBuf += m_serial->readAll();
while (true) {
int nl = m_rxBuf.indexOf('\n');
if (nl < 0) break;
QString line = QString::fromUtf8(m_rxBuf.left(nl)).trimmed();
m_rxBuf.remove(0, nl + 1);
m_log->append("<font color='#a6e3a1'>RX: " + line + "</font>");
}
}
};
Lab 2 — Modbus RTU Frame Builder
// Simple Modbus RTU helper
class ModbusRtu {
public:
// Build Read Holding Registers request (FC=0x03)
static QByteArray readHoldingRegs(quint8 addr, quint16 startReg, quint16 count) {
QByteArray pdu;
pdu.append(static_cast<char>(addr));
pdu.append(0x03); // FC
pdu.append(static_cast<char>(startReg >> 8));
pdu.append(static_cast<char>(startReg & 0xFF));
pdu.append(static_cast<char>(count >> 8));
pdu.append(static_cast<char>(count & 0xFF));
quint16 crc = crc16(pdu);
pdu.append(static_cast<char>(crc & 0xFF));
pdu.append(static_cast<char>(crc >> 8));
return pdu;
}
static QList<quint16> parseResponse(const QByteArray &resp) {
QList<quint16> regs;
if (resp.size() < 5) return regs;
int byteCount = static_cast<quint8>(resp[2]);
for (int i = 0; i < byteCount; i += 2) {
quint16 val = (static_cast<quint8>(resp[3+i]) << 8)
| static_cast<quint8>(resp[4+i]);
regs.append(val);
}
return regs;
}
private:
static quint16 crc16(const QByteArray &data) {
quint16 crc = 0xFFFF;
for (quint8 b : data) {
crc ^= b;
for (int i = 0; i < 8; ++i)
crc = (crc & 1) ? (crc >> 1) ^ 0xA001 : crc >> 1;
}
return crc;
}
};
// Usage
QByteArray req = ModbusRtu::readHoldingRegs(0x01, 0x0000, 10);
serial.write(req);
if (serial.waitForReadyRead(500)) {
QByteArray resp = serial.readAll();
auto regs = ModbusRtu::parseResponse(resp);
for (int i = 0; i < regs.size(); ++i)
qDebug() << QString("Reg[%1] = %2").arg(i).arg(regs[i]);
}
Interview Questions
Q1: What is the difference between readAll() and read(n) on QSerialPort?
readAll()returns all currently available bytes.read(n)returns up to n bytes. For framed protocols, use a buffer and check for complete frames — don’t rely on onereadAll()per frame since TCP/serial data often arrives in fragments.
Q2: Why should you use async (signal-based) reading instead of blocking waitForReadyRead()?
Blocking calls freeze the event loop — UI stops updating, timers stop firing. Async reading via
readyReadkeeps the application responsive. Only use blocking reads in worker threads or simple command-line tools.
Q3: How do you reconnect automatically when a USB serial device is unplugged?
Connect to
QSerialPort::errorOccurredand watch forResourceError. Use aQTimerto pollQSerialPortInfo::availablePorts()and re-open when the port reappears.
Q4: What baud rate mismatch causes?
Garbled data (framing errors, parity errors). The receiver interprets bit timings incorrectly. Always ensure both sides use the same baud, data bits, parity, and stop bits.
Q5: How do you handle half-duplex RS-485 with QSerialPort?
RS-485 requires toggling a direction pin (DE/RE) before sending and after receiving. On Linux, use
setRequestToSend(true/false)or a custom GPIO. Some USB-RS485 adapters handle this automatically.
Application: Sensor Gateway
class SensorGateway : public QObject {
Q_OBJECT
QSerialPort *m_port;
QByteArray m_rxBuf;
QTimer *m_pollTimer;
quint8 m_pollAddr = 1; // Modbus device address
public:
SensorGateway(const QString &portName, QObject *p = nullptr)
: QObject(p)
{
m_port = new QSerialPort(this);
m_port->setPortName(portName);
m_port->setBaudRate(QSerialPort::Baud9600);
m_port->setDataBits(QSerialPort::Data8);
m_port->setParity(QSerialPort::NoParity);
m_port->setStopBits(QSerialPort::OneStop);
connect(m_port, &QSerialPort::readyRead, this, &SensorGateway::onRead);
connect(m_port, &QSerialPort::errorOccurred, this, &SensorGateway::onError);
m_pollTimer = new QTimer(this);
m_pollTimer->setInterval(1000);
connect(m_pollTimer, &QTimer::timeout, this, &SensorGateway::poll);
}
bool start() {
if (!m_port->open(QIODevice::ReadWrite)) {
qWarning() << "Cannot open port:" << m_port->errorString();
return false;
}
m_pollTimer->start();
qDebug() << "Gateway started on" << m_port->portName();
return true;
}
signals:
void sensorData(quint8 addr, quint16 reg0, quint16 reg1);
private slots:
void poll() {
QByteArray req = ModbusRtu::readHoldingRegs(m_pollAddr, 0, 2);
m_port->write(req);
}
void onRead() {
m_rxBuf += m_port->readAll();
if (m_rxBuf.size() >= 9) { // Modbus response: addr+fc+count+2regs+crc
auto regs = ModbusRtu::parseResponse(m_rxBuf);
if (regs.size() >= 2)
emit sensorData(m_pollAddr, regs[0], regs[1]);
m_rxBuf.clear();
}
}
void onError(QSerialPort::SerialPortError err) {
if (err == QSerialPort::ResourceError) {
qWarning() << "Device disconnected";
m_pollTimer->stop();
}
}
};
// main.cpp
SensorGateway gw("/dev/ttyUSB0");
QObject::connect(&gw, &SensorGateway::sensorData,
[](quint8 addr, quint16 r0, quint16 r1) {
float temp = r0 / 10.0f; // e.g., 365 → 36.5°C
float volt = r1 / 100.0f; // e.g., 330 → 3.30V
qDebug() << QString("Addr:%1 T=%2°C V=%3V")
.arg(addr).arg(temp).arg(volt);
});
gw.start();
References
| Resource | Link |
|---|---|
| Qt Docs: QSerialPort | https://doc.qt.io/qt-6/qserialport.html |
| Qt Docs: QSerialPortInfo | https://doc.qt.io/qt-6/qserialportinfo.html |
| Qt Docs: Qt Serial Port Module | https://doc.qt.io/qt-6/qtserialport-index.html |
Next tutorial → Qt Database (SQLite)