在工业4.0浪潮下,你是否还在为每个新项目重复编写Modbus通信代码而头疼?是否因为硬编码的寄存器配置而在客户现场手忙脚乱地修改代码?
作为一名在工业自动化领域摸爬滚打多年的C#开发者,我深知这些痛点。今天分享一套配置驱动的Modbus插件系统,让你彻底告别重复造轮子,通过JSON配置文件即可适配不同设备,真正做到"一次开发,处处复用"!
痛点1:硬编码灾难
c#// 传统写法 - 每个项目都要重写
var temperature = master.ReadHoldingRegisters(1, 40001, 2);
float temp = ConvertToFloat(temperature); // 又要写转换逻辑
痛点2:设备适配困难
痛点3:维护成本高昂
c#using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace AppModbusPlugin.Models
{
public class ModbusConfiguration
{
[JsonProperty("modbusConfig")]
public ModbusConfig ModbusConfig { get; set; }
}
public class ModbusConfig
{
[JsonProperty("connectionSettings")]
public ConnectionSettings ConnectionSettings { get; set; }
[JsonProperty("registers")]
public List<ModbusRegister> Registers { get; set; } = new List<ModbusRegister>();
}
public class ConnectionSettings
{
[JsonProperty("type")]
public string Type { get; set; } // TCP, RTU, ASCII
[JsonProperty("host")]
public string Host { get; set; }
[JsonProperty("port")]
public int Port { get; set; } = 502;
[JsonProperty("slaveId")]
public byte SlaveId { get; set; } = 1;
[JsonProperty("timeout")]
public int Timeout { get; set; } = 3000;
[JsonProperty("retries")]
public int Retries { get; set; } = 3;
[JsonProperty("serialPort")]
public string SerialPort { get; set; }
[JsonProperty("baudRate")]
public int BaudRate { get; set; } = 9600;
[JsonProperty("parity")]
public string Parity { get; set; } = "None";
[JsonProperty("dataBits")]
public int DataBits { get; set; } = 8;
[JsonProperty("stopBits")]
public int StopBits { get; set; } = 1;
}
public class ModbusRegister
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("address")]
public int Address { get; set; }
[JsonProperty("functionCode")]
public int FunctionCode { get; set; }
[JsonProperty("dataType")]
public string DataType { get; set; }
[JsonProperty("byteOrder")]
public string ByteOrder { get; set; } = "AB";
[JsonProperty("length")]
public int Length { get; set; } = 1;
[JsonProperty("scale")]
public double Scale { get; set; } = 1.0;
[JsonProperty("offset")]
public double Offset { get; set; } = 0.0;
[JsonProperty("unit")]
public string Unit { get; set; }
[JsonProperty("readOnly")]
public bool ReadOnly { get; set; } = true;
[JsonProperty("description")]
public string Description { get; set; }
}
}
c#// 🔥 核心功能:自动将读功能码转换为写功能码
private int GetWriteFunctionCode(int readFunctionCode)
{
switch (readFunctionCode)
{
case 1: return 5; // 读线圈 -> 写单个线圈
case 3: return 6; // 读保持寄存器 -> 写单个寄存器
case 2: case 4:
throw new NotSupportedException("输入寄存器不支持写入");
default:
return readFunctionCode; // 已经是写功能码
}
}
⚡ 关键优势:配置文件中只需要写功能码3(读),插件自动转换为功能码6进行写入!
c#private object ConvertData(ushort[] data, ModbusRegister register)
{
switch (register.DataType.ToUpper())
{
case "INT16":
short value = (short)data[0];
return (value * register.Scale) + register.Offset;
case "FLOAT32":
float floatValue = ConvertToFloat(data, register.ByteOrder);
return (floatValue * register.Scale) + register.Offset;
case "STRING":
return ConvertToString(data);
default:
return data[0];
}
}
// 🔥 支持多种字节序的浮点转换
private float ConvertToFloat(ushort[] data, string byteOrder)
{
uint intValue = byteOrder.ToUpper() switch
{
"ABCD" => ((uint)data[0] << 16) | data[1], // 大端
"DCBA" => ((uint)data[1] << 16) | data[0], // 小端
_ => ((uint)data[0] << 16) | data[1]
};
return BitConverter.ToSingle(BitConverter.GetBytes(intValue), 0);
}
json{
"modbusConfig": {
"connectionSettings": {
"type": "TCP",
"host": "192.168.1.100",
"port": 502,
"slaveId": 1,
"timeout": 3000
},
"registers": [
{
"name": "车间温度",
"address": 40001,
"functionCode": 3,
"dataType": "Float32",
"byteOrder": "ABCD",
"length": 2,
"scale": 1.0,
"unit": "°C",
"readOnly": true
},
{
"name": "温度设定值",
"address": 40010,
"functionCode": 3,
"dataType": "Int16",
"scale": 0.1,
"readOnly": false
}
]
}
}
c#using AppModbusPlugin.Core;
using AppModbusPlugin.Events;
namespace AppModbusPlugin
{
class Program
{
static async Task Main(string[] args)
{
var plugin = new ModbusPlugin();
// 订阅事件
plugin.DataChanged += OnDataChanged;
plugin.ErrorOccurred += OnErrorOccurred;
plugin.ConnectionStateChanged += OnConnectionStateChanged;
try
{
Console.WriteLine("=== Modbus 插件演示 ===");
// 加载配置
Console.WriteLine("1. 加载配置文件...");
if (await plugin.LoadConfigurationAsync("modbus_config.json"))
{
Console.WriteLine(" 配置加载成功");
// 显示配置信息
var registers = plugin.GetRegisters();
Console.WriteLine($" 发现 {registers.Count} 个寄存器配置");
foreach (var reg in registers)
{
Console.WriteLine($" - {reg.Name}: 地址{reg.Address}, 功能码{reg.FunctionCode}, {reg.DataType}");
}
}
else
{
Console.WriteLine(" 配置加载失败");
return;
}
Console.WriteLine("\n2. 连接设备...");
if (await plugin.ConnectAsync())
{
Console.WriteLine(" 设备连接成功");
// 测试连接
Console.WriteLine("\n3. 测试连接...");
if (await plugin.TestConnectionAsync())
{
Console.WriteLine(" 连接测试通过");
}
// 读取所有寄存器
Console.WriteLine("\n4. 读取所有寄存器...");
var allData = await plugin.ReadAllRegistersAsync();
foreach (var kvp in allData)
{
Console.WriteLine($" {kvp.Key}: {kvp.Value}");
}
// 演示写入操作
var writableRegisters = plugin.GetWritableRegisters();
if (writableRegisters.Count > 0)
{
Console.WriteLine("\n5. 演示写入操作...");
var firstWritable = writableRegisters[0];
Console.WriteLine($" 写入寄存器: {firstWritable.Name}");
// 根据数据类型写入测试值
object testValue = GetTestValue(firstWritable.DataType);
var writeSuccess = await plugin.WriteRegisterAsync(firstWritable.Name, testValue);
if (writeSuccess)
{
Console.WriteLine($" 写入成功: {testValue}");
// 读取验证
var readBack = await plugin.ReadRegisterAsync(firstWritable.Name);
Console.WriteLine($" 读取验证: {readBack}");
}
}
Console.WriteLine("\n6. 持续监控 (按任意键停止)...");
var monitorTask = MonitorRegisters(plugin);
Console.ReadKey();
Console.WriteLine("\n停止监控...");
}
else
{
Console.WriteLine(" 设备连接失败");
}
}
catch (Exception ex)
{
Console.WriteLine($"\n程序异常: {ex.Message}");
}
finally
{
plugin.Dispose();
Console.WriteLine("\n程序结束,按任意键退出...");
Console.ReadKey();
}
}
static async Task MonitorRegisters(ModbusPlugin plugin)
{
while (plugin.IsConnected)
{
try
{
await plugin.ReadAllRegistersAsync();
await Task.Delay(5000); // 5秒读取一次
}
catch (Exception ex)
{
Console.WriteLine($"监控出错: {ex.Message}");
await Task.Delay(1000);
}
}
}
static object GetTestValue(string dataType)
{
switch (dataType.ToUpper())
{
case "BOOL": return true;
case "INT16": return (short)123;
case "UINT16": return (ushort)456;
case "INT32": return 789;
case "UINT32": return 1234u;
case "FLOAT32":
case "FLOAT": return 12.34f;
case "DOUBLE":
case "FLOAT64": return 56.78;
case "STRING": return "TEST";
default: return 100;
}
}
static void OnDataChanged(object sender, DataChangedEventArgs e)
{
Console.WriteLine($"[数据变化] {e.Timestamp:HH:mm:ss} - {e.RegisterName}: {e.Value}");
}
private static void OnErrorOccurred(object? sender, Events.ErrorEventArgs e)
{
Console.WriteLine($"[错误] {e.Message}");
}
static void OnConnectionStateChanged(object sender, ConnectionStateChangedEventArgs e)
{
Console.WriteLine($"[连接状态] {(e.IsConnected ? "已连接" : "已断开")} - {e.Message}");
}
}
}

❌ 错误写法:
json{
"name": "设定值",
"functionCode": 3, // 3是读功能码
"readOnly": false // 但配置为可写
}
报错:不支持写入功能码: 3
✅ 正确理解:
c#// 🔥 不同PLC的字节序差异巨大
// 西门子PLC:通常使用ABCD(大端)
// 施耐德PLC:通常使用DCBA(小端)
// 三菱PLC:通常使用BADC
c#// 插件自动处理地址偏移
private int GetAddressOffset(int functionCode)
{
switch (functionCode)
{
case 1: return 1; // 线圈 00001-09999
case 3: return 40001; // 保持寄存器 40001-49999
case 4: return 30001; // 输入寄存器 30001-39999
default: return 0;
}
}
c#public class ModbusMonitor
{
private ModbusPlugin _plugin;
private Timer _timer;
public event Action<string, object> DataChanged;
public async Task StartMonitoring(string configPath, int interval = 1000)
{
_plugin = new ModbusPlugin();
_plugin.DataChanged += (s, e) => DataChanged?.Invoke(e.RegisterName, e.Value);
await _plugin.LoadConfigurationAsync(configPath);
await _plugin.ConnectAsync();
_timer = new Timer(async _ =>
{
await _plugin.ReadAllRegistersAsync();
}, null, 0, interval);
}
}
c#public class DeviceHealthChecker
{
public async Task<bool> CheckDeviceHealth(ModbusPlugin plugin)
{
try
{
// 读取关键寄存器测试连通性
await plugin.TestConnectionAsync();
// 检查设备状态寄存器
var deviceStatus = await plugin.ReadRegisterAsync("设备状态");
return deviceStatus != null;
}
catch
{
return false;
}
}
}
这套插件系统已经在我们公司的多个工业项目中稳定运行,从化工厂的温度监控到纺织厂的设备管理,都能完美胜任。
你在工业自动化开发中遇到过哪些通信协议的坑? 欢迎在评论区分享你的经验,让我们一起打造更强大的工业C#开发工具链!
觉得有用的话,请转发给更多做工业开发的同行 🚀,让更多人告别重复造轮子的痛苦!
关注我,获取更多C#工业开发干货,下期分享《OPC UA客户端插件化开发实战》
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!