C#编写电量采集系统,MODBUS 485通信读取70块电量表电度数(电表品牌:中电),里面有完整的采集源码,采集数据10分钟存盘一次存至数量库,随时可查看或计算区间电量。 提供,运行稳定,不受使用时间限制,完整的协议代码非DLL,容易理解,便于修改,稍作修改就能移植到自己的项目

最近在工业园区的配电房改造项目中,接触到了中电HDZY-3系列电表。现场70块表计需要通过MODBUS 485总线采集数据,用C#撸了个稳定运行的电量采集系统。实测连续运行两个月无宕机,这里分享几个关键实现点。

先看硬件拓扑:485总线手拉手连70块表,每块表配置了不同的设备地址(1-70)。采用轮询方式采集,通过USB转485转换器接入工控机。这里有个坑——485总线长度超过800米时需加中继器,不过这次运气好布线在500米内搞定了。

核心代码部分先从MODBUS协议解析开始。自己撸的协议解析器比调用DLL灵活得多,特别是处理不同电表型号时:

//MODBUS RTU请求帧生成
byte[] BuildReadRequest(byte slaveId, ushort startAddress, ushort length)
{
    var frame = new List<byte> { slaveId, 0x03 };
    frame.AddRange(BitConverter.GetBytes(startAddress).Reverse());
    frame.AddRange(BitConverter.GetBytes(length).Reverse());
    var crc = CalcCRC(frame.ToArray());
    frame.AddRange(crc);
    return frame.ToArray();
}

//CRC校验算法(这才是协议栈的灵魂)
ushort CalcCRC(byte[] data)
{
    ushort crc = 0xFFFF;
    foreach (byte b in data) {
        crc ^= b;
        for (int i = 0; i < 8; i++) {
            crc = (crc & 1) != 0 ? (ushort)((crc >> 1) ^ 0xA001) : (ushort)(crc >> 1);
        }
    }
    return (ushort)((crc << 8) | (crc >> 8)); //注意字节序
}

电表数据的解析需要特别注意IEEE754浮点数的处理。中电的表计返回4字节数据,但字节顺序和常规写法不同:

float ParseEnergyValue(byte[] data) 
{
    // 示例数据:0x43 0x9E 0x00 0x00 实际对应312.0度
    if (data.Length != 4) throw new ArgumentException("电度数据长度异常");
    
    // 中电表计的浮点排列是Big-endian
    Array.Reverse(data); //转换为小端模式
    return BitConverter.ToSingle(data, 0);
}

采集任务调度采用定时器+异步模式,避免界面卡顿。这里用了个取巧的队列机制防止设备响应超时:

// 轮询控制器
class PollingScheduler
{
    private ConcurrentQueue<int> _deviceQueue = new ConcurrentQueue<int>(Enumerable.Range(1, 70));
    private System.Timers.Timer _timer = new System.Timers.Timer(200); //200ms间隔
    
    public void Start()
    {
        _timer.Elapsed += async (s, e) => {
            if(_deviceQueue.TryDequeue(out int deviceId)) {
                var result = await ReadEnergyAsync(deviceId);
                SaveToBuffer(result);
                _deviceQueue.Enqueue(deviceId); //重新入队循环
            }
        };
        _timer.Start();
    }
}

数据存储方面,用SQLite做本地缓存,10分钟触发一次批量写入。这里有个性能优化点——采用WAL写入模式:

// 定时存盘
using (var conn = new SQLiteConnection("Data Source=energy.db;Journal Mode=WAL"))
{
    conn.Open();
    using (var trans = conn.BeginTransaction())
    {
        var cmd = conn.CreateCommand();
        cmd.CommandText = "INSERT INTO records(dt,device_id,kwh) VALUES(@dt,@id,@kwh)";
        
        foreach (var data in _buffer)
        {
            cmd.Parameters.Clear();
            cmd.Parameters.AddWithValue("@dt", data.Timestamp);
            cmd.Parameters.AddWithValue("@id", data.DeviceId);
            cmd.Parameters.AddWithValue("@kwh", data.Kwh);
            cmd.ExecuteNonQuery();
        }
        trans.Commit();
    }
}

系统还做了几个增强稳定性的处理:

  1. 串口异常时自动重连机制
  2. 设备连续三次无响应后自动跳过(记录故障日志)
  3. 电压/电流等非必要参数采集开关配置
  4. 采集间隔动态调整(夜间负载低时加快采集频率)

这套系统在部署时遇到过一个坑:现场有变频器干扰导致偶发通讯失败。后来通过修改485终端电阻(加120Ω电阻)和调整波特率(从9600降到4800)解决。代码层面增加了错误数据过滤规则,比如排除负电量和突变值。

想移植到自己项目的话,重点改这几个地方:

  1. 设备地址范围和轮询顺序(DeviceQueue生成逻辑)
  2. 数据解析方法(不同厂家字节顺序可能不同)
  3. 存盘时间间隔和数据库类型(支持切MySQL只需改连接字符串)
  4. 增加Modbus TCP支持(协议帧头调整即可)

源码里最值得参考的是协议层实现,完全从字节层面处理避免了第三方库的依赖。实测在Intel NUC工控机上CPU占用长期低于3%,内存稳定在50MB左右,真正做到了7x24无监护运行。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐