赞
踩
本文是在上一篇VS上实现modbus通讯的基础上,在Qt上做了一些简单的修改,以实现Qt上modbus的通讯。(也算是曲线救国了)
一直以来,我对于Modbus-Master(主机端)和Modbus-Slave(从机端)通讯中,关于到底是在哪一端对寄存器赋值有点纠结。
主要原因:
嵌入式STM32学习笔记(8)——libmodbus+Qt上位机测试
文中所展示的功能即是在Modbus Slave工具中对寄存器进行赋值,连接成功后,在命令窗口可看到值的传递情况。
但是对于我的实际情况中,Qt作为上位机,要通过对Slave端传递数值,以达到发送指令的效果。所以需要,对其中的代码做一些修改才能为我所用。
在写代码前,需要对思路进行验证,看是否合理,首先借助Modbus辅助工具。
借用辅助工具,建立Modbus Poll和Modbus Slave连接,然后分别以Modbus Poll端传递数据给Modbus Slave端,接着以Modbus Slave端传递数据给Modbus Poll端。
1.从Modbus Poll端分别传递1值给Modbus Slave端,如下图1所示。
相应的报文为:
(1)Modbus Poll:
Tx: 01 03 00 00 00 01 84 0A
Rx: 01 03 02 00 01 79 84
(2)Modbus Slave:
Rx: 01 03 00 00 00 01 84 0A
Tx: 01 03 02 00 01 79 84
其中,Tx表示发送,Rx表示接收。
报文格式:01 03 00 00 00 01 84 0A
01——从机地址
03——功能码(读取数据)
00——高位地址
00——低位地址
00——寄存器个数高位
01——寄存器个数低位
84——CRC高位
0A——CRC低位
报文格式:01 03 02 00 01 79 84
01——从机地址
03——功能码(读取数据)
02——数据长度
00——数据高位字节
01——数据低位字节
79——CRC高位
84——CRC低位
这个过程这样描述:
Modbus Poll端,手动输入数值后,主机端读取该地址处输入的数据值,发送给从机端,从机端响应主机端(我收到了)。
2.从Modbus Slave端传递2值给Modbus Poll端,如下图2所示。
相应的报文为:
(1)Modbus Poll:
Tx: 01 03 00 00 00 00 01 84 0A
Rx: 01 03 02 00 02 39 85
(2)Modbus Slave:
Rx: 01 03 00 00 00 00 01 84 0A
Tx: 01 03 02 00 02 39 85
其中,Tx表示发送,Rx表示接收。
相应的报文格式同上。
与上一样,说明寄存器赋值的地方不影响通讯方式。
以上位机Qt作Modbus-Master(主机端),并在主机端,对寄存器进行赋值,采用了随机采样赋值方法。
先贴上代码:
1.mywidget.h
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
#include <libmodbus/modbus.h>
#define ADDRESS_START 0
#define ADDRESS_END 9
#define LOOP 1
QT_BEGIN_NAMESPACE
namespace Ui { class myWidget; }
QT_END_NAMESPACE
class myWidget : public QWidget
{
Q_OBJECT
public:
myWidget(QWidget *parent = nullptr);
~myWidget();
private slots:
void on_pushButton_clicked();
private:
Ui::myWidget *ui;
modbus_t *ctx;
uint16_t *tab_reg;
uint16_t *send_tab_reg;
int nb;
int addr;
modbus_mapping_t *mb_mapping;
int nb_fail;
int nb_loop;
};
#endif // MYWIDGET_H
2.mywidget.cpp
#include "mywidget.h"
#include "ui_mywidget.h"
#include "libmodbus/modbus.h"
#include "QThread"
#include <QtDebug>
#include <errno.h>
#include <stdlib.h>
#include <malloc.h>
myWidget::myWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::myWidget)
{
ui->setupUi(this);
}
myWidget::~myWidget()
{
delete ui;
}
void myWidget::on_pushButton_clicked()
{
int rc;
int i;
ctx = modbus_new_rtu("COM3", 115200, 'N', 8, 1);
modbus_set_slave(ctx,1);
modbus_set_debug(ctx,TRUE);
if (modbus_connect(ctx)==-1)
{
fprintf(stderr, "Connection failed: %s\n",modbus_strerror(errno));
modbus_free(ctx);
}
/*
uint32_t old_response_to_sec;//秒
uint32_t old_response_to_usec;//微秒,1秒=1,000,000微秒
modbus_get_response_timeout(ctx, &old_response_to_sec, &old_response_to_usec);//获取当前设置的超时
modbus_set_response_timeout(ctx,0,50000);//设定超时,默认50ms(50,000us)
qDebug()<<"get timeout sec:"<<old_response_to_sec;
qDebug()<<"get timeout usec:"<<old_response_to_usec;
printf("\n----------------\n");*/
nb=ADDRESS_END-ADDRESS_START;
tab_reg=(uint16_t *)malloc(nb * sizeof(uint16_t));
memset(tab_reg,0,nb * sizeof(uint16_t));
/*写入保持寄存器数值*/
nb_loop=nb_fail=0;
while(nb_loop++<LOOP)
{
for(addr=ADDRESS_START;addr<ADDRESS_END;addr++)
{
int i;
for(i=0;i<nb;i++)
{
tab_reg[i]=(uint16_t)(65535*rand()/(RAND_MAX+1.0));
}
nb=ADDRESS_END-addr;
rc=modbus_write_register(ctx,addr,tab_reg[0]);
if(rc !=1)
{
printf("ERROR modbus_write_register(%d)\n",rc);
nb_fail++;
}
else
{
rc = modbus_read_registers(ctx,addr,1, tab_reg); //03#读取保持寄存器的值,(对象,起始地址,读取数量,存储读取到的值)
if (rc != 1)
{
fprintf(stderr,"%s\n", modbus_strerror(errno));
qDebug()<<"rc错误"<<modbus_strerror(errno);
nb_fail++;
}
else
{
qDebug()<<"链接成功";
for (i=0; i<10; i++)
{
printf("reg[%d] = %d(0x%x)\n", i, tab_reg[i], tab_reg[i]);
qDebug()<<"rc收到:"<<tab_reg[i];
}
}
}
}
}
modbus_close(ctx); //关闭modbus连接
modbus_free(ctx); //释放modbus资源,使用完libmodbus需要释放掉
}
执行后的效果,如下图所示:
可以看到,寄存器赋的值已经传递进Modbus Slave中,说明此代码在Modbus Master端赋值寄存器的值有效。
另外,在做这一测试时,需要参考我的上两篇文章的内容,即libmodbus库的编辑以及modbus通讯模拟。
实现通讯连接之后,就需要对报文进行解析。如下图所示,单寄存器10位的数值在左侧栏可清楚看到,报文信息在右侧可详细看到,那么,现在就来分析报文与数值是怎么对应的。
(1)首地址的数值对应的报文解析:
首地址处的数值为-23
接收到的报文信息是:
01 06 00 00 FF E9 09 B4
其中RTU报文的格式是这样定义的:
01——从机地址
06——功能码
00——寄存器高位地址
00——寄存器低位地址
FF——数据高位
E9——数据低位
09——CRC高位
B4——CRC低位
现在主要来计算数据位的计算:
-23,首先23对应的16进制数为0017,那么-23=0-0017=FFFF -0017+1=FFE9
至于CRC的计算,因为有CRC函数就不需要考虑了。
(2)首地址的数值传递的报文解析:
Rx表示接收,Tx表示传递
过程是这样的:
先是从机端接收到这样一条报文信息,然后将收到的数值以报文的信息再发送回给主机端,(告诉我收到了),主机端读取报文信息,从机端再应答一下。
为了验证一下思路是否正确,再做一个
(3)地址为1的数值传递
地址1处的数值为-21157
接收到的报文信息是:
01 06 00 01 AD 5B E5 61
按照上面报文格式的介绍,只对其中数据高低位进行计算:
21157对应的16进制数为52A5,
-21157=0-52A5=FFFF-52A5+1=AD5B
数据高位 AD 数据低位 5B
(4)地址1处的数值传递报文解析:
如图所示,数值传递的过程是这样的:
先是从机端接收到这样一条报文,然后对主机端进行应答,发送相应的报文,然后主机端对报文进行读取,即读取01地址处的值,从机端响应主机端,将地址处的信息发回。
接下来要做的是对数据包的处理。
参考1:libmodbus在Windows端Qt 5上的使用注意事项
参考2:Visual Studio 上基于libmodbus库的modbus RTU模式的通讯模拟
参考3:嵌入式STM32学习笔记(8)——libmodbus+Qt上位机测试
参考4:Modbus RTU报文格式
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。