串口(Serial Port)是计算机用于与外部设备进行数据通信的一种通信接口。它是一种通过逐位传输数据的方式来进行通信的接口,与并行口(Parallel Port)相对。串口通常用于连接各种外部设备,如调制解调器、打印机、传感器、嵌入式系统等。
RS-232串口通信
RS-232(Recommended Standard 232)是一种串行通信接口标准,定义了在计算机和外设之间进行串行数据传输的规则和电气特性。它包括一个DB-25或DE-9串口连接器和一组通信协议,用于实现点对点的串行通信。
在RS-232串口通信中,没有明确的主从之分,而是采用点对点通信的方式。
RS-232串口通信采用的是全双工通信方式。
相关类:
QSerialPort类:该类提供了对串口通信的支持。它可以用于打开、关闭、读写串口数据等操作,还可以设置串口的参数,如波特率、数据位、校验位、停止位等。
QSerialPortInfo类:该类提供了有关计算机上可用串口列表的信息,例如串口名称和描述。
在pro文件中添加模块:
QT += serialport
QSerialPort类
bool QSerialPort::open(QIODevice::OpenMode mode)
打开串口,mode参数指定打开模式。如果打开成功,返回true;否则返回false。
void QSerialPort::close()
关闭串口,并释放相关资源。
qint64 QSerialPort::read(char *data, qint64 maxSize)
从串口读取数据,最多读取maxSize个字节,存储在data中。返回实际读取的字节数。
QByteArray QSerialPort::readAll()
读取串口缓冲区中所有可用的数据,并以QByteArray类型返回。
qint64 QSerialPort::write(const char *data, qint64 maxSize)
向串口写入数据,写入最多maxSize个字节,数据存储在data中。返回实际写入的字节数。
void QSerialPort::clear()
清空串口缓冲区。
void QSerialPort::setPortName(const QString &name)
设置串口的名称。
void QSerialPort::setBaudRate(qint32 baudRate, QSerialPort::Directions directions = AllDirections)
设置串口的波特率。directions参数指定设置的方向,可选的值为AllDirections、Input、Output。
void QSerialPort::setDataBits(QSerialPort::DataBits dataBits)
设置串口数据位的个数。
void QSerialPort::setParity(QSerialPort::Parity parity)
设置串口的校验位类型。
void QSerialPort::setStopBits(QSerialPort::StopBits stopBits)
设置串口的停止位的个数。
void QSerialPort::setDataTerminalReady(bool set = true)
设置串口的数据终端就绪状态(DTR)。
void QSerialPort::setRequestToSend(bool set = true)
设置串口的请求发送状态(RTS)。
void QSerialPort::errorOccurred(QSerialPort::SerialPortError error)
当串口出现错误时,会发出errorOccurred信号。
void QSerialPort::readyRead()
当串口有数据可读取时,会发出readyRead信号。
bool QSerialPort::isOpen() const
判断串口是否已经打开。
QSerialPortInfo类
static QList<QSerialPortInfo> QSerialPortInfo::availablePorts()
这个静态函数返回一个QList<QSerialPortInfo>
列表,包含了当前计算机上可用的串口信息。可以通过遍历这个列表来获取每个串口的详细信息。
QString QSerialPortInfo::portName() const
返回串口的名称。串口名称一般以"COM"或"/dev/tty"开头,后面跟着一个数字或字母,用于唯一标识每个串口。
QString QSerialPortInfo::description() const
返回串口的描述信息。描述信息通常包括串口的型号和其他相关信息。
QString QSerialPortInfo::manufacturer() const
返回串口的制造商信息。制造商信息用于标识串口的生产厂家。
bool QSerialPortInfo::isNull() const
判断当前QSerialPortInfo对象是否为空。如果为空,则表示该对象不包含有效的串口信息。
使用流程:
- 创建QSerialPort对象,并设置串口参数(波特率、数据位、校验位、停止位等)。
- 打开串口,开始通信。
- 通过QSerialPort类的read()函数读取串口数据,通过write()函数向串口发送数据。
- 在不需要通信时,关闭串口并释放资源。
配置串口:
串口参数主要包括波特率、数据位、校验位和停止位。
波特率(Baud Rate)
指单位时间内传输的比特数。在串口通信中,波特率用于标识数据传输速率,它定义了每秒钟发送或接收的比特数。常见的波特率有9600、19200、38400、57600等。
RS-232-C标准规定的传输速率为50,75,100,150,300,600,1200,2400,4800,9600,19200,38400,常用9600bps
serialPort.setBaudRate(QSerialPort::Baud9600);
数据位(Data Bits)
指在一个字符中,用于传输数据的比特位数。一般情况下,数据位为5、6、7、8位之一。其中,7位和8位比较常用。
标准ASCII码是0127(7位),扩展的ASCII码是0255(8位),如果数据使用简单的文本(标准ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始\停止位,数据位和奇偶校验位
serialPort.setDataBits(QSerialPort::Data8);
校验位(Parity)
校验位是为了保证数据的正确性而在数据位后增加的一位校验码。
校验位可以采用奇校验、偶校验、无校验三种方式:
- 奇校验表示校验位被设置为1使得整个字符中1的个数为奇数;
- 偶校验表示校验位被设置为0使得整个字符中1的个数为偶数;
- 无校验表示不进行校验。
serialPort.setParity(QSerialPort::NoParity);
停止位(Stop Bits)
指在一个字符的末尾用于标识字符结束的比特位数。一般情况下,停止位为1或2位。其中,1位比较常用。
serialPort.setStopBits(QSerialPort::OneStop);
代码示例:
#include <QtSerialPort/QSerialPort>
#include <QtSerialPort/QSerialPortInfo>
//打印当前计算机上可用的串口列表
void printSerialPortList()
{
QList<QSerialPortInfo> serialPortInfos = QSerialPortInfo::availablePorts();
for (const QSerialPortInfo &serialPortInfo : serialPortInfos) {
qDebug() << "Name: " << serialPortInfo.portName();
qDebug() << "Description: " << serialPortInfo.description();
qDebug() << "Manufacturer: " << serialPortInfo.manufacturer();
ui->cmb_PortChose->addItem(serialPortInfo.portName() + ":" + serialPortInfo.description(),serialPortInfo.portName());
}
//获取波特率
QList<int> BaudRates = QSerialPortInfo::standardBaudRates();
for(const int& brData : BaudRates)
{
ui->cmb_baudRate->addItem(QString::number(brData),brData);
}
//设置默认波特率:9600
ui->cmb_baudRate->setCurrentText("9600");
//设置停止位
ui->cmb_stopBits->addItem("1",QSerialPort::OneStop);
ui->cmb_stopBits->addItem("1.5",QSerialPort::OneAndHalfStop);
ui->cmb_stopBits->addItem("2",QSerialPort::TwoStop);
//设置数据位
ui->cmb_databits->addItem("5",QSerialPort::Data5);
ui->cmb_databits->addItem("6",QSerialPort::Data6);
ui->cmb_databits->addItem("7",QSerialPort::Data7);
ui->cmb_databits->addItem("8",QSerialPort::Data8);
//设置校验位
ui->cmb_parity->addItem("no parity",QSerialPort::NoParity);
ui->cmb_parity->addItem("EvenParity",QSerialPort::EvenParity);
ui->cmb_parity->addItem("OddParity",QSerialPort::OddParity);
ui->cmb_parity->addItem("SpaceParity",QSerialPort::SpaceParity);
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QSerialPort serialPort;
//获取串口名
QString portName = ui->cmb_PortChose->currentData().toString();
//获取波特率
QSerialPort::BaudRate baudRate = ui->cmb_baudRate->currentData().value<QSerialPort::BaudRate>();
//获取数据位
QSerialPort::DataBits databits = ui->cmb_databits->currentData().value<QSerialPort::DataBits>();
//获取停止位
QSerialPort::StopBits stopBits = ui->cmb_stopBits->currentData().value<QSerialPort::StopBits>();
//获取校验位
QSerialPort::Parity parity = ui->cmb_parity->currentData().value< QSerialPort::Parity>();
//设置串口参数
//serialPort.setPortName("COM1");
//serialPort.setBaudRate(QSerialPort::Baud9600);
//serialPort.setDataBits(QSerialPort::Data8);
//serialPort.setParity(QSerialPort::NoParity);
//serialPort.setStopBits(QSerialPort::OneStop);
serialPort.setPortName(portName);
serialPort.setBaudRate(baudRate);
serialPort.setDataBits(databits);
serialPort.setStopBits(stopBits);
serialPort.setParity(parity);
//打开串口
if (!serialPort.open(QIODevice::ReadWrite)) {
qDebug() << "Failed to open the serial port!";
return -1;
}
//向串口写入数据
QByteArray sendData = "Hello, serial port!";
serialPort.write(sendData);
//从串口读取数据
//QByteArray receiveData = serialPort.readAll();
//qDebug() << "Received data: " << receiveData;
connect(serialPort, &QSerialPort::readyRead, [=]() {
QByteArray data = serialPort->readAll();
qDebug() << "Received data:" << data;
});
//关闭串口
serialPort.close();
return a.exec();
}
串口数据包处理
当串口数据出现分包时,我们可以采取以下方法来正确完整接收数据:
- 设置合适的包头和包尾:在数据传输中,可以定义一个特定的字节序列作为包头和包尾,用于标识一个完整的数据包的开始和结束。确保包头和包尾的值在数据中是唯一且不会与实际数据冲突。
- 使用缓冲区进行数据累积:创建一个缓冲区来存储从串口读取的数据。每次读取串口数据时,将读取到的数据追加到缓冲区末尾。
- 查找包头和包尾位置:从缓冲区中查找包头和包尾的位置。可以使用 indexOf 函数来查找包头和包尾的位置索引。
- 提取完整的数据包:如果同时存在包头和包尾,并且包头在包尾之前,则认为找到了一个完整的数据包。可以使用 mid 函数提取出完整的数据包,并进行进一步的处理。
- 处理剩余数据:如果找到了一个完整的数据包,将其处理后,将缓冲区中该数据包及之前的数据删除,以便处理下一个数据包。如果未找到完整的数据包,则等待下一次读取到更多数据,并继续查找包头和包尾。
发送数据包
在要发送的数据包前添加包头,然后发送完整的数据包。
QByteArray data; // 要发送的数据
QByteArray header = QByteArrayLiteral("\x02"); // 包头
QByteArray footer = QByteArrayLiteral("\x03"); // 包尾
// 添加包头和包尾到数据包
data = header + data + footer;
// 发送完整的数据包
serial.write(data);
接收数据包
使用循环读取串口数据,检测包头和包尾来提取完整的数据包
QByteArray receivedData; // 接收到的数据
QByteArray header = QByteArrayLiteral("\x02"); // 包头
QByteArray footer = QByteArrayLiteral("\x03"); // 包尾
//读取数据并设置等待超时时间
while (serial.waitForReadyRead(1000)) {
// 读取串口数据
QByteArray newData = serial.readAll();
// 将新数据添加到接收到的数据中
receivedData.append(newData);
// 检测包头和包尾
int headerIndex = receivedData.indexOf(header);
int footerIndex = receivedData.indexOf(footer);
if (headerIndex >= 0 && footerIndex >= 0 && headerIndex < footerIndex) {
// 提取完整的数据包
QByteArray packet = receivedData.mid(headerIndex + header.size(), footerIndex - headerIndex - header.size());
// 处理数据包
// ...
// 清除已处理的数据包
receivedData.remove(0, footerIndex + footer.size());
}
}