
前期准备:选对工具,事半功倍
做跨平台工控软件,最怕被串口和网络库卡住。以前用.NET Framework时,System.IO.Ports在Linux上根本跑不了。现在有了.NET Core(以及.NET 5+),配合正确的第三方库,一套代码通吃Windows、Linux甚至树莓派。
核心依赖库:
- NModbus4:这是目前最活跃、功能最全的开源Modbus库(NuGet包 NModbus)。它支持 Modbus RTU(串口)、Modbus TCP(以太网)和 Modbus ASCII,并且完美兼容.NET Standard 2.0,是跨平台开发的首选。
- System.IO.Ports:.NET Core 2.1之后,官方终于提供了跨平台的串口支持。通过NuGet安装 System.IO.Ports 包即可。
硬件与环境:
- 在Linux上使用串口,需要确保当前用户有权限访问 /dev/ttyS* 或 /dev/ttyUSB* 设备。通常需要将用户加入 dialout 组:sudo usermod -a -G dialout $USER。
- 测试时,可以用 Modbus Slave(Windows)或 mbserver(Linux)模拟从站设备。
分步实操:一套代码,两种协议
1. 抽象通信接口,隔离协议差异
为了同时支持RTU和TCP,我们先定义一个统一的接口。
public interface IModbusMaster
{
Task<ushort[]> ReadHoldingRegisters(byte unitId, ushort startAddress, ushort numRegisters);
Task WriteSingleRegister(byte unitId, ushort address, ushort value);
void Disconnect();
}
这样,上层业务逻辑就不用关心底层到底是走串口还是网线。
2. Modbus TCP客户端实现
TCP实现最简单,因为底层就是标准的Socket。
using (var client = new TcpClient())
{
await client.ConnectAsync(ipAddress, port); // 默认端口502
using (var stream = client.GetStream())
{
var master = ModbusIpMaster.CreateIp(stream);
// 读取10个保持寄存器,从地址40001开始(协议地址0)
ushort[] registers = await master.ReadHoldingRegistersAsync(unitId, 0, 10);
// 写入单个寄存器
await master.WriteSingleRegisterAsync(unitId, 0, 1234);
}
}
关键点:unitId 是从站地址,在TCP中通常可以忽略(设为1),但有些设备会用它来区分内部不同模块。
3. Modbus RTU客户端实现
RTU稍微复杂点,需要配置串口参数。
// 创建并配置串口
var serialPort = new SerialPort
{
PortName = "/dev/ttyUSB0", // Linux下通常是这个
BaudRate = 9600,
DataBits = 8,
Parity = Parity.None,
StopBits = StopBits.One
};
serialPort.Open();
// 创建RTU主站
var master = ModbusSerialMaster.CreateRtu(serialPort);
// 读写操作与TCP几乎一致
ushort[] registers = await master.ReadHoldingRegistersAsync(unitId, 0, 10);
await master.WriteSingleRegisterAsync(unitId, 0, 1234);
// 注意:RTU操作后通常需要短暂延迟,让总线稳定
await Task.Delay(10);
关键点:RTU是主从轮询模式,总线上同一时间只能有一个主站发指令。操作之间加个 Task.Delay 能有效避免通信冲突。
问题排查:跨平台下的那些“坑”
- Linux下串口打不开:最常见的原因是权限不足。执行 ls -l /dev/ttyUSB0 查看权限,如果不是 crw-rw—- 1 root dialout …,就需要按前面说的加用户组,然后重启或重新登录。
- RTU通信乱码:99%是串口参数(波特率、校验位)和从站设备没对上。用USB转RS485模块时,注意是否需要手动控制 DE/RE 使能引脚,有些模块不支持自动流控。
- TCP连接慢:TcpClient.ConnectAsync 在目标IP不存在时会超时很久(几十秒)。可以在调用前先 Ping 一下,或者用 CancellationToken 设置一个合理的超时时间。
- 数据大小端问题:Modbus协议规定寄存器是大端(Big-Endian),但Intel CPU是小端。NModbus 库已经处理好了,但如果你自己解析 ushort[] 到 float 或 int,记得用 BitConverter.ToXXX 并注意字节序。
总结:务实高效的跨平台之道
用.NET Core + NModbus4开发跨平台上位机,核心优势在于 一次开发,多端部署。无论是部署在Windows工控机,还是嵌入式Linux盒子,甚至是云端服务器做数据汇聚,代码几乎不用改动。
记住几个要点:
把这套方案吃透,你就能轻松应对各种Modbus设备的集成需求,真正实现高效、稳定的跨平台工业通信。



