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;
}

/// /// 读保持寄存器03 /// /// 数据读取延迟 /// 设备从站地址 /// 数据起始地址 /// 寄存器数量 /// 返回的寄存器数值 /// 返回异常描述 /// 是否读取成功 public bool ReadHoldReg(int timeout, byte slaveAddress, ushort startAddress, ushort regCountIn, out ushort[] holdRegs, out ModbusException ex) /// /// 读输入寄存器04 /// /// 数据读取延迟 /// 设备从站地址 /// 数据地址 /// 寄存器数量 /// 返回的寄存器数值 /// 返回异常描述 /// 是否读取成功 public bool ReadInputReg(int timeout, byte slaveAddress, ushort startAddress, ushort regCountIn, out ushort[] InputRegs, out ModbusException ex) /// /// 写单寄存器(06功能码) /// /// 数据读取延迟 /// 设备从站地址 /// 寄存器地址 /// 寄存器值 /// 返回异常描述 /// 是否写入成功 public bool WriteSingleReg(int timeout, byte slaveAddress, ushort regAdr, ushort regValue, out ModbusException ex) /// /// 写单个线圈(05功能码) /// /// 数据读取延迟 /// 设备从站地址 /// 寄存器地址 /// 寄存器值 /// 返回异常描述 /// 是否写入成功 public bool WriteSingleCoil(int timeout, byte slaveAddress, ushort regAdr, ushort ONorOFF, out ModbusException ex) /// /// 写多寄存器(10功能码) /// /// 数据读取延迟 /// 设备从站地址 /// 寄存器起始地址 /// 寄存器值 /// 返回异常描述 /// 是否写入成功 public bool WriteMutilReg(int timeout, byte slaveAddress, ushort regAdr, ushort[] regValue, out ModbusException ex) ....................
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值