在类构造函数中将UI初始化和串口对象定义以及查找串口
foreach (const QSerialPortInfo ports, QSerialPortInfo::availablePorts()){ ui->comboBox->addItem(ports.portName()); } serialPort=new QSerialPort(this); // 串口申请的内存在析构函数中需要调用close serialPort->close();
串口设置
一般的RS-485传感器配置
波特率:9600
奇偶校验:无校验
停止位:一位
数据位:8位
具体实现(逻辑代码部分需要自己去判断下面只给出部分实现的子函数):
// 串口号默认打开cmb下拉框第一个 serialPort->setPortName(ui->comboBox->currentText().trimmed()); // 数据位 serialPort->setDataBits(QSerialPort::Data8); // 奇偶校验:无校验 serialPort->setParity(QSerialPort::NoParity); // 奇偶校验:偶校验 serialPort->setParity(QSerialPort::EvenParity); // 奇偶校验:奇校验 serialPort->setParity(QSerialPort::OddParity); // 停止位:1位 serialPort->setStopBits(QSerialPort::OneStop); // 停止位:2位 serialPort->setStopBits(QSerialPort::TwoStop); // 波特率 serialPort->setBaudRate(ui->comboBox_2->currentText().toInt()); // 打开串口为读写模式 并将按钮状态进行切换 打开失败弹窗提醒 if(serialPort->open(QIODevice::ReadWrite)){ serialPort->clear(); ui->pushButton_2->setDisabled(true); ui->pushButton->setEnabled(true); ui->pushButtonGonder->setEnabled(true); }else{ QMessageBox::critical(this, tr("Error"), serialPort->errorString()); }
串口接受
部分函数的实现:该函数需要绑定串口接受信号,此语句需要加入到构造函数中来实现
connect(serialPort,&QSerialPort::readyRead,this,&AnaPencere::readData); //绑定串口读信号
Delay_MSec(20); //在项目开发中发现接受串口信号的时候会出现接受两次的情况,有可能是硬件上数据发送过慢所以在这里我加了一个20ms的延时 rData=serialPort->readAll(); QString str; str.append(rData.toHex().toUpper()); ui->textEdit->append("接受:"+rData.toHex().toUpper());
QT延时函数
//延时函数 void AnaPencere::Delay_MSec(unsigned int msec) { QTime _Timer = QTime::currentTime().addMSecs(msec); while( QTime::currentTime() < _Timer ) QCoreApplication::processEvents(QEventLoop::AllEvents, 100); }
CRC校验
int j; unsigned int reg_crc=0xFFFF; while(length--) { reg_crc ^= *data++; for(j=0;j<8;j++) { if(reg_crc & 0x01) { reg_crc=(reg_crc>>1) ^ 0xA001; }else { reg_crc=reg_crc >>1; } } } // 如果这里需要调换crc高八位和低八位的位置可以用下面的方法 // QByteArray crc_value; // crc_value.append(char(reg_crc&0x00ff)); // crc_value.append(char(reg_crc>>8)); return reg_crc;
发送串口数据函数
QString data="Hello RS-485!"; QByteArray sendbuff; // 分割两位数据 for(int i=0;i<data.length();i+=2){ QString a1=data.mid(i,2); bool ok; unsigned int den=a1.toUInt(&ok,16); sendbuff.append(char(den)); } // 计算CRC unsigned char uzunluk=uchar(sendbuff.length()); unsigned int crc=crc_chk(reinterpret_cast<unsigned char*> (sendbuff.data()),uzunluk); // 发送buff加入CRC校验 sendbuff.append(char(crc&0x00FF)); sendbuff.append(char(crc>>8)); // 将计算好的crc显示到文本框上 ui->edit_crr_data->setText( QString("%1").arg(crc , 0, 16)); ui->edit_display->append("发送:"+sendbuff.toHex().toUpper()); // 发送到串口 serialPort->write(sendbuff);
总结
本项目的核心代码也就是上面的函数了,如果能理解的话自己做一个美观的RS485 ModbusRTU调试助手是肯定没有问题的,其实和串口助手基本相同,只是说在串口助手的基础上添加了crc校验,发送数据的额时候是根据modbus消息基本的数据格式来发送的。
一般Modbus-RTU 通讯规约格式如下:
初始结构 ≥4 字节的时间
地址码 = 1 字节
功能码 = 1 字节
数据区 = N 字节
错误校验 = 16 位 CRC 码
结束结构 ≥4 字节的时间
地址码:485地址
功能码: 0x03(读取寄存器数据)0x06(写寄存器数据)
CRC 码:二字节的校验码,注意高低位位置
实现这样的的一个功能只需要将几个文本框的内容加到一起再通过上面的数据发送函数发送就可以实现,寄存器值部分只需要在接受函数中去截取寄存器的数值(可以通过寄存器个数文本框的数值来做截取)。