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.
 
 
 

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