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.
 
 
 

382 regels
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. Widget::Widget(QWidget *parent) :
  12. QWidget(parent),
  13. ui(new Ui::Widget)
  14. {
  15. modbus = new MyModbus();
  16. ui->setupUi(this);
  17. setFixedSize(700,500);
  18. serialComm = new SerialCommunicator(this); // 初始化串口通信类
  19. // 配置串口参数
  20. serialComm->setMaxRetry(3); // 最大重发3次
  21. serialComm->setRecvTimeout(50); // 接收超时50ms
  22. serialComm->setResendTimeout(1000); // 重发间隔1s
  23. // 连接信号槽(串口->界面)
  24. //当串口接收到完整数据后,SerialCommunicator会发出dataReceived信号
  25. connect(serialComm, &SerialCommunicator::dataReceived, this, &Widget::onSerialDataReceived);
  26. //当串口状态改变时,SerialCommunicator会发出statusChanged信号
  27. connect(serialComm, &SerialCommunicator::statusChanged, this, &Widget::onSerialStatusChanged);
  28. //当发送数据超过超时重发次数后仍未收到数据,SerialCommunicator会发出timeoutOccurred信号
  29. connect(serialComm, &SerialCommunicator::timeoutOccurred, this, &Widget::onSerialTimeout);
  30. //当串口连接出现错误时(例如串口突然断开),SerialCommunicator会发出physicalDisconnected信号
  31. connect(serialComm, &SerialCommunicator::physicalDisconnected, this, [=](){
  32. ui->btnConnect->setText("连接");
  33. ui->btn_read->setEnabled(false);
  34. ui->pushWrite->setEnabled(false);
  35. QMessageBox::warning(this, "警告", "串口已断开连接");
  36. });
  37. //设置默认波特率、数据位、校验位
  38. ui->comboBox_baudRate->setCurrentIndex(3);
  39. ui->comboBox_dataBit->setCurrentIndex(3);
  40. ui->comboBox_xiaoyan->setCurrentIndex(2);
  41. //串口未连接前锁定读取和写入按钮
  42. ui->btn_read->setEnabled(0);
  43. ui->pushWrite->setEnabled(0);
  44. //检查当前可用串口
  45. QList<QSerialPortInfo> serialList = QSerialPortInfo::availablePorts();
  46. for(QSerialPortInfo serialInfo : serialList)
  47. {
  48. ui->comboBox_serialNum->addItem(serialInfo.portName());
  49. }
  50. }
  51. Widget::~Widget()
  52. {
  53. delete modbus;
  54. delete serialComm;
  55. delete ui;
  56. }
  57. //串口连接
  58. void Widget::on_btnConnect_clicked()
  59. {
  60. if (ui->btnConnect->text() == "连接")
  61. {
  62. // 获取界面配置的串口参数
  63. QString portName = ui->comboBox_serialNum->currentText();
  64. qint32 baudRate = ui->comboBox_baudRate->currentText().toInt();
  65. QSerialPort::DataBits dataBits = QSerialPort::DataBits(ui->comboBox_dataBit->currentText().toInt());
  66. QSerialPort::Parity parity;
  67. switch (ui->comboBox_xiaoyan->currentIndex())
  68. {
  69. case 0: parity = QSerialPort::NoParity; break;
  70. case 1: parity = QSerialPort::OddParity; break;
  71. case 2: parity = QSerialPort::EvenParity; break;
  72. default: parity = QSerialPort::NoParity;
  73. }
  74. QSerialPort::StopBits stopBits = QSerialPort::StopBits(ui->comboBox_stopBit->currentText().toInt());
  75. // 配置并打开串口
  76. serialComm->setPortParams(portName, baudRate, dataBits, parity, stopBits);
  77. if (serialComm->open())
  78. {
  79. ui->btnConnect->setText("断开");
  80. ui->btn_read->setEnabled(true);
  81. ui->pushWrite->setEnabled(true);
  82. }
  83. else
  84. {
  85. QMessageBox::warning(this, "提示", "串口连接失败");
  86. }
  87. }
  88. else
  89. {
  90. // 断开串口
  91. serialComm->close();
  92. ui->btnConnect->setText("连接");
  93. ui->btn_read->setEnabled(false);
  94. ui->pushWrite->setEnabled(false);
  95. }
  96. }
  97. //写线圈和写寄存器
  98. void Widget::on_pushWrite_clicked()
  99. {
  100. switch (ui->comboBox_gongnengma->currentIndex()) //判断当前功能码
  101. {
  102. case 2: //写多个线圈
  103. {
  104. QString sendData = ui->lineEdit->text().trimmed(); //读取输入框中的数据
  105. //判断输入框中是否为空
  106. if (sendData.isEmpty())
  107. {
  108. QMessageBox::warning(this, "提示", "请至少输入一个数据");
  109. return;
  110. }
  111. sendData.remove(" "); //移除输入数据中的空格
  112. //判断输入数据是否为二进制格式
  113. for (QChar ch : sendData)
  114. {
  115. if (ch != '0' && ch != '1')
  116. {
  117. QMessageBox::warning(this, "提示", "只允许输入 0 或 1!");
  118. return;
  119. }
  120. }
  121. //拆分数据
  122. QVector<bool> coils;
  123. for (QChar ch : sendData)
  124. {
  125. coils.append(ch == '1');
  126. }
  127. //配置从站参数
  128. quint16 stationAddress = ui->lineEdit_stationAddress->text().toInt();
  129. quint16 functionCode = 0x0f;
  130. quint16 stratAddress = ui->lineEdit_stratAddress->text().toInt();
  131. quint16 length = ui->lineEdit_length->text().toInt();
  132. //判断输入数据长度与设置的长度是否一致
  133. if (coils.size() != length)
  134. {
  135. QMessageBox::warning(this, "提示", "输入数据数与设置的长度不匹配");
  136. return;
  137. }
  138. //将从站参数传入modbus中
  139. modbus->Set(stationAddress,functionCode,stratAddress,length);
  140. modbus->WriteCoil(coils); //使用WriteCoil方法生成命令报文
  141. serialComm->sendData(modbus->SendCommand()); //发送生成的命令报文
  142. //锁定按钮
  143. ui->btn_read->setEnabled(0);
  144. ui->pushWrite->setEnabled(0);
  145. break;
  146. }
  147. case 3: //写多个寄存器
  148. {
  149. QString sendData = ui->lineEdit->text().trimmed(); //读取数据
  150. //判断输入框中是否为空
  151. if (sendData.isEmpty())
  152. {
  153. QMessageBox::warning(this, "提示", "请输入完整的寄存器数据");
  154. return;
  155. }
  156. //拆分输入的寄存器数据
  157. QStringList sl = sendData.split(',');
  158. QVector<quint16> values;
  159. bool ok;
  160. // 检查输入是否是十进制
  161. bool isDecimal = true;
  162. for (const QString &s : sl)
  163. {
  164. s.toInt(&ok);
  165. if (!ok)
  166. {
  167. isDecimal = false;
  168. break;
  169. }
  170. }
  171. //若判断输入的数据不是10进制,则按16进制数据处理
  172. for (const QString &s : sl)
  173. {
  174. quint16 v;
  175. if (isDecimal)
  176. {
  177. // 如果是十进制输入
  178. v = s.toUShort(&ok, 10);
  179. if (!ok)
  180. {
  181. QMessageBox::warning(this, "提示", "请输入正确的十进制值(0-65535)");
  182. return;
  183. }
  184. }
  185. else
  186. {
  187. // 默认按十六进制处理
  188. v = s.toUShort(&ok, 16);
  189. if (!ok)
  190. {
  191. QMessageBox::warning(this, "提示", "请输入正确的十六进制值(0-FFFF),或检查逗号格式");
  192. return;
  193. }
  194. }
  195. values.append(v);
  196. }
  197. //配置从站参数
  198. quint16 stationAddress = ui->lineEdit_stationAddress->text().toInt();
  199. quint16 functionCode = 0x10;
  200. quint16 stratAddress = ui->lineEdit_stratAddress->text().toInt();
  201. quint16 length = ui->lineEdit_length->text().toInt();
  202. //判断输入数据长度与设置的长度是否一致
  203. if (values.size() != length)
  204. {
  205. QMessageBox::warning(this, "提示", "输入数据数与设置的长度不匹配");
  206. return;
  207. }
  208. //将从站参数传入modbus中
  209. modbus->Set(stationAddress,functionCode,stratAddress,length);
  210. modbus->WriteRegister(values); //使用WriteRegister方法生成命令报文
  211. serialComm->sendData(modbus->SendCommand()); //发送生成的命令报文
  212. //锁定按钮
  213. ui->btn_read->setEnabled(0);
  214. ui->pushWrite->setEnabled(0);
  215. break;
  216. }
  217. default:
  218. {
  219. //若当前操作设置错误则进行提示
  220. QMessageBox::warning(this, "提示", "请将“操作”切换为写线圈或写寄存器");
  221. break;
  222. }
  223. }
  224. }
  225. //发送读线圈和寄存器报文
  226. void Widget::on_btn_read_clicked()
  227. {
  228. //若当前操作设置错误则进行提示
  229. if (ui->comboBox_gongnengma->currentIndex() == 2 ||
  230. ui->comboBox_gongnengma->currentIndex() == 3)
  231. {
  232. QMessageBox::warning(this, "提示", "请将“操作”切换为读线圈或读寄存器");
  233. return;
  234. }
  235. //设置从站参数
  236. quint16 stationAddress = ui->lineEdit_stationAddress->text().toInt();
  237. quint16 functionCode;
  238. quint16 stratAddress = ui->lineEdit_stratAddress->text().toInt();
  239. quint16 length = ui->lineEdit_length->text().toInt();
  240. if (ui->comboBox_gongnengma->currentIndex() == 0) //读线圈
  241. {
  242. functionCode = 0x01;
  243. }
  244. else if(ui->comboBox_gongnengma->currentIndex() == 1) //读寄存器
  245. {
  246. functionCode = 0x03;
  247. }
  248. //将从站参数传入modbus中
  249. modbus->Set(stationAddress,functionCode,stratAddress,length);
  250. modbus->ReadCoilAndReg(); //使用ReadCoilAndReg方法生成命令报文
  251. serialComm->sendData(modbus->SendCommand()); //发送生成的命令报文
  252. //锁定按钮
  253. ui->btn_read->setEnabled(0);
  254. ui->pushWrite->setEnabled(0);
  255. }
  256. //保存通信历史数据
  257. void Widget::on_btn_SaveDate_clicked()
  258. {
  259. SaveDate(this,ui->textEdit_2);
  260. }
  261. //读取通信历史数据
  262. void Widget::on_btn_ReadDate_clicked()
  263. {
  264. ReadDate(this,ui->textEdit_2);
  265. }
  266. //清空状态通知文本框中的信息
  267. void Widget::on_btn_ClearDate_clicked()
  268. {
  269. ui->textEdit_2->clear();
  270. }
  271. //清空数据读取文本框中的信息
  272. void Widget::on_btn_ClearRead_clicked()
  273. {
  274. ui->textEdit->clear();
  275. }
  276. //处理接收到的返回报文
  277. void Widget::onSerialDataReceived(const QByteArray &data)
  278. {
  279. QByteArray revMessage = modbus->Receive(data); // 接收的原始数据交给Modbus解析
  280. // 启用操作按钮
  281. ui->btn_read->setEnabled(true);
  282. ui->pushWrite->setEnabled(true);
  283. if (revMessage.isEmpty()) //若modbus检测响应报文格式错误则丢弃数据,返回值为空
  284. {
  285. QMessageBox::warning(this, "提示", "返回报文站地址或CRC校验错误,请重试");
  286. return;
  287. }
  288. //显示接收到的响应报文
  289. ui->textEdit_2->append("接收报文:" + revMessage.toHex().toUpper());
  290. // 检查响应报文中是否带有错误码
  291. int exCode = modbus->ErrorCheck();
  292. if (exCode)
  293. {
  294. QString errorMsg;
  295. switch (exCode) {
  296. case 0x01: errorMsg = "非法功能码"; break;
  297. case 0x02: errorMsg = "非法数据地址"; break;
  298. case 0x03: errorMsg = "非法数据值"; break;
  299. case 0x04: errorMsg = "从站设备故障"; break;
  300. default: errorMsg = "未知异常"; break;
  301. }
  302. QMessageBox::warning(this, "异常响应",
  303. QString("错误码: 0x%1(%2)").arg(QString::number(exCode, 16).toUpper(), errorMsg));
  304. return;
  305. }
  306. // 解析并显示数据(根据功能码)
  307. switch (ui->comboBox_gongnengma->currentIndex())
  308. {
  309. case 0:
  310. { // 读线圈
  311. QVector<bool> coils = modbus->AnalReadCoil(); //使用AnalReadCoil方法解析线圈数据
  312. ui->textEdit->append("线圈状态:");
  313. for (int i = 0; i < coils.size(); i++)
  314. {
  315. ui->textEdit->append(QString("线圈%1: %2").arg(i+1).arg(coils[i] ? "1" : "0"));
  316. }
  317. break;
  318. }
  319. case 1:
  320. { // 读寄存器
  321. QVector<quint16> regs = modbus->AnalReadReg(); //使用AnalReadReg方法解析寄存器数据
  322. ui->textEdit->append("寄存器值:");
  323. for (int i = 0; i < regs.size(); i++)
  324. {
  325. ui->textEdit->append(QString("寄存器%1: %2").arg(i+1).arg(regs[i]));
  326. }
  327. break;
  328. }
  329. }
  330. }
  331. //显示串口状态
  332. void Widget::onSerialStatusChanged(const QString &status)
  333. {
  334. ui->textEdit_2->append(status); // 显示状态信息(如连接成功、超时重发等)
  335. }
  336. //超过超时重发次数后触发
  337. void Widget::onSerialTimeout()
  338. {
  339. QMessageBox::warning(this, "提示", "等待响应超时,请检查设备");
  340. ui->btn_read->setEnabled(true);
  341. ui->pushWrite->setEnabled(true);
  342. }