概要
该类采用串口通讯,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;
}