协议实战 · 2026-01-21

C#上位机基于Modbus读取仪器波形

大家好!我是付工。

最近在做一个仪器测量的上位机项目,涉及到采集仪器波形,跟大家分享一下其中的关键技术点。

项目背景

这个项目涉及到两款仪器,分别是青智C2和青智F2,其中C2是采用Modbus协议,F2是采用自定义协议,需要对实验过程中的波形数据进行采集并存储,便于后续查看报告。

原文配图 1

本文主要针对青智C2仪表进行说明,青智C2是一个发电机专用的测试仪,通信协议是标准ModbusRTU协议。

原文配图 2

协议说明

这个仪表的协议和我们平常看到的所有不同,正常的Modbus地址表,每个地址会对应唯一的参数。

这种仪表可以做很多种实验,每种实验的数据量比较大,Modbus保持型存储区的65536个寄存器不够用,因为仪表内部做了一个分页面的机制,不同的页面对应不同的实验数据,当我们需要读取数据时,首先切换对应的页面,再进行读取。

原文配图 3
原文配图 4
原文配图 5

波形读取

然后我们来看波形数据是如何读取的。

一个完整波形应该包含如下几个元素:

1.曲线点数

2.X轴数据

3.Y(1)-Y(n)数据

因此每个波形的寄存器地址表设计如下:

原文配图 6

我们来分析一个这个地址表:

  • 002A对应曲线点数(N<=800)
  • 002B对应X轴间距系数
  • 002C-034B一共800个寄存器对应800个X轴数据
  • 034C-098B一共1600个寄存器对应800个线路1突加电压曲线Y轴数据
  • 098C-0FCB一共1600个寄存器对应800个线路2突加电压曲线Y轴数据
  • 0FCC-160B一共1600个寄存器对应800个线路3突加电压曲线Y轴数据
  • 读取后续6个波形Y轴数据

我们按照这个思路来进行编写代码:

第1步:切换界面至界面1

第2步:读取从002A开始的两个寄存器,第一个寄存器作为数量count,第二个寄存器的值作为X轴间距系数

第3步:读v取002C开始的count个寄存器作为X轴数据

第4步:读取034C开始的count*2个寄存器作为线路1电压数据

第5步:读取098C开始的count*2个寄存器作为线路2电压数据

第6步:读取0FCC开始的count*2个寄存器作为线路3电压数据

第7-12步:读取后续6个Y轴曲线

第13步:解锁界面

整个过程封装成一个方法,参考代码如下:

public override OperateResult<List<string>> GetTJChart(){
    //【1】锁定页面1
    var lockResult = this.LockPage(1);
    if (!lockResult.IsSuccess)
    {
        return OperateResult.CreateFailResult<List<string>>(lockResult);
    }
    List<string> result = new List<string>();
    //【2】读取数据点数和X轴系数
    var readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x002A, 2);
    if (!readResult.IsSuccess)
    {
        return OperateResult.CreateFailResult<List<string>>(readResult);
    }
    byte[] data = readResult.Content;
    ushort[] data1 = UShortLib.GetUShortArrayFromByteArray(data, this.dataFormat);
    result.AddRange(data1.Skip(0).Take(data1.Length).ToArray().Select(c => c.ToString()));
    //确定点数并判断是否超过范围
    ushort count = data1[0];
    if (count > 800)
    {
        return OperateResult.CreateFailResult<List<string>>("读取数据过大:" + count);
    }
    //【3】读取X曲线
    readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x002C, count);
    if (!readResult.IsSuccess)
    {
        return OperateResult.CreateFailResult<List<string>>(readResult);
    }
    data = readResult.Content;
    short[] data2 = ShortLib.GetShortArrayFromByteArray(data, this.dataFormat);
    result.Add(string.Join("_", data2));
    //【4】读取线路1Y曲线
    readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x034C, (ushort)(count * 2));
    if (!readResult.IsSuccess)
    {
        return OperateResult.CreateFailResult<List<string>>(readResult);
    }
    data = readResult.Content;
    float[] data3 = FloatLib.GetFloatArrayFromByteArray(data, this.dataFormat);
    result.Add(string.Join("_", data3));
    //【5】读取线路2Y曲线
    readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x098C, (ushort)(count * 2));
    if (!readResult.IsSuccess)
    {
        return OperateResult.CreateFailResult<List<string>>(readResult);
    }
    data = readResult.Content;
    float[] data4 = FloatLib.GetFloatArrayFromByteArray(data, this.dataFormat);
    result.Add(string.Join("_", data4));
    //【6】读取线路3Y曲线
    readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x0FCC, (ushort)(count * 2));
    if (!readResult.IsSuccess)
    {
        return OperateResult.CreateFailResult<List<string>>(readResult);
    }
    data = readResult.Content;
    float[] data5 = FloatLib.GetFloatArrayFromByteArray(data, this.dataFormat);
    result.Add(string.Join("_", data5));
    //【7】读取平均突加电压Y曲线
    readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x160C, (ushort)(count * 2));
    if (!readResult.IsSuccess)
    {
        return OperateResult.CreateFailResult<List<string>>(readResult);
    }
    data = readResult.Content;
    float[] data6 = FloatLib.GetFloatArrayFromByteArray(data, this.dataFormat);
    result.Add(string.Join("_", data6));
    //【8】读取突加频率Y曲线
    readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x160C, (ushort)(count * 2));
    if (!readResult.IsSuccess)
    {
        return OperateResult.CreateFailResult<List<string>>(readResult);
    }
    data = readResult.Content;
    float[] data7 = FloatLib.GetFloatArrayFromByteArray(data, this.dataFormat);
    result.Add(string.Join("_", data7));
    //【9】读取线路1突加电流Y曲线
    readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x228C, (ushort)(count * 2));
    if (!readResult.IsSuccess)
    {
        return OperateResult.CreateFailResult<List<string>>(readResult);
    }
    data = readResult.Content;
    float[] data8 = FloatLib.GetFloatArrayFromByteArray(data, this.dataFormat);
    result.Add(string.Join("_", data8));
    //【10】读取线路2突加电流Y曲线
    readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x28CC, (ushort)(count * 2));
    if (!readResult.IsSuccess)
    {
        return OperateResult.CreateFailResult<List<string>>(readResult);
    }
    data = readResult.Content;
    float[] data9 = FloatLib.GetFloatArrayFromByteArray(data, this.dataFormat);
    result.Add(string.Join("_", data9));
    //【11】读取线路3突加电流Y曲线
    readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x2F0C, (ushort)(count * 2));
    if (!readResult.IsSuccess)
    {
        return OperateResult.CreateFailResult<List<string>>(readResult);
    }
    data = readResult.Content;
    float[] data10 = FloatLib.GetFloatArrayFromByteArray(data, this.dataFormat);
    result.Add(string.Join("_", data10));
    //【12】读取平均突加电流Y曲线
    readResult = this.modbus.Read(ModbusArea.保持寄存器, 0x354C, (ushort)(count * 2));
    if (!readResult.IsSuccess)
    {
        return OperateResult.CreateFailResult<List<string>>(readResult);
    }
    data = readResult.Content;
    float[] data11 = FloatLib.GetFloatArrayFromByteArray(data, this.dataFormat);
    result.Add(string.Join("_", data11));
    //【13】解锁页面
        this.UnLockPage(0);
    return OperateResult.CreateSuccessResult(result);
}

将读取的数据存储到数据库中,将每个Y轴作为一个字段,里面存储的若干个数据采用字符串拼接成一个字符串进行存储。

原文配图 7

这样在测试报告中可以通过查询数据库,将数据通过波形图展示出来。

原文配图 8

多寄存器读取

如果数据点数为800,数据类型为浮点数,那么每个Y轴需要读取1600个寄存器,这时候应该怎么去读呢?

Modbus一次性最多可以读取125个寄存器,需要分1600/125=13次去读。

对于这种不确定读取数量的,最好是在通信库底层去实现。

public OperateResult<byte[]> Read(ModbusArea area, byte devId, ushort startAddress, ushort length){
    var resultBytes = new List<byte>();
    int index = 0;
    while (length > 0)
    {
        var maxToRead = Math.Min(length, (area==ModbusArea.保持寄存器||area==ModbusArea.输入寄存器)?this.MaxRegisterPduSize:this.MaxCoilPduSize);
                 var read = ReadWithSingleRequest(area, devId, (ushort)(startAddress + index), maxToRead);
        if (!read.IsSuccess)
        {
            return OperateResult.CreateFailResult<byte[]>(read);
        }
        resultBytes.AddRange(read.Content);
        length -= maxToRead;
        index += maxToRead;
    }
    return OperateResult.CreateSuccessResult(resultBytes.ToArray());
}
本文为原文文字快照,已收录 8 张技术配图;二维码与推广图未收录。
标签:#ModbusRTU #波形