基于 GC6502 的 RS485 温湿度监测系统

📅 2026/7/17 4:55:37
基于 GC6502 的 RS485 温湿度监测系统
前言本文记录了基于嵌入式工业计算机 GC6502搭建一套RS485温湿度采集 Modbus TCP网络传输 Qt上位机可视化监测系统的全流程。内容覆盖设备端采集服务源码、ARM交叉编译详细部署步骤、PC端Qt上位机设计思路与核心代码可直接复现用于工业环境监测、机房温湿度管控等场景。一、系统整体框架系统采用分层架构设计底层通过RS485总线采集传感器数据中间由GC6502完成数据解析与协议转换向上提供标准Modbus TCP服务上位机通过502端口访问获取数据并完成可视化与存储。核心功能清单RS485串口数据采集标准Modbus RTU协议与CRC16校验GC6502端搭建Modbus TCP服务端支持多客户端访问Qt上位机自动连接、断线自动重连温湿度实时数值显示与动态曲线绘制温湿度上下限超限报警与历史记录本地CSV格式数据日志自动存储二、GC6502设备端的开发与部署设备端基于纯C语言开发依赖Linux系统调用完成串口与网络操作无第三方库依赖体积小、启动快适配GC6502的嵌入式Linux环境。1.核心采集服务设计设备端程序采用模块化设计整体分为串口驱动、CRC 校验、传感器采集、TCP 服务四大模块程序入口完成信号注册、资源初始化后进入主循环持续响应客户端请求。1.1 Modbus 标准 CRC16 校验模块CRC16是Modbus RTU协议的核心校验机制用于检测数据在传输或存储过程中是否发送了错误。因为Modbus传输是先发字节低位接收到的数据流顺序倒置这里使用的多项式是将标准多项式0x8005二进制1000 0000 0000 0101反转得到的0xA001二进制1010 0000 0000 0001进行计算初始值为0xFFFF。/** * brief Modbus标准CRC16校验算法 * param data 待校验字节数组 * param len 数据长度 * return 16位CRC校验结果 */ unsigned short crc16(unsigned char *data, int len) { unsigned short crc 0xFFFF; for (int i 0; i len; i) { crc ^ data[i]; for (int j 0; j 8; j) { if (crc 1) crc (crc 1) ^ 0xA001; else crc crc 1; } } return crc; }1.2 开启并初始化RS485串口/** * brief 打开并初始化RS485串口 * param device 串口设备路径 * param baud 波特率 * return 成功返回文件描述符失败返回-1 */ int open_serial(const char *device, int baud) { int fd open(device, O_RDWR | O_NOCTTY); if (fd 0) return -1; struct termios opt; tcgetattr(fd, opt); // 波特率匹配与基础参数配置 speed_t speed B4800; // 此处省略波特率switch匹配逻辑 cfsetispeed(opt, speed); cfsetospeed(opt, speed); opt.c_cflag | (CLOCAL | CREAD); opt.c_cflag ~PARENB; // 无校验 opt.c_cflag ~CSTOPB; // 1停止位 opt.c_cflag ~CSIZE; opt.c_cflag | CS8; // 8数据位 opt.c_lflag ~(ICANON | ECHO | ECHOE | ISIG); // 原始模式 opt.c_oflag ~OPOST; opt.c_cc[VMIN] 7; // 最少读取7字节 opt.c_cc[VTIME] 5; // 超时500ms tcsetattr(fd, TCSANOW, opt); tcflush(fd, TCIOFLUSH); return fd; }1.3 Modbus TCP 服务端核心逻辑主程序完成串口初始化后创建 TCP 套接字绑定 502 端口进入监听状态阻塞等待客户端连接连接成功后循环接收请求解析 Modbus TCP 报文并组装响应帧返回。int main(int argc, char *argv[]) { signal(SIGINT, sig_handler); //监听Ctrl C 结束程序 signal(SIGTERM, sig_handler); //监听kill 杀死进程 // 1. 初始化串口 /dev/ttyS1波特率4800 serial_fd open_serial(/dev/ttyS1, 4800); if (serial_fd 0) { printf(ERROR: Cannot open serial port\n); return 1; } // 2. 创建TCP服务端绑定502端口 int server_fd socket(AF_INET, SOCK_STREAM, 0); // 此处省略端口复用、地址绑定、监听配置 listen(server_fd, 5); printf(Server listening on port 502...\n); unsigned short temp_reg 0, hum_reg 0; while (running) { // 阻塞等待客户端连接 int client_fd accept(server_fd, ...); // 循环处理当前客户端的Modbus请求 while (running) { int n recv(client_fd, buf, sizeof(buf), 0); if (n 0) break; // 解析MBAP报文头处理功能码03读寄存器请求 if (n 12 buf[7] 0x03) { read_sensor(temp_reg, hum_reg); // 组装Modbus TCP响应帧填充温湿度寄存器数据 // 此处省略响应帧组装与发送逻辑 send(client_fd, rsp, ..., 0); } } close(client_fd); } // 释放资源 close(server_fd); close(serial_fd); return 0; }2.ARM交叉编译GC6502采用ARM架构处理器无法直接运行Windows/Linux x86平台编译的程序需要通过交叉编译生成的ARM架构可执行文件。2.1 交叉编译器安装编译器版本arm-none-linux-gnueabihf-gcc 12.3 rel1适配 GC6502 的 Linux 5.10 内核与 armv7l 架构支持硬浮点运算官网下载Arm GNU Toolchain Downloads | 12.3.Rel1 – Arm Developer。安装路径本文示例路径为D:\arm-gcc\12.3 rel1\bin\可根据实际安装位置调整。安装验证打开 Windows CMD执行以下命令若输出版本信息则安装成功D:\arm-gcc\12.3 rel1\bin\arm-none-linux-gnueabihf-gcc -v也可以将编译器 bin 目录加入系统环境变量后续可直接使用arm-none-linux-gnueabihf-gcc命令。2.2 编译将源码文件gc6502_modbus.c放置在指定目录本文示例为D:\qt\在 CMD 中执行以下编译命令D:\arm-gcc\12.3 rel1\bin\arm-none-linux-gnueabihf-gcc -static -o D:\qt\gc6502_modbus D:\qt\gc6502_modbus.c编译完成后会在输出目录生成gc6502_modbus可执行文件Linux 下无后缀名。3 程序上传与运行在CMD中通过scp命令将编译好的可执行文件上传到GC6502的临时目录设备重启后会自动清除适合临时测试scp D:\qt\gc6502_modbus root192.168.1.230:/tmp/如果需要永久保存可以上传至其他目录如root用户目录scp D:\qt\gc6502_modbus root192.168.1.230:/root/上传完成后可通过SSH、Telnet或者串口工具登录GC6502终端执行以下操作1.赋予程序执行权限chmod x gc6502_modbus2.启动程序./gc6502_modbus正常情况输出GC6502 Modbus TCP Server v1.0 Opening serial port /dev/ttyS1... Serial port OK Server listening on port 502...3.程序停止方式常规停止终端按下CtrlC后台进程结束killall gc6502_modbus进程卡死强制结束先查询进程ID再killps aux | grep gc6502 kill -9 进程ID号极端情况执行reboot重启设备4.开机自启动配置如果需要设备上电自动运行程序可以将程序写进程序启动脚本 /etc/init.d/rcS:echo /root/gc6502_modbus /etc/init.d/rcS末尾加上表示后台运行不阻塞程序启动流程。5.查看是否添加成功cat /etc/init.d/rcS | grep gc6502正常输出/root/gc6502_modbus 6.删除开机自启动sed -i /gc6502_modbus/d /etc/init.d/rcS三、PC端Qt上位机上位机基于 Qt5 Widgets 开发采用模块化分层设计整体分为通信层、业务逻辑层、可视化层与数据存储层。4.1 项目基础配置新建Qt Widgets项目在.pro文件中添加核心代码模块# 核心依赖GUI界面、控件、网络模块 QT core gui widgets network CONFIG c11 TARGET ModbusMonitor TEMPLATE app # 源文件与头文件根据实际项目结构添加 SOURCES main.cpp mainwindow.cpp modbustcpclient.cpp realtimeplot.cpp datalogger.cpp HEADERS mainwindow.h modbustcpclient.h realtimeplot.h datalogger.h FORMS mainwindow.ui4.2 Modbus TCP通信核心逻辑通信模块是上位机的核心负责TCP连接管理、Modbus帧封装解析、断线重连与周期数据读取。/** * brief 封装Modbus TCP读保持寄存器请求帧功能码03 */ void ModbusTcpClient::sendReadRequest() { m_transactionId; // 事务ID自增保证帧唯一性 QByteArray request; request.resize(12); // MBAP报文头 request[0] (m_transactionId 8) 0xFF; // 事务ID高字节 request[1] m_transactionId 0xFF; // 事务ID低字节 request[2] 0x00; // 协议标识固定00 00 request[3] 0x00; request[4] 0x00; // 后续长度固定00 06 request[5] 0x06; request[6] m_slaveId; // 从站地址 request[7] 0x03; // 功能码读保持寄存器 request[8] 0x00; // 起始寄存器地址高字节 request[9] 0x00; // 起始寄存器地址低字节 request[10] 0x00; // 寄存器数量高字节 request[11] 0x02; // 寄存器数量低字节读2个寄存器 m_socket-write(request); } /** * brief 解析Modbus TCP响应帧提取温湿度数据 */ void ModbusTcpClient::parseResponse(const QByteArray data) { if (data.size() 9) return; quint8 funcCode (quint8)data[7]; // 异常响应处理 if (funcCode 0x80) { quint8 errorCode (quint8)data[8]; emit errorOccurred(QString(Modbus异常错误码: %1).arg(errorCode)); return; } // 正常响应解析03功能码4字节有效数据 if (funcCode 0x03 data[8] 0x04) { // 拼接寄存器值原始值/10得到实际温湿度 quint16 tempRaw ((quint8)data[9] 8) | (quint8)data[10]; quint16 humRaw ((quint8)data[11] 8) | (quint8)data[12]; float temperature tempRaw / 10.0f; float humidity humRaw / 10.0f; // 通过信号向外推送数据 emit dataReceived(temperature, humidity); } }4.3 主窗口业务逻辑主窗口负责界面展示、数据刷新、报警判断与状态管理核心是通过信号槽机制与通信模块解耦。核心数据处理与报警逻辑片段如下/** * brief 接收温湿度数据刷新界面、更新曲线、写入日志、判断报警 */ void MainWindow::onDataReceived(float temperature, float humidity) { // 刷新界面数值显示 ui-labelTemp-setText(QString(温度\n%1 ℃).arg(temperature, 0, f, 1)); ui-labelHum-setText(QString(湿度\n%1 %).arg(humidity, 0, f, 1)); // 更新实时曲线 m_plotTemp-addDataPoint(temperature); m_plotHum-addDataPoint(humidity); // 写入本地日志 m_logger-log(temperature, humidity); // 超限报警判断 checkAlarm(temperature, humidity); } /** * brief 温湿度超限报警逻辑 */ void MainWindow::checkAlarm(float temp, float hum) { QString alarmMsg; QString timeStr QDateTime::currentDateTime().toString(hh:mm:ss); // 温度超限判断 if (temp m_tempHighLimit) { alarmMsg QString([%1] 高温报警温度: %2℃ (上限: %3℃)) .arg(timeStr).arg(temp, 0, f, 1).arg(m_tempHighLimit); } else if (temp m_tempLowLimit) { alarmMsg QString([%1] 低温报警温度: %2℃ (下限: %3℃)) .arg(timeStr).arg(temp, 0, f, 1).arg(m_tempLowLimit); } // 湿度超限判断 if (hum m_humHighLimit) { if (!alarmMsg.isEmpty()) alarmMsg \n; alarmMsg QString([%1] 高湿报警湿度: %2% (上限: %3%)) .arg(timeStr).arg(hum, 0, f, 1).arg(m_humHighLimit); } // 追加报警信息到界面 if (!alarmMsg.isEmpty()) { ui-textAlarm-append(alarmMsg); } }