温湿度监控系统:C# + Modbus RTU 实现车间环境数据实时采集与曲线分析
2026/8/28 14:25:03 网站建设 项目流程

车间环境温湿度是生产质量的核心影响因素,电子、制药、食品加工等场景下,温湿度超标会直接导致良品率下降、原料失效。传统人工巡检效率低、数据滞后,无法做到实时预警与趋势分析。基于Modbus RTU的总线式采集方案布线简单、抗干扰能力强,配合C#上位机实现数据可视化与曲线分析,是中小型车间监控的高性价比落地方案。

本文从硬件接线、协议封装、采集服务到曲线展示,完整拆解一套可直接复用的工程实现,包含实际项目中踩过的硬件坑、通信坑与性能优化点。

方案架构与技术选型

整体采用三层分层架构,感知层通过RS485总线挂载多台温湿度变送器,采集层负责Modbus协议解析与数据校验,应用层实现数据展示、曲线分析与告警管理,各层职责解耦,便于扩展多类型传感器与上层系统对接。

核心技术选型

  • 硬件:RS485接口温湿度变送器(支持Modbus RTU)、屏蔽双绞线、USB转RS485模块
  • UI框架:WPF + CommunityToolkit.Mvvm,遵循MVVM分层架构,保证可维护性
  • 通信协议栈:NModbus,工业级成熟Modbus库,原生支持RTU/TCP模式,内置CRC校验
  • 曲线控件:ScottPlot.WPF,高性能轻量绘图库,实时滚动无卡顿,适合工业高频数据场景
  • 数据存储:SQLite,轻量本地数据库,无需额外部署服务,支持历史数据快速查询

Modbus RTU 通信层封装

Modbus RTU是工业现场最通用的串行总线协议,通过RS485物理层可实现最远1200米传输,一条总线最多挂载254台设备。通信层的核心是屏蔽底层协议细节,向上提供统一的寄存器读取接口。

协议参数与寄存器映射

工业级温湿度变送器默认参数通常统一,实际开发时需对照设备手册确认:

  • 从站地址:默认1,可通过配置软件修改
  • 串口参数:波特率9600、数据位8、停止位1、无校验
  • 温度寄存器:40001(保持寄存器),量程-40~125℃,分辨率0.1℃,有符号数
  • 湿度寄存器:40002(保持寄存器),量程0~100%RH,分辨率0.1%RH,无符号数

踩坑提示:Modbus寄存器地址分为逻辑地址与偏移地址,手册上的40001是逻辑地址,实际通信时偏移量从0开始,即40001对应地址0,地址偏移1位就会读错数据。

通信服务接口与实现

封装独立的Modbus RTU服务,包含连接管理、寄存器读取、异常重试能力,上层业务无需关心协议细节。

public interface IModbusRtuService : IDisposable { bool IsConnected { get; } bool Connect(string portName, int baudRate = 9600, int dataBits = 8, Parity parity = Parity.None, StopBits stopBits = StopBits.One); void Disconnect(); ushort[] ReadHoldingRegisters(byte slaveId, ushort startAddress, ushort count); }
public class ModbusRtuService : IModbusRtuService { private SerialPort _serialPort; private IModbusMaster _modbusMaster; public bool IsConnected => _serialPort?.IsOpen ?? false; public bool Connect(string portName, int baudRate, int dataBits, Parity parity, StopBits stopBits) { try { _serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits) { ReadTimeout = 1000, WriteTimeout = 1000 }; _serialPort.Open(); _modbusMaster = ModbusSerialMaster.CreateRtu(_serialPort); _modbusMaster.Transport.Retries = 3; _modbusMaster.Transport.WaitToRetryMilliseconds = 20; return true; } catch (Exception ex) { Disconnect(); throw new InvalidOperationException($"Modbus RTU连接失败:{ex.Message}", ex); } } public ushort[] ReadHoldingRegisters(byte slaveId, ushort startAddress, ushort count) { if (!IsConnected) throw new InvalidOperationException("Modbus设备未连接"); return _modbusMaster.ReadHoldingRegisters(slaveId, startAddress, count); } public void Disconnect() { _modbusMaster?.Dispose(); if (_serialPort != null) { if (_serialPort.IsOpen) _serialPort.Close(); _serialPort.Dispose(); } } public void Dispose() => Disconnect(); }

数据采集服务设计

采集服务是系统的核心,负责定时轮询设备、数据解析校验、异常处理与状态上报,采用事件驱动模式解耦采集逻辑与业务逻辑。

采集流程设计

采用定时轮询机制,单总线多节点依次读取,避免总线冲突;加入数据校验与异常重试,保证数据可靠性。

采集服务核心实现

轮询周期默认1秒,可根据场景配置;内置滑动平均滤波消除现场电磁干扰导致的数据抖动;异常事件向上抛出,由UI层统一处理。

public class TemperatureHumidityCollector : IDisposable { private readonly IModbusRtuService _modbusService; private readonly System.Timers.Timer _collectTimer; private readonly Queue<float> _tempFilterQueue = new Queue<float>(5); private readonly Queue<float> _humidFilterQueue = new Queue<float>(5); private const int DefaultPollInterval = 1000; public event EventHandler<SensorData> DataReceived; public event EventHandler<string> ErrorOccurred; public TemperatureHumidityCollector(IModbusRtuService modbusService) { _modbusService = modbusService; _collectTimer = new System.Timers.Timer(DefaultPollInterval); _collectTimer.Elapsed += OnCollectTimerElapsed; } public void Start() { if (!_modbusService.IsConnected) throw new InvalidOperationException("请先建立Modbus连接"); _collectTimer.Start(); } public void Stop() => _collectTimer.Stop(); private void OnCollectTimerElapsed(object sender, ElapsedEventArgs e) { try { // 读取从站1的2个保持寄存器(温度+湿度) var registers = _modbusService.ReadHoldingRegisters( slaveId: 1, startAddress: 0, count: 2); // 量程转换:分辨率0.1,温度为有符号数 float rawTemp = (short)registers[0] * 0.1f; float rawHumid = registers[1] * 0.1f; // 合法性校验:超出物理量程直接丢弃 if (rawTemp < -40 || rawTemp > 125 || rawHumid < 0 || rawHumid > 100) { ErrorOccurred?.Invoke(this, "数据超出量程,已丢弃"); return; } // 滑动平均滤波,消除干扰抖动 float temperature = FilterValue(rawTemp, _tempFilterQueue, 5); float humidity = FilterValue(rawHumid, _humidFilterQueue, 5); DataReceived?.Invoke(this, new SensorData { Temperature = temperature, Humidity = humidity, Timestamp = DateTime.Now }); } catch (Exception ex) { ErrorOccurred?.Invoke(this, $"采集异常:{ex.Message}"); } } private float FilterValue(float value, Queue<float> queue, int windowSize) { queue.Enqueue(value); if (queue.Count > windowSize) queue.Dequeue(); return queue.Average(); } public void Dispose() { _collectTimer?.Stop(); _collectTimer?.Dispose(); _modbusService?.Dispose(); } }

MVVM 层与实时曲线实现

基于MVVM模式实现UI与业务逻辑分离,使用ScottPlot实现高性能实时滚动曲线,保留最近30分钟数据,支持缩放、拖拽查看。

ViewModel 状态管理

通过ObservableObject实现属性通知,通过RelayCommand绑定界面操作;采集事件通过Dispatcher调度到UI线程更新,避免跨线程操作异常。

public partial class MainViewModel : ObservableObject { private readonly TemperatureHumidityCollector _collector; private readonly List<SensorData> _historyData = new List<SensorData>(); private const int MaxHistoryPoints = 1800; // 1秒1点,保留30分钟 [ObservableProperty] private float _currentTemperature; [ObservableProperty] private float _currentHumidity; [ObservableProperty] private bool _isCollecting; [ObservableProperty] private string _statusMessage = "未连接"; public IRelayCommand ConnectCommand { get; } public IRelayCommand StartCollectCommand { get; } public IRelayCommand StopCollectCommand { get; } public Plot ChartPlot { get; } = new Plot(); public MainViewModel(TemperatureHumidityCollector collector) { _collector = collector; _collector.DataReceived += OnDataReceived; _collector.ErrorOccurred += OnErrorOccurred; ConnectCommand = new RelayCommand(ExecuteConnect); StartCollectCommand = new RelayCommand(ExecuteStart, () => !IsCollecting); StopCollectCommand = new RelayCommand(ExecuteStop, () => IsCollecting); InitChart(); } private void InitChart() { ChartPlot.Title("车间温湿度实时趋势"); ChartPlot.XLabel("时间"); ChartPlot.YLabel("数值"); ChartPlot.Legend(Alignment.UpperRight); ChartPlot.XAxis.DateTimeFormat(true); } private void OnDataReceived(object sender, SensorData data) { CurrentTemperature = data.Temperature; CurrentHumidity = data.Humidity; lock (_historyData) { _historyData.Add(data); if (_historyData.Count > MaxHistoryPoints) _historyData.RemoveAt(0); } // 后台优先级更新曲线,避免阻塞UI Application.Current.Dispatcher.InvokeAsync( RefreshChart, DispatcherPriority.Background); } private void RefreshChart() { lock (_historyData) { double[] times = _historyData.Select(d => d.Timestamp.ToOADate()).ToArray(); double[] temps = _historyData.Select(d => (double)d.Temperature).ToArray(); double[] humids = _historyData.Select(d => (double)d.Humidity).ToArray(); ChartPlot.Clear(); ChartPlot.AddScatter(times, temps, Color.Red, 2, label: "温度(℃)"); ChartPlot.AddScatter(times, humids, Color.Blue, 2, label: "湿度(%RH)"); } } }

曲线性能优化

工业场景长时间运行容易出现曲线卡顿,核心优化点有三个:一是限制历史数据点数量,超出后移除旧点;二是降低UI刷新频率,采集和渲染分离,比如采集1秒一次,曲线每2秒刷新一次;三是复用数据数组,避免频繁创建大对象导致内存碎片。

告警与历史数据分析

阈值告警机制

支持配置温湿度上下限阈值,数据超限时触发告警,可扩展声光报警器、PLC联动、消息推送等输出方式。告警事件支持防抖,避免数据波动导致的频繁告警。

历史数据查询

使用SQLite存储分钟级聚合数据,支持按时间范围查询并生成历史曲线,可用于追溯环境变化、分析质量问题与环境的关联。数据保留策略可配置:原始数据保留7天,小时级统计数据保留3个月,降低存储压力。

常见问题排查与避坑

1. 通信完全失败

优先排查硬件:RS485的A、B线是否接反,总线两端是否接120Ω终端电阻,USB转485模块驱动是否正常;再检查软件参数:串口号、波特率、校验位、从站地址是否与设备一致。

2. 数据跳变与乱码

大概率是现场电磁干扰,远离变频器、电机等强电设备,使用屏蔽双绞线且屏蔽层单端接地;软件层面增加滑动平均滤波、异常值剔除,严重时可降低波特率提升稳定性。

3. 多节点采集冲突

同一总线上多个设备不能同时通信,必须采用轮询机制,节点之间预留10ms以上间隔,避免总线数据冲突;节点数量较多时可适当延长轮询周期。

4. 串口资源占用

程序异常退出后串口未释放,下次启动提示“端口已被占用”。一定要在Dispose方法和全局异常处理中加入串口关闭逻辑,必要时通过系统进程强制释放端口。

5. 长时间运行内存上涨

注意释放非托管资源,及时清理历史数据集合;ScottPlot避免每次刷新都重新创建图层,复用绘图对象;数据库操作使用using保证连接及时释放。

工程化落地建议

  1. 断线自动重连:通信失败后采用指数退避策略自动重试,短时间内多次失败则延长重试间隔,避免频繁占用总线,恢复后自动继续采集。
  2. 数据分级存储:高频原始数据短期留存,聚合数据长期保存,兼顾查询性能与存储成本。
  3. 日志分级记录:通信日志、数据日志、告警日志分开存储,异常时可快速定位问题环节。
  4. 多类型传感器扩展:基于Modbus接口抽象,可无缝接入压力、液位、光照等其他类型传感器,只需新增寄存器映射与解析逻辑。
  5. 上位机联动:可通过OPC UA、Modbus TCP等协议对接PLC或MES系统,实现温湿度超标自动触发空调、除湿机等设备调节。

总结

这套方案硬件成本低、部署快、稳定性高,非常适合中小型车间的环境监控场景。Modbus RTU的总线通用性配合C#上位机的灵活开发能力,既能满足实时采集的要求,又能通过曲线分析掌握环境变化趋势,为生产质量管控提供数据支撑。实际项目中可根据现场规模与业务需求,灵活扩展测点数量、传感器类型与上层系统对接能力。

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

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

立即咨询