ModBus-RTU 封装的电流源库

简介: ModBus-RTU 封装的电流源库

概要

该类采用串口通讯,modbus-RTU协议,来与电流源通讯,设置电流,电压等功能。

整体架构流程

msvc 2017+QT 5.9.0

单独一个类,也可以采用纯c++代码实现,由于我是界面操作,所以使用了QT

技术名词解释

MODBUS 是一种通信协议,用于在工业自动化领域中的设备之间进行通信。MODBUS 协议有两种传输方式:RTU 和 ASCII。其中,RTU 是一种二进制传输方式,具有传输速度快、可靠性高等优点,因此被广泛应用。

MODBUS-RTU 协议是基于串口通信的,主要用于连接传感器、执行器、PLC 等设备。在 MODBUS-RTU 中,设备通过地址进行识别和通信,可以进行数据读取、写入等操作,具有广泛的应用场景。

MODBUS-RTU 协议的帧结构如下:

地址 功能码 数据 CRC

1 字节 1 字节 0~252 字节 2 字节

其中,地址用于识别设备,功能码用于指定操作类型,数据用于传输具体的信息,CRC 用于校验数据的正确性。

在 MODBUS-RTU 中,常见的操作类型包括:

读取线圈状态(读取开关量输入状态)

读取输入状态(读取开关量输出状态)

读取保持寄存器(读取模拟量输入状态)

读取输入寄存器(读取模拟量输出状态)

写单个线圈(写入开关量输出状态)

写单个保持寄存器(写入模拟量输出状态)

写多个线圈(批量写入开关量输出状态)

写多个保持寄存器(批量写入模拟量输出状态)

在使用 MODBUS-RTU 通信时,需要注意以下几点:

通信双方的波特率、数据位、停止位、校验位等参数必须一致。

在进行读写操作时,需要指定设备地址、寄存器地址等信息。

在进行数据传输时,需要对数据进行字节序的转换,以保证数据正确传输。

在实际应用中,可以使用现成的 MODBUS-RTU 库进行开发,例如 libmodbus、modbus-tk 等。

技术细节

CRC码的计算,封装成函数,一起发送到从机,函数如下:

返回值是QByteArray,与上面的从机地址+功能码+寄存器地址+内存+CRC码一起拼装成新的QByteArray发送至串口,然后等待返回值。

QByteArray CurrentSource::calculateCRC16(const uint8_t* data, size_t length)
{
  uint16_t crc = 0xFFFF;
  for (size_t i = 0; i < length; ++i)
  {
    crc ^= data[i];
    for (int j = 0; j < 8; ++j)
    {
      if (crc & 0x0001)
      {
        crc >>= 1;
        crc ^= 0xA001;
      }
      else
      {
        crc >>= 1;
      }
    }
  }
  QByteArray crcByte;
  crcByte.append(static_cast<char>(crc & 0xFF));
  crcByte.append(static_cast<char>((crc >> 8) & 0xFF));
  return crcByte;
}

完整的代码在最下方

小结

对MODBUS-RTU协议开发,主要是两点需要注意,

一个是CRC码如何转换:

CRC码计算方式:可以访问网站 http://www.ip33.com/crc.html 在线计算自己的 CRC 码是否正确。以例 3 中

的数据为例,发送报文为 01 10 00 00 00 02 04 09 60 05 DC CRC 码为 F2E4。

在网站中计算 CRC16-MODBUS,计算得出结果为 E4F2,因为 MCU 电脑互为大小端模式,将两

个字节交换,就得出了正确的 CRC 码 F2E4。

一个是返回值读取:例如我需要获取显示的电压值,我写了一个定时器,每隔1s调用函数来获取电压值。如下:

// 创建定时器
    QTimer* timer = new QTimer(this);
    // 设置触发间隔为1秒
    timer->setInterval(1000);
    // 连接定时器的 timeout 信号到 getCurrent 槽函数
    QObject::connect(timer, &QTimer::timeout, this, &CurrentSource::getCurrent);
    // 创建线程
    QThread* thread = new QThread(this);
    // 将定时器移到线程中
    timer->moveToThread(thread);
    // 连接线程的 started 信号到定时器的 start 槽函数
    QObject::connect(thread, SIGNAL(started()), timer, SLOT(start()));
    // 启动线程
    thread->start();
void CurrentSource::getCurrent()
{
  // 打开串口
  if (port->open(QIODevice::ReadWrite)) {
    qDebug() << "Serial port opened successfully!";
    QByteArray _DevAddr = "01";
    QByteArray _FunCode = "03";
    QByteArray _RegisterAddr = "0003";
    QByteArray _Current = "0001";
    uint8_t tmpdata[] = { 0x01, 0x03, 0x00, 0x03, 0x00,0x01 };
    size_t length = sizeof(tmpdata) / sizeof(tmpdata[0]);
    QByteArray _CRC = calculateCRC16(reinterpret_cast<const uint8_t*>(tmpdata), length);
    // 发送数据
    QByteArray data = _DevAddr + _FunCode + _RegisterAddr + _Current + _CRC;
    port->write(data);
    // 关闭串口
    port->close();
    QObject::connect(this->port, &QSerialPort::readyRead, this, &CurrentSource::onReadyRead);
  }
  else {
    qDebug() << "Failed to open serial port!";
  }
  return ;
}
void CurrentSource::onReadyRead()
{
  QByteArray data = port->readAll(); // 读取所有可用数据
  //解析,只处理输出电流值报文//根据你自己的协议来解析回文
  if (data.length() >= 10 && data.at(2) == '0' && data.at(3) == '3') 
  {
    // 获取第7-10位的字节子串
    QByteArray subData = data.mid(6, 4);
    // 将十六进制字符串转换为十进制的 int
    int decimal = subData.toInt(nullptr, 16);
    //电流值更新到_current
    _current = decimal / 100;
    //signal发出到主线程
    emit GetCurrent(_current);
  }
  else if (data== setCurrentData)
  {
    //设置电流成功
    emit setCurrentOK();
  }
  else if (data == setVoltageData)
  {
    //设置电压成功
    emit setVoltageOK();
  }
  else if (data == setOutData)
  {
    //设置开关成功
    emit setOutOK();
  }
  else 
  {
    return;
  }
  return;
}

总体代码附上

//CurrentSource.h
#pragma once
#include <QSerialPort>
#include <QDebug>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <QObject>
#include <QThread>
#include <QTimer>
class CurrentSource : public QObject
{
  //必须加入Q_OBJECT才可以使用信号和槽
  Q_OBJECT
private:
  float _current; // 电流值
  float _voltage;//电压值
  QSerialPort* port; // 串口对象
  QString port_name;//串口号
  //CRC校验
  QByteArray calculateCRC16(const uint8_t* data, size_t length);
  //浮点数转为QByteArray
  QByteArray changeToArray(float data);
  QByteArray setCurrentData;
  QByteArray setVoltageData;
  QByteArray setOutData;
public:
  CurrentSource();
  //连接电流源
  bool currentConnect(const QString& name);
  // 设置电流值
  void setCurrent(float current);
  //设置电压值
  void setVoltage(float voltage);
  //设置开关
  void setOut(bool isOut);
  //设置上电初始值
  void setInitVal(float current, float voltage, bool isOut);
public slots:
  // 获取电流值
  void getCurrent();
  //读取从机回报
  void onReadyRead();
signals:
  void GetCurrent(float _current);
  void setCurrentOK();
  void setVoltageOK();
  void setOutOK();
};
//CurrentSource.cpp
#include "CurrentSource.h"
QByteArray CurrentSource::calculateCRC16(const uint8_t* data, size_t length)
{
  uint16_t crc = 0xFFFF;
  for (size_t i = 0; i < length; ++i)
  {
    crc ^= data[i];
    for (int j = 0; j < 8; ++j)
    {
      if (crc & 0x0001)
      {
        crc >>= 1;
        crc ^= 0xA001;
      }
      else
      {
        crc >>= 1;
      }
    }
  }
  QByteArray crcByte;
  crcByte.append(static_cast<char>(crc & 0xFF));
  crcByte.append(static_cast<char>((crc >> 8) & 0xFF));
  return crcByte;
}
QByteArray CurrentSource::changeToArray(float data)
{
  int i =(int)data*100;
  // 将int转换为16进制字符串
  std::stringstream ss;
  ss << std::hex << i;
  std::string hexStr = ss.str();
  // 将16进制字符串保存到QByteArray中
  QByteArray byteArray(hexStr.c_str(), hexStr.length());
  return byteArray;
}
CurrentSource::CurrentSource()
{
}
bool CurrentSource::currentConnect(const QString & name)
{
  port_name = name;
  port = new QSerialPort();
  port->setPortName(port_name); // 设置串口号
  port->setBaudRate(QSerialPort::Baud9600); // 设置波特率
  port->setDataBits(QSerialPort::Data8); // 设置数据位
  port->setParity(QSerialPort::NoParity); // 设置校验位
  port->setStopBits(QSerialPort::OneStop); // 设置停止位
  port->setFlowControl(QSerialPort::NoFlowControl); // 设置流控制
  if (port->open(QIODevice::ReadWrite))
  {
    // 创建定时器
    QTimer* timer = new QTimer(this);
    // 设置触发间隔为1秒
    timer->setInterval(1000);
    // 连接定时器的 timeout 信号到 getCurrent 槽函数
    QObject::connect(timer, &QTimer::timeout, this, &CurrentSource::getCurrent);
    // 创建线程
    QThread* thread = new QThread(this);
    // 将定时器移到线程中
    timer->moveToThread(thread);
    // 连接线程的 started 信号到定时器的 start 槽函数
    QObject::connect(thread, SIGNAL(started()), timer, SLOT(start()));
    // 启动线程
    thread->start();
    port->close();
    return true;
  }
  return false;
}
void CurrentSource::getCurrent()
{
  // 打开串口
  if (port->open(QIODevice::ReadWrite)) {
    qDebug() << "Serial port opened successfully!";
    QByteArray _DevAddr = "01";
    QByteArray _FunCode = "03";
    QByteArray _RegisterAddr = "0003";
    QByteArray _Current = "0001";
    uint8_t tmpdata[] = { 0x01, 0x03, 0x00, 0x03, 0x00,0x01 };
    size_t length = sizeof(tmpdata) / sizeof(tmpdata[0]);
    QByteArray _CRC = calculateCRC16(reinterpret_cast<const uint8_t*>(tmpdata), length);
    // 发送数据
    QByteArray data = _DevAddr + _FunCode + _RegisterAddr + _Current + _CRC;
    port->write(data);
    // 关闭串口
    port->close();
    QObject::connect(this->port, &QSerialPort::readyRead, this, &CurrentSource::onReadyRead);
  }
  else {
    qDebug() << "Failed to open serial port!";
  }
  return ;
}
void CurrentSource::setCurrent(float current)
{
  // 打开串口
  if (port->open(QIODevice::ReadWrite)) {
    qDebug() << "Serial port opened successfully!";
    _current = current;
    QByteArray _DevAddr = "01";
    QByteArray _FunCode = "06";
    QByteArray _RegisterAddr = "0001";
    QByteArray _Current = changeToArray(current);
    int intValue = current * 100;
    int high8Bits = (intValue >> 8) & 0xFF;
    int low8Bits = intValue & 0xFF;
    uint8_t tmpdata[] = { 0x01, 0x06, 0x00, 0x01};
    uint8_t* ptr = tmpdata; 
    *(ptr + 4) = high8Bits;
    *(ptr + 5) = low8Bits;
    size_t length = sizeof(tmpdata) / sizeof(tmpdata[0]);
    length += 2;
    QByteArray _CRC = calculateCRC16(tmpdata, length);
    // 发送数据
    QByteArray data = _DevAddr + _FunCode + _RegisterAddr + _Current + _CRC;
    setCurrentData = data;
    port->write(data);
    // 关闭串口
    port->close();
  }
  else {
    qDebug() << "Failed to open serial port!";
  }
}
void CurrentSource::setVoltage(float voltage)
{
  // 打开串口
  if (port->open(QIODevice::ReadWrite)) {
    qDebug() << "Serial port opened successfully!";
    _voltage = voltage;
    QByteArray _DevAddr = "01";
    QByteArray _FunCode = "06";
    QByteArray _RegisterAddr = "0000";
    QByteArray _Current = changeToArray(voltage);
    int intValue = voltage * 100;
    int high8Bits = (intValue >> 8) & 0xFF;
    int low8Bits = intValue & 0xFF;
    uint8_t tmpdata[] = { 0x01, 0x06, 0x00, 0x01 };
    uint8_t* ptr = tmpdata;
    *(ptr + 4) = high8Bits;
    *(ptr + 5) = low8Bits;
    size_t length = sizeof(tmpdata) / sizeof(tmpdata[0]);
    length += 2;
    QByteArray _CRC = calculateCRC16(tmpdata, length);
    // 发送数据
    QByteArray data = _DevAddr + _FunCode + _RegisterAddr + _Current + _CRC;
    setVoltageData = data;
    port->write(data);
    // 关闭串口
    port->close();
  }
  else {
    qDebug() << "Failed to open serial port!";
  }
}
void CurrentSource::setOut(bool isOut)
{
  // 打开串口
  if (port->open(QIODevice::ReadWrite)) {
    qDebug() << "Serial port opened successfully!";
    if (isOut)
    {
      QByteArray _DevAddr = "01";
      QByteArray _FunCode = "03";
      QByteArray _RegisterAddr = "0009";
      QByteArray _Current = "0001";
      uint8_t tmpdata[] = { 0x01, 0x03, 0x00, 0x09, 0x00,0x01 };
      size_t length = sizeof(tmpdata) / sizeof(tmpdata[0]);
      QByteArray _CRC = calculateCRC16(reinterpret_cast<const uint8_t*>(tmpdata), length);
      // 发送数据
      QByteArray data = _DevAddr + _FunCode + _RegisterAddr + _Current + _CRC;
      setOutData = data;
      port->write(data);
      // 关闭串口
      port->close();
    }
    else
    {
      QByteArray _DevAddr = "01";
      QByteArray _FunCode = "03";
      QByteArray _RegisterAddr = "0009";
      QByteArray _Current = "0000";
      uint8_t tmpdata[] = { 0x01, 0x03, 0x00, 0x09, 0x00,0x00 };
      size_t length = sizeof(tmpdata) / sizeof(tmpdata[0]);
      QByteArray _CRC = calculateCRC16(reinterpret_cast<const uint8_t*>(tmpdata), length);
      // 发送数据
      QByteArray data = _DevAddr + _FunCode + _RegisterAddr + _Current + _CRC;
      setOutData = data;
      port->write(data);
      // 关闭串口
      port->close();
    }
  }
  else {
    qDebug() << "Failed to open serial port!";
  }
}
void CurrentSource::setInitVal(float current, float voltage, bool isOut)
{
  setCurrent(current);
  setVoltage(voltage);
  setOut(isOut);
}
void CurrentSource::onReadyRead()
{
  QByteArray data = port->readAll(); // 读取所有可用数据
  //解析,只处理输出电流值报文
  if (data.length() >= 10 && data.at(2) == '0' && data.at(3) == '3') 
  {
    // 获取第7-10位的字节子串
    QByteArray subData = data.mid(6, 4);
    // 将十六进制字符串转换为十进制的 int
    int decimal = subData.toInt(nullptr, 16);
    //电流值更新到_current
    _current = decimal / 100;
    //signal发出到主线程
    emit GetCurrent(_current);
  }
  else if (data== setCurrentData)
  {
    //设置电流成功
    emit setCurrentOK();
  }
  else if (data == setVoltageData)
  {
    //设置电压成功
    emit setVoltageOK();
  }
  else if (data == setOutData)
  {
    //设置开关成功
    emit setOutOK();
  }
  else 
  {
    return;
  }
  return;
}
目录
相关文章
|
7月前
【MODBUS】libmodbus库从Modbus从站读取值
【MODBUS】libmodbus库从Modbus从站读取值
235 0
|
7月前
|
传感器 算法 数据格式
QT Modbus RTU调试助手(包含算法实现CRC MODBUS16校验)
QT Modbus RTU调试助手(包含算法实现CRC MODBUS16校验)
443 0
|
前端开发 JavaScript 物联网
JavaScript使用Modbus协议实现RTU设备连云
在阿里云物联网平台下发物模型属性设置数据,HaaS600Kit 接收并解析云端数据后控制 Modbus 继电器设备进行开关动作。
1961 15
JavaScript使用Modbus协议实现RTU设备连云
|
9天前
|
传感器
Modbus协议深入解析
Modbus协议是由Modicon公司(现施耐德电气)于1979年发明的串行通信协议,主要用于工业自动化系统中的PLC通信。本文深入解析了Modbus协议的主从模式、数据类型(线圈、离散输入、保持寄存器、输入寄存器)、帧结构和通信过程,并介绍了其应用场景和重要性。
15 0
|
2月前
|
网络协议 数据格式
【通信协议讲解】单片机基础重点通信协议解析与总结之ModBus(五)
【通信协议讲解】单片机基础重点通信协议解析与总结之ModBus(五)
|
3月前
|
传感器 监控 网络协议
modbus协议的定义-钡铼技术
Modbus协议是一种广泛应用于工业自动化和控制系统中的开放通信协议,由Modicon公司于1979年发布。该协议定义了消息结构,支持RS232、RS485和TCP/IP等多种电气接口和传输介质,具备开放性、简单性和适应性强等特点。Modbus采用主从通信模式,支持ASCII、RTU和TCP三种传输模式,确保数据传输的可靠性和准确性。其主要作用包括设备通信、监控与控制以及提供标准化接口,简化系统集成并适用于多种应用场景。
|
6月前
|
传感器 监控 网络协议
Modbus协议详细解析与案例分享
Modbus协议详细解析与案例分享
243 0
|
6月前
|
监控 网络协议 C#
一款基于C#开发的通讯调试工具(支持Modbus RTU、MQTT调试)
一款基于C#开发的通讯调试工具(支持Modbus RTU、MQTT调试)
102 0
|
7月前
Modbus4j核心代码讲解
Modbus4j核心代码讲解
213 1
|
7月前
|
存储 监控 网络协议
【MODBUS】Modbus协议入门简介
【MODBUS】Modbus协议入门简介
165 1