达州市网站建设_网站建设公司_MongoDB_seo优化
2026/1/7 18:59:11 网站建设 项目流程

在qt中使用ZH-44043d采集器

在 Qt 中实现 RS485 通讯(与 ZH-44043D-14NI 采集器交互),核心是利用 Qt 的QSerialPort模块实现串口数据收发,并整合之前的 Modbus CRC16 算法完成指令封装与响应解析。下面是完整的实现步骤、代码示例和注意事项。
一、前期准备

  1. Qt 环境配置(关键)
    Qt 中操作串口需要依赖SerialPort模块,必须在项目的.pro文件中添加以下配置,否则无法编译通过:

QT += core gui serialport # 若为控制台项目,可去掉gui

其他原有配置(如SOURCES、HEADERS等)保持不变

  1. 硬件准备
    RS485 硬件:USB 转 485 模块(如 CH340/PL2303+485 转换芯片),或开发板自带 485 接口
    接线:模块D+对应采集器D+,模块D-对应采集器D-,确保供电与接地稳定
    串口参数:与采集器一致(115200 波特率、8 数据位、1 停止位、无校验、无流控)

二、核心实现步骤(Qt Widgets 示例)
步骤 1:包含必要头文件

include

include

include

include

include

include

// 提前声明CRC相关辅助函数(复用之前的C++算法)
uint8_t reverseByte(uint8_t byte);
uint16_t reverseWord(uint16_t word);
uint16_t calculateModbusCRC16(const uint8_t* data, uint16_t length);
void splitCRCToBytes(uint16_t crc, uint8_t& lowByte, uint8_t& highByte);

步骤 2:定义类成员变量(串口对象 + CRC 辅助函数)
在窗口类(如MainWindow)中定义私有成员变量,用于管理串口和数据:
class MainWindow : public QWidget
{
Q_OBJECT

public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();

private slots:
// 串口接收数据槽函数(响应readyRead()信号)
void onSerialPortReadyRead();
// 发送Modbus指令的槽函数(可绑定按钮点击)
void sendRead3rdCurrentCmd();

private:
QSerialPort *m_serialPort; // 串口对象指针
};

// 实现CRC辅助函数(与之前的C++算法完全一致,适配Qt)
uint8_t reverseByte(uint8_t byte) {
uint8_t reversed = 0;
for (int i = 0; i < 8; ++i) {
reversed |= ((byte >> i) & 0x01) << (7 - i);
}
return reversed;
}

uint16_t reverseWord(uint16_t word) {
uint16_t reversed = 0;
for (int i = 0; i < 16; ++i) {
reversed |= ((word >> i) & 0x0001) << (15 - i);
}
return reversed;
}

uint16_t calculateModbusCRC16(const uint8_t* data, uint16_t length) {
const uint16_t POLY = 0x8005;
uint16_t crc = 0xFFFF;

if (length == 0) return crc;for (uint16_t i = 0; i < length; ++i) {uint8_t reversedByte = reverseByte(data[i]);crc ^= (static_cast<uint16_t>(reversedByte) << 8);for (int bit = 0; bit < 8; ++bit) {if (crc & 0x8000) {crc = (crc << 1) ^ POLY;} else {crc <<= 1;}}
}crc = reverseWord(crc);
crc ^= 0x0000;
return crc;

}

void splitCRCToBytes(uint16_t crc, uint8_t& lowByte, uint8_t& highByte) {
lowByte = crc & 0xFF;
highByte = (crc >> 8) & 0xFF;
}

步骤 3:初始化串口(构造函数中)
配置串口参数并打开串口,关联readyRead()信号(有数据接收时触发槽函数):
MainWindow::MainWindow(QWidget *parent)
: QWidget(parent)
{
// 1. 初始化串口对象
m_serialPort = new QSerialPort(this);

// 2. 选择串口端口(需根据你的USB转485模块修改,如"COM3"、"/dev/ttyUSB0")
QString portName = "COM3";  // Windows示例,Linux请改为"/dev/ttyUSB0"
m_serialPort->setPortName(portName);// 3. 配置串口参数(匹配ZH-44043D-14NI)
m_serialPort->setBaudRate(QSerialPort::Baud115200);  // 115200波特率
m_serialPort->setDataBits(QSerialPort::Data8);       // 8数据位
m_serialPort->setStopBits(QSerialPort::OneStop);     // 1停止位
m_serialPort->setParity(QSerialPort::NoParity);      // 无校验
m_serialPort->setFlowControl(QSerialPort::NoFlowControl);  // 无流控// 4. 打开串口(只读/只写/读写,这里选读写)
if (m_serialPort->open(QSerialPort::ReadWrite)) {qDebug() << "串口打开成功:" << portName;// 5. 关联串口接收信号与槽函数connect(m_serialPort, &QSerialPort::readyRead, this, &MainWindow::onSerialPortReadyRead);
} else {qDebug() << "串口打开失败:" << m_serialPort->errorString();
}// 测试:发送读取第3路电流指令(可绑定按钮点击,这里直接调用演示)
sendRead3rdCurrentCmd();

}

MainWindow::~MainWindow()
{
// 关闭并释放串口
if (m_serialPort->isOpen()) {
m_serialPort->close();
}
delete m_serialPort;
}

步骤 4:封装并发送 Modbus 指令
实现发送读取第 3 路电流的指令,自动计算 CRC 并拼接完整帧:
void MainWindow::sendRead3rdCurrentCmd()
{
if (!m_serialPort->isOpen()) {
qDebug() << "串口未打开,无法发送指令";
return;
}

// 1. 准备指令前6字节(不含CRC):01 03 00 06 00 01
uint8_t cmdPrefix[] = {0x01, 0x03, 0x00, 0x06, 0x00, 0x01};
int prefixLen = sizeof(cmdPrefix);// 2. 计算Modbus CRC16
uint16_t crcValue = calculateModbusCRC16(cmdPrefix, prefixLen);
uint8_t crcLow, crcHigh;
splitCRCToBytes(crcValue, crcLow, crcHigh);// 3. 拼接完整指令(前6字节+CRC低位+CRC高位)
QByteArray sendData;
for (int i = 0; i < prefixLen; ++i) {sendData.append(static_cast<char>(cmdPrefix[i]));
}
sendData.append(static_cast<char>(crcLow));   // CRC低位:0x64
sendData.append(static_cast<char>(crcHigh));  // CRC高位:0x0B// 4. 发送指令到串口
qint64 sendLen = m_serialPort->write(sendData);
if (sendLen == -1) {qDebug() << "指令发送失败:" << m_serialPort->errorString();
} else {qDebug() << "指令发送成功,发送内容(十六进制):" << sendData.toHex(' ').toUpper();
}

}

步骤 5:接收并解析串口响应
实现onSerialPortReadyRead()槽函数,读取采集器返回的数据并解析:
void MainWindow::onSerialPortReadyRead()
{
// 1. 读取串口缓冲区所有数据
QByteArray recvData = m_serialPort->readAll();
if (recvData.isEmpty()) {
return;
}

// 2. 打印接收结果(十六进制格式,方便调试)
qDebug() << "收到响应(十六进制):" << recvData.toHex(' ').toUpper();// 3. 合法性校验(响应帧应为7字节:01 03 02 XX XX CRC1 CRC2)
if (recvData.length() != 7) {qDebug() << "响应帧长度异常,预期7字节,实际" << recvData.length() << "字节";return;
}// 4. 逐字节拆解响应(对应你的接收数据:01 03 02 00 00 B8 44)
uint8_t devAddr = static_cast<uint8_t>(recvData.at(0));       // 第1字节:设备地址(01)
uint8_t funcCode = static_cast<uint8_t>(recvData.at(1));      // 第2字节:功能码(03)
uint8_t dataLen = static_cast<uint8_t>(recvData.at(2));       // 第3字节:数据区长度(02)
uint8_t dataLow = static_cast<uint8_t>(recvData.at(3));       // 第4字节:数据低位(00)
uint8_t dataHigh = static_cast<uint8_t>(recvData.at(4));      // 第5字节:数据高位(00)
uint8_t recvCrcLow = static_cast<uint8_t>(recvData.at(5));    // 第6字节:响应CRC低位(B8)
uint8_t recvCrcHigh = static_cast<uint8_t>(recvData.at(6));   // 第7字节:响应CRC高位(44)// 5. 验证功能码(无错误,错误功能码为0x83)
if (funcCode != 0x03) {qDebug() << "采集器返回错误,功能码:" << QString::number(funcCode, 16).toUpper();return;
}// 6. 验证数据区长度(预期2字节,对应1个16位寄存器)
if (dataLen != 0x02) {qDebug() << "数据区长度异常,预期2字节,实际" << static_cast<int>(dataLen) << "字节";return;
}// 7. 计算响应数据的CRC(验证传输是否无误)
uint8_t crcCheckData[] = {devAddr, funcCode, dataLen, dataLow, dataHigh};
uint16_t crcCheckValue = calculateModbusCRC16(crcCheckData, sizeof(crcCheckData));
uint8_t checkCrcLow, checkCrcHigh;
splitCRCToBytes(crcCheckValue, checkCrcLow, checkCrcHigh);if (checkCrcLow != recvCrcLow || checkCrcHigh != recvCrcHigh) {qDebug() << "响应CRC校验失败,预期:" << QString::number(checkCrcLow, 16).toUpper()<< " " << QString::number(checkCrcHigh, 16).toUpper();return;
}// 8. 解析第3路电流值(核心计算)
uint16_t currentData = (static_cast<uint16_t>(dataHigh) << 8) | dataLow;  // 拼接16位数据(00 00 → 0)
float actualCurrent = static_cast<float>(currentData) / 10000.0f;         // 基础计算:DATA/10000// 补充:乘以你的采集器电流量程(如5A、10A,需根据实际配置修改)
float currentRange = 5.0f;  // 示例:5A量程,根据你的设备修改
actualCurrent *= currentRange;// 9. 输出最终结果
qDebug() << "第3路电流解析结果:";
qDebug() << "  寄存器原始值(十六进制):" << QString::number(currentData, 16).toUpper();
qDebug() << "  实际电流值(A):" << actualCurrent;

}

三、关键注意事项

  1. 串口端口选择
    Windows:在设备管理器中查看 USB 转 485 模块对应的 COM 口(如 COM3、COM4)
    Linux:查看/dev/ttyUSB或/dev/ttyACM,需赋予串口权限(sudo chmod 777 /dev/ttyUSB0)
    可通过QSerialPortInfo::availablePorts()枚举所有可用串口,方便用户选择。
  2. RS485 硬件适配
    Qt 仅负责软件串口数据收发,RS485 的差分信号转换由硬件完成(USB 转 485 模块)
    多设备组网时,需在总线两端添加 120Ω 终端电阻,避免信号反射
    通讯线使用屏蔽双绞线,远离强电磁干扰源(如变频器、电机)。
  3. 数据粘包处理
    若串口接收数据不完整或粘包,可添加延时读取(QTimer::singleShot)或帧尾判断(基于 Modbus 帧长度规则)
    示例中假设单次接收完整 7 字节响应,实际项目中可缓存接收数据,待数据长度达标后再解析。
  4. CRC 算法兼容性
    示例中复用了之前的 C++ CRC 算法,完全适配 Qt 的QByteArray数据格式,无需修改核心逻辑
    若需优化性能,可将 CRC 算法改为 Qt 风格(使用QVector替代原生数组),但功能完全一致。

总结
在 Qt 中使用 RS485 与 ZH-44043D-14NI 通讯的核心要点:
配置.pro文件引入serialport模块,是串口操作的前提;
用QSerialPort配置与采集器一致的串口参数,关联readyRead()信号接收数据;
复用 Modbus CRC16 算法完成指令封装与响应校验,确保通讯可靠性;
按照 Modbus-RTU 帧结构解析响应数据,结合采集器的参数计算规则得到实际测量值。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询