横川横川IEC上位机modbus协议通信流程
OQSModbus RTU 通信流程文档
一、读取流程(上位机 → 读 → 下位机)
1:读取
on_Btn_Read_clicked()
1 2 3 4 5 6 7 8 9 10 11 12
| void MainWindow::on_Btn_Read_clicked() { QList<int> plist = requestRowId(); for (int i = 0; i < plist.size(); i++) { QVector<int> addrs, nums, value; QVector<QString> sta_id_no_name; int fun = 0x03; paraOfRequestDataInit(plist[i], ..., addrs, nums, fun, value, sta_id_no_name); emit requestData(addrs, nums, fun, value, sta_id_no_name); } }
|
2:参数初始化
paraOfRequestDataInit()
从数据库/视图模型拼装三个核心参数:
| 参数 |
来源 |
代码 |
| 寄存器地址 |
Code 列(索引10) |
(gID << 8) | adress |
| 写入数据 |
CurValue ÷ Resolution(读操作时用不到) |
CurValue(5) / Resolution(9) |
| 追踪标识 |
sta_id_no_name |
格式 "1_{ID}_{序号}_{Ascii}" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void MainWindow::paraOfRequestDataInit(int i, ..., QVector<int> &addrs, ..., int &fun, QVector<QString> &sta_id_no_name) { QString str = m_viewModel->data(m_viewModel->index(i, 10)).toString(); sscanf(str.toStdString().c_str() + str.length() - 2, "%2hhx", &adress); sscanf(str.toStdString().c_str() + str.length() - 4, "%2hhx", &gID); addrs << int((gID << 8) | adress);
value << qRound(m_viewModel->data(m_viewModel->index(i, 5)).toDouble() m_viewModel->data(m_viewModel->index(i, 9)).toDouble());
sta_id_no_name << "1_" + strID + "_" + strNo + "_" + strName; }
|
3:信号传递到工作线程
1
| connect(this, &MainWindow::requestData, m_ModbusWorker, &ModbusWorker::doSendDataWork);
|
4:Modbus 工作线程执行读写
doSendDataWork()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| void ModbusWorker::doSendDataWork(const QVector<int> addrs, const QVector<int> nums, const int fun, QVector<int> value, const QVector<QString> sta_id_no_name) { modbus_set_slave(modbus, slave);
switch (fun) { case MODBUS_FC_READ_HOLDING_REGISTERS: ret = modbus_read_registers(modbus, addr, num, dest32); break; case MODBUS_FC_WRITE_SINGLE_REGISTER: ret = modbus_write_register(modbus, addr, value[i]); break; }
if (ret == num) { for (int ii = 0; ii < num; ++ii) { emit sendResultToGui(data, sta_id_no_name.at(...)); } } }
|
三个参数的最终去向:
| Modbus 参数 |
实际值 |
设置位置 |
| 从机地址 |
1(硬编码) |
modbus_set_slave(modbus, 1) |
| 寄存器地址 |
addrs[i] → 如 0x1303 |
作为第2个参数传入 modbus_read_registers(modbus, addr, ...) |
| 数据 |
value[i] → 原始整数 |
作为第2个参数传入 modbus_write_register(modbus, addr, value) |
二、写入流程(上位机 → 写 → 下位机)
1:批量写入选中行
on_Btn_Write_clicked() (mainwindow.cpp:3133)
流程与读取基本一致,区别:
- 功能码
fun = 0x06(写单个寄存器)
paraOfRequestDataInit() 中的 value 会被使用:CurValue ÷ Resolution
1 2 3 4 5 6 7 8 9 10
| void MainWindow::on_Btn_Write_clicked() { for (int i = 0; i < nsel; i++) { int fun = 0x06; paraOfRequestDataInit(plist[i], ..., addrs, nums, fun, value, sta_id_no_name); emit requestData(addrs, nums, fun, value, sta_id_no_name); } }
|
2:控件联动写入
SendDataToD()
UI 控件值变化时自动触发写入,通过 paraOfAddrNameInit() 查数据库获取寄存器地址:
1 2 3 4 5 6 7 8 9
| void MainWindow::SendDataToD(QString ascii, QString data) { int fun = 0x06; sta_id_no_name << ascii; value << data.toInt();
paraOfAddrNameInit(addrs, nums, fun, sta_id_no_name); emit requestData(addrs, nums, fun, value, sta_id_no_name); }
|
paraOfAddrNameInit()
根据 Ascii 参数名查数据库:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void MainWindow::paraOfAddrNameInit(QVector<int> &addrs, ..., QVector<QString> &sta_id_no_name) { QString sql = "select * from NewParameters where Ascii = \"" + sta_id_no_name.at(0) + "\"";
query.exec(); while (query.next()) { QString str = query.value(10).toString(); addrs[index] = int((gID << 8) | adress);
sta_id_no_name[index] = QString("2_%1_000_%2").arg(strID).arg(strName); } }
|
三、下位机响应解析
入口函数
handleResult()
1
| void MainWindow::handleResult(int data, QString sta_id_no_name)
|
通过信号连接接收:
1
| connect(m_ModbusWorker, &ModbusWorker::sendResultToGui, this, &MainWindow::handleResult);
|
解析过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 收到信号: handleResult(data=123, sta_id_no_name="0_001_000_MotorType") │ ┌───────────────┼───────────────┐ │ │ │ mid(0,1) mid(2,3) mid(10) = "0" = "001" = "MotorType" │ │ │ ▼ ▼ ▼ 读回显(0) 数据库 ID=1 更新 lineDDSC 仅刷新控件 查 NewParameters 等对应控件 不写库 取 Resolution │ ▼ 原始值 × Resolution = 工程值 123 × 0.1 = 12.3 │ ▼ 写入数据库 CurValue 刷新 UI 控件显示
|
sta_id_no_name
格式:X_YYY_ZZZ_AsciiName
| 位置 |
内容 |
示例 |
说明 |
| 字符0 |
X |
0/1/2 |
类型:0=读回显, 1=读请求, 2=写入 |
| 字符1 |
_ |
_ |
分隔符 |
| 字符2-4 |
YYY |
001 |
数据库主键 ID |
| 字符5 |
_ |
_ |
分隔符 |
| 字符6-8 |
ZZZ |
000 |
行号(3位数字,最后一行=999) |
| 字符9 |
_ |
_ |
分隔符 |
| 字符10+ |
AsciiName |
MotorType |
参数名 |
三种类型的构造:
1 2 3 4 5 6 7
| sta_id_no_name = "1_001_000_MotorType"; sta_id_no_name = "2_001_000_MotorType";
sta_id_no_name = "0_001_000_MotorType"; sta_id_no_name = "2_001_000_MotorType";
|
分辨率换算(Resolution)
handleResult() 中的换算逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| double fdata = data; QString floatString = query.value(9).toString(); double unit = floatString.toDouble();
fdata *= unit;
double resolution = unit; int count = 0; while (resolution - std::floor(resolution) > 0) { resolution *= 10; count++; } QString fdataString = QString::number(fdata, 'f', count);
setOneText2sql(id, 5, fdataString);
|
四、Resolution 的作用
Modbus 寄存器是 16 位整数(0~65535),无法直接传输小数。
Resolution(换算系数) 充当 Modbus 原始整数与用户界面工程值之间的桥梁:
| 场景 |
操作 |
公式 |
| 读取 |
下位机→上位机 |
工程值 = Modbus原始值 × Resolution |
| 写入 |
上位机→下位机 |
发送值 = CurValue ÷ Resolution |
五、通信线程
初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void MainWindow::modbusThreadInit() { m_ModbusWorker = new ModbusWorker(m_modbus, 1); m_ModbusWorker->moveToThread(m_ModbusThread);
connect(this, &MainWindow::requestData, m_ModbusWorker, &ModbusWorker::doSendDataWork);
connect(m_ModbusWorker, &ModbusWorker::sendResultToGui, this, &MainWindow::handleResult);
m_ModbusThread->start(); }
|