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