基于C#和RS485串口通信的温湿度上位机实现方案

简介: 基于C#和RS485串口通信的温湿度上位机实现方案,整合了Modbus RTU协议通信、实时数据显示、数据存储及报警功能,代码可直接运行于.NET Framework 4.8环境

基于C#和RS485串口通信的温湿度上位机实现方案,整合了Modbus RTU协议通信、实时数据显示、数据存储及报警功能,代码可直接运行于.NET Framework 4.8环境:


一、系统架构设计

1.png


二、核心代码实现

1. 串口通信模块(SerialPortManager.cs)

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)

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)

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)

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)

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)

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. 通信可靠性增强

// 自动重连机制
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. 性能优化

  • 双缓冲绘图:防止曲线闪烁

    this.SetStyle(ControlStyles.AllPaintingInWmPaint | 
                 ControlStyles.UserPaint | 
                 ControlStyles.DoubleBuffer, true);
    
  • 数据压缩存储:使用差分编码减少存储空间

    public 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/contentalg/112096.html

六、调试与测试

1. 模拟数据测试

// 生成模拟数据
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. 实际设备调试

// 扫描可用串口
private void ScanPorts()
{
   
    string[] ports = SerialPort.GetPortNames();
    cboPortName.Items.Clear();
    cboPortName.Items.AddRange(ports);
    if (ports.Length > 0) cboPortName.SelectedIndex = 0;
}

七、部署方案

1. 安装包制作

<!-- 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. 系统服务化

public class BackgroundService : ServiceBase
{
   
    protected override void OnStart(string[] args)
    {
   
        Task.Run(() => 
        {
   
            var manager = new SerialPortManager();
            manager.Connect("COM3");
        });
    }
}

八、扩展功能建议

  1. Modbus TCP支持:通过ModbusIpMaster实现网络通信

  2. 多设备管理:添加设备地址配置界面

  3. 数据导出:支持Excel/CSV格式导出历史数据

  4. Web监控:集成SignalR实现浏览器实时查看

相关文章
|
4月前
|
缓存 安全 测试技术
基于C#实现Modbus RTU通信
基于C#实现Modbus RTU通信
|
存储 SQL 关系型数据库
你对MySQL的int(11)真的了解吗?
首先,需要明确的是,int(3)和int(11)都是表示整数类型,而不是定义整数的存储长度。在MySQL中,INT类型占据4个字节(32位),可以存储范围是-2^31到2^31-1(约-2.1亿到2.1亿),这个范围是固定的,不受括号中数字的影响。
1250 3
你对MySQL的int(11)真的了解吗?
|
关系型数据库 API 数据库
盘点10个.NetCore实用的开源框架项目
盘点10个.NetCore实用的开源框架项目
3437 0
盘点10个.NetCore实用的开源框架项目
|
小程序 C# 数据库
3个.NET开源、免费、强大的商城系统
今天大姚给大家分享3个.NET开源、免费、强大的商城系统,希望可以帮助到有商城系统开发需求的同学。
466 1
|
移动开发 网络协议 NoSQL
.NET Core WebSocket实现简易、高性能、集群即时通讯组件
.NET Core WebSocket实现简易、高性能、集群即时通讯组件
1138 0
|
5月前
|
网络协议 安全 C#
C# 实现 Modbus TCP 通信
C# 实现 Modbus TCP 通信
1045 4
|
2月前
|
存储 人工智能 运维
Dify开发AI智能体的费用
Dify采用“开源免费+云端订阅”双轨计费,支持自托管(零平台费)或云端沙盒/专业/团队版($0–$159/月)。费用=平台费+模型Token费(直付厂商,Dify不抽成),本地部署可零模型成本。高性价比方案:自建Dify+国产低价API。
|
2月前
|
数据采集 测试技术
LabVIEW 2025 安装教程:详细步骤+桌面快捷方式(64位)
LabVIEW是一款广泛应用于工业、科研与教育领域的图形化编程环境,专长于数据采集、仪器控制与自动化测试,是行业标准软件。(239字)
|
9月前
|
存储 人工智能 关系型数据库
阿里云AnalyticDB for PostgreSQL 入选VLDB 2025:统一架构破局HTAP,Beam+Laser引擎赋能Data+AI融合新范式
在数据驱动与人工智能深度融合的时代,企业对数据仓库的需求早已超越“查得快”这一基础能力。面对传统数仓挑战,阿里云瑶池数据库AnalyticDB for PostgreSQL(简称ADB-PG)创新性地构建了统一架构下的Shared-Nothing与Shared-Storage双模融合体系,并自主研发Beam混合存储引擎与Laser向量化执行引擎,全面解决HTAP场景下性能、弹性、成本与实时性的矛盾。 近日,相关研究成果发表于在英国伦敦召开的数据库领域顶级会议 VLDB 2025,标志着中国自研云数仓技术再次登上国际舞台。
1095 1