You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何对单片机进行轮询?基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:43:48