如何对单片机进行轮询?基于EasyModbus的ModbusTCP实时读取需求
实现ModbusTCP近实时轮询与寄存器状态检测
嘿,你已经有了基础的寄存器读取代码,要实现近实时检测状态变化,核心就是定时重复读取目标寄存器 + 对比新旧值触发变量更新。下面是具体的落地方案,结合EasyModbus库来完成:
核心思路拆解
- 先建立并维护ModbusTCP连接,还要处理断连后的重连逻辑(毕竟网络或单片机偶尔可能离线)
- 用定时器或者循环任务定期读取你指定的寄存器(起始地址2500,读5个)
- 缓存上一次读取的寄存器值,每次新读回来就和旧值对比
- 只要检测到某个寄存器值变了,就更新对应的变量/字段
完整代码实现
从你的代码片段来看是用C#,那我就用System.Timers.Timer来做轮询,同时把异常处理也加上(这部分很重要,不然网络一断程序就崩了):
using System; using System.Timers; using EasyModbus; public class ModbusPollingService { // Modbus客户端实例 private static ModbusClient _modbusClient = new ModbusClient(); // 轮询用的定时器 private static Timer _pollingTimer; // 缓存上一次的寄存器值,用来对比变化 private static int[] _lastRegisterValues; // 你的目标寄存器参数:起始地址2500,读5个 private const int StartAddress = 2500; private const int RegisterCount = 5; // 轮询间隔(毫秒,自己按需调,比如100ms就是0.1秒一次) private const int PollingInterval = 100; public static void StartPolling(string deviceIp, int port = 502) { // 配置Modbus客户端 _modbusClient.IPAddress = deviceIp; _modbusClient.Port = port; // 初始化定时器 _pollingTimer = new Timer(PollingInterval); _pollingTimer.Elapsed += OnPollingTick; _pollingTimer.AutoReset = true; // 先尝试连一次设备 TryReconnect(); // 启动轮询 _pollingTimer.Start(); Console.WriteLine("Modbus轮询已启动,开始监听寄存器变化"); } private static void OnPollingTick(object sender, ElapsedEventArgs e) { // 先确保客户端是连接状态 if (!_modbusClient.Connected) { TryReconnect(); return; } try { // 读取寄存器(就是你原来的那段代码) int[] currentValues = _modbusClient.ReadHoldingRegisters(StartAddress, RegisterCount); // 如果是第一次读,先把旧值缓存起来,同时初始化变量 if (_lastRegisterValues == null) { _lastRegisterValues = (int[])currentValues.Clone(); UpdateAllVariables(currentValues); return; } // 逐个对比新旧值,找变化的寄存器 for (int i = 0; i < RegisterCount; i++) { if (currentValues[i] != _lastRegisterValues[i]) { Console.WriteLine($"寄存器{StartAddress + i}值变啦:{_lastRegisterValues[i]} → {currentValues[i]}"); // 更新对应的变量 UpdateSingleVariable(i, currentValues[i]); // 更新缓存的旧值 _lastRegisterValues[i] = currentValues[i]; } } } catch (Exception ex) { Console.WriteLine($"读取寄存器出错:{ex.Message}"); // 出错大概率是连接断了,主动断开,下次轮询再重连 _modbusClient.Disconnect(); } } private static void TryReconnect() { try { if (!_modbusClient.Connected) { _modbusClient.Connect(); Console.WriteLine("成功连到单片机啦!"); } } catch (Exception ex) { Console.WriteLine("连不上设备,下次轮询再试:" + ex.Message); } } // 自定义方法:第一次读取时初始化所有变量 private static void UpdateAllVariables(int[] registerValues) { // 这里换成你自己的变量赋值逻辑,比如: // YourGlobalVar1 = registerValues[0]; // YourGlobalVar2 = registerValues[1]; // ... 剩下的寄存器对应变量 Console.WriteLine("初始化所有变量完成"); } // 自定义方法:更新单个变化的变量 private static void UpdateSingleVariable(int registerIndex, int value) { // 根据寄存器索引对应到你的变量,比如: switch (registerIndex) { case 0: // YourGlobalVar1 = value; break; case 1: // YourGlobalVar2 = value; break; // 其他寄存器对应的变量更新逻辑 } } public static void StopPolling() { _pollingTimer?.Stop(); _modbusClient?.Disconnect(); Console.WriteLine("Modbus轮询已停止"); } } // 使用示例(放在Main方法里就行) // class Program // { // static void Main() // { // // 替换成你的单片机IP地址 // ModbusPollingService.StartPolling("192.168.1.100"); // Console.ReadLine(); // ModbusPollingService.StopPolling(); // } // }
关键细节提醒
- 轮询间隔别乱调:
PollingInterval太小(比如10ms)会把网络和单片机撑满,太大又会延迟。一般100ms-500ms是兼顾实时性和性能的范围,自己测试着来。 - 异常处理不能少:网络不稳定、单片机重启都是常事,加了异常处理程序才不会崩,还能自动重连。
- 只更改变了的值:用缓存对比的方式,避免每次读取都给变量赋值,减少不必要的操作。
- 记得释放资源:停止程序的时候,一定要关掉定时器和Modbus连接,不然会占着资源不释放。
另一种实现方式(用Task循环)
如果你不想用Timer,也可以用async/await做一个无限循环轮询,代码更简洁:
public static async Task StartPollingWithTask(string deviceIp, int port = 502) { _modbusClient.IPAddress = deviceIp; _modbusClient.Port = port; while (true) { if (!_modbusClient.Connected) { TryReconnect(); } try { int[] currentValues = _modbusClient.ReadHoldingRegisters(StartAddress, RegisterCount); // 对比和更新逻辑和上面完全一样,这里就不重复写了 } catch (Exception ex) { Console.WriteLine($"读取失败:{ex.Message}"); _modbusClient.Disconnect(); } // 等够轮询间隔再继续下一次 await Task.Delay(PollingInterval); } }
这样就能实现你要的近实时检测寄存器变化啦,核心就是“定时读+对比更新”的逻辑,照着改改就能用到你的项目里~
内容的提问来源于stack exchange,提问作者user11703431
相关产品推荐
相关产品推荐

