C# 上位机工具实战:快速调试与数据导出功能实现
·
【轻量化开发】C# 上位机工具实战:快速调试与数据导出功能实现
一、引言
在工业调试、设备研发、产线维护等场景中,开发者经常需要一款轻量、启动快、专注核心功能的上位机工具,而不是功能臃肿的大型监控系统。
本文将手把手教你使用 C# + WPF 开发一款轻量化上位机调试工具,核心功能包括:
- 快速串口 / Modbus TCP 调试
- 实时数据监控与指令收发
- 一键数据导出(Excel / CSV)
- 日志记录与历史回放
该工具启动速度快(<1秒)、内存占用低,非常适合研发调试、现场维护和临时数据采集场景。
二、系统架构设计
设计原则:
- 轻量化:单项目、无复杂框架、功能聚焦
- 高响应:异步通信 + 响应式UI
- 易扩展:支持后续增加 Modbus RTU/TCP、OPC UA 等协议
核心模块:
- 通信模块(SerialPort + Modbus)
- 调试控制台模块
- 实时数据监控模块
- 数据导出模块
- 日志系统
三、项目搭建与核心实现
1. 项目创建与NuGet包
推荐NuGet包:
CommunityToolkit.Mvvm(MVVM)ClosedXML(Excel导出,推荐)Serilog+Serilog.Sinks.File(日志)OxyPlot.Wpf(可选实时曲线)
2. 轻量级串口调试实现
public partial class MainViewModel : ObservableObject
{
private SerialPort? _serialPort;
[ObservableProperty] private string portName = "COM3";
[ObservableProperty] private int baudRate = 9600;
[ObservableProperty] private string sendCommand = "";
[ObservableProperty] private string receiveLog = "";
[RelayCommand]
private void OpenPort()
{
try
{
_serialPort = new SerialPort(PortName, BaudRate, Parity.None, 8, StopBits.One);
_serialPort.DataReceived += SerialPort_DataReceived;
_serialPort.Open();
Log.Information("串口 {Port} 打开成功", PortName);
}
catch (Exception ex)
{
Log.Error(ex, "串口打开失败");
}
}
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
string data = _serialPort?.ReadExisting() ?? "";
ReceiveLog += $"[{DateTime.Now:HH:mm:ss}] {data}\n";
}
[RelayCommand]
private void Send()
{
if (_serialPort?.IsOpen == true && !string.IsNullOrEmpty(SendCommand))
{
_serialPort.WriteLine(SendCommand);
Log.Information("发送: {Cmd}", SendCommand);
}
}
}
3. Modbus TCP 快速调试
public class ModbusDebugService
{
private ModbusTcpMaster? _master;
public async Task ConnectAsync(string ip, int port = 502)
{
_master = new ModbusTcpMaster(ip, port);
await _master.ConnectAsync();
}
public async Task<ushort[]> ReadRegistersAsync(ushort address, ushort count)
{
return await _master!.ReadHoldingRegistersAsync(1, address, count);
}
}
4. 数据导出功能(核心)
[RelayCommand]
private void ExportToExcel()
{
try
{
using var workbook = new XLWorkbook();
var worksheet = workbook.Worksheets.Add("采集数据");
// 写入表头
worksheet.Cell(1, 1).Value = "时间";
worksheet.Cell(1, 2).Value = "设备ID";
worksheet.Cell(1, 3).Value = "参数";
worksheet.Cell(1, 4).Value = "数值";
// 写入数据(示例)
var dataList = _dataService.GetCollectedData();
for (int i = 0; i < dataList.Count; i++)
{
var row = i + 2;
worksheet.Cell(row, 1).Value = dataList[i].Timestamp;
worksheet.Cell(row, 2).Value = dataList[i].DeviceId;
worksheet.Cell(row, 3).Value = dataList[i].Parameter;
worksheet.Cell(row, 4).Value = dataList[i].Value;
}
var fileName = $"DebugData_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx";
workbook.SaveAs(fileName);
Log.Information("数据已导出:{FileName}", fileName);
MessageBox.Show("导出成功!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
Log.Error(ex, "导出失败");
}
}
CSV导出(更轻量):
private void ExportToCsv()
{
var csv = new StringBuilder();
csv.AppendLine("时间,设备ID,参数,数值");
foreach (var item in _dataList)
csv.AppendLine($"{item.Timestamp},{item.DeviceId},{item.Parameter},{item.Value}");
File.WriteAllText($"Data_{DateTime.Now:yyyyMMddHHmmss}.csv", csv.ToString());
}
五、界面设计建议(轻量化WPF)
- 使用
TabControl分页:串口调试 | Modbus TCP | 数据监控 | 历史记录 - 左侧配置区 + 右侧日志/数据区
- 状态栏显示当前连接状态
六、生产级增强
- 使用 ValueTask 优化高频采集
- Serilog 异步日志记录
- 支持配置文件保存常用设备参数
- 添加“自动重连”功能
七、部署与打包
使用 .NET 9 Native AOT 单文件发布:
dotnet publish -c Release -r win-x64 --self-contained true /p:PublishAot=true
最终生成单个exe文件,体积小、启动快,非常适合U盘携带调试。
结语:一款优秀的轻量化上位机工具,核心不在于功能多,而在于够快、够稳、够实用。通过本文的方案,你可以快速打造出一款适用于研发、调试、维护场景的C#上位机工具,大幅提升工作效率。
本工具可作为基础框架,后续轻松扩展为完整监控系统。
需要我继续补充以下任意部分吗?
- 完整 MainWindow.xaml + Dashboard 布局代码
- 串口 + Modbus 双协议切换 完整实现
- 实时数据网格 + 自动刷新 示例
- 整个项目的 App.xaml.cs 初始化代码
我对之前的代码示例进行了全面结构优化,重点提升可维护性、可扩展性、可测试性和工业级规范。
一、优化后的工程代码结构(推荐)
IndustrialMonitor.WPF/
├── IndustrialMonitor/
│ ├── App.xaml
│ ├── App.xaml.cs # 程序启动入口 + DI 配置
│ │
│ ├── Common/ # 通用基础
│ │ ├── Constants.cs
│ │ ├── Extensions/
│ │ └── Helpers/
│ │
│ ├── Core/ # 领域核心
│ │ ├── Models/ # 实体、DTO
│ │ ├── Enums/
│ │ └── Events/ # Prism 或自定义事件
│ │
│ ├── Interfaces/ # 接口层(重要!)
│ │ ├── IDeviceService.cs
│ │ ├── IModbusService.cs
│ │ ├── IAlarmService.cs
│ │ ├── ILinkageService.cs
│ │ └── IExportService.cs
│ │
│ ├── Services/ # 实现层
│ │ ├── Device/
│ │ ├── Modbus/
│ │ │ ├── ModbusTcpService.cs
│ │ │ └── ModbusRtuService.cs
│ │ ├── LinkageService.cs
│ │ ├── AlarmService.cs
│ │ ├── ExportService.cs
│ │ └── LoggingService.cs
│ │
│ ├── ViewModels/ # MVVM
│ │ ├── MainViewModel.cs
│ │ ├── DashboardViewModel.cs
│ │ └── LinkageRuleViewModel.cs
│ │
│ ├── Views/ # 界面
│ │ ├── DashboardView.xaml
│ │ └── LinkageRuleEditorView.xaml
│ │
│ └── Configuration/ # 配置管理
│ └── AppSettings.cs
│
├── IndustrialMonitor.Core/ # 可复用核心库(推荐)
└── IndustrialMonitor.Tests/
二、优化后的核心代码示例
1. 接口定义(Interfaces/IModbusService.cs)
public interface IModbusService : IAsyncDisposable
{
Task<bool> ConnectAsync(ModbusConfig config);
Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort count);
Task WriteSingleRegisterAsync(ushort address, ushort value);
Task<bool> IsConnected { get; }
event EventHandler<ModbusDataReceivedEventArgs>? DataReceived;
}
2. 服务实现(推荐使用依赖注入)
public class ModbusTcpService : IModbusService
{
private ModbusTcpMaster? _master;
private readonly ILogger _logger;
private readonly IOptions<ModbusConfig> _config;
public ModbusTcpService(ILogger<ModbusTcpService> logger, IOptions<ModbusConfig> config)
{
_logger = logger;
_config = config;
}
public async Task<bool> ConnectAsync(ModbusConfig config)
{
try
{
_master = new ModbusTcpMaster(config.IpAddress, config.Port);
await _master.ConnectAsync();
_logger.Information("Modbus TCP 连接成功 {Ip}:{Port}", config.IpAddress, config.Port);
return true;
}
catch (Exception ex)
{
_logger.Error(ex, "Modbus TCP 连接失败");
return false;
}
}
public async Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort count)
{
if (_master == null || !_master.IsConnected)
throw new InvalidOperationException("Modbus 未连接");
return await _master.ReadHoldingRegistersAsync(1, startAddress, count);
}
public async ValueTask DisposeAsync()
{
_master?.Dispose();
}
}
3. ViewModel 优化(使用 CommunityToolkit.Mvvm)
public partial class DashboardViewModel : ObservableObject
{
private readonly IModbusService _modbusService;
private readonly IExportService _exportService;
[ObservableProperty] private bool isConnected;
[ObservableProperty] private string statusText = "已断开";
public DashboardViewModel(IModbusService modbusService, IExportService exportService)
{
_modbusService = modbusService;
_exportService = exportService;
}
[RelayCommand]
private async Task ConnectAsync()
{
IsConnected = await _modbusService.ConnectAsync(new ModbusConfig { /*...*/ });
StatusText = IsConnected ? "已连接" : "连接失败";
}
[RelayCommand]
private async Task ExportDataAsync()
{
await _exportService.ExportToExcelAsync(_currentData);
}
}
4. 依赖注入配置(App.xaml.cs)
public partial class App : Application
{
public App()
{
Services = ConfigureServices();
SerilogConfig.Initialize();
}
public static IServiceProvider Services { get; private set; } = null!;
private static IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
// 配置
services.Configure<ModbusConfig>(options => { /* 从appsettings加载 */ });
// 服务注册
services.AddSingleton<IModbusService, ModbusTcpService>();
services.AddSingleton<IExportService, ExportService>();
services.AddSingleton<ILinkageService, LinkageService>();
// ViewModels
services.AddTransient<DashboardViewModel>();
services.AddTransient<LinkageRuleViewModel>();
return services.BuildServiceProvider();
}
}
优化总结
| 优化点 | 优化前 | 优化后 | 收益 |
|---|---|---|---|
| 结构 | 全部写在一个文件 | 分层清晰 | 可维护性大幅提升 |
| 依赖管理 | new 直接实例化 | 依赖注入 + 接口 | 易于单元测试和扩展 |
| 日志 | Console.WriteLine | Serilog 结构化日志 | 生产级可追溯 |
| 异步 | 混用同步 | 全面异步 + ValueTask | 高并发下更稳定 |
| 配置 | 硬编码 | IOptions + 配置文件 | 支持不同环境切换 |
需要我继续优化以下任意部分吗?
- 完整 ExportService(Excel + CSV)
- 串口粘包解析器 的优化最终版
- 报警中心完整实现
- 整个项目的 App.xaml.cs + MainWindow 初始化代码
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)