You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

373 line
12 KiB

  1. #include "widget.h"
  2. #include "ui_widget.h"
  3. #include <QSerialPortInfo>
  4. #include <QDebug>
  5. #include <QSerialPort>
  6. #include <QByteArray>
  7. #include <QString>
  8. #include <QVector>
  9. #include <QMessageBox>
  10. #include "crc.h"
  11. Widget::Widget(QWidget *parent) :
  12. QWidget(parent),
  13. ui(new Ui::Widget)
  14. {
  15. ui->setupUi(this);
  16. serialPort = new QSerialPort(this);
  17. connect(serialPort,&QSerialPort::readyRead,this,&Widget::on_SerialData_ReadyToRead);
  18. QObject::connect(&timer, &QTimer::timeout, [&]{
  19. QMessageBox::warning(this, "提示", "等待响应超时,请检查设备状态。");
  20. timer.stop();
  21. });
  22. ui->comboBox_baudRate->setCurrentIndex(3);
  23. ui->comboBox_dataBit->setCurrentIndex(3);
  24. ui->comboBox_xiaoyan->setCurrentIndex(2);
  25. ui->btn_read->setEnabled(0);
  26. ui->pushWrite->setEnabled(0);
  27. QList<QSerialPortInfo> serialList = QSerialPortInfo::availablePorts();
  28. for(QSerialPortInfo serialInfo : serialList)
  29. {
  30. ui->comboBox_serialNum->addItem(serialInfo.portName());
  31. }
  32. }
  33. Widget::~Widget()
  34. {
  35. delete ui;
  36. }
  37. //串口连接
  38. void Widget::on_btnConnect_clicked()
  39. {
  40. if (ui->btnConnect->text() == "连接")
  41. {
  42. //配置串口号
  43. serialPort->setPortName(ui->comboBox_serialNum->currentText());
  44. //配置波特率
  45. serialPort->setBaudRate(ui->comboBox_baudRate->currentText().toInt());
  46. //配置数据位
  47. serialPort->setDataBits(QSerialPort::DataBits(ui->comboBox_dataBit->currentText().toInt()));
  48. //配置校验位
  49. switch (ui->comboBox_xiaoyan->currentIndex())
  50. {
  51. case 0:
  52. serialPort->setParity(QSerialPort::NoParity);
  53. break;
  54. case 1:
  55. serialPort->setParity(QSerialPort::OddParity);
  56. break;
  57. case 2:
  58. serialPort->setParity(QSerialPort::EvenParity);
  59. }
  60. //配置停止位
  61. serialPort->setStopBits(QSerialPort::StopBits(ui->comboBox_stopBit->currentText().toInt()));
  62. //打开串口
  63. if (serialPort->open(QIODevice::ReadWrite))
  64. {
  65. qDebug() << "Serial open success";
  66. ui->btnConnect->setText("断开");
  67. ui->btn_read->setEnabled(1);
  68. ui->pushWrite->setEnabled(1);
  69. }
  70. else
  71. {
  72. qDebug() << "error";
  73. }
  74. }
  75. else
  76. {
  77. serialPort->close();
  78. ui->btn_read->setEnabled(0);
  79. ui->pushWrite->setEnabled(0);
  80. qDebug() << "Serial close";
  81. ui->btnConnect->setText("连接");
  82. }
  83. }
  84. //写线圈和写寄存器
  85. void Widget::on_pushWrite_clicked()
  86. {
  87. switch (ui->comboBox_gongnengma->currentIndex())
  88. {
  89. case 2: //写多个线圈
  90. {
  91. QString sendData = ui->lineEdit->text().trimmed();
  92. if (sendData.isEmpty())
  93. {
  94. QMessageBox::warning(this, "提示", "请至少输入一个数据,缺少的数据默认为0");
  95. return;
  96. }
  97. for (QChar ch : sendData) {
  98. if (ch != '0' && ch != '1') {
  99. QMessageBox::warning(this, "提示", "只允许输入 0 或 1!");
  100. return;
  101. }
  102. }
  103. QVector<bool> coils;
  104. for (QChar ch : sendData)
  105. {
  106. coils.append(ch == '1');
  107. }
  108. quint16 coilCount = coils.size();
  109. QByteArray SendCommand; //要发送的报文
  110. SendCommand.append(ui->comboBox_stationAddress->currentText().toInt()%256); //加入站地址
  111. SendCommand.append(0x0f);
  112. SendCommand.append(ui->lineEdit_stratAddress->text().toInt()/256); //加入起始地址
  113. SendCommand.append(ui->lineEdit_stratAddress->text().toInt()%256);
  114. SendCommand.append(ui->lineEdit_length->text().toInt()/256); //加入长度
  115. SendCommand.append(ui->lineEdit_length->text().toInt()%256);
  116. int byteCount = (coilCount + 7) / 8;
  117. SendCommand.append(byteCount); //字节数
  118. for (int i = 0; i < byteCount; ++i)
  119. {
  120. quint8 byte = 0;
  121. for (int j = 0; j < 8; ++j)
  122. {
  123. int bitIndex = i * 8 + j;
  124. if (bitIndex < coils.size() && coils[bitIndex])
  125. byte |= (1 << j);
  126. }
  127. SendCommand.append(static_cast<char>(byte));
  128. }
  129. quint16 temp = calculateCrc(SendCommand); //计算crc
  130. SendCommand.append(temp%256); //加入计算的crc值
  131. SendCommand.append(temp/256);
  132. ui->textEdit_2->append("发送报文"+SendCommand.toHex().toUpper());
  133. serialPort->write(SendCommand);
  134. timer.isActive();
  135. timer.start(2000); // 2 s 后触发超时
  136. qDebug() << "SenOk" <<sendData;
  137. break;
  138. }
  139. case 3:
  140. {
  141. QString sendData = ui->lineEdit->text().trimmed();
  142. if (sendData.isEmpty())
  143. {
  144. QMessageBox::warning(this, "提示", "请输入完整的寄存器数据");
  145. return;
  146. }
  147. QStringList sl = sendData.split(',');
  148. QVector<quint16> values;
  149. bool ok;
  150. for (const QString &s : sl)
  151. {
  152. quint16 v = s.toUShort(&ok, 16);
  153. if (!ok)
  154. {
  155. QMessageBox::warning(this, "提示", "请输入正确的十六进制值,或检查逗号是否是英文格式!");
  156. return;
  157. }
  158. values.append(v);
  159. }
  160. QByteArray SendCommand; //要发送的报文
  161. SendCommand.append(ui->comboBox_stationAddress->currentText().toInt()%256); //加入站地址
  162. SendCommand.append(0x10);
  163. SendCommand.append(ui->lineEdit_stratAddress->text().toInt()/256); //加入起始地址
  164. SendCommand.append(ui->lineEdit_stratAddress->text().toInt()%256);
  165. SendCommand.append(ui->lineEdit_length->text().toInt()/256); //加入长度
  166. SendCommand.append(ui->lineEdit_length->text().toInt()%256);
  167. SendCommand.append(static_cast<char>(values.size() * 2));
  168. for (quint16 v : values)
  169. {
  170. SendCommand.append(static_cast<char>((v >> 8) & 0xFF));
  171. SendCommand.append(static_cast<char>(v & 0xFF));
  172. }
  173. quint16 temp = calculateCrc(SendCommand); //计算crc
  174. SendCommand.append(temp%256); //加入计算的crc值
  175. SendCommand.append(temp/256);
  176. ui->textEdit_2->append("发送报文"+SendCommand.toHex().toUpper());
  177. serialPort->write(SendCommand);
  178. timer.isActive();
  179. timer.start(2000); // 2 s 后触发超时
  180. qDebug() << "SenOk" <<sendData;
  181. break;
  182. }
  183. default:
  184. {
  185. QMessageBox::warning(this, "提示", "请将“操作”切换为写线圈或写寄存器");
  186. break;
  187. }
  188. }
  189. }
  190. //处理回复报文
  191. void Widget::on_SerialData_ReadyToRead()
  192. {
  193. timer.stop();
  194. QByteArray revMessage = serialPort->readAll();
  195. QString hexData = revMessage.toHex().toUpper();
  196. qDebug() << hexData;
  197. ui->textEdit_2->append("接收报文"+hexData);
  198. //首先对接收报文的长度进行检验
  199. if (revMessage.size() < 3) {
  200. QMessageBox::warning(this, "警告", "无效的响应格式");
  201. qDebug() << "响应报文过短,长度 =" << revMessage.size();
  202. return;
  203. }
  204. //对接收的报文进行CRC校验
  205. QByteArray payload = revMessage.left(revMessage.length() - 2);
  206. //分离接收值的crc校验位
  207. quint8 receivedCrcLow = static_cast<quint8>(revMessage.at(revMessage.length() - 2));
  208. quint8 receivedCrcHigh = static_cast<quint8>(revMessage.at(revMessage.length() - 1));
  209. //计算返回的报文的crc
  210. quint16 crc = calculateCrc(payload);
  211. quint8 calcCrcLow = crc & 0xFF;
  212. quint8 calcCrcHigh = (crc >> 8) & 0xFF;
  213. //比较计算的crc值和接收到的crc值是否一致
  214. if(calcCrcLow == receivedCrcLow && calcCrcHigh == receivedCrcHigh)
  215. {
  216. qDebug() << "接收成功";
  217. if ((revMessage.at(1) & 0x80) == 0x80) {
  218. // MODBUS异常响应结构:地址 | 功能码+0x80 | 异常码 | CRC
  219. quint8 originalFunctionCode = revMessage.at(1) & 0x7F; // 去掉最高位
  220. quint8 exCode = revMessage.at(2);
  221. QString errorMsg;
  222. switch (exCode) {
  223. case 0x01: errorMsg = "非法功能码"; break;
  224. case 0x02: errorMsg = "非法数据地址"; break;
  225. case 0x03: errorMsg = "非法数据值"; break;
  226. case 0x04: errorMsg = "从站设备故障"; break;
  227. case 0x05: errorMsg = "确认"; break;
  228. case 0x06: errorMsg = "从站设备忙"; break;
  229. case 0x07: errorMsg = "负确认"; break;
  230. case 0x08: errorMsg = "存储奇偶错误"; break;
  231. default: errorMsg = "未知异常"; break;
  232. }
  233. QMessageBox::warning(this, "异常响应",
  234. QString("功能码 0x%1 异常\n错误码: 0x%2 (%3)")
  235. .arg(QString::number(originalFunctionCode, 16).toUpper())
  236. .arg(QString::number(exCode, 16).toUpper().rightJustified(2, '0'))
  237. .arg(errorMsg));
  238. return;
  239. }
  240. switch (ui->comboBox_gongnengma->currentIndex())
  241. {
  242. //解析读线圈的返回报文
  243. case 0:
  244. {
  245. quint8 byteCount = static_cast<quint8>(revMessage[2]);
  246. for (int byteIndex = 0; byteIndex < byteCount; byteIndex++)
  247. {
  248. quint8 byteValue = static_cast<quint8>(revMessage[3 + byteIndex]);
  249. // 解析每个字节的8个位
  250. for (int bitIndex = 0; bitIndex < 8; bitIndex++)
  251. {
  252. int coilIndex = byteIndex * 8 + bitIndex;
  253. if (coilIndex < ui->lineEdit_length->text().toInt())
  254. {
  255. bool state = byteValue & (1 << bitIndex);
  256. qDebug() << coilIndex <<" " << state;
  257. ui->textEdit->append("线圈"+QString::number(coilIndex+1)+":"+QString::number(state));
  258. }
  259. }
  260. }
  261. break;
  262. }
  263. //解析读寄存器的返回报文
  264. case 1:
  265. {
  266. if (revMessage.size() >= 5 && revMessage.at(1) == 0x03)
  267. {
  268. int byteCount = revMessage.at(2);
  269. QByteArray data = revMessage.mid(3, byteCount);
  270. QVector<quint16> registers;
  271. for (int i = 0; i < data.size(); i += 2)
  272. {
  273. quint16 value = (static_cast<quint8>(data[i]) << 8) | static_cast<quint8>(data[i+1]);
  274. registers.append(value);
  275. }
  276. for (int i = 0; i < registers.size(); i++)
  277. {
  278. qDebug() << "Register value:" << registers.at(i);
  279. ui->textEdit->append("寄存器"+QString::number(i)+":"+QString::number(registers.at(i)));
  280. }
  281. }
  282. break;
  283. }
  284. }
  285. }
  286. else
  287. {
  288. qDebug() << "接收失败";
  289. }
  290. }
  291. //发送读线圈和寄存器报文
  292. void Widget::on_btn_read_clicked()
  293. {
  294. if (ui->comboBox_gongnengma->currentIndex() == 2 ||
  295. ui->comboBox_gongnengma->currentIndex() == 3)
  296. {
  297. QMessageBox::warning(this, "提示", "请将“操作”切换为读线圈或读寄存器");
  298. return;
  299. }
  300. QByteArray SendCommand; //要发送的报文
  301. SendCommand.append(ui->comboBox_stationAddress->currentText().toInt()%256); //加入站地址
  302. if (ui->comboBox_gongnengma->currentIndex() == 0) //读线圈
  303. {
  304. SendCommand.append(0x01);
  305. }
  306. else if(ui->comboBox_gongnengma->currentIndex() == 1) //读寄存器
  307. {
  308. SendCommand.append(0x03);
  309. }
  310. SendCommand.append(ui->lineEdit_stratAddress->text().toInt()/256); //加入起始地址
  311. SendCommand.append(ui->lineEdit_stratAddress->text().toInt()%256);
  312. SendCommand.append(ui->lineEdit_length->text().toInt()/256); //加入长度
  313. SendCommand.append(ui->lineEdit_length->text().toInt()%256);
  314. quint16 temp = calculateCrc(SendCommand); //计算crc
  315. SendCommand.append(temp%256); //加入计算的crc值
  316. SendCommand.append(temp/256);
  317. qDebug() << SendCommand.toHex();
  318. ui->textEdit_2->append("发送报文"+SendCommand.toHex().toUpper());
  319. serialPort->write(SendCommand); //发送报文
  320. timer.isActive();
  321. timer.start(2000); // 2 s 后触发超时
  322. }