基于C#和RS485串口通信的温湿度上位机实现方案,整合了Modbus RTU协议通信、实时数据显示、数据存储及报警功能,代码可直接运行于.NET Framework 4.8环境:
一、系统架构设计
RS485串口
上位机
温湿度传感器
Modbus RTU协议
数据解析
实时显示
数据存储
异常报警
二、核心代码实现
1. 串口通信模块(SerialPortManager.cs)
csharp
using System.IO.Ports;
using Modbus.Device;
public class SerialPortManager : IDisposable
{
private SerialPort _port;
private IModbusMaster _master;
private const int BaudRate = 9600;
private const Parity Parity = Parity.None;
private const StopBits StopBits = StopBits.One;
private const int DataBits = 8;
public event Action<byte[]> DataReceived;
public void Connect(string portName, byte slaveId = 1)
{
_port = new SerialPort(portName, BaudRate, Parity, DataBits, StopBits)
{
ReadTimeout = 1000,
WriteTimeout = 1000
};
_master = ModbusSerialMaster.CreateRtu(_port);
_port.Open();
// 启动数据采集定时器
var timer = new System.Timers.Timer(1000);
timer.Elapsed += async (s, e) => await ReadDataAsync();
timer.Start();
}
private async Task ReadDataAsync()
{
try
{
ushort[] data = await _master.ReadHoldingRegistersAsync(slaveId, 0, 4);
var buffer = new byte[8];
Array.Copy(BitConverter.GetBytes(data[0]), 0, buffer, 0, 2);
Array.Copy(BitConverter.GetBytes(data[1]), 0, buffer, 2, 2);
Array.Copy(BitConverter.GetBytes(data[2]), 0, buffer, 4, 2);
Array.Copy(BitConverter.GetBytes(data[3]), 0, buffer, 6, 2);
DataReceived?.Invoke(buffer);
}
catch(ModbusException ex)
{
HandleCommunicationError(ex);
}
}
public void Disconnect() => _port?.Close();
public void Dispose() => _port?.Dispose();
}
2. 数据处理模块(DataProcessor.cs)
csharp
public class DataProcessor
{
public (float Temperature, float Humidity)? ParseData(byte[] data)
{
if (data.Length < 8) return null;
// 假设数据格式:温度高字节+温度低字节+湿度高字节+湿度低字节
short tempRaw = (short)(data[0] << 8 | data[1]);
short humiRaw = (short)(data[2] << 8 | data[3]);
return (tempRaw / 10.0f, humiRaw / 10.0f);
}
}
3. 实时数据显示(MainForm.cs)
csharp
using System.Windows.Forms.DataVisualization.Charting;
public partial class MainForm : Form
{
private SerialPortManager _serialManager;
private DataProcessor _dataProcessor;
private Chart _tempChart, _humiChart;
private Queue<float> _tempBuffer = new Queue<float>(100);
private Queue<float> _humiBuffer = new Queue<float>(100);
public MainForm()
{
InitializeComponent();
InitializeComponents();
_serialManager = new SerialPortManager();
_dataProcessor = new DataProcessor();
_serialManager.DataReceived += OnDataReceived;
btnConnect.Click += (s,e) => _serialManager.Connect("COM3");
}
private void InitializeComponents()
{
// 温度曲线初始化
_tempChart = new Chart { Dock = DockStyle.Fill };
var tempSeries = new Series("Temperature") { ChartType = SeriesChartType.Spline };
_tempChart.Series.Add(tempSeries);
// 湿度曲线初始化
_humiChart = new Chart { Dock = DockStyle.Fill };
var humiSeries = new Series("Humidity") { ChartType = SeriesChartType.Spline };
_humiChart.Series.Add(humiSeries);
// 控件布局
var splitContainer = new SplitContainer();
splitContainer.Panel1.Controls.Add(_tempChart);
splitContainer.Panel2.Controls.Add(_humiChart);
this.Controls.Add(splitContainer);
}
private void OnDataReceived(byte[] data)
{
if (InvokeRequired)
{
Invoke(new Action(() => OnDataReceived(data)));
return;
}
var result = _dataProcessor.ParseData(data);
if (result.HasValue)
{
UpdateUI(result.Value.Temperature, result.Value.Humidity);
CheckAlarmConditions(result.Value);
}
}
private void UpdateUI(float temp, float humi)
{
lblTemp.Text = $"{temp:F1} °C";
lblHumi.Text = $"{humi:F1} %RH";
if (_tempBuffer.Count >= 100) _tempBuffer.Dequeue();
if (_humiBuffer.Count >= 100) _humiBuffer.Dequeue();
_tempBuffer.Enqueue(temp);
_humiBuffer.Enqueue(humi);
// 更新曲线
UpdateChart(_tempChart.Series[0], _tempBuffer);
UpdateChart(_humiChart.Series[1], _humiBuffer);
}
private void UpdateChart(Series series, Queue<float> buffer)
{
series.Points.Clear();
for (int i = 0; i < buffer.Count; i++)
{
series.Points.AddXY(i, buffer.ElementAt(i));
}
}
}
三、关键功能实现
1. 异常报警模块(AlarmManager.cs)
csharp
public class AlarmManager
{
private const float TempUpper = 30.0f;
private const float TempLower = 10.0f;
private const float HumiUpper = 70.0f;
private const float HumiLower = 30.0f;
public void CheckAlarm(float temp, float humi)
{
bool tempAlarm = temp > TempUpper || temp < TempLower;
bool humiAlarm = humi > HumiUpper || humi < HumiLower;
if (tempAlarm || humiAlarm)
{
PlayAlertSound();
FlashAlarmLight();
LogAlarm(temp, humi);
}
}
private void PlayAlertSound()
{
using (var player = new SoundPlayer("alert.wav"))
{
player.Play();
}
}
private void FlashAlarmLight()
{
panelAlarm.BackColor = panelAlarm.BackColor == Color.Red
? Color.Transparent
: Color.Red;
}
private void LogAlarm(float temp, float humi)
{
string log = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} - 温度异常: {temp:F1}°C, 湿度异常: {humi:F1}%RH";
File.AppendAllText("alarm.log", log);
}
}
2. 数据持久化(DataStorage.cs)
csharp
using System.Data.SQLite;
public class DataStorage
{
private const string DbPath = "data.sqlite";
private SQLiteConnection _connection;
public DataStorage()
{
_connection = new SQLiteConnection($"Data Source={DbPath};Version=3;");
_connection.Open();
CreateTable();
}
private void CreateTable()
{
string sql = @"CREATE TABLE IF NOT EXISTS measurements (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
temperature REAL,
humidity REAL)";
using (var cmd = new SQLiteCommand(sql, _connection))
{
cmd.ExecuteNonQuery();
}
}
public void SaveData(float temp, float humi)
{
string sql = "INSERT INTO measurements (temperature, humidity) VALUES (@temp, @humi)";
using (var cmd = new SQLiteCommand(sql, _connection))
{
cmd.Parameters.AddWithValue("@temp", temp);
cmd.Parameters.AddWithValue("@humi", humi);
cmd.ExecuteNonQuery();
}
}
}
四、界面设计(MainForm.Designer.cs)
csharp
partial class MainForm
{
private System.ComponentModel.IContainer components = null;
private Label lblTemp;
private Label lblHumi;
private Panel panelAlarm;
private AlarmManager _alarmManager;
private DataStorage _dataStorage;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.lblTemp = new System.Windows.Forms.Label();
this.lblHumi = new System.Windows.Forms.Label();
this.panelAlarm = new System.Windows.Forms.Panel();
this.SuspendLayout();
// 温度显示
this.lblTemp.AutoSize = true;
this.lblTemp.Font = new System.Drawing.Font("微软雅黑", 24F);
this.lblTemp.Location = new System.Drawing.Point(20, 20);
this.lblTemp.Name = "lblTemp";
this.lblTemp.Size = new System.Drawing.Size(100, 37);
this.lblTemp.TabIndex = 0;
this.lblTemp.Text = "00.0 °C";
// 湿度显示
this.lblHumi.AutoSize = true;
this.lblHumi.Font = new System.Drawing.Font("微软雅黑", 24F);
this.lblHumi.Location = new System.Drawing.Point(20, 80);
this.lblHumi.Name = "lblHumi";
this.lblHumi.Size = new System.Drawing.Size(100, 37);
this.lblHumi.TabIndex = 1;
this.lblHumi.Text = "00.0 %RH";
// 报警面板
this.panelAlarm.BackColor = System.Drawing.Color.Transparent;
this.panelAlarm.Location = new System.Drawing.Point(20, 140);
this.panelAlarm.Name = "panelAlarm";
this.panelAlarm.Size = new System.Drawing.Size(20, 20);
this.panelAlarm.TabIndex = 2;
// 布局设置
this.Controls.Add(this.panelAlarm);
this.Controls.Add(this.lblHumi);
this.Controls.Add(this.lblTemp);
this.Name = "MainForm";
this.Size = new System.Drawing.Size(800, 600);
this.ResumeLayout(false);
this.PerformLayout();
}
}
五、工业级优化方案
1. 通信可靠性增强
csharp
// 自动重连机制
private async Task ReconnectAsync()
{
int retryCount = 0;
while (retryCount < 3)
{
try
{
_port.Close();
_port.Open();
return;
}
catch (IOException)
{
await Task.Delay(2000);
retryCount++;
}
}
throw new TimeoutException("无法连接设备");
}
// CRC校验增强
private ushort CalculateCrc(byte[] data)
{
ushort crc = 0xFFFF;
for (int i = 0; i < data.Length; i++)
{
crc ^= (ushort)data[i] << 8;
for (int j = 0; j < 8; j++)
{
if ((crc & 0x8000) != 0)
crc = (ushort)((crc << 1) ^ 0xA001);
else
crc <<= 1;
}
}
return crc;
}
2. 性能优化
-
双缓冲绘图:防止曲线闪烁
csharpthis.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.DoubleBuffer, true); -
数据压缩存储:使用差分编码减少存储空间
csharppublic void SaveData(float temp, float humi) { if (_lastTemp == 0) _lastTemp = temp; if (_lastHumi == 0) _lastHumi = humi; var deltaTemp = temp - _lastTemp; var deltaHumi = humi - _lastHumi; string sql = "INSERT INTO measurements (temperature, humidity) " + $"VALUES ({_lastTemp + deltaTemp}, {_lastHumi + deltaHumi})"; // ... }
参考代码 基于c#的温湿度上位机源码,利用485串口通信 www.youwenfan.com/contentcsr/112096.html
六、调试与测试
1. 模拟数据测试
matlab
// 生成模拟数据
private void GenerateTestData()
{
var rand = new Random();
for(int i=0; i<1000; i++)
{
var temp = 20 + rand.NextDouble()*10;
var humi = 40 + rand.NextDouble()*20;
UpdateUI(temp, humi);
}
}
2. 实际设备调试
matlab
// 扫描可用串口
private void ScanPorts()
{
string[] ports = SerialPort.GetPortNames();
cboPortName.Items.Clear();
cboPortName.Items.AddRange(ports);
if (ports.Length > 0) cboPortName.SelectedIndex = 0;
}
七、部署方案
1. 安装包制作
matlab
<!-- Inno Setup脚本 -->
[Files]
Source: "YourApp.exe"; DestDir: "{app}"; Flags: ignoreversion
Source: "data.sqlite"; DestDir: "{app}"; Flags: ignoreversion
Source: "alert.wav"; DestDir: "{app}"; Flags: ignoreversion
[Icons]
Name: "{group}\温湿度监控"; Filename: "{app}\YourApp.exe"
2. 系统服务化
matlab
public class BackgroundService : ServiceBase
{
protected override void OnStart(string[] args)
{
Task.Run(() =>
{
var manager = new SerialPortManager();
manager.Connect("COM3");
});
}
}
八、扩展功能建议
-
Modbus TCP支持 :通过
ModbusIpMaster实现网络通信 -
多设备管理:添加设备地址配置界面
-
数据导出:支持Excel/CSV格式导出历史数据
-
Web监控:集成SignalR实现浏览器实时查看